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.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/commands/archive.d.ts +4 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +71 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/doctor-all.d.ts +3 -0
- package/dist/commands/doctor-all.d.ts.map +1 -0
- package/dist/commands/doctor-all.js +193 -0
- package/dist/commands/doctor-all.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +74 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/help.d.ts +4 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +8 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +304 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +22 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/rename.d.ts +3 -0
- package/dist/commands/rename.d.ts.map +1 -0
- package/dist/commands/rename.js +115 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/snooze.d.ts +3 -0
- package/dist/commands/snooze.d.ts.map +1 -0
- package/dist/commands/snooze.js +52 -0
- package/dist/commands/snooze.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +48 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +38 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/upgrade.d.ts +3 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +52 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/audit.d.ts +12 -0
- package/dist/lib/audit.d.ts.map +1 -0
- package/dist/lib/audit.js +35 -0
- package/dist/lib/audit.js.map +1 -0
- package/dist/lib/auth.d.ts +26 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +73 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/capsule.d.ts +27 -0
- package/dist/lib/capsule.d.ts.map +1 -0
- package/dist/lib/capsule.js +130 -0
- package/dist/lib/capsule.js.map +1 -0
- package/dist/lib/config-restart.d.ts +23 -0
- package/dist/lib/config-restart.d.ts.map +1 -0
- package/dist/lib/config-restart.js +129 -0
- package/dist/lib/config-restart.js.map +1 -0
- package/dist/lib/doctor-checks.d.ts +50 -0
- package/dist/lib/doctor-checks.d.ts.map +1 -0
- package/dist/lib/doctor-checks.js +421 -0
- package/dist/lib/doctor-checks.js.map +1 -0
- package/dist/lib/include-generator.d.ts +35 -0
- package/dist/lib/include-generator.d.ts.map +1 -0
- package/dist/lib/include-generator.js +140 -0
- package/dist/lib/include-generator.js.map +1 -0
- package/dist/lib/registry.d.ts +27 -0
- package/dist/lib/registry.d.ts.map +1 -0
- package/dist/lib/registry.js +154 -0
- package/dist/lib/registry.js.map +1 -0
- package/dist/lib/security.d.ts +57 -0
- package/dist/lib/security.d.ts.map +1 -0
- package/dist/lib/security.js +133 -0
- package/dist/lib/security.js.map +1 -0
- package/dist/lib/telegram.d.ts +55 -0
- package/dist/lib/telegram.d.ts.map +1 -0
- package/dist/lib/telegram.js +254 -0
- package/dist/lib/telegram.js.map +1 -0
- package/dist/lib/types.d.ts +120 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +85 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/setup.d.ts +3 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +333 -0
- package/dist/setup.js.map +1 -0
- package/dist/tool.d.ts +15 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +201 -0
- package/dist/tool.js.map +1 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +48 -0
- package/skills/topic/SKILL.md +35 -0
- package/src/commands/archive.ts +89 -0
- package/src/commands/doctor-all.ts +243 -0
- package/src/commands/doctor.ts +100 -0
- package/src/commands/help.ts +11 -0
- package/src/commands/init.ts +376 -0
- package/src/commands/list.ts +28 -0
- package/src/commands/rename.ts +140 -0
- package/src/commands/snooze.ts +69 -0
- package/src/commands/status.ts +59 -0
- package/src/commands/sync.ts +46 -0
- package/src/commands/upgrade.ts +64 -0
- package/src/index.ts +54 -0
- package/src/lib/audit.ts +44 -0
- package/src/lib/auth.ts +96 -0
- package/src/lib/capsule.ts +206 -0
- package/src/lib/config-restart.ts +167 -0
- package/src/lib/doctor-checks.ts +639 -0
- package/src/lib/include-generator.ts +174 -0
- package/src/lib/registry.ts +197 -0
- package/src/lib/security.ts +174 -0
- package/src/lib/telegram.ts +311 -0
- package/src/lib/types.ts +172 -0
- package/src/setup.ts +402 -0
- package/src/templates/base/COMMANDS.md +3 -0
- package/src/templates/base/CRON.md +3 -0
- package/src/templates/base/LINKS.md +3 -0
- package/src/templates/base/NOTES.md +3 -0
- package/src/templates/base/README.md +3 -0
- package/src/templates/base/STATUS.md +13 -0
- package/src/templates/base/TODO.md +11 -0
- package/src/templates/overlays/coding/ARCHITECTURE.md +3 -0
- package/src/templates/overlays/coding/DEPLOY.md +3 -0
- package/src/templates/overlays/marketing/CAMPAIGNS.md +3 -0
- package/src/templates/overlays/marketing/METRICS.md +3 -0
- package/src/templates/overlays/research/FINDINGS.md +3 -0
- package/src/templates/overlays/research/SOURCES.md +3 -0
- 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
|
+
[](https://github.com/jcoulaud/openclaw-telegram-manager/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/openclaw-telegram-manager)
|
|
5
|
+
[](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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|