openclaw-telegram-manager 2.7.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,17 +9136,65 @@ 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}
9140
+
9141
+ ## What is this about?
9152
9142
 
9153
9143
  _Describe what this topic is about._
9144
+
9145
+ ## Goal
9146
+
9147
+ _What does success look like?_
9148
+
9149
+ ## Key resources
9150
+
9151
+ _URLs, repos, paths, dashboards \u2014 anything the AI needs after a reset._
9152
+ `;
9153
+ var README_TYPE_SECTIONS = {
9154
+ coding: () => `
9155
+ ## Architecture
9156
+
9157
+ _Components, data flow, key decisions._
9158
+
9159
+ ## Deployment
9160
+
9161
+ _Environments, deploy steps, rollback._
9162
+
9163
+ ## Commands
9164
+
9165
+ _Build, test, deploy \u2014 kept here so they survive a reset._
9154
9166
  `,
9167
+ research: () => `
9168
+ ## Sources
9169
+
9170
+ _Papers, articles, datasets, APIs._
9171
+
9172
+ ## Findings
9173
+
9174
+ _Key findings, evidence, recommendations._
9175
+ `,
9176
+ marketing: () => `
9177
+ ## Campaigns
9178
+
9179
+ _Active campaigns, audiences, channels._
9180
+
9181
+ ## Metrics
9182
+
9183
+ _KPIs, targets, tracking dashboards._
9184
+ `,
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
+ },
9155
9193
  "STATUS.md": (name) => `# Status: ${name}
9156
9194
 
9157
9195
  ## Last done (UTC)
9158
9196
 
9159
9197
  ${(/* @__PURE__ */ new Date()).toISOString()}
9160
-
9161
9198
  Topic created. Waiting for first instructions.
9162
9199
 
9163
9200
  ## Next actions (now)
@@ -9167,34 +9204,14 @@ _None yet._
9167
9204
  ## Upcoming actions
9168
9205
 
9169
9206
  _None yet._
9170
- `,
9171
- "TODO.md": (name) => `# TODO: ${name}
9172
9207
 
9173
9208
  ## Backlog
9174
9209
 
9175
9210
  - [T-1] _e.g. Set up project scaffolding_
9176
- - [T-2] _Waiting for next task_
9177
- - [T-3] _Waiting for next task_
9178
9211
 
9179
9212
  ## Completed
9180
9213
 
9181
9214
  _None yet._
9182
- `,
9183
- "COMMANDS.md": (name) => `# Commands: ${name}
9184
-
9185
- _Build, deploy, test, and other commands for this topic. Kept here so they're not lost on reset._
9186
- `,
9187
- "LINKS.md": (name) => `# Links: ${name}
9188
-
9189
- _URLs, paths, and service endpoints for this topic._
9190
- `,
9191
- "CRON.md": (name) => `# Cron: ${name}
9192
-
9193
- _Cron job IDs and schedules for this topic._
9194
- `,
9195
- "NOTES.md": (name) => `# Notes: ${name}
9196
-
9197
- _Anything worth remembering about this topic._
9198
9215
  `,
9199
9216
  "LEARNINGS.md": (name) => `# Learnings: ${name}
9200
9217
 
@@ -9202,32 +9219,6 @@ _Hard-won insights, mistakes, and workarounds._
9202
9219
  _Agent prepends here automatically. Most recent entries first._
