openclaw-telegram-manager 2.8.0 → 2.9.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/dist/plugin.js CHANGED
@@ -8816,7 +8816,7 @@ __export(value_exports2, {
8816
8816
 
8817
8817
  // src/lib/types.ts
8818
8818
  var CURRENT_REGISTRY_VERSION = 6;
8819
- var CAPSULE_VERSION = 3;
8819
+ var CAPSULE_VERSION = 4;
8820
8820
  var MAX_POST_ERROR_LENGTH = 500;
8821
8821
  var MAX_NAME_LENGTH = 100;
8822
8822
  var DOCTOR_ALL_COOLDOWN_MS = 60 * 60 * 1e3;
@@ -8867,20 +8867,9 @@ var RegistrySchema = Type.Object({
8867
8867
  maxTopics: Type.Integer({ minimum: 1 }),
8868
8868
  topics: Type.Record(Type.String(), TopicEntrySchema)
8869
8869
  });
8870
- var OVERLAY_FILES = {
8871
- coding: ["ARCHITECTURE.md", "DEPLOY.md"],
8872
- research: ["SOURCES.md", "FINDINGS.md"],
8873
- marketing: ["CAMPAIGNS.md", "METRICS.md"],
8874
- general: []
8875
- };
8876
8870
  var BASE_FILES = [
8877
8871
  "README.md",
8878
8872
  "STATUS.md",
8879
- "TODO.md",
8880
- "COMMANDS.md",
8881
- "LINKS.md",
8882
- "CRON.md",
8883
- "NOTES.md",
8884
8873
  "LEARNINGS.md"
8885
8874
  ];
8886
8875
  function topicKey(groupId, threadId) {
@@ -9147,151 +9136,87 @@ function buildAuditEntry(userId, cmd, slug, detail) {
9147
9136
  // src/lib/capsule.ts
9148
9137
  import * as fs4 from "node:fs";
9149
9138
  import * as path4 from "node:path";
9150
- var BASE_TEMPLATES = {
9151
- "README.md": (name) => `# ${name}
9139
+ var README_UNIVERSAL = (name) => `# ${name}
9152
9140
 
9153
- _Describe what this topic is about._
9154
- `,
9155
- "STATUS.md": (name) => `# Status: ${name}
9156
-
9157
- ## Last done (UTC)
9158
-
9159
- ${(/* @__PURE__ */ new Date()).toISOString()}
9141
+ ## What is this about?
9160
9142
 
9161
- Topic created. Waiting for first instructions.
9143
+ _Describe what this topic is about._
9162
9144
 
9163
- ## Next actions (now)
9145
+ ## Goal
9164
9146
 
9165
- _None yet._
9147
+ _What does success look like?_
9166
9148
 
9167
- ## Upcoming actions
9149
+ ## Key resources
9168
9150
 
9169
- _None yet._
9170
- `,
9171
- "TODO.md": (name) => `# TODO: ${name}
9151
+ _URLs, repos, paths, dashboards \u2014 anything the AI needs after a reset._
9152
+ `;
9153
+ var README_TYPE_SECTIONS = {
9154
+ coding: () => `
9155
+ ## Architecture
9172
9156
 
9173
- ## Backlog
9157
+ _Components, data flow, key decisions._
9174
9158
 
9175
- - [T-1] _e.g. Set up project scaffolding_
9176
- - [T-2] _Waiting for next task_
9177
- - [T-3] _Waiting for next task_
9159
+ ## Deployment
9178
9160
 
9179
- ## Completed
9161
+ _Environments, deploy steps, rollback._
9180
9162
 
9181
- _None yet._
9182
- `,
9183
- "COMMANDS.md": (name) => `# Commands: ${name}
9163
+ ## Commands
9184
9164
 
9185
- _Build, deploy, test, and other commands for this topic. Kept here so they're not lost on reset._
9165
+ _Build, test, deploy \u2014 kept here so they survive a reset._
9186
9166
  `,
9187
- "LINKS.md": (name) => `# Links: ${name}
9167
+ research: () => `
9168
+ ## Sources
9188
9169
 
9189
- ## Key paths & URLs
9170
+ _Papers, articles, datasets, APIs._
9190
9171
 
9191
- _Not set yet \u2014 add repository paths, dashboards, service URLs, or anything the agent needs after a reset._
9192
- `,
9193
- "CRON.md": (name) => `# Cron: ${name}
9172
+ ## Findings
9194
9173
 
9195
- _Cron job IDs and schedules for this topic._
9174
+ _Key findings, evidence, recommendations._
9196
9175
  `,
9197
- "NOTES.md": (name) => `# Notes: ${name}
9198
-
9199
- _Anything worth remembering about this topic._
9200
- `,
9201
- "LEARNINGS.md": (name) => `# Learnings: ${name}
9202
-
9203
- _Hard-won insights, mistakes, and workarounds._
9204
- _Agent prepends here automatically. Most recent entries first._
9205
- `
9206
- };
9207
- var OVERLAY_TEMPLATES = {
9208
- "ARCHITECTURE.md": (name) => `# Architecture: ${name}
9176
+ marketing: () => `
9177
+ ## Campaigns
9209
9178
 
9210
- ## Components
9211
-
9212
- _None yet._
9179
+ _Active campaigns, audiences, channels._
9213
9180
 
9214
- ## Data flow
9181
+ ## Metrics
9215
9182
 
9216
- _None yet._
9217
-
9218
- ## Key decisions
9219
-
9220
- _None yet._
9183
+ _KPIs, targets, tracking dashboards._
9221
9184
  `,
9222
- "DEPLOY.md": (name) => `# Deployment: ${name}
9223
-
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._
9239
- `,
9240
- "SOURCES.md": (name) => `# Sources: ${name}
9241
-
9242
- ## Papers & articles
9243
-
9244
- _None yet._
9245
-
9246
- ## Datasets & APIs
9247
-
9248
- _None yet._
9249
-
9250
- ## Other references
9251
-
9252
- _None yet._
9253
- `,
9254
- "FINDINGS.md": (name) => `# Findings: ${name}
9255
-
9256
- ## Key findings
9257
-
9258
- _None yet._
9185
+ general: () => ""
9186
+ };
9187
+ var BASE_TEMPLATES = {
9188
+ "README.md": (name, type) => {
9189
+ const universal = README_UNIVERSAL(name);
9190
+ const extra = type ? README_TYPE_SECTIONS[type](name) : "";
9191
+ return universal + extra;
9192
+ },
9193
+ "STATUS.md": (name) => `# Status: ${name}
9259
9194
 
9260
- ## Evidence & data
9195
+ ## Last done (UTC)
9261
9196
 
9262
- _None yet._
9197
+ ${(/* @__PURE__ */ new Date()).toISOString()}
9198
+ Topic created. Waiting for first instructions.
9263
9199
 
9264
- ## Recommendations
9200
+ ## Next actions (now)
9265
9201
 
9266
9202
  _None yet._
9267
- `,
9268
- "CAMPAIGNS.md": (name) => `# Campaigns: ${name}
9269
9203
 
9270
- ## Active campaigns
9204
+ ## Upcoming actions
9271
9205
 
9272
9206
  _None yet._
9273
9207
 
9274
- ## Audiences & channels
9208
+ ## Backlog
9275
9209
 
9276
- _None yet._
9210
+ - [T-1] _e.g. Set up project scaffolding_
9277
9211
 
9278
- ## Timeline & budget
9212
+ ## Completed
9279
9213
 
9280
9214
  _None yet._
9281
9215
  `,
9282
- "METRICS.md": (name) => `# Metrics: ${name}
9283
-
9284
- ## KPIs & targets
9285
-
9286
- _None yet._
9287
-
9288
- ## Tracking & dashboards
9289
-
9290
- _None yet._
9291
-
9292
- ## Performance data
9216
+ "LEARNINGS.md": (name) => `# Learnings: ${name}
9293
9217
 
9294
- _None yet._
9218
+ _Hard-won insights, mistakes, and workarounds._
9219
+ _Agent prepends here automatically. Most recent entries first._
9295
9220
  `
9296
9221
  };
9297
9222
  var CAPSULE_FILE_MODE = 416;
@@ -9308,15 +9233,7 @@ function scaffoldCapsule(projectsBase, slug, name, type) {
9308
9233
  const templateFn = BASE_TEMPLATES[file];
9309
9234
  if (templateFn) {
9310
9235
  const filePath = path4.join(capsuleDir, file);
9311
- fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
9312
- }
9313
- }
9314
- const overlays = OVERLAY_FILES[type];
9315
- for (const file of overlays) {
9316
- const templateFn = OVERLAY_TEMPLATES[file];
9317
- if (templateFn) {
9318
- const filePath = path4.join(capsuleDir, file);
9319
- fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
9236
+ fs4.writeFileSync(filePath, templateFn(name, type), { mode: CAPSULE_FILE_MODE });
9320
9237
  }
9321
9238
  }
9322
9239
  }
@@ -9337,19 +9254,28 @@ function upgradeCapsule(projectsBase, slug, name, type, currentVersion) {
9337
9254
  if (!fs4.existsSync(filePath)) {
9338
9255
  const templateFn = BASE_TEMPLATES[file];
9339
9256
  if (templateFn) {
9340
- fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
9257
+ fs4.writeFileSync(filePath, templateFn(name, type), { mode: CAPSULE_FILE_MODE });
9341
9258
  addedFiles.push(file);
9342
9259
  }
9343
9260
  }
9344
9261
  }
9345
- const overlays = OVERLAY_FILES[type];
9346
- for (const file of overlays) {
9347
- const filePath = path4.join(capsuleDir, file);
9348
- if (!fs4.existsSync(filePath)) {
9349
- const templateFn = OVERLAY_TEMPLATES[file];
9350
- if (templateFn) {
9351
- fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
9352
- addedFiles.push(file);
9262
+ if (currentVersion < 4) {
9263
+ const statusPath = path4.join(capsuleDir, "STATUS.md");
9264
+ if (fs4.existsSync(statusPath)) {
9265
+ const content = fs4.readFileSync(statusPath, "utf-8");
9266
+ if (!content.includes("## Backlog")) {
9267
+ const appendix = "\n## Backlog\n\n- [T-1] _e.g. Set up project scaffolding_\n\n## Completed\n\n_None yet._\n";
9268
+ fs4.writeFileSync(statusPath, content.trimEnd() + "\n" + appendix, { mode: CAPSULE_FILE_MODE });
9269
+ }
9270
+ }
9271
+ const readmePath = path4.join(capsuleDir, "README.md");
9272
+ if (fs4.existsSync(readmePath)) {
9273
+ const content = fs4.readFileSync(readmePath, "utf-8");
9274
+ if (content.includes("_Describe what this topic is about._") && !content.includes("## Goal")) {
9275
+ const templateFn = BASE_TEMPLATES["README.md"];
9276
+ if (templateFn) {
9277
+ fs4.writeFileSync(readmePath, templateFn(name, type), { mode: CAPSULE_FILE_MODE });
9278
+ }
9353
9279
  }
9354
9280
  }
9355
9281
  }
@@ -9710,8 +9636,8 @@ Context hierarchy (strictly ordered):
9710
9636
 
9711
9637
  Determinism rules:
9712
9638
  - After /reset, /new, or context compaction: ALWAYS re-read your topic files
9713
- first \u2014 STATUS.md, then TODO.md, then LEARNINGS.md (last 20 entries), then
9714
- COMMANDS.md \u2014 before doing anything else. These are your ground truth.
9639
+ first \u2014 STATUS.md, then LEARNINGS.md (last 20 entries), then README.md for
9640
+ topic context \u2014 before doing anything else. These are your ground truth.
9715
9641
  Do not rely on summarized memory or workspace-level files for this topic's
9716
9642
  tasks, status, or goals.
9717
9643
  - Before context compaction or when the conversation is long: proactively
@@ -9719,13 +9645,12 @@ Determinism rules:
9719
9645
  "Next actions (now)") so compaction cannot erase critical state.
9720
9646
  Use the standard file write tool directly \u2014 do not route through /tm.
9721
9647
  - Keep STATUS.md accurate: always maintain "Last done (UTC)", "Next actions (now)",
9722
- and "Upcoming actions".
9723
- - When new commands appear, add them to COMMANDS.md (don't leave them only in chat).
9724
- - When new links/paths/services appear, add them to LINKS.md.
9725
- - If automation/cron is involved, record job IDs + schedules in CRON.md.
9726
- - Task IDs (e.g., [T-1]) must stay consistent between STATUS.md and TODO.md.
9648
+ "Upcoming actions", "Backlog", and "Completed".
9649
+ - Task IDs (e.g., [T-1]) must stay consistent across STATUS.md sections.
9650
+ Move completed tasks from Backlog to Completed.
9727
9651
  - STATUS.md has two priority sections: "Next actions (now)" for immediate work
9728
9652
  and "Upcoming actions" for the near-future pipeline.
9653
+ - Key resources, commands, and context belong in README.md.
9729
9654
 
9730
9655
  Learning capture:
9731
9656
  - When you discover something unexpected, a mistake, a workaround, or a
@@ -10434,19 +10359,6 @@ function runCapsuleChecks(entry, projectsBase) {
10434
10359
  check(Severity.ERROR, "statusMissing", "Status file is missing", true, "Run /tm upgrade to recreate it")
10435
10360
  );
10436
10361
  }
10437
- if (!fs8.existsSync(path8.join(capsuleDir, "TODO.md"))) {
10438
- results.push(
10439
- check(Severity.WARN, "todoMissing", "TODO file is missing", true, "Run /tm upgrade to recreate it")
10440
- );
10441
- }
10442
- const overlays = OVERLAY_FILES[entry.type] ?? [];
10443
- for (const file of overlays) {
10444
- if (!fs8.existsSync(path8.join(capsuleDir, file))) {
10445
- results.push(
10446
- check(Severity.INFO, `overlayMissing:${file}`, `Optional overlay ${file} missing for type "${entry.type}"`, true)
10447
- );
10448
- }
10449
- }
10450
10362
  if (entry.capsuleVersion < CAPSULE_VERSION) {
10451
10363
  results.push(
10452
10364
  check(
@@ -10523,7 +10435,8 @@ function runStatusQualityChecks(statusContent, entry) {
10523
10435
  }
10524
10436
  return results;
10525
10437
  }
10526
- function runNextVsTodoChecks(statusContent, todoContent) {
10438
+ var BACKLOG_RE = /^##\s*Backlog/im;
10439
+ function runNextVsBacklogChecks(statusContent) {
10527
10440
  const results = [];
10528
10441
  const nextActionsIndex = statusContent.search(NEXT_ACTIONS_RE);
10529
10442
  if (nextActionsIndex < 0) return results;
@@ -10532,14 +10445,19 @@ function runNextVsTodoChecks(statusContent, todoContent) {
10532
10445
  const nextActionsSection = nextSectionIndex > 0 ? sectionAfter.slice(0, nextSectionIndex) : sectionAfter;
10533
10446
  const nextTaskIds = nextActionsSection.match(TASK_ID_RE) ?? [];
10534
10447
  if (nextTaskIds.length === 0) return results;
10535
- const todoTaskIds = new Set(todoContent.match(TASK_ID_RE) ?? []);
10536
- const missing = nextTaskIds.filter((id) => !todoTaskIds.has(id));
10448
+ const backlogIndex = statusContent.search(BACKLOG_RE);
10449
+ if (backlogIndex < 0) return results;
10450
+ const backlogAfter = statusContent.slice(backlogIndex);
10451
+ const backlogNextSection = backlogAfter.indexOf("\n## ", 1);
10452
+ const backlogSection = backlogNextSection > 0 ? backlogAfter.slice(0, backlogNextSection) : backlogAfter;
10453
+ const backlogTaskIds = new Set(backlogSection.match(TASK_ID_RE) ?? []);
10454
+ const missing = nextTaskIds.filter((id) => !backlogTaskIds.has(id));
10537
10455
  if (missing.length >= 2) {
10538
10456
  results.push(
10539
10457
  check(
10540
10458
  Severity.WARN,
10541
- "nextNotInTodo",
10542
- `${missing.length} tasks referenced in next actions don't exist in the TODO list: ${missing.join(", ")}`,
10459
+ "nextNotInBacklog",
10460
+ `${missing.length} tasks referenced in next actions don't exist in the backlog: ${missing.join(", ")}`,
10543
10461
  false,
10544
10462
  "The AI will clean these up on next interaction"
10545
10463
  )
@@ -10547,62 +10465,18 @@ function runNextVsTodoChecks(statusContent, todoContent) {
10547
10465
  }
10548
10466
  return results;
10549
10467
  }
10550
- function runCommandsLinksChecks(entry, capsuleFiles) {
10551
- const results = [];
10552
- if (entry.type === "coding") {
10553
- const commandsContent = capsuleFiles.get("COMMANDS.md");
10554
- if (commandsContent !== void 0 && isEffectivelyEmpty(commandsContent)) {
10555
- results.push(
10556
- check(Severity.INFO, "commandsEmpty", "COMMANDS.md is empty for a coding topic", false, "Add build/test/deploy commands to COMMANDS.md")
10557
- );
10558
- }
10559
- }
10560
- if (entry.type === "coding" || entry.type === "research") {
10561
- const linksContent = capsuleFiles.get("LINKS.md");
10562
- if (linksContent !== void 0 && isEffectivelyEmpty(linksContent)) {
10563
- results.push(
10564
- check(Severity.INFO, "linksEmpty", "LINKS.md is empty for a coding/research topic", false, "Add URLs and endpoints to LINKS.md")
10565
- );
10566
- }
10567
- }
10568
- return results;
10569
- }
10570
- function isEffectivelyEmpty(content) {
10571
- const stripped = content.replace(/^#.*$/gm, "").replace(/^_.*_$/gm, "").replace(/\s+/g, "").trim();
10572
- return stripped.length === 0;
10573
- }
10574
- var JOB_ID_RE = /[a-zA-Z0-9_-]{8,}/;
10575
- function runCronChecks(cronContent, cronJobsPath) {
10468
+ function runUnfilledContextCheck(capsuleFiles) {
10576
10469
  const results = [];
10577
- if (isEffectivelyEmpty(cronContent)) return results;
10578
- const lines = cronContent.split("\n").filter((l) => !l.startsWith("#") && l.trim().length > 0);
10579
- const hasJobIds = lines.some((line) => JOB_ID_RE.test(line));
10580
- if (!hasJobIds) {
10470
+ const readmeContent = capsuleFiles.get("README.md");
10471
+ if (readmeContent && readmeContent.includes("_Describe what this topic is about._")) {
10581
10472
  results.push(
10582
- check(Severity.WARN, "cronNoJobIds", "Scheduled jobs are listed but have no recognizable job IDs", false)
10473
+ check(
10474
+ Severity.INFO,
10475
+ "contextUnfilled",
10476
+ "Topic context is empty \u2014 tell the AI about your project to get better help.",
10477
+ false
10478
+ )
10583
10479
  );
10584
- return results;
10585
- }
10586
- if (cronJobsPath && fs8.existsSync(cronJobsPath)) {
10587
- try {
10588
- const jobsRaw = fs8.readFileSync(cronJobsPath, "utf-8");
10589
- const jobs = JSON.parse(jobsRaw);
10590
- const knownJobIds = new Set(Object.keys(jobs));
10591
- for (const line of lines) {
10592
- const match = line.match(JOB_ID_RE);
10593
- if (match && !knownJobIds.has(match[0])) {
10594
- results.push(
10595
- check(
10596
- Severity.WARN,
10597
- "cronJobNotFound",
10598
- `Scheduled job "${match[0]}" not found in the jobs registry`,
10599
- false
10600
- )
10601
- );
10602
- }
10603
- }
10604
- } catch {
10605
- }
10606
10480
  }
10607
10481
  return results;
10608
10482
  }
@@ -10700,7 +10574,7 @@ function runPostErrorCheck(entry) {
10700
10574
  }
10701
10575
  return results;
10702
10576
  }
10703
- function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cronJobsPath) {
10577
+ function runAllChecksForTopic(entry, projectsBase, includeContent, registry) {
10704
10578
  const results = [];
10705
10579
  const capsuleDir = path8.join(projectsBase, entry.slug);
10706
10580
  results.push(...runRegistryChecks(entry, projectsBase));
@@ -10711,16 +10585,9 @@ function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cro
10711
10585
  const statusContent = capsuleFiles.get("STATUS.md");
10712
10586
  if (statusContent) {
10713
10587
  results.push(...runStatusQualityChecks(statusContent, entry));
10714
- const todoContent = capsuleFiles.get("TODO.md");
10715
- if (todoContent) {
10716
- results.push(...runNextVsTodoChecks(statusContent, todoContent));
10717
- }
10718
- }
10719
- results.push(...runCommandsLinksChecks(entry, capsuleFiles));
10720
- const cronContent = capsuleFiles.get("CRON.md");
10721
- if (cronContent) {
10722
- results.push(...runCronChecks(cronContent, cronJobsPath));
10588
+ results.push(...runNextVsBacklogChecks(statusContent));
10723
10589
  }
10590
+ results.push(...runUnfilledContextCheck(capsuleFiles));
10724
10591
  if (includeContent && registry) {
10725
10592
  results.push(...runConfigChecks(entry, includeContent, registry));
10726
10593
  }
@@ -10731,7 +10598,7 @@ function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cro
10731
10598
  return results;
10732
10599
  }
10733
10600
  var BACKUP_DIR = ".tm-backup";
10734
- var BACKUP_FILES = ["STATUS.md", "TODO.md"];
10601
+ var BACKUP_FILES = ["STATUS.md"];
10735
10602
  function backupCapsuleIfHealthy(projectsBase, slug, results) {
10736
10603
  const hasIssues = results.some((r) => r.severity === Severity.ERROR || r.severity === Severity.WARN);
10737
10604
  if (hasIssues) return;
@@ -10750,22 +10617,7 @@ function backupCapsuleIfHealthy(projectsBase, slug, results) {
10750
10617
  }
10751
10618
  function readCapsuleFiles(capsuleDir) {
10752
10619
  const files = /* @__PURE__ */ new Map();
10753
- const filenames = [
10754
- "STATUS.md",
10755
- "TODO.md",
10756
- "COMMANDS.md",
10757
- "LINKS.md",
10758
- "CRON.md",
10759
- "NOTES.md",
10760
- "README.md",
10761
- "LEARNINGS.md",
10762
- "ARCHITECTURE.md",
10763
- "DEPLOY.md",
10764
- "SOURCES.md",
10765
- "FINDINGS.md",
10766
- "CAMPAIGNS.md",
10767
- "METRICS.md"
10768
- ];
10620
+ const filenames = ["README.md", "STATUS.md", "LEARNINGS.md"];
10769
10621
  for (const name of filenames) {
10770
10622
  const filePath = path8.join(capsuleDir, name);
10771
10623
  try {
@@ -10810,13 +10662,11 @@ async function handleDoctor(ctx) {
10810
10662
  }
10811
10663
  } catch {
10812
10664
  }
10813
- const cronJobsPath = path9.join(configDir, "cron", "jobs.json");
10814
10665
  const results = runAllChecksForTopic(
10815
10666
  entry,
10816
10667
  projectsBase,
10817
10668
  includeContent,
10818
- registry,
10819
- cronJobsPath
10669
+ registry
10820
10670
  );
10821
10671
  backupCapsuleIfHealthy(projectsBase, entry.slug, results);
10822
10672
  const reportText = buildDoctorReport(entry.name, results);
@@ -10869,9 +10719,8 @@ async function handleDailyReport(ctx) {
10869
10719
  return { text: "Topic files not found. Run /tm init to set up this topic." };
10870
10720
  }
10871
10721
  const statusContent = readFileOrNull(path10.join(capsuleDir, "STATUS.md"));
10872
- const todoContent = readFileOrNull(path10.join(capsuleDir, "TODO.md"));
10873
10722
  const doneContent = extractDoneSection(statusContent);
10874
- const blockers = extractBlockers(todoContent);
10723
+ const blockers = extractBlockers(statusContent);
10875
10724
  const nextContent = extractNextActions(statusContent);
10876
10725
  const reportData = {
10877
10726
  name: entry.name,
@@ -10925,9 +10774,11 @@ function extractDoneSection(statusContent) {
10925
10774
  const text = match[1]?.trim();
10926
10775
  return text || "Empty.";
10927
10776
  }
10928
- function extractBlockers(todoContent) {
10929
- if (!todoContent) return "No tasks recorded yet.";
10930
- const lines = todoContent.split("\n");
10777
+ function extractBlockers(statusContent) {
10778
+ if (!statusContent) return "No tasks recorded yet.";
10779
+ const backlogMatch = statusContent.match(/^##\s*Backlog\b.*\n((?:(?!\n## )[\s\S])*)/im);
10780
+ const backlogSection = backlogMatch ? backlogMatch[1] ?? "" : statusContent;
10781
+ const lines = backlogSection.split("\n");
10931
10782
  const blockerLines = lines.filter(
10932
10783
  (l) => /\[BLOCKED\]/i.test(l) || /\bblocked\b/i.test(l)
10933
10784
  );
@@ -11087,13 +10938,13 @@ async function handleDoctorAll(ctx) {
11087
10938
  }
11088
10939
  } catch {
11089
10940
  }
11090
- const cronJobsPath = path11.join(configDir, "cron", "jobs.json");
11091
10941
  const allEntries = Object.entries(registry.topics);
11092
10942
  const reports = [];
11093
10943
  const errors = [];
11094
10944
  const checkedTopics = [];
11095
10945
  const skippedTopics = [];
11096
10946
  const groupPostResults = /* @__PURE__ */ new Map();
10947
+ const upgradedSlugs = /* @__PURE__ */ new Set();
11097
10948
  for (const [_key, entry] of allEntries) {
11098
10949
  const capsuleDir = path11.join(projectsBase, entry.slug);
11099
10950
  const statusForEligibility = readFileOrNull(path11.join(capsuleDir, "STATUS.md"));
@@ -11104,12 +10955,22 @@ async function handleDoctorAll(ctx) {
11104
10955
  continue;
11105
10956
  }
11106
10957
  try {
10958
+ if (entry.capsuleVersion < CAPSULE_VERSION) {
10959
+ const oldVersion = entry.capsuleVersion;
10960
+ try {
10961
+ upgradeCapsule(projectsBase, entry.slug, entry.name, entry.type, entry.capsuleVersion);
10962
+ upgradedSlugs.add(entry.slug);
10963
+ logger.info(`[doctor-all] Auto-upgraded ${entry.slug} v${oldVersion} \u2192 v${CAPSULE_VERSION}`);
10964
+ } catch (err) {
10965
+ const msg = err instanceof Error ? err.message : String(err);
10966
+ logger.error(`[doctor-all] Auto-upgrade failed for ${entry.slug}: ${msg}`);
10967
+ }
10968
+ }
11107
10969
  const results = runAllChecksForTopic(
11108
10970
  entry,
11109
10971
  projectsBase,
11110
10972
  includeContent,
11111
- registry,
11112
- cronJobsPath
10973
+ registry
11113
10974
  );
11114
10975
  const isSpam = entry.consecutiveSilentDoctors >= SPAM_THRESHOLD;
11115
10976
  if (isSpam) {
@@ -11211,6 +11072,9 @@ async function handleDoctorAll(ctx) {
11211
11072
  entry.consecutiveSilentDoctors++;
11212
11073
  }
11213
11074
  entry.lastDoctorRunAt = now.toISOString();
11075
+ if (upgradedSlugs.has(entry.slug)) {
11076
+ entry.capsuleVersion = CAPSULE_VERSION;
11077
+ }
11214
11078
  if (entry.consecutiveSilentDoctors >= SPAM_THRESHOLD) {
11215
11079
  entry.snoozeUntil = new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString();
11216
11080
  entry.status = "snoozed";
@@ -11310,10 +11174,10 @@ function hasBlockers(blockers) {
11310
11174
  return blockers !== "None." && blockers !== "No tasks recorded yet.";
11311
11175
  }
11312
11176
  function formatStatus(data) {
11313
- const { name, type, statusContent, todoContent, expanded } = data;
11177
+ const { name, type, statusContent, expanded } = data;
11314
11178
  const timestamp = extractTimestamp(statusContent);
11315
11179
  const nextRaw = extractSection(statusContent, NEXT_ACTIONS_RE2);
11316
- const blockers = extractBlockers(todoContent);
11180
+ const blockers = extractBlockers(statusContent);
11317
11181
  const doneMatch = statusContent.match(LAST_DONE_RE2);
11318
11182
  let lastDoneBody = "";
11319
11183
  if (doneMatch) {
@@ -11378,13 +11242,11 @@ async function handleStatus2(ctx, args) {
11378
11242
  const expanded = args.trim() === "--expanded";
11379
11243
  try {
11380
11244
  const statusContent = fs12.readFileSync(statusPath, "utf-8");
11381
- const todoContent = readFileOrNull(path12.join(capsuleDir, "TODO.md"));
11382
11245
  return {
11383
11246
  text: truncateMessage(formatStatus({
11384
11247
  name: entry.name,
11385
11248
  type: entry.type,
11386
11249
  statusContent,
11387
- todoContent,
11388
11250
  expanded
11389
11251
  }))
11390
11252
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-telegram-manager",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "type": "module",
5
5
  "description": "Deterministic Telegram forum topic management for OpenClaw",
6
6
  "keywords": [
@@ -15,27 +15,33 @@ If you detect any of these conditions, invoke `topic_manager` proactively:
15
15
 
16
16
  1. **After /reset, /new, or context compaction**: call `topic_manager` with
17
17
  command "status" to re-read the topic files and rehydrate context.
18
- Rehydration order: STATUS.md, TODO.md, LEARNINGS.md (last 20 entries),
19
- COMMANDS.md, LINKS.md, then any overlay files present in the topic directory.
18
+ Rehydration order: STATUS.md, then LEARNINGS.md (last 20 entries),
19
+ then README.md for topic context.
20
20
  2. **Before context gets large**: proactively flush current progress to
21
21
  STATUS.md using the standard file write tool (update "Last done (UTC)"
22
22
  and "Next actions (now)"). Do NOT route this through /tm — write directly.
23
23
  3. **When you notice a topic has no persistent memory**: suggest `/tm init`.
24
24
  4. **When you discover something unexpected** (a mistake, workaround, or
25
25
  constraint): prepend a dated entry to LEARNINGS.md in the topic folder.
26
- 5. **When the user shares important project information** that would be needed
27
- after a session reset, persist it immediately to the appropriate topic file.
28
- Don't wait for the user to ask this is the first context lost on reset.
26
+ 5. **When you see a fresh topic** (STATUS.md says "Waiting for first instructions"
27
+ or README.md has unfilled context sections like `_Describe what this topic is about._`),
28
+ proactively ask the user 2-3 questions about what this topic is about, what the
29
+ goal is, and where the key resources are. Persist answers to README.md immediately.
30
+ 6. **When the user shares important project information** that would be needed
31
+ after a session reset, persist it immediately to the appropriate section
32
+ in README.md. Don't wait for the user to ask — this is the first context
33
+ lost on reset.
29
34
 
30
35
  Examples by topic type:
31
36
  - **Coding**: repository paths, runtime/data paths, branch names, service URLs,
32
- environment details → LINKS.md, DEPLOY.md, ARCHITECTURE.md
37
+ environment details, build/test/deploy commands README.md sections
38
+ (Architecture, Deployment, Commands, Key resources)
33
39
  - **Research**: key sources, data locations, API endpoints, methodology decisions
34
- SOURCES.md, LINKS.md, FINDINGS.md
40
+ README.md sections (Sources, Findings, Key resources)
35
41
  - **Marketing**: campaign URLs, analytics dashboards, social accounts, brand
36
- guidelines location → CAMPAIGNS.md, LINKS.md, METRICS.md
42
+ guidelines location → README.md sections (Campaigns, Metrics, Key resources)
37
43
  - **General / any type**: reference URLs, contacts, key decisions, file paths
38
- LINKS.md, NOTES.md
44
+ README.md sections (Key resources)
39
45
 
40
46
  Rule of thumb: if losing this information would cause the agent to make a
41
47
  wrong assumption after reset, it must be written down now.