openclaw-telegram-manager 2.3.2 → 2.5.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/README.md +75 -58
- package/dist/commands/autopilot.d.ts +4 -0
- package/dist/commands/autopilot.d.ts.map +1 -1
- package/dist/commands/autopilot.js +14 -11
- package/dist/commands/autopilot.js.map +1 -1
- package/dist/commands/daily-report.d.ts +6 -0
- package/dist/commands/daily-report.d.ts.map +1 -1
- package/dist/commands/daily-report.js +17 -16
- package/dist/commands/daily-report.js.map +1 -1
- package/dist/commands/doctor-all.d.ts.map +1 -1
- package/dist/commands/doctor-all.js +72 -11
- package/dist/commands/doctor-all.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +3 -11
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +51 -33
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/rename.d.ts.map +1 -1
- package/dist/commands/rename.js +2 -4
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/snooze.js +2 -2
- package/dist/commands/snooze.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +74 -6
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.js +1 -1
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/upgrade.js +2 -2
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/lib/capsule.js +3 -3
- package/dist/lib/capsule.js.map +1 -1
- package/dist/lib/doctor-checks.d.ts.map +1 -1
- package/dist/lib/doctor-checks.js +26 -59
- package/dist/lib/doctor-checks.js.map +1 -1
- package/dist/lib/include-generator.d.ts +1 -1
- package/dist/lib/include-generator.js +4 -4
- package/dist/lib/include-generator.js.map +1 -1
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/registry.js +3 -0
- package/dist/lib/registry.js.map +1 -1
- package/dist/lib/telegram.d.ts +9 -4
- package/dist/lib/telegram.d.ts.map +1 -1
- package/dist/lib/telegram.js +87 -76
- package/dist/lib/telegram.js.map +1 -1
- package/dist/lib/types.d.ts +2 -2
- package/dist/lib/types.js +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/plugin.js +665 -591
- package/dist/setup.js +49 -4
- package/dist/setup.js.map +1 -1
- package/dist/tool.js +0 -18
- package/dist/tool.js.map +1 -1
- package/package.json +10 -1
- package/skills/tm/SKILL.md +8 -8
package/README.md
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
> **This project is a work in progress and is not yet functional.** I'm actively working on it — expect breaking changes, incomplete features, and rough edges. Feel free to watch the repo, but don't use it in production yet.
|
|
1
|
+
# Persistent Memory for OpenClaw Telegram Topics
|
|
4
2
|
|
|
5
3
|
[](https://github.com/jcoulaud/openclaw-telegram-manager/actions/workflows/ci.yml)
|
|
6
4
|
[](https://www.npmjs.com/package/openclaw-telegram-manager)
|
|
7
5
|
[](LICENSE)
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
`openclaw-telegram-manager` — an [OpenClaw](https://openclaw.ai) plugin that gives each Telegram topic its own persistent memory, so nothing gets lost when the AI resets or context gets compacted.
|
|
10
8
|
|
|
11
9
|
## The problem
|
|
12
10
|
|
|
13
|
-
When OpenClaw manages a Telegram group with topics, each topic is basically a separate project. But after a reset or context compaction, the
|
|
11
|
+
When OpenClaw manages a Telegram group with topics, each topic is basically a separate project. But after a reset or context compaction, the AI forgets everything: what it was working on, what's left to do, what commands matter.
|
|
14
12
|
|
|
15
|
-
This plugin fixes that. Each topic gets a folder of
|
|
13
|
+
This plugin fixes that. Each topic gets a folder of persistent files that the AI reads automatically on startup. It picks up right where it left off.
|
|
16
14
|
|
|
17
15
|
## Prerequisites
|
|
18
16
|
|
|
19
17
|
- [OpenClaw](https://openclaw.ai) `>=2026.1.0` installed and running
|
|
20
18
|
- A Telegram group with [topics enabled](https://telegram.org/blog/tms-in-groups-collectible-usernames#topics-in-groups) and managed by OpenClaw
|
|
21
19
|
|
|
22
|
-
##
|
|
20
|
+
## Install
|
|
23
21
|
|
|
24
22
|
```bash
|
|
25
23
|
npx openclaw-telegram-manager setup
|
|
@@ -27,59 +25,78 @@ npx openclaw-telegram-manager setup
|
|
|
27
25
|
|
|
28
26
|
That's it. The setup script installs the plugin, patches your config, creates the workspace, and restarts the OpenClaw gateway. It's idempotent — running it twice won't break anything.
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
<details>
|
|
29
|
+
<summary>Security warnings during install</summary>
|
|
30
|
+
|
|
31
|
+
OpenClaw's automatic scanner may flag `child_process` and `process.env` usage. These are expected — the setup script calls `openclaw --version`, `openclaw plugins install`, and `openclaw gateway restart`, and reads `process.env` for config directory detection. No data is sent externally.
|
|
32
|
+
|
|
33
|
+
</details>
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
1. **One-time setup per topic**
|
|
38
|
+
Open a Telegram topic and type `/tm init`. Pick a type (Coding, Research, Marketing, or Custom). Done.
|
|
39
|
+
|
|
40
|
+
2. **Everything else is automatic**
|
|
41
|
+
The AI reads and updates the topic's files on its own — tracking progress, TODOs, decisions, and learnings. When context gets compacted or the AI resets, it re-reads these files and continues where it left off.
|
|
42
|
+
|
|
43
|
+
3. **Health checks and daily reports run in the background**
|
|
44
|
+
Autopilot is enabled by default — the plugin checks all your topics daily and posts a progress report, only when something needs attention.
|
|
45
|
+
|
|
46
|
+
You can also skip the interactive flow: `/tm init my-project coding`
|
|
47
|
+
|
|
48
|
+
## What gets tracked
|
|
31
49
|
|
|
32
|
-
|
|
50
|
+
Each topic gets its own folder with files the AI maintains automatically:
|
|
33
51
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
| File | Purpose |
|
|
53
|
+
|------|---------|
|
|
54
|
+
| `STATUS.md` | Last activity, next actions, upcoming work |
|
|
55
|
+
| `TODO.md` | Task list |
|
|
56
|
+
| `LEARNINGS.md` | Insights, mistakes, workarounds |
|
|
57
|
+
| `COMMANDS.md` | Build/deploy/test commands |
|
|
58
|
+
| `LINKS.md` | URLs and endpoints |
|
|
59
|
+
| `CRON.md` | Scheduled jobs |
|
|
60
|
+
| `NOTES.md` | Anything else worth keeping |
|
|
61
|
+
| `README.md` | What this topic is about |
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
Depending on the topic type, extra files are added:
|
|
64
|
+
- **Coding** adds `ARCHITECTURE.md` and `DEPLOY.md`
|
|
65
|
+
- **Research** adds `SOURCES.md` and `FINDINGS.md`
|
|
66
|
+
- **Marketing** adds `CAMPAIGNS.md` and `METRICS.md`
|
|
41
67
|
|
|
42
|
-
##
|
|
68
|
+
## Optional commands
|
|
43
69
|
|
|
44
|
-
|
|
70
|
+
You don't need any of these — everything runs automatically. They're there if you want to check on things or make changes.
|
|
71
|
+
|
|
72
|
+
**Check on things**
|
|
73
|
+
|
|
74
|
+
| Command | What it does |
|
|
75
|
+
|---------|-------------|
|
|
76
|
+
| `/tm status` | See current progress |
|
|
77
|
+
| `/tm doctor` | Run health checks |
|
|
78
|
+
| `/tm doctor --all` | Health check all topics at once |
|
|
79
|
+
| `/tm daily-report` | Post a daily summary |
|
|
80
|
+
| `/tm list` | List all topics |
|
|
81
|
+
|
|
82
|
+
**Make changes**
|
|
45
83
|
|
|
46
84
|
| Command | What it does |
|
|
47
85
|
|---------|-------------|
|
|
48
|
-
| `/tm
|
|
49
|
-
| `/tm
|
|
50
|
-
| `/tm status` | Show the current STATUS.md |
|
|
51
|
-
| `/tm list` | List all topics, grouped by status |
|
|
52
|
-
| `/tm doctor` | Run health checks on the current topic |
|
|
53
|
-
| `/tm doctor --all` | Health check all active topics at once |
|
|
54
|
-
| `/tm sync` | Regenerate the include file from the registry |
|
|
55
|
-
| `/tm rename <new-name>` | Rename a topic's display name |
|
|
56
|
-
| `/tm upgrade` | Upgrade the capsule to the latest template version |
|
|
57
|
-
| `/tm snooze <duration>` | Snooze a topic (e.g. `7d`, `30d`) |
|
|
86
|
+
| `/tm rename <new-name>` | Rename a topic |
|
|
87
|
+
| `/tm snooze <duration>` | Pause health checks (e.g. `7d`, `30d`) |
|
|
58
88
|
| `/tm archive` | Archive a topic |
|
|
59
89
|
| `/tm unarchive` | Bring back an archived topic |
|
|
60
|
-
| `/tm
|
|
61
|
-
| `/tm
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- `TODO.md` — task backlog
|
|
71
|
-
- `COMMANDS.md` — build/deploy/test commands worth remembering
|
|
72
|
-
- `LINKS.md` — URLs and endpoints
|
|
73
|
-
- `CRON.md` — scheduled jobs
|
|
74
|
-
- `NOTES.md` — anything else worth keeping
|
|
75
|
-
- `LEARNINGS.md` — hard-won insights, mistakes, and workarounds
|
|
76
|
-
- `README.md` — what this topic is about
|
|
77
|
-
|
|
78
|
-
**Extra files by type:**
|
|
79
|
-
- `coding` adds `ARCHITECTURE.md` and `DEPLOY.md`
|
|
80
|
-
- `research` adds `SOURCES.md` and `FINDINGS.md`
|
|
81
|
-
- `marketing` adds `CAMPAIGNS.md` and `METRICS.md`
|
|
82
|
-
- `custom` adds nothing — bring your own
|
|
90
|
+
| `/tm upgrade` | Update topic files to the latest version |
|
|
91
|
+
| `/tm sync` | Fix config if something is out of sync |
|
|
92
|
+
|
|
93
|
+
**Autopilot** (enabled by default)
|
|
94
|
+
|
|
95
|
+
| Command | What it does |
|
|
96
|
+
|---------|-------------|
|
|
97
|
+
| `/tm autopilot status` | Check if autopilot is on and when it last ran |
|
|
98
|
+
| `/tm autopilot disable` | Turn off automatic health checks and daily reports |
|
|
99
|
+
| `/tm autopilot enable` | Re-enable if you previously disabled it |
|
|
83
100
|
|
|
84
101
|
## Permissions
|
|
85
102
|
|
|
@@ -93,7 +110,7 @@ The first person to run `/tm init` automatically becomes admin.
|
|
|
93
110
|
|
|
94
111
|
- Path traversal protection (jail checks + symlink rejection)
|
|
95
112
|
- HMAC-signed inline keyboard callbacks
|
|
96
|
-
- HTML escaping on direct Telegram API posts
|
|
113
|
+
- HTML escaping on direct Telegram API posts
|
|
97
114
|
- Schema validation on every registry read (bad entries get quarantined)
|
|
98
115
|
- File locking to prevent concurrent write corruption
|
|
99
116
|
|
|
@@ -105,7 +122,7 @@ See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
|
|
|
105
122
|
npx openclaw-telegram-manager uninstall
|
|
106
123
|
```
|
|
107
124
|
|
|
108
|
-
This removes the plugin
|
|
125
|
+
This removes the plugin, the config reference, and the generated include file, then restarts the gateway. You'll be asked whether to delete your topic data. To skip the prompt and delete everything:
|
|
109
126
|
|
|
110
127
|
```bash
|
|
111
128
|
npx openclaw-telegram-manager uninstall --purge-data
|
|
@@ -119,19 +136,19 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
119
136
|
|
|
120
137
|
```
|
|
121
138
|
src/
|
|
122
|
-
index.ts — plugin entry point
|
|
139
|
+
index.ts — plugin entry point
|
|
123
140
|
tool.ts — routes /tm sub-commands
|
|
124
141
|
setup.ts — the setup CLI
|
|
125
142
|
commands/ — one file per command
|
|
126
|
-
lib/ — core logic (registry,
|
|
127
|
-
templates/ — markdown templates for new
|
|
143
|
+
lib/ — core logic (registry, security, auth, etc.)
|
|
144
|
+
templates/ — markdown templates for new topics
|
|
128
145
|
dist/
|
|
129
|
-
plugin.js — bundled plugin (
|
|
146
|
+
plugin.js — bundled plugin (esbuild, all deps included)
|
|
130
147
|
skills/
|
|
131
|
-
tm/SKILL.md —
|
|
148
|
+
tm/SKILL.md — AI behavior hints (not user-invocable)
|
|
132
149
|
```
|
|
133
150
|
|
|
134
|
-
`npm run build` compiles TypeScript then bundles
|
|
151
|
+
`npm run build` compiles TypeScript then bundles into `dist/plugin.js`. The setup script copies only the bundle — no `node_modules` needed at runtime.
|
|
135
152
|
|
|
136
153
|
## License
|
|
137
154
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { CommandContext, CommandResult } from './help.js';
|
|
2
|
+
export declare const MARKER_START = "<!-- TM_AUTOPILOT_START -->";
|
|
3
|
+
export declare const MARKER_END = "<!-- TM_AUTOPILOT_END -->";
|
|
4
|
+
export declare const HEARTBEAT_BLOCK = "<!-- TM_AUTOPILOT_START -->\n## Topic Manager \u2014 Balanced Autopilot\n\nExecute these steps IN ORDER. Do not skip steps.\n\n1. CALL `topic_manager` with command \"doctor --all\" to health-check all active topics.\n This runs health checks AND posts daily progress reports for each eligible topic automatically.\n2. For each topic where STATUS.md \"Last done (UTC)\" is >3 days old:\n post a brief nudge in that topic asking the user for a status update.\n3. If no actions were taken in steps 1-2, do nothing (HEARTBEAT_OK).\n<!-- TM_AUTOPILOT_END -->";
|
|
5
|
+
export declare const HEARTBEAT_FILENAME = "HEARTBEAT.md";
|
|
2
6
|
export declare function handleAutopilot(ctx: CommandContext, args: string): Promise<CommandResult>;
|
|
3
7
|
//# sourceMappingURL=autopilot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autopilot.d.ts","sourceRoot":"","sources":["../../src/commands/autopilot.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"autopilot.d.ts","sourceRoot":"","sources":["../../src/commands/autopilot.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI/D,eAAO,MAAM,YAAY,gCAAgC,CAAC;AAC1D,eAAO,MAAM,UAAU,8BAA8B,CAAC;AAEtD,eAAO,MAAM,eAAe,kjBAUd,CAAC;AAEf,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAIjD,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA0B/F"}
|
|
@@ -2,21 +2,22 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { readRegistry, withRegistry } from '../lib/registry.js';
|
|
4
4
|
import { checkAuthorization } from '../lib/auth.js';
|
|
5
|
+
import { relativeTime } from '../lib/telegram.js';
|
|
5
6
|
// ── Marker constants ────────────────────────────────────────────────
|
|
6
|
-
const MARKER_START = '<!-- TM_AUTOPILOT_START -->';
|
|
7
|
-
const MARKER_END = '<!-- TM_AUTOPILOT_END -->';
|
|
8
|
-
const HEARTBEAT_BLOCK = `${MARKER_START}
|
|
7
|
+
export const MARKER_START = '<!-- TM_AUTOPILOT_START -->';
|
|
8
|
+
export const MARKER_END = '<!-- TM_AUTOPILOT_END -->';
|
|
9
|
+
export const HEARTBEAT_BLOCK = `${MARKER_START}
|
|
9
10
|
## Topic Manager — Balanced Autopilot
|
|
10
11
|
|
|
11
12
|
Execute these steps IN ORDER. Do not skip steps.
|
|
12
13
|
|
|
13
14
|
1. CALL \`topic_manager\` with command "doctor --all" to health-check all active topics.
|
|
14
|
-
This
|
|
15
|
+
This runs health checks AND posts daily progress reports for each eligible topic automatically.
|
|
15
16
|
2. For each topic where STATUS.md "Last done (UTC)" is >3 days old:
|
|
16
17
|
post a brief nudge in that topic asking the user for a status update.
|
|
17
18
|
3. If no actions were taken in steps 1-2, do nothing (HEARTBEAT_OK).
|
|
18
19
|
${MARKER_END}`;
|
|
19
|
-
const HEARTBEAT_FILENAME = 'HEARTBEAT.md';
|
|
20
|
+
export const HEARTBEAT_FILENAME = 'HEARTBEAT.md';
|
|
20
21
|
// ── Main handler ────────────────────────────────────────────────────
|
|
21
22
|
export async function handleAutopilot(ctx, args) {
|
|
22
23
|
const { workspaceDir, userId } = ctx;
|
|
@@ -69,7 +70,7 @@ async function handleEnable(ctx) {
|
|
|
69
70
|
data.autopilotEnabled = true;
|
|
70
71
|
});
|
|
71
72
|
return {
|
|
72
|
-
text: '**Autopilot enabled.**\
|
|
73
|
+
text: '**Autopilot enabled.**\nHealth checks will run automatically every day.',
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
// ── Disable ─────────────────────────────────────────────────────────
|
|
@@ -80,14 +81,14 @@ async function handleDisable(ctx) {
|
|
|
80
81
|
await withRegistry(workspaceDir, (data) => {
|
|
81
82
|
data.autopilotEnabled = false;
|
|
82
83
|
});
|
|
83
|
-
return { text: 'Autopilot is
|
|
84
|
+
return { text: 'Autopilot is already disabled.' };
|
|
84
85
|
}
|
|
85
86
|
let content = fs.readFileSync(heartbeatPath, 'utf-8');
|
|
86
87
|
if (!content.includes(MARKER_START)) {
|
|
87
88
|
await withRegistry(workspaceDir, (data) => {
|
|
88
89
|
data.autopilotEnabled = false;
|
|
89
90
|
});
|
|
90
|
-
return { text: 'Autopilot is
|
|
91
|
+
return { text: 'Autopilot is already disabled.' };
|
|
91
92
|
}
|
|
92
93
|
// Remove everything between markers (inclusive)
|
|
93
94
|
const startIdx = content.indexOf(MARKER_START);
|
|
@@ -107,7 +108,7 @@ async function handleDisable(ctx) {
|
|
|
107
108
|
data.autopilotEnabled = false;
|
|
108
109
|
});
|
|
109
110
|
return {
|
|
110
|
-
text: '**Autopilot disabled.**\
|
|
111
|
+
text: '**Autopilot disabled.**\nAutomatic health checks are now off.',
|
|
111
112
|
};
|
|
112
113
|
}
|
|
113
114
|
// ── Status ──────────────────────────────────────────────────────────
|
|
@@ -115,10 +116,12 @@ async function handleStatus(ctx) {
|
|
|
115
116
|
const { workspaceDir } = ctx;
|
|
116
117
|
const registry = readRegistry(workspaceDir);
|
|
117
118
|
const enabled = registry.autopilotEnabled;
|
|
118
|
-
const lastRun = registry.lastDoctorAllRunAt
|
|
119
|
+
const lastRun = registry.lastDoctorAllRunAt
|
|
120
|
+
? relativeTime(registry.lastDoctorAllRunAt)
|
|
121
|
+
: 'never';
|
|
119
122
|
const lines = [
|
|
120
123
|
`**Autopilot:** ${enabled ? 'enabled' : 'disabled'}`,
|
|
121
|
-
`**Last
|
|
124
|
+
`**Last health check run:** ${lastRun}`,
|
|
122
125
|
];
|
|
123
126
|
return {
|
|
124
127
|
text: lines.join('\n'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autopilot.js","sourceRoot":"","sources":["../../src/commands/autopilot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"autopilot.js","sourceRoot":"","sources":["../../src/commands/autopilot.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { CommandContext, CommandResult } from './help.js';
|
|
2
2
|
export declare function handleDailyReport(ctx: CommandContext): Promise<CommandResult>;
|
|
3
|
+
export declare function readFileOrNull(filePath: string): string | null;
|
|
4
|
+
export declare function extractDoneSection(statusContent: string | null): string;
|
|
5
|
+
export declare function extractTodayLearnings(learningsContent: string | null): string;
|
|
6
|
+
export declare function extractBlockers(todoContent: string | null): string;
|
|
7
|
+
export declare function extractNextActions(statusContent: string | null): string;
|
|
8
|
+
export declare function extractUpcoming(statusContent: string | null): string;
|
|
3
9
|
export declare function computeHealth(lastMessageAt: string | null, statusContent: string | null, blockers: string): 'fresh' | 'stale' | 'blocked';
|
|
4
10
|
//# sourceMappingURL=daily-report.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daily-report.d.ts","sourceRoot":"","sources":["../../src/commands/daily-report.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"daily-report.d.ts","sourceRoot":"","sources":["../../src/commands/daily-report.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAqFnF;AAID,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM9D;AAED,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAMvE;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAqB7E;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAOlE;AAED,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAMvE;AAED,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAMpE;AAED,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,QAAQ,EAAE,MAAM,GACf,OAAO,GAAG,OAAO,GAAG,SAAS,CAW/B"}
|
|
@@ -15,8 +15,8 @@ export async function handleDailyReport(ctx) {
|
|
|
15
15
|
return { text: 'This topic is not registered. Run /tm init first.' };
|
|
16
16
|
}
|
|
17
17
|
// Dedup: skip if already reported today
|
|
18
|
-
if (entry.
|
|
19
|
-
const lastReport = new Date(entry.
|
|
18
|
+
if (entry.lastDailyReportAt) {
|
|
19
|
+
const lastReport = new Date(entry.lastDailyReportAt);
|
|
20
20
|
const now = new Date();
|
|
21
21
|
if (lastReport.getUTCFullYear() === now.getUTCFullYear() &&
|
|
22
22
|
lastReport.getUTCMonth() === now.getUTCMonth() &&
|
|
@@ -27,7 +27,7 @@ export async function handleDailyReport(ctx) {
|
|
|
27
27
|
const projectsBase = path.join(workspaceDir, 'projects');
|
|
28
28
|
const capsuleDir = path.join(projectsBase, entry.slug);
|
|
29
29
|
if (!fs.existsSync(capsuleDir)) {
|
|
30
|
-
return { text:
|
|
30
|
+
return { text: 'Topic files not found. Run /tm init to set up this topic.' };
|
|
31
31
|
}
|
|
32
32
|
// Read capsule files
|
|
33
33
|
const statusContent = readFileOrNull(path.join(capsuleDir, 'STATUS.md'));
|
|
@@ -40,7 +40,7 @@ export async function handleDailyReport(ctx) {
|
|
|
40
40
|
const nextContent = extractNextActions(statusContent);
|
|
41
41
|
const upcomingContent = extractUpcoming(statusContent);
|
|
42
42
|
const health = computeHealth(entry.lastMessageAt, statusContent, blockers);
|
|
43
|
-
const
|
|
43
|
+
const reportData = {
|
|
44
44
|
name: entry.name,
|
|
45
45
|
doneContent,
|
|
46
46
|
learningsContent: newLearnings,
|
|
@@ -48,15 +48,16 @@ export async function handleDailyReport(ctx) {
|
|
|
48
48
|
nextContent,
|
|
49
49
|
upcomingContent,
|
|
50
50
|
health,
|
|
51
|
-
}
|
|
51
|
+
};
|
|
52
52
|
// Post to topic if postFn available
|
|
53
53
|
if (ctx.postFn) {
|
|
54
54
|
try {
|
|
55
|
-
|
|
55
|
+
const htmlReport = buildDailyReport(reportData, 'html');
|
|
56
|
+
await ctx.postFn(groupId, threadId, htmlReport);
|
|
56
57
|
await withRegistry(workspaceDir, (data) => {
|
|
57
58
|
const e = data.topics[key];
|
|
58
59
|
if (e) {
|
|
59
|
-
e.
|
|
60
|
+
e.lastDailyReportAt = new Date().toISOString();
|
|
60
61
|
}
|
|
61
62
|
});
|
|
62
63
|
}
|
|
@@ -67,18 +68,18 @@ export async function handleDailyReport(ctx) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
else {
|
|
70
|
-
// Update
|
|
71
|
+
// Update lastDailyReportAt even without posting
|
|
71
72
|
await withRegistry(workspaceDir, (data) => {
|
|
72
73
|
const e = data.topics[key];
|
|
73
74
|
if (e) {
|
|
74
|
-
e.
|
|
75
|
+
e.lastDailyReportAt = new Date().toISOString();
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
}
|
|
78
|
-
return { text:
|
|
79
|
+
return { text: buildDailyReport(reportData, 'markdown') };
|
|
79
80
|
}
|
|
80
81
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
81
|
-
function readFileOrNull(filePath) {
|
|
82
|
+
export function readFileOrNull(filePath) {
|
|
82
83
|
try {
|
|
83
84
|
return fs.readFileSync(filePath, 'utf-8');
|
|
84
85
|
}
|
|
@@ -86,7 +87,7 @@ function readFileOrNull(filePath) {
|
|
|
86
87
|
return null;
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
|
-
function extractDoneSection(statusContent) {
|
|
90
|
+
export function extractDoneSection(statusContent) {
|
|
90
91
|
if (!statusContent)
|
|
91
92
|
return '_No STATUS.md found._';
|
|
92
93
|
const match = statusContent.match(/^##\s*Last done\s*\(UTC\)\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
@@ -95,7 +96,7 @@ function extractDoneSection(statusContent) {
|
|
|
95
96
|
const text = match[1]?.trim();
|
|
96
97
|
return text || '_Empty._';
|
|
97
98
|
}
|
|
98
|
-
function extractTodayLearnings(learningsContent) {
|
|
99
|
+
export function extractTodayLearnings(learningsContent) {
|
|
99
100
|
if (!learningsContent)
|
|
100
101
|
return '_No LEARNINGS.md found._';
|
|
101
102
|
const today = new Date().toISOString().slice(0, 10);
|
|
@@ -116,14 +117,14 @@ function extractTodayLearnings(learningsContent) {
|
|
|
116
117
|
}
|
|
117
118
|
return todayLines.length > 0 ? todayLines.join('\n') : '_None today._';
|
|
118
119
|
}
|
|
119
|
-
function extractBlockers(todoContent) {
|
|
120
|
+
export function extractBlockers(todoContent) {
|
|
120
121
|
if (!todoContent)
|
|
121
122
|
return '_No TODO.md found._';
|
|
122
123
|
const lines = todoContent.split('\n');
|
|
123
124
|
const blockerLines = lines.filter((l) => /\[BLOCKED\]/i.test(l) || /\bblocked\b/i.test(l));
|
|
124
125
|
return blockerLines.length > 0 ? blockerLines.join('\n') : '_None._';
|
|
125
126
|
}
|
|
126
|
-
function extractNextActions(statusContent) {
|
|
127
|
+
export function extractNextActions(statusContent) {
|
|
127
128
|
if (!statusContent)
|
|
128
129
|
return '_No STATUS.md found._';
|
|
129
130
|
const match = statusContent.match(/^##\s*Next (?:3 )?actions(?: \(now\))?\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
@@ -132,7 +133,7 @@ function extractNextActions(statusContent) {
|
|
|
132
133
|
const text = match[1]?.trim();
|
|
133
134
|
return text || '_Empty._';
|
|
134
135
|
}
|
|
135
|
-
function extractUpcoming(statusContent) {
|
|
136
|
+
export function extractUpcoming(statusContent) {
|
|
136
137
|
if (!statusContent)
|
|
137
138
|
return '_No STATUS.md found._';
|
|
138
139
|
const match = statusContent.match(/^##\s*Upcoming actions\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daily-report.js","sourceRoot":"","sources":["../../src/commands/daily-report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAmB;IACzD,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAExD,IAAI,CAAC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,sDAAsD,EAAE,CAAC;IAC1E,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAEnC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,mDAAmD,EAAE,CAAC;IACvE,CAAC;IAED,wCAAwC;IACxC,IAAI,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"daily-report.js","sourceRoot":"","sources":["../../src/commands/daily-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-all.d.ts","sourceRoot":"","sources":["../../src/commands/doctor-all.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor-all.d.ts","sourceRoot":"","sources":["../../src/commands/doctor-all.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAW/D,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA6SjF"}
|
|
@@ -3,9 +3,10 @@ import * as path from 'node:path';
|
|
|
3
3
|
import { readRegistry, withRegistry } from '../lib/registry.js';
|
|
4
4
|
import { checkAuthorization } from '../lib/auth.js';
|
|
5
5
|
import { DOCTOR_ALL_COOLDOWN_MS, DOCTOR_PER_TOPIC_CAP_MS, INACTIVE_AFTER_DAYS, MAX_POST_ERROR_LENGTH, SPAM_THRESHOLD, } from '../lib/types.js';
|
|
6
|
-
import { buildDoctorReport, buildDoctorButtons, createRateLimitedPoster } from '../lib/telegram.js';
|
|
6
|
+
import { buildDoctorReport, buildDoctorButtons, buildDailyReport, createRateLimitedPoster } from '../lib/telegram.js';
|
|
7
7
|
import { runAllChecksForTopic, backupCapsuleIfHealthy } from '../lib/doctor-checks.js';
|
|
8
8
|
import { includePath } from '../lib/include-generator.js';
|
|
9
|
+
import { readFileOrNull, extractDoneSection, extractTodayLearnings, extractBlockers, extractNextActions, extractUpcoming, computeHealth, } from './daily-report.js';
|
|
9
10
|
export async function handleDoctorAll(ctx) {
|
|
10
11
|
const { workspaceDir, configDir, userId, logger } = ctx;
|
|
11
12
|
if (!userId) {
|
|
@@ -24,7 +25,7 @@ export async function handleDoctorAll(ctx) {
|
|
|
24
25
|
if (elapsed < DOCTOR_ALL_COOLDOWN_MS) {
|
|
25
26
|
const remainingMin = Math.ceil((DOCTOR_ALL_COOLDOWN_MS - elapsed) / 60_000);
|
|
26
27
|
return {
|
|
27
|
-
text: `
|
|
28
|
+
text: `Health checks were run ${Math.floor(elapsed / 60_000)} minutes ago. Try again in ${remainingMin} minute(s).`,
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -138,7 +139,62 @@ export async function handleDoctorAll(ctx) {
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
//
|
|
142
|
+
// Daily report fan-out: generate and post daily reports for eligible topics
|
|
143
|
+
let dailyReportSuccesses = 0;
|
|
144
|
+
let dailyReportSkipped = 0;
|
|
145
|
+
const dailyReportKeys = new Set();
|
|
146
|
+
if (ctx.postFn && reports.length > 0) {
|
|
147
|
+
const rateLimitedPost = createRateLimitedPoster(ctx.postFn);
|
|
148
|
+
const nowDate = now.toISOString().slice(0, 10);
|
|
149
|
+
for (const report of reports) {
|
|
150
|
+
const key = `${report.groupId}:${report.threadId}`;
|
|
151
|
+
const entry = registry.topics[key];
|
|
152
|
+
if (!entry)
|
|
153
|
+
continue;
|
|
154
|
+
// Dedup: skip if already reported today
|
|
155
|
+
if (entry.lastDailyReportAt) {
|
|
156
|
+
const lastReport = new Date(entry.lastDailyReportAt);
|
|
157
|
+
const lastDate = `${lastReport.getUTCFullYear()}-${String(lastReport.getUTCMonth() + 1).padStart(2, '0')}-${String(lastReport.getUTCDate()).padStart(2, '0')}`;
|
|
158
|
+
if (lastDate === nowDate) {
|
|
159
|
+
dailyReportSkipped++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Read capsule files
|
|
164
|
+
const capsuleDir = path.join(projectsBase, entry.slug);
|
|
165
|
+
const statusContent = readFileOrNull(path.join(capsuleDir, 'STATUS.md'));
|
|
166
|
+
const todoContent = readFileOrNull(path.join(capsuleDir, 'TODO.md'));
|
|
167
|
+
const learningsContent = readFileOrNull(path.join(capsuleDir, 'LEARNINGS.md'));
|
|
168
|
+
// Extract sections
|
|
169
|
+
const doneContent = extractDoneSection(statusContent);
|
|
170
|
+
const newLearnings = extractTodayLearnings(learningsContent);
|
|
171
|
+
const blockers = extractBlockers(todoContent);
|
|
172
|
+
const nextContent = extractNextActions(statusContent);
|
|
173
|
+
const upcomingContent = extractUpcoming(statusContent);
|
|
174
|
+
const health = computeHealth(entry.lastMessageAt, statusContent, blockers);
|
|
175
|
+
const reportData = {
|
|
176
|
+
name: entry.name,
|
|
177
|
+
doneContent,
|
|
178
|
+
learningsContent: newLearnings,
|
|
179
|
+
blockersContent: blockers,
|
|
180
|
+
nextContent,
|
|
181
|
+
upcomingContent,
|
|
182
|
+
health,
|
|
183
|
+
};
|
|
184
|
+
try {
|
|
185
|
+
const htmlReport = buildDailyReport(reportData, 'html');
|
|
186
|
+
await rateLimitedPost(report.groupId, report.threadId, htmlReport);
|
|
187
|
+
dailyReportSuccesses++;
|
|
188
|
+
dailyReportKeys.add(key);
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
192
|
+
logger.error(`[doctor-all] Daily report post failed for ${entry.slug}: ${msg}`);
|
|
193
|
+
// Daily report failures don't fail the overall run
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Update registry: lastDoctorAllRunAt, per-topic timestamps, and daily report timestamps
|
|
142
198
|
await withRegistry(workspaceDir, (data) => {
|
|
143
199
|
data.lastDoctorAllRunAt = now.toISOString();
|
|
144
200
|
for (const [_key, entry] of Object.entries(data.topics)) {
|
|
@@ -169,17 +225,25 @@ export async function handleDoctorAll(ctx) {
|
|
|
169
225
|
entry.consecutiveSilentDoctors = 0;
|
|
170
226
|
}
|
|
171
227
|
}
|
|
228
|
+
// Batch-update lastDailyReportAt for successful daily reports
|
|
229
|
+
for (const key of dailyReportKeys) {
|
|
230
|
+
const entry = data.topics[key];
|
|
231
|
+
if (entry) {
|
|
232
|
+
entry.lastDailyReportAt = now.toISOString();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
172
235
|
});
|
|
173
236
|
// Build summary
|
|
174
237
|
const lines = [
|
|
175
|
-
`**
|
|
238
|
+
`**Health Check Summary**`,
|
|
176
239
|
'',
|
|
177
|
-
`
|
|
178
|
-
`Skipped
|
|
179
|
-
`Total: ${allEntries.length}`,
|
|
240
|
+
`Checked: ${processed}`,
|
|
241
|
+
`Skipped: ${skipped}`,
|
|
242
|
+
`Total topics: ${allEntries.length}`,
|
|
180
243
|
];
|
|
181
244
|
if (ctx.postFn) {
|
|
182
245
|
lines.push(`Posted: ${postSuccesses}, Post failures: ${postErrors}`);
|
|
246
|
+
lines.push(`Daily reports: ${dailyReportSuccesses} sent, ${dailyReportSkipped} skipped`);
|
|
183
247
|
}
|
|
184
248
|
if (errors.length > 0) {
|
|
185
249
|
lines.push('');
|
|
@@ -193,10 +257,7 @@ export async function handleDoctorAll(ctx) {
|
|
|
193
257
|
}
|
|
194
258
|
if (migrationGroups.length > 0) {
|
|
195
259
|
lines.push('');
|
|
196
|
-
lines.push(
|
|
197
|
-
for (const gid of migrationGroups) {
|
|
198
|
-
lines.push(`- Group ${gid}: all topics failed. Check for group migration.`);
|
|
199
|
-
}
|
|
260
|
+
lines.push(`**Warning:** ${migrationGroups.length} group(s) had all topics fail. The group may have been migrated or deleted.`);
|
|
200
261
|
}
|
|
201
262
|
return {
|
|
202
263
|
text: lines.join('\n'),
|