9203
9220
  `
9204
9221
  };
9205
- var OVERLAY_TEMPLATES = {
9206
- "ARCHITECTURE.md": (name) => `# Architecture: ${name}
9207
-
9208
- _Components, data flow, dependencies, and design decisions._
9209
- `,
9210
- "DEPLOY.md": (name) => `# Deployment: ${name}
9211
-
9212
- _Environments, deployment steps, rollback procedures, and infra details._
9213
- `,
9214
- "SOURCES.md": (name) => `# Sources: ${name}
9215
-
9216
- _Papers, articles, datasets, APIs, and other reference material._
9217
- `,
9218
- "FINDINGS.md": (name) => `# Findings: ${name}
9219
-
9220
- _Conclusions, insights, data summaries, and recommendations._
9221
- `,
9222
- "CAMPAIGNS.md": (name) => `# Campaigns: ${name}
9223
-
9224
- _Active campaigns, target audiences, channels, timelines, and budgets._
9225
- `,
9226
- "METRICS.md": (name) => `# Metrics: ${name}
9227
-
9228
- _KPIs, conversion rates, engagement stats, and performance data._
9229
- `
9230
- };
9231
9222
  var CAPSULE_FILE_MODE = 416;
9232
9223
  function scaffoldCapsule(projectsBase, slug, name, type) {
9233
9224
  const capsuleDir = path4.join(projectsBase, slug);
@@ -9242,15 +9233,7 @@ function scaffoldCapsule(projectsBase, slug, name, type) {
9242
9233
  const templateFn = BASE_TEMPLATES[file];
9243
9234
  if (templateFn) {
9244
9235
  const filePath = path4.join(capsuleDir, file);
9245
- fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
9246
- }
9247
- }
9248
- const overlays = OVERLAY_FILES[type];
9249
- for (const file of overlays) {
9250
- const templateFn = OVERLAY_TEMPLATES[file];
9251
- if (templateFn) {
9252
- const filePath = path4.join(capsuleDir, file);
9253
- fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
9236
+ fs4.writeFileSync(filePath, templateFn(name, type), { mode: CAPSULE_FILE_MODE });
9254
9237
  }
9255
9238
  }
9256
9239
  }
@@ -9271,19 +9254,28 @@ function upgradeCapsule(projectsBase, slug, name, type, currentVersion) {
9271
9254
  if (!fs4.existsSync(filePath)) {
9272
9255
  const templateFn = BASE_TEMPLATES[file];
9273
9256
  if (templateFn) {
9274
- fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
9257
+ fs4.writeFileSync(filePath, templateFn(name, type), { mode: CAPSULE_FILE_MODE });
9275
9258
  addedFiles.push(file);
9276
9259
  }
9277
9260
  }
9278
9261
  }
9279
- const overlays = OVERLAY_FILES[type];
9280
- for (const file of overlays) {
9281
- const filePath = path4.join(capsuleDir, file);
9282
- if (!fs4.existsSync(filePath)) {
9283
- const templateFn = OVERLAY_TEMPLATES[file];
9284
- if (templateFn) {
9285
- fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
9286
- 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
+ }
9287
9279
  }
9288
9280
  }
9289
9281
  }
@@ -9644,8 +9636,8 @@ Context hierarchy (strictly ordered):
9644
9636
 
9645
9637
  Determinism rules:
9646
9638
  - After /reset, /new, or context compaction: ALWAYS re-read your topic files
9647
- first \u2014 STATUS.md, then TODO.md, then LEARNINGS.md (last 20 entries), then
9648
- 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.
9649
9641
  Do not rely on summarized memory or workspace-level files for this topic's
9650
9642
  tasks, status, or goals.
9651
9643
  - Before context compaction or when the conversation is long: proactively
@@ -9653,13 +9645,12 @@ Determinism rules:
9653
9645
  "Next actions (now)") so compaction cannot erase critical state.
9654
9646
  Use the standard file write tool directly \u2014 do not route through /tm.
9655
9647
  - Keep STATUS.md accurate: always maintain "Last done (UTC)", "Next actions (now)",
9656
- and "Upcoming actions".
9657
- - When new commands appear, add them to COMMANDS.md (don't leave them only in chat).
9658
- - When new links/paths/services appear, add them to LINKS.md.
9659
- - If automation/cron is involved, record job IDs + schedules in CRON.md.
9660
- - 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.
9661
9651
  - STATUS.md has two priority sections: "Next actions (now)" for immediate work
9662
9652
  and "Upcoming actions" for the near-future pipeline.
9653
+ - Key resources, commands, and context belong in README.md.
9663
9654
 
9664
9655
  Learning capture:
9665
9656
  - When you discover something unexpected, a mistake, a workaround, or a
@@ -10368,19 +10359,6 @@ function runCapsuleChecks(entry, projectsBase) {
10368
10359
  check(Severity.ERROR, "statusMissing", "Status file is missing", true, "Run /tm upgrade to recreate it")
10369
10360
  );
10370
10361
  }
10371
- if (!fs8.existsSync(path8.join(capsuleDir, "TODO.md"))) {
10372
- results.push(
10373
- check(Severity.WARN, "todoMissing", "TODO file is missing", true, "Run /tm upgrade to recreate it")
10374
- );
10375
- }
10376
- const overlays = OVERLAY_FILES[entry.type] ?? [];
10377
- for (const file of overlays) {
10378
- if (!fs8.existsSync(path8.join(capsuleDir, file))) {
10379
- results.push(
10380
- check(Severity.INFO, `overlayMissing:${file}`, `Optional overlay ${file} missing for type "${entry.type}"`, true)
10381
- );
10382
- }
10383
- }
10384
10362
  if (entry.capsuleVersion < CAPSULE_VERSION) {
10385
10363
  results.push(
10386
10364
  check(
@@ -10457,7 +10435,8 @@ function runStatusQualityChecks(statusContent, entry) {
10457
10435
  }
10458
10436
  return results;
10459
10437
  }
10460
- function runNextVsTodoChecks(statusContent, todoContent) {
10438
+ var BACKLOG_RE = /^##\s*Backlog/im;
10439
+ function runNextVsBacklogChecks(statusContent) {
10461
10440
  const results = [];
10462
10441
  const nextActionsIndex = statusContent.search(NEXT_ACTIONS_RE);
10463
10442
  if (nextActionsIndex < 0) return results;
@@ -10466,14 +10445,19 @@ function runNextVsTodoChecks(statusContent, todoContent) {
10466
10445
  const nextActionsSection = nextSectionIndex > 0 ? sectionAfter.slice(0, nextSectionIndex) : sectionAfter;
10467
10446
  const nextTaskIds = nextActionsSection.match(TASK_ID_RE) ?? [];
10468
10447
  if (nextTaskIds.length === 0) return results;
10469
- const todoTaskIds = new Set(todoContent.match(TASK_ID_RE) ?? []);
10470
- 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));
10471
10455
  if (missing.length >= 2) {
10472
10456
  results.push(
10473
10457
  check(
10474
10458
  Severity.WARN,
10475
- "nextNotInTodo",
10476
- `${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(", ")}`,
10477
10461
  false,
10478
10462
  "The AI will clean these up on next interaction"
10479
10463
  )
@@ -10481,62 +10465,18 @@ function runNextVsTodoChecks(statusContent, todoContent) {
10481
10465
  }
10482
10466
  return results;
10483
10467
  }
10484
- function runCommandsLinksChecks(entry, capsuleFiles) {
10485
- const results = [];
10486
- if (entry.type === "coding") {
10487
- const commandsContent = capsuleFiles.get("COMMANDS.md");
10488
- if (commandsContent !== void 0 && isEffectivelyEmpty(commandsContent)) {
10489
- results.push(
10490
- check(Severity.INFO, "commandsEmpty", "COMMANDS.md is empty for a coding topic", false, "Add build/test/deploy commands to COMMANDS.md")
10491
- );
10492
- }
10493
- }
10494
- if (entry.type === "coding" || entry.type === "research") {
10495
- const linksContent = capsuleFiles.get("LINKS.md");
10496
- if (linksContent !== void 0 && isEffectivelyEmpty(linksContent)) {
10497
- results.push(
10498
- check(Severity.INFO, "linksEmpty", "LINKS.md is empty for a coding/research topic", false, "Add URLs and endpoints to LINKS.md")
10499
- );
10500
- }
10501
- }
10502
- return results;
10503
- }
10504
- function isEffectivelyEmpty(content) {
10505
- const stripped = content.replace(/^#.*$/gm, "").replace(/^_.*_$/gm, "").replace(/\s+/g, "").trim();
10506
- return stripped.length === 0;
10507
- }
10508
- var JOB_ID_RE = /[a-zA-Z0-9_-]{8,}/;
10509
- function runCronChecks(cronContent, cronJobsPath) {
10468
+ function runUnfilledContextCheck(capsuleFiles) {
10510
10469
  const results = [];
10511
- if (isEffectivelyEmpty(cronContent)) return results;
10512
- const lines = cronContent.split("\n").filter((l) => !l.startsWith("#") && l.trim().length > 0);
10513
- const hasJobIds = lines.some((line) => JOB_ID_RE.test(line));
10514
- if (!hasJobIds) {
10470
+ const readmeContent = capsuleFiles.get("README.md");
10471
+ if (readmeContent && readmeContent.includes("_Describe what this topic is about._")) {
10515
10472
  results.push(
10516
- 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
+ )
10517
10479
  );
10518
- return results;
10519
- }
10520
- if (cronJobsPath && fs8.existsSync(cronJobsPath)) {
10521
- try {
10522
- const jobsRaw = fs8.readFileSync(cronJobsPath, "utf-8");
10523
- const jobs = JSON.parse(jobsRaw);
10524
- const knownJobIds = new Set(Object.keys(jobs));
10525
- for (const line of lines) {
10526
- const match = line.match(JOB_ID_RE);
10527
- if (match && !knownJobIds.has(match[0])) {
10528
- results.push(
10529
- check(
10530
- Severity.WARN,
10531
- "cronJobNotFound",
10532
- `Scheduled job "${match[0]}" not found in the jobs registry`,
10533
- false
10534
- )
10535
- );
10536
- }
10537
- }
10538
- } catch {
10539
- }
10540
10480
  }
10541
10481
  return results;
10542
10482
  }
@@ -10634,7 +10574,7 @@ function runPostErrorCheck(entry) {
10634
10574
  }
10635
10575
  return results;
10636
10576
  }
10637
- function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cronJobsPath) {
10577
+ function runAllChecksForTopic(entry, projectsBase, includeContent, registry) {
10638
10578
  const results = [];
10639
10579
  const capsuleDir = path8.join(projectsBase, entry.slug);
10640
10580
  results.push(...runRegistryChecks(entry, projectsBase));
@@ -10645,16 +10585,9 @@ function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cro
10645
10585
  const statusContent = capsuleFiles.get("STATUS.md");
10646
10586
  if (statusContent) {
10647
10587
  results.push(...runStatusQualityChecks(statusContent, entry));
10648
- const todoContent = capsuleFiles.get("TODO.md");
10649
- if (todoContent) {
10650
- results.push(...runNextVsTodoChecks(statusContent, todoContent));
10651
- }
10652
- }
10653
- results.push(...runCommandsLinksChecks(entry, capsuleFiles));
10654
- const cronContent = capsuleFiles.get("CRON.md");
10655
- if (cronContent) {
10656
- results.push(...runCronChecks(cronContent, cronJobsPath));
10588
+ results.push(...runNextVsBacklogChecks(statusContent));
10657
10589
  }
10590
+ results.push(...runUnfilledContextCheck(capsuleFiles));
10658
10591
  if (includeContent && registry) {
10659
10592
  results.push(...runConfigChecks(entry, includeContent, registry));
10660
10593
  }
@@ -10665,7 +10598,7 @@ function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cro
10665
10598
  return results;
10666
10599
  }
10667
10600
  var BACKUP_DIR = ".tm-backup";
10668
- var BACKUP_FILES = ["STATUS.md", "TODO.md"];
10601
+ var BACKUP_FILES = ["STATUS.md"];
10669
10602
  function backupCapsuleIfHealthy(projectsBase, slug, results) {
10670
10603
  const hasIssues = results.some((r) => r.severity === Severity.ERROR || r.severity === Severity.WARN);
10671
10604
  if (hasIssues) return;
@@ -10684,22 +10617,7 @@ function backupCapsuleIfHealthy(projectsBase, slug, results) {
10684
10617
  }
10685
10618
  function readCapsuleFiles(capsuleDir) {
10686
10619
  const files = /* @__PURE__ */ new Map();
10687
- const filenames = [
10688
- "STATUS.md",
10689
- "TODO.md",
10690
- "COMMANDS.md",
10691
- "LINKS.md",
10692
- "CRON.md",
10693
- "NOTES.md",
10694
- "README.md",
10695
- "LEARNINGS.md",
10696
- "ARCHITECTURE.md",
10697
- "DEPLOY.md",
10698
- "SOURCES.md",
10699
- "FINDINGS.md",
10700
- "CAMPAIGNS.md",
10701
- "METRICS.md"
10702
- ];
10620
+ const filenames = ["README.md", "STATUS.md", "LEARNINGS.md"];
10703
10621
  for (const name of filenames) {
10704
10622
  const filePath = path8.join(capsuleDir, name);
10705
10623
  try {
@@ -10744,13 +10662,11 @@ async function handleDoctor(ctx) {
10744
10662
  }
10745
10663
  } catch {
10746
10664
  }
10747
- const cronJobsPath = path9.join(configDir, "cron", "jobs.json");
10748
10665
  const results = runAllChecksForTopic(
10749
10666
  entry,
10750
10667
  projectsBase,
10751
10668
  includeContent,
10752
- registry,
10753
- cronJobsPath
10669
+ registry
10754
10670
  );
10755
10671
  backupCapsuleIfHealthy(projectsBase, entry.slug, results);
10756
10672
  const reportText = buildDoctorReport(entry.name, results);
@@ -10803,9 +10719,8 @@ async function handleDailyReport(ctx) {
10803
10719
  return { text: "Topic files not found. Run /tm init to set up this topic." };
10804
10720
  }
10805
10721
  const statusContent = readFileOrNull(path10.join(capsuleDir, "STATUS.md"));
10806
- const todoContent = readFileOrNull(path10.join(capsuleDir, "TODO.md"));
10807
10722
  const doneContent = extractDoneSection(statusContent);
10808
- const blockers = extractBlockers(todoContent);
10723
+ const blockers = extractBlockers(statusContent);
10809
10724
  const nextContent = extractNextActions(statusContent);
10810
10725
  const reportData = {
10811
10726
  name: entry.name,
@@ -10859,9 +10774,11 @@ function extractDoneSection(statusContent) {
10859
10774
  const text = match[1]?.trim();
10860
10775
  return text || "Empty.";
10861
10776
  }
10862
- function extractBlockers(todoContent) {
10863
- if (!todoContent) return "No tasks recorded yet.";
10864
- 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");
10865
10782
  const blockerLines = lines.filter(
10866
10783
  (l) => /\[BLOCKED\]/i.test(l) || /\bblocked\b/i.test(l)
10867
10784
  );
@@ -11021,13 +10938,13 @@ async function handleDoctorAll(ctx) {
11021
10938
  }
11022
10939
  } catch {
11023
10940
  }
11024
- const cronJobsPath = path11.join(configDir, "cron", "jobs.json");
11025
10941
  const allEntries = Object.entries(registry.topics);
11026
10942
  const reports = [];
11027
10943
  const errors = [];
11028
10944
  const checkedTopics = [];
11029
10945
  const skippedTopics = [];
11030
10946
  const groupPostResults = /* @__PURE__ */ new Map();
10947
+ const upgradedSlugs = /* @__PURE__ */ new Set();
11031
10948
  for (const [_key, entry] of allEntries) {
11032
10949
  const capsuleDir = path11.join(projectsBase, entry.slug);
11033
10950
  const statusForEligibility = readFileOrNull(path11.join(capsuleDir, "STATUS.md"));
@@ -11038,12 +10955,22 @@ async function handleDoctorAll(ctx) {
11038
10955
  continue;
11039
10956
  }
11040
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
+ }
11041
10969
  const results = runAllChecksForTopic(
11042
10970
  entry,
11043
10971
  projectsBase,
11044
10972
  includeContent,
11045
- registry,
11046
- cronJobsPath
10973
+ registry
11047
10974
  );
11048
10975
  const isSpam = entry.consecutiveSilentDoctors >= SPAM_THRESHOLD;
11049
10976
  if (isSpam) {
@@ -11145,6 +11072,9 @@ async function handleDoctorAll(ctx) {
11145
11072
  entry.consecutiveSilentDoctors++;
11146
11073
  }
11147
11074
  entry.lastDoctorRunAt = now.toISOString();
11075
+ if (upgradedSlugs.has(entry.slug)) {
11076
+ entry.capsuleVersion = CAPSULE_VERSION;
11077
+ }
11148
11078
  if (entry.consecutiveSilentDoctors >= SPAM_THRESHOLD) {
11149
11079
  entry.snoozeUntil = new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString();
11150
11080
  entry.status = "snoozed";
@@ -11244,10 +11174,10 @@ function hasBlockers(blockers) {
11244
11174
  return blockers !== "None." && blockers !== "No tasks recorded yet.";
11245
11175
  }
11246
11176
  function formatStatus(data) {
11247
- const { name, type, statusContent, todoContent, expanded } = data;
11177
+ const { name, type, statusContent, expanded } = data;
11248
11178
  const timestamp = extractTimestamp(statusContent);
11249
11179
  const nextRaw = extractSection(statusContent, NEXT_ACTIONS_RE2);
11250
- const blockers = extractBlockers(todoContent);
11180
+ const blockers = extractBlockers(statusContent);
11251
11181
  const doneMatch = statusContent.match(LAST_DONE_RE2);
11252
11182
  let lastDoneBody = "";
11253
11183
  if (doneMatch) {
@@ -11312,13 +11242,11 @@ async function handleStatus2(ctx, args) {
11312
11242
  const expanded = args.trim() === "--expanded";
11313
11243
  try {
11314
11244
  const statusContent = fs12.readFileSync(statusPath, "utf-8");
11315
- const todoContent = readFileOrNull(path12.join(capsuleDir, "TODO.md"));
11316
11245
  return {
11317
11246
  text: truncateMessage(formatStatus({
11318
11247
  name: entry.name,
11319
11248
  type: entry.type,
11320
11249
  statusContent,
11321
- todoContent,
11322
11250
  expanded
11323
11251
  }))
11324
11252
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-telegram-manager",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "type": "module",
5
5
  "description": "Deterministic Telegram forum topic management for OpenClaw",
6
6
  "keywords": [
@@ -15,14 +15,36 @@ 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
- then COMMANDS.md.
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 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.
34
+
35
+ Examples by topic type:
36
+ - **Coding**: repository paths, runtime/data paths, branch names, service URLs,
37
+ environment details, build/test/deploy commands → README.md sections
38
+ (Architecture, Deployment, Commands, Key resources)
39
+ - **Research**: key sources, data locations, API endpoints, methodology decisions
40
+ → README.md sections (Sources, Findings, Key resources)
41
+ - **Marketing**: campaign URLs, analytics dashboards, social accounts, brand
42
+ guidelines location → README.md sections (Campaigns, Metrics, Key resources)
43
+ - **General / any type**: reference URLs, contacts, key decisions, file paths
44
+ → README.md sections (Key resources)
45
+
46
+ Rule of thumb: if losing this information would cause the agent to make a
47
+ wrong assumption after reset, it must be written down now.
26
48
 
27
49
  ## Autopilot context
28
50