openclaw-telegram-manager 1.0.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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/dist/commands/archive.d.ts +4 -0
  4. package/dist/commands/archive.d.ts.map +1 -0
  5. package/dist/commands/archive.js +71 -0
  6. package/dist/commands/archive.js.map +1 -0
  7. package/dist/commands/doctor-all.d.ts +3 -0
  8. package/dist/commands/doctor-all.d.ts.map +1 -0
  9. package/dist/commands/doctor-all.js +193 -0
  10. package/dist/commands/doctor-all.js.map +1 -0
  11. package/dist/commands/doctor.d.ts +3 -0
  12. package/dist/commands/doctor.d.ts.map +1 -0
  13. package/dist/commands/doctor.js +74 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/help.d.ts +4 -0
  16. package/dist/commands/help.d.ts.map +1 -0
  17. package/dist/commands/help.js +8 -0
  18. package/dist/commands/help.js.map +1 -0
  19. package/dist/commands/init.d.ts +17 -0
  20. package/dist/commands/init.d.ts.map +1 -0
  21. package/dist/commands/init.js +304 -0
  22. package/dist/commands/init.js.map +1 -0
  23. package/dist/commands/list.d.ts +3 -0
  24. package/dist/commands/list.d.ts.map +1 -0
  25. package/dist/commands/list.js +22 -0
  26. package/dist/commands/list.js.map +1 -0
  27. package/dist/commands/rename.d.ts +3 -0
  28. package/dist/commands/rename.d.ts.map +1 -0
  29. package/dist/commands/rename.js +115 -0
  30. package/dist/commands/rename.js.map +1 -0
  31. package/dist/commands/snooze.d.ts +3 -0
  32. package/dist/commands/snooze.d.ts.map +1 -0
  33. package/dist/commands/snooze.js +52 -0
  34. package/dist/commands/snooze.js.map +1 -0
  35. package/dist/commands/status.d.ts +3 -0
  36. package/dist/commands/status.d.ts.map +1 -0
  37. package/dist/commands/status.js +48 -0
  38. package/dist/commands/status.js.map +1 -0
  39. package/dist/commands/sync.d.ts +3 -0
  40. package/dist/commands/sync.d.ts.map +1 -0
  41. package/dist/commands/sync.js +38 -0
  42. package/dist/commands/sync.js.map +1 -0
  43. package/dist/commands/upgrade.d.ts +3 -0
  44. package/dist/commands/upgrade.d.ts.map +1 -0
  45. package/dist/commands/upgrade.js +52 -0
  46. package/dist/commands/upgrade.js.map +1 -0
  47. package/dist/index.d.ts +25 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +30 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/lib/audit.d.ts +12 -0
  52. package/dist/lib/audit.d.ts.map +1 -0
  53. package/dist/lib/audit.js +35 -0
  54. package/dist/lib/audit.js.map +1 -0
  55. package/dist/lib/auth.d.ts +26 -0
  56. package/dist/lib/auth.d.ts.map +1 -0
  57. package/dist/lib/auth.js +73 -0
  58. package/dist/lib/auth.js.map +1 -0
  59. package/dist/lib/capsule.d.ts +27 -0
  60. package/dist/lib/capsule.d.ts.map +1 -0
  61. package/dist/lib/capsule.js +130 -0
  62. package/dist/lib/capsule.js.map +1 -0
  63. package/dist/lib/config-restart.d.ts +23 -0
  64. package/dist/lib/config-restart.d.ts.map +1 -0
  65. package/dist/lib/config-restart.js +129 -0
  66. package/dist/lib/config-restart.js.map +1 -0
  67. package/dist/lib/doctor-checks.d.ts +50 -0
  68. package/dist/lib/doctor-checks.d.ts.map +1 -0
  69. package/dist/lib/doctor-checks.js +421 -0
  70. package/dist/lib/doctor-checks.js.map +1 -0
  71. package/dist/lib/include-generator.d.ts +35 -0
  72. package/dist/lib/include-generator.d.ts.map +1 -0
  73. package/dist/lib/include-generator.js +140 -0
  74. package/dist/lib/include-generator.js.map +1 -0
  75. package/dist/lib/registry.d.ts +27 -0
  76. package/dist/lib/registry.d.ts.map +1 -0
  77. package/dist/lib/registry.js +154 -0
  78. package/dist/lib/registry.js.map +1 -0
  79. package/dist/lib/security.d.ts +57 -0
  80. package/dist/lib/security.d.ts.map +1 -0
  81. package/dist/lib/security.js +133 -0
  82. package/dist/lib/security.js.map +1 -0
  83. package/dist/lib/telegram.d.ts +55 -0
  84. package/dist/lib/telegram.d.ts.map +1 -0
  85. package/dist/lib/telegram.js +254 -0
  86. package/dist/lib/telegram.js.map +1 -0
  87. package/dist/lib/types.d.ts +120 -0
  88. package/dist/lib/types.d.ts.map +1 -0
  89. package/dist/lib/types.js +85 -0
  90. package/dist/lib/types.js.map +1 -0
  91. package/dist/setup.d.ts +3 -0
  92. package/dist/setup.d.ts.map +1 -0
  93. package/dist/setup.js +333 -0
  94. package/dist/setup.js.map +1 -0
  95. package/dist/tool.d.ts +15 -0
  96. package/dist/tool.d.ts.map +1 -0
  97. package/dist/tool.js +201 -0
  98. package/dist/tool.js.map +1 -0
  99. package/openclaw.plugin.json +9 -0
  100. package/package.json +48 -0
  101. package/skills/topic/SKILL.md +35 -0
  102. package/src/commands/archive.ts +89 -0
  103. package/src/commands/doctor-all.ts +243 -0
  104. package/src/commands/doctor.ts +100 -0
  105. package/src/commands/help.ts +11 -0
  106. package/src/commands/init.ts +376 -0
  107. package/src/commands/list.ts +28 -0
  108. package/src/commands/rename.ts +140 -0
  109. package/src/commands/snooze.ts +69 -0
  110. package/src/commands/status.ts +59 -0
  111. package/src/commands/sync.ts +46 -0
  112. package/src/commands/upgrade.ts +64 -0
  113. package/src/index.ts +54 -0
  114. package/src/lib/audit.ts +44 -0
  115. package/src/lib/auth.ts +96 -0
  116. package/src/lib/capsule.ts +206 -0
  117. package/src/lib/config-restart.ts +167 -0
  118. package/src/lib/doctor-checks.ts +639 -0
  119. package/src/lib/include-generator.ts +174 -0
  120. package/src/lib/registry.ts +197 -0
  121. package/src/lib/security.ts +174 -0
  122. package/src/lib/telegram.ts +311 -0
  123. package/src/lib/types.ts +172 -0
  124. package/src/setup.ts +402 -0
  125. package/src/templates/base/COMMANDS.md +3 -0
  126. package/src/templates/base/CRON.md +3 -0
  127. package/src/templates/base/LINKS.md +3 -0
  128. package/src/templates/base/NOTES.md +3 -0
  129. package/src/templates/base/README.md +3 -0
  130. package/src/templates/base/STATUS.md +13 -0
  131. package/src/templates/base/TODO.md +11 -0
  132. package/src/templates/overlays/coding/ARCHITECTURE.md +3 -0
  133. package/src/templates/overlays/coding/DEPLOY.md +3 -0
  134. package/src/templates/overlays/marketing/CAMPAIGNS.md +3 -0
  135. package/src/templates/overlays/marketing/METRICS.md +3 -0
  136. package/src/templates/overlays/research/FINDINGS.md +3 -0
  137. package/src/templates/overlays/research/SOURCES.md +3 -0
  138. package/src/tool.ts +282 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenClaw Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # openclaw-telegram-manager
2
+
3
+ [![CI](https://github.com/jcoulaud/openclaw-telegram-manager/actions/workflows/ci.yml/badge.svg)](https://github.com/jcoulaud/openclaw-telegram-manager/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/openclaw-telegram-manager)](https://www.npmjs.com/package/openclaw-telegram-manager)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ An [OpenClaw](https://openclaw.ai) plugin that gives each Telegram forum topic its own persistent workspace — status, todos, commands, links, notes — so nothing gets lost when the agent resets or context gets compacted.
8
+
9
+ ## The problem
10
+
11
+ When OpenClaw manages a Telegram group with forum topics, each topic is basically a separate project. But after a reset or context compaction, the agent forgets everything: what it was working on, what's left to do, what commands matter.
12
+
13
+ This plugin fixes that. Each topic gets a folder of markdown files (a "capsule") that the agent reads on startup. It picks up right where it left off.
14
+
15
+ ## Getting started
16
+
17
+ Requires OpenClaw `>=2026.1.0`.
18
+
19
+ ```bash
20
+ npx openclaw-telegram-manager setup
21
+ ```
22
+
23
+ That's it. The setup script installs the plugin, patches your config, creates the workspace, and restarts the Gateway. It's idempotent — running it twice won't break anything.
24
+
25
+ Once that's done:
26
+
27
+ 1. Open any topic in your OpenClaw-managed Telegram group
28
+ 2. Type `/topic init`
29
+ 3. Confirm the suggested slug, then pick a topic type
30
+ 4. The plugin creates a capsule folder and confirms in chat
31
+ 5. From now on, the agent reads the capsule on every session start — no context lost
32
+
33
+ You can also skip the interactive flow: `/topic init my-project coding`
34
+
35
+ ## Usage
36
+
37
+ | Command | What it does |
38
+ |---------|-------------|
39
+ | `/topic init` | Interactive setup — confirm slug, pick type |
40
+ | `/topic init <slug> [type]` | One-step setup. Types: `coding`, `research`, `marketing`, `custom` |
41
+ | `/topic status` | Show the current STATUS.md |
42
+ | `/topic list` | List all topics, grouped by status |
43
+ | `/topic doctor` | Run health checks on the current topic |
44
+ | `/topic doctor --all` | Health check all active topics at once |
45
+ | `/topic sync` | Regenerate the include file from the registry |
46
+ | `/topic rename <new-slug>` | Rename a topic |
47
+ | `/topic upgrade` | Upgrade the capsule to the latest template version |
48
+ | `/topic snooze <duration>` | Snooze a topic (e.g. `7d`, `30d`) |
49
+ | `/topic archive` | Archive a topic |
50
+ | `/topic unarchive` | Bring back an archived topic |
51
+ | `/topic help` | Show this command list in Telegram |
52
+
53
+ ## What's in a capsule
54
+
55
+ Each topic gets a folder at `~/.openclaw/workspace/projects/<slug>/` with these files:
56
+
57
+ **Always included:**
58
+ - `STATUS.md` — what's happening, last activity, next 3 actions
59
+ - `TODO.md` — task backlog
60
+ - `COMMANDS.md` — build/deploy/test commands worth remembering
61
+ - `LINKS.md` — URLs and endpoints
62
+ - `CRON.md` — scheduled jobs
63
+ - `NOTES.md` — anything else worth keeping
64
+ - `README.md` — what this topic is about
65
+
66
+ **Extra files by type:**
67
+ - `coding` adds `ARCHITECTURE.md` and `DEPLOY.md`
68
+ - `research` adds `SOURCES.md` and `FINDINGS.md`
69
+ - `marketing` adds `CAMPAIGNS.md` and `METRICS.md`
70
+ - `custom` adds nothing — bring your own
71
+
72
+ ## Permissions
73
+
74
+ Two roles:
75
+ - **User** — can manage topics they have access to
76
+ - **Admin** — can run `doctor --all` and manage anyone's topics
77
+
78
+ The first person to run `/topic init` automatically becomes admin.
79
+
80
+ ## Project layout
81
+
82
+ ```
83
+ src/
84
+ index.ts — plugin entry point
85
+ tool.ts — routes /topic subcommands
86
+ setup.ts — the setup CLI
87
+ commands/ — one file per command
88
+ lib/ — core logic (registry, capsules, security, auth, etc.)
89
+ templates/ — markdown templates for new capsules
90
+ skills/
91
+ topic/SKILL.md — skill definition with rehydration behavior
92
+ ```
93
+
94
+ ## Security
95
+
96
+ - Path traversal protection (jail checks + symlink rejection)
97
+ - HMAC-signed inline keyboard callbacks
98
+ - HTML escaping on all Telegram output
99
+ - Schema validation on every registry read (bad entries get quarantined)
100
+ - File locking to prevent concurrent write corruption
101
+
102
+ See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
103
+
104
+ ## Contributing
105
+
106
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
107
+
108
+ ## License
109
+
110
+ [MIT](LICENSE)
@@ -0,0 +1,4 @@
1
+ import type { CommandContext, CommandResult } from './help.js';
2
+ export declare function handleArchive(ctx: CommandContext): Promise<CommandResult>;
3
+ export declare function handleUnarchive(ctx: CommandContext): Promise<CommandResult>;
4
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/commands/archive.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAE/E;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAEjF"}
@@ -0,0 +1,71 @@
1
+ import { withRegistry, readRegistry } from '../lib/registry.js';
2
+ import { checkAuthorization } from '../lib/auth.js';
3
+ import { topicKey } from '../lib/types.js';
4
+ import { htmlEscape } from '../lib/security.js';
5
+ import { appendAudit, buildAuditEntry } from '../lib/audit.js';
6
+ import { generateInclude } from '../lib/include-generator.js';
7
+ import { triggerRestart, getConfigWrites } from '../lib/config-restart.js';
8
+ export async function handleArchive(ctx) {
9
+ return handleArchiveToggle(ctx, true);
10
+ }
11
+ export async function handleUnarchive(ctx) {
12
+ return handleArchiveToggle(ctx, false);
13
+ }
14
+ async function handleArchiveToggle(ctx, archive) {
15
+ const { workspaceDir, configDir, userId, groupId, threadId, rpc, logger } = ctx;
16
+ const command = archive ? 'archive' : 'unarchive';
17
+ if (!userId || !groupId || !threadId) {
18
+ return { text: 'Missing context: userId, groupId, or threadId not available.' };
19
+ }
20
+ const registry = readRegistry(workspaceDir);
21
+ // Auth check (admin tier)
22
+ const auth = checkAuthorization(userId, command, registry);
23
+ if (!auth.authorized) {
24
+ return { text: auth.message ?? 'Not authorized.' };
25
+ }
26
+ const key = topicKey(groupId, threadId);
27
+ const entry = registry.topics[key];
28
+ if (!entry) {
29
+ return { text: 'This topic is not registered. Run /topic init first.' };
30
+ }
31
+ if (archive && entry.status === 'archived') {
32
+ return { text: `Topic <code>${htmlEscape(entry.slug)}</code> is already archived.` };
33
+ }
34
+ if (!archive && entry.status !== 'archived') {
35
+ return { text: `Topic <code>${htmlEscape(entry.slug)}</code> is not archived.` };
36
+ }
37
+ const newStatus = archive ? 'archived' : 'active';
38
+ await withRegistry(workspaceDir, (data) => {
39
+ const topic = data.topics[key];
40
+ if (topic) {
41
+ topic.status = newStatus;
42
+ if (!archive) {
43
+ topic.snoozeUntil = null;
44
+ }
45
+ }
46
+ });
47
+ // Regenerate include if configWrites enabled
48
+ let restartMsg = '';
49
+ const configWritesEnabled = await getConfigWrites(ctx.rpc);
50
+ if (configWritesEnabled) {
51
+ try {
52
+ const updatedRegistry = readRegistry(workspaceDir);
53
+ generateInclude(workspaceDir, updatedRegistry, configDir);
54
+ }
55
+ catch (err) {
56
+ const msg = err instanceof Error ? err.message : String(err);
57
+ restartMsg = `\nWarning: include generation failed: ${htmlEscape(msg)}`;
58
+ }
59
+ const result = await triggerRestart(rpc, logger);
60
+ if (!result.success && result.fallbackMessage) {
61
+ restartMsg += '\n' + result.fallbackMessage;
62
+ }
63
+ }
64
+ appendAudit(workspaceDir, buildAuditEntry(userId, command, entry.slug, `Status changed to ${newStatus}`));
65
+ const action = archive ? 'archived' : 'unarchived';
66
+ return {
67
+ text: `Topic <code>${htmlEscape(entry.slug)}</code> ${action}.${restartMsg}`,
68
+ parseMode: 'HTML',
69
+ };
70
+ }
71
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/commands/archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CommandContext, CommandResult } from './help.js';
2
+ export declare function handleDoctorAll(ctx: CommandContext): Promise<CommandResult>;
3
+ //# sourceMappingURL=doctor-all.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-all.d.ts","sourceRoot":"","sources":["../../src/commands/doctor-all.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAW/D,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAiMjF"}
@@ -0,0 +1,193 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { readRegistry, withRegistry } from '../lib/registry.js';
4
+ import { checkAuthorization } from '../lib/auth.js';
5
+ import { DOCTOR_ALL_COOLDOWN_MS, DOCTOR_PER_TOPIC_CAP_MS, INACTIVE_AFTER_DAYS, SPAM_THRESHOLD, } from '../lib/types.js';
6
+ import { htmlEscape } from '../lib/security.js';
7
+ import { buildDoctorReport, buildDoctorButtons } from '../lib/telegram.js';
8
+ import { runAllChecksForTopic } from '../lib/doctor-checks.js';
9
+ import { includePath } from '../lib/include-generator.js';
10
+ export async function handleDoctorAll(ctx) {
11
+ const { workspaceDir, configDir, userId, logger } = ctx;
12
+ if (!userId) {
13
+ return { text: 'Missing context: userId not available.' };
14
+ }
15
+ const registry = readRegistry(workspaceDir);
16
+ // Auth check (admin tier)
17
+ const auth = checkAuthorization(userId, 'doctor-all', registry);
18
+ if (!auth.authorized) {
19
+ return { text: auth.message ?? 'Not authorized.' };
20
+ }
21
+ // Global cooldown check
22
+ if (registry.lastDoctorAllRunAt) {
23
+ const lastRun = new Date(registry.lastDoctorAllRunAt).getTime();
24
+ const elapsed = Date.now() - lastRun;
25
+ if (elapsed < DOCTOR_ALL_COOLDOWN_MS) {
26
+ const remainingMin = Math.ceil((DOCTOR_ALL_COOLDOWN_MS - elapsed) / 60_000);
27
+ return {
28
+ text: `Doctor-all was run ${Math.floor(elapsed / 60_000)} minutes ago. Try again in ${remainingMin} minute(s).`,
29
+ };
30
+ }
31
+ }
32
+ const now = new Date();
33
+ const projectsBase = path.join(workspaceDir, 'projects');
34
+ // Read include content for config checks (optional)
35
+ let includeContent;
36
+ const incPath = includePath(configDir);
37
+ try {
38
+ if (fs.existsSync(incPath)) {
39
+ includeContent = fs.readFileSync(incPath, 'utf-8');
40
+ }
41
+ }
42
+ catch {
43
+ // Not critical
44
+ }
45
+ const cronJobsPath = path.join(configDir, 'cron', 'jobs.json');
46
+ const allEntries = Object.entries(registry.topics);
47
+ const reports = [];
48
+ const errors = [];
49
+ let processed = 0;
50
+ let skipped = 0;
51
+ // Group tracking for migration detection
52
+ const groupPostResults = new Map();
53
+ for (const [_key, entry] of allEntries) {
54
+ // Eligibility gating
55
+ if (!isEligible(entry, now)) {
56
+ skipped++;
57
+ continue;
58
+ }
59
+ try {
60
+ const results = runAllChecksForTopic(entry, projectsBase, includeContent, registry, cronJobsPath);
61
+ // Spam control: auto-snooze if threshold reached
62
+ const isSpam = entry.consecutiveSilentDoctors >= SPAM_THRESHOLD;
63
+ if (isSpam) {
64
+ // Auto-snooze is handled via the check result; we just note it
65
+ logger.info(`[doctor-all] Auto-snoozing ${entry.slug} (${entry.consecutiveSilentDoctors} silent runs)`);
66
+ }
67
+ const reportText = buildDoctorReport(entry.slug, results);
68
+ const keyboard = buildDoctorButtons(entry.slug, entry.groupId, entry.threadId, registry.callbackSecret);
69
+ reports.push({
70
+ slug: entry.slug,
71
+ groupId: entry.groupId,
72
+ threadId: entry.threadId,
73
+ text: reportText,
74
+ keyboard,
75
+ });
76
+ // Track group post results for migration detection
77
+ const gk = entry.groupId;
78
+ if (!groupPostResults.has(gk)) {
79
+ groupPostResults.set(gk, { total: 0, failed: 0 });
80
+ }
81
+ const group = groupPostResults.get(gk);
82
+ group.total++;
83
+ processed++;
84
+ }
85
+ catch (err) {
86
+ const msg = err instanceof Error ? err.message : String(err);
87
+ errors.push(`${entry.slug}: ${msg}`);
88
+ logger.error(`[doctor-all] Error processing ${entry.slug}: ${msg}`);
89
+ // Track failures for migration detection
90
+ const gk = entry.groupId;
91
+ if (!groupPostResults.has(gk)) {
92
+ groupPostResults.set(gk, { total: 0, failed: 0 });
93
+ }
94
+ const group = groupPostResults.get(gk);
95
+ group.total++;
96
+ group.failed++;
97
+ }
98
+ }
99
+ // Migration detection: all topics in a group failed
100
+ const migrationGroups = [];
101
+ for (const [gid, stats] of groupPostResults) {
102
+ if (stats.total > 0 && stats.failed === stats.total) {
103
+ migrationGroups.push(gid);
104
+ }
105
+ }
106
+ // Update registry: lastDoctorAllRunAt and per-topic timestamps
107
+ await withRegistry(workspaceDir, (data) => {
108
+ data.lastDoctorAllRunAt = now.toISOString();
109
+ for (const [_key, entry] of Object.entries(data.topics)) {
110
+ if (!isEligible(entry, now))
111
+ continue;
112
+ // Update consecutiveSilentDoctors (compare against old lastDoctorRunAt before overwriting)
113
+ if (entry.lastMessageAt) {
114
+ const lastMsg = new Date(entry.lastMessageAt).getTime();
115
+ const lastDoctor = entry.lastDoctorRunAt
116
+ ? new Date(entry.lastDoctorRunAt).getTime()
117
+ : 0;
118
+ if (lastMsg > lastDoctor) {
119
+ // User interacted since last doctor run — reset counter
120
+ entry.consecutiveSilentDoctors = 0;
121
+ }
122
+ else {
123
+ entry.consecutiveSilentDoctors++;
124
+ }
125
+ }
126
+ else {
127
+ entry.consecutiveSilentDoctors++;
128
+ }
129
+ entry.lastDoctorRunAt = now.toISOString();
130
+ // Auto-snooze for spam control
131
+ if (entry.consecutiveSilentDoctors >= SPAM_THRESHOLD) {
132
+ entry.snoozeUntil = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
133
+ entry.status = 'snoozed';
134
+ entry.consecutiveSilentDoctors = 0;
135
+ }
136
+ }
137
+ });
138
+ // Build summary
139
+ const lines = [
140
+ `<b>Doctor All Summary</b>`,
141
+ '',
142
+ `Processed: ${processed}`,
143
+ `Skipped (ineligible): ${skipped}`,
144
+ `Total: ${allEntries.length}`,
145
+ ];
146
+ if (errors.length > 0) {
147
+ lines.push('');
148
+ lines.push(`<b>Errors (${errors.length}):</b>`);
149
+ for (const e of errors.slice(0, 10)) {
150
+ lines.push(`- ${htmlEscape(e)}`);
151
+ }
152
+ if (errors.length > 10) {
153
+ lines.push(`... and ${errors.length - 10} more`);
154
+ }
155
+ }
156
+ if (migrationGroups.length > 0) {
157
+ lines.push('');
158
+ lines.push('<b>Possible group migrations detected:</b>');
159
+ for (const gid of migrationGroups) {
160
+ lines.push(`- Group ${htmlEscape(gid)}: all topics failed. Check for group migration.`);
161
+ }
162
+ }
163
+ // Per-topic reports are returned as a summary for the calling context.
164
+ // The tool router is responsible for posting individual reports to each topic.
165
+ // Here we return the summary to the current thread.
166
+ return {
167
+ text: lines.join('\n'),
168
+ parseMode: 'HTML',
169
+ };
170
+ }
171
+ function isEligible(entry, now) {
172
+ // Skip archived
173
+ if (entry.status === 'archived')
174
+ return false;
175
+ // Skip snoozed
176
+ if (entry.snoozeUntil && new Date(entry.snoozeUntil).getTime() > now.getTime())
177
+ return false;
178
+ // Skip inactive (no activity for INACTIVE_AFTER_DAYS)
179
+ if (entry.lastMessageAt) {
180
+ const lastActive = new Date(entry.lastMessageAt).getTime();
181
+ const inactiveMs = INACTIVE_AFTER_DAYS * 24 * 60 * 60 * 1000;
182
+ if (now.getTime() - lastActive > inactiveMs)
183
+ return false;
184
+ }
185
+ // Skip if reported in last 24 hours
186
+ if (entry.lastDoctorReportAt) {
187
+ const lastReport = new Date(entry.lastDoctorReportAt).getTime();
188
+ if (now.getTime() - lastReport < DOCTOR_PER_TOPIC_CAP_MS)
189
+ return false;
190
+ }
191
+ return true;
192
+ }
193
+ //# sourceMappingURL=doctor-all.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-all.js","sourceRoot":"","sources":["../../src/commands/doctor-all.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CommandContext, CommandResult } from './help.js';
2
+ export declare function handleDoctor(ctx: CommandContext): Promise<CommandResult>;
3
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,wBAAsB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAwF9E"}
@@ -0,0 +1,74 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { readRegistry, withRegistry } from '../lib/registry.js';
4
+ import { checkAuthorization } from '../lib/auth.js';
5
+ import { topicKey } from '../lib/types.js';
6
+ import { jailCheck, rejectSymlink } from '../lib/security.js';
7
+ import { buildDoctorReport, buildDoctorButtons } from '../lib/telegram.js';
8
+ import { runAllChecksForTopic } from '../lib/doctor-checks.js';
9
+ import { includePath } from '../lib/include-generator.js';
10
+ export async function handleDoctor(ctx) {
11
+ const { workspaceDir, configDir, userId, groupId, threadId } = ctx;
12
+ if (!userId || !groupId || !threadId) {
13
+ return { text: 'Missing context: userId, groupId, or threadId not available.' };
14
+ }
15
+ const registry = readRegistry(workspaceDir);
16
+ // Auth check (user tier)
17
+ const auth = checkAuthorization(userId, 'doctor', registry);
18
+ if (!auth.authorized) {
19
+ return { text: auth.message ?? 'Not authorized.' };
20
+ }
21
+ const key = topicKey(groupId, threadId);
22
+ const entry = registry.topics[key];
23
+ if (!entry) {
24
+ return { text: 'This topic is not registered. Run /topic init first.' };
25
+ }
26
+ const projectsBase = path.join(workspaceDir, 'projects');
27
+ // Path safety
28
+ if (!jailCheck(projectsBase, entry.slug)) {
29
+ return { text: 'Path safety check failed.' };
30
+ }
31
+ const capsuleDir = path.join(projectsBase, entry.slug);
32
+ if (rejectSymlink(capsuleDir)) {
33
+ return { text: 'Capsule directory is a symlink. Aborting for security.' };
34
+ }
35
+ // Read include content for config checks (optional)
36
+ let includeContent;
37
+ const incPath = includePath(configDir);
38
+ try {
39
+ if (fs.existsSync(incPath)) {
40
+ includeContent = fs.readFileSync(incPath, 'utf-8');
41
+ }
42
+ }
43
+ catch {
44
+ // Not critical
45
+ }
46
+ // Run all checks
47
+ const cronJobsPath = path.join(configDir, 'cron', 'jobs.json');
48
+ const results = runAllChecksForTopic(entry, projectsBase, includeContent, registry, cronJobsPath);
49
+ // Build report
50
+ const reportText = buildDoctorReport(entry.slug, results);
51
+ // Build inline keyboard with HMAC-signed callbacks
52
+ const keyboard = buildDoctorButtons(entry.slug, groupId, threadId, registry.callbackSecret);
53
+ // Append text command equivalents
54
+ const textCommands = [
55
+ '',
56
+ 'Or use text commands:',
57
+ '/topic snooze 7d',
58
+ '/topic snooze 30d',
59
+ '/topic archive',
60
+ ].join('\n');
61
+ // Update lastDoctorReportAt
62
+ await withRegistry(workspaceDir, (data) => {
63
+ const topic = data.topics[key];
64
+ if (topic) {
65
+ topic.lastDoctorReportAt = new Date().toISOString();
66
+ }
67
+ });
68
+ return {
69
+ text: reportText + textCommands,
70
+ parseMode: 'HTML',
71
+ inlineKeyboard: keyboard,
72
+ };
73
+ }
74
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { CommandContext, CommandResult } from '../lib/types.js';
2
+ export type { CommandContext, CommandResult };
3
+ export declare function handleHelp(_ctx: CommandContext): CommandResult;
4
+ //# sourceMappingURL=help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/commands/help.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;AAE9C,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,aAAa,CAK9D"}
@@ -0,0 +1,8 @@
1
+ import { buildHelpCard } from '../lib/telegram.js';
2
+ export function handleHelp(_ctx) {
3
+ return {
4
+ text: buildHelpCard(),
5
+ parseMode: 'HTML',
6
+ };
7
+ }
8
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/commands/help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAKnD,MAAM,UAAU,UAAU,CAAC,IAAoB;IAC7C,OAAO;QACL,IAAI,EAAE,aAAa,EAAE;QACrB,SAAS,EAAE,MAAM;KAClB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { TopicType } from '../lib/types.js';
2
+ import type { CommandContext, CommandResult } from './help.js';
3
+ export declare function handleInit(ctx: CommandContext, args: string): Promise<CommandResult>;
4
+ /**
5
+ * Entry point for `/topic init`. If args are provided, delegates straight
6
+ * to `handleInit`. Otherwise starts the interactive two-step flow.
7
+ */
8
+ export declare function handleInitInteractive(ctx: CommandContext, args: string): Promise<CommandResult>;
9
+ /**
10
+ * Step 2 (callback `is`): re-validate, then show the type picker.
11
+ */
12
+ export declare function handleInitSlugConfirm(ctx: CommandContext, slug: string): Promise<CommandResult>;
13
+ /**
14
+ * Step 3 (callbacks `ic`/`ir`/`im`/`ix`): complete init with chosen type.
15
+ */
16
+ export declare function handleInitTypeSelect(ctx: CommandContext, slug: string, type: TopicType): Promise<CommandResult>;
17
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,iBAAiB,CAAC;AAgB7D,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI/D,wBAAsB,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAwM1F;AAwBD;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAKrG;AA0ED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6BrG;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAErH"}