im-hub-pro 0.2.36 → 0.2.37
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/CHANGELOG.md +59 -0
- package/README.md +135 -33
- package/README.zh-CN.md +135 -33
- package/dist/cli.js +115 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-helper.d.ts +45 -0
- package/dist/core/agent-helper.d.ts.map +1 -0
- package/dist/core/agent-helper.js +124 -0
- package/dist/core/agent-helper.js.map +1 -0
- package/dist/core/approval-bus.d.ts +18 -0
- package/dist/core/approval-bus.d.ts.map +1 -1
- package/dist/core/approval-bus.js +89 -0
- package/dist/core/approval-bus.js.map +1 -1
- package/dist/core/approval-router.d.ts +2 -2
- package/dist/core/approval-router.d.ts.map +1 -1
- package/dist/core/approval-router.js +117 -24
- package/dist/core/approval-router.js.map +1 -1
- package/dist/core/commands/remind.d.ts +3 -0
- package/dist/core/commands/remind.d.ts.map +1 -0
- package/dist/core/commands/remind.js +271 -0
- package/dist/core/commands/remind.js.map +1 -0
- package/dist/core/pending-reminder.d.ts +25 -0
- package/dist/core/pending-reminder.d.ts.map +1 -0
- package/dist/core/pending-reminder.js +53 -0
- package/dist/core/pending-reminder.js.map +1 -0
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +2 -0
- package/dist/core/registry.js.map +1 -1
- package/dist/core/remind-intent.d.ts +25 -0
- package/dist/core/remind-intent.d.ts.map +1 -0
- package/dist/core/remind-intent.js +185 -0
- package/dist/core/remind-intent.js.map +1 -0
- package/dist/core/reminder-rpc.d.ts +17 -0
- package/dist/core/reminder-rpc.d.ts.map +1 -0
- package/dist/core/reminder-rpc.js +169 -0
- package/dist/core/reminder-rpc.js.map +1 -0
- package/dist/core/reminders.d.ts +159 -0
- package/dist/core/reminders.d.ts.map +1 -0
- package/dist/core/reminders.js +977 -0
- package/dist/core/reminders.js.map +1 -0
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +14 -1
- package/dist/core/router.js.map +1 -1
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts +12 -0
- package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts.map +1 -1
- package/dist/plugins/agents/claude-code/mcp-approval-server.js +165 -18
- package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -1
- package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts +11 -0
- package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts.map +1 -0
- package/dist/plugins/agents/opencode/ensure-mcp-config.js +100 -0
- package/dist/plugins/agents/opencode/ensure-mcp-config.js.map +1 -0
- package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts.map +1 -1
- package/dist/plugins/agents/opencode/opencode-http-adapter.js +44 -1
- package/dist/plugins/agents/opencode/opencode-http-adapter.js.map +1 -1
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts +8 -0
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts.map +1 -1
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.js +36 -2
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.js.map +1 -1
- package/dist/plugins/messengers/email/email-adapter.d.ts +34 -0
- package/dist/plugins/messengers/email/email-adapter.d.ts.map +1 -0
- package/dist/plugins/messengers/email/email-adapter.js +137 -0
- package/dist/plugins/messengers/email/email-adapter.js.map +1 -0
- package/dist/plugins/messengers/wechat/context-store.d.ts +18 -0
- package/dist/plugins/messengers/wechat/context-store.d.ts.map +1 -0
- package/dist/plugins/messengers/wechat/context-store.js +105 -0
- package/dist/plugins/messengers/wechat/context-store.js.map +1 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -1
- package/dist/plugins/messengers/wechat/ilink-adapter.js +46 -8
- package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -1
- package/dist/web/public/reminders.html +233 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +93 -1
- package/dist/web/server.js.map +1 -1
- package/package.json +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.37] - 2026-05-10 — Reminders + Email + Multi-agent MCP
|
|
6
|
+
|
|
7
|
+
> 设计文档:`docs/architecture/reminders.md`
|
|
8
|
+
|
|
9
|
+
### Added — `/remind` 子系统
|
|
10
|
+
|
|
11
|
+
**核心**
|
|
12
|
+
- `/remind` slash command(一次性 + 定期 + 邮件 + 字面/LLM 模式)
|
|
13
|
+
- 时间解析:中文 NL 时长 / 时点 / 循环表达式(每天 N 点 / 每周 X / 工作日 / 周末 / 每隔 N),英文 / HH:MM / ISO
|
|
14
|
+
- 5 秒 tick 调度器;崩溃恢复(`firing → pending`);7 天 fireAt 上限;50 条 / 用户配额
|
|
15
|
+
|
|
16
|
+
**LLM 增强(默认开启)**
|
|
17
|
+
- **F1 LLM 意图识别**:非 slash 消息走 detector,时间词正则预过滤后调当前 thread 的 active agent 抽 JSON。置信度 ≥ 0.85 弹"要不要为这件事设个提醒?"卡片,y/n 确认。可 `/remind aiwatch off` 关
|
|
18
|
+
- **F2 LLM 投递润色**:fire 时把字面文本作为 prompt 喂给当前 agent 重写成自然投递语(系统约束:"不献媚、不过度幽默、不夸大",≤ 80 字)。20s 超时 fallback 字面。`/remind literal …` 强制字面投递。每 N 分钟(< 1h)+ LLM 模式被拒绝(防止高频 LLM 调用)
|
|
19
|
+
- **Agent context snippet**:每次 agent 调用前在 prompt 前注入 ≤ 5 条 pending 提醒摘要,agent 不用主动 list_reminders 就能看到现状
|
|
20
|
+
|
|
21
|
+
**Agent MCP 工具(提醒兜底创建)**
|
|
22
|
+
- 4 个工具:`create_reminder` / `list_reminders` / `cancel_reminder` / `snooze_reminder`
|
|
23
|
+
- claude-code:复用现有 `--mcp-config` per-spawn 注入
|
|
24
|
+
- opencode (stdio):`prepareCommand` 注入 `IMHUB_RUN_ID` extraEnv,全局 `mcp.imhub` config 自动注册
|
|
25
|
+
- opencode (http):常驻 daemon 共享 MCP server,agent-asserted `_im_context` 兜底(**单用户限定**——agent 可伪造身份)
|
|
26
|
+
- codex:未实施(`codex exec` 不支持外部 MCP)
|
|
27
|
+
|
|
28
|
+
**Web 管理**
|
|
29
|
+
- `/reminders` 页面(dark mode、状态筛选、取消、延后)
|
|
30
|
+
- 3 个 REST endpoint:`GET /api/reminders`、`POST /api/reminders/:id/cancel`、`POST /api/reminders/:id/snooze`
|
|
31
|
+
|
|
32
|
+
### Added — 邮件投递通道
|
|
33
|
+
- 注册为 `email` messenger(`threadId` 即邮箱地址)
|
|
34
|
+
- nodemailer + Gmail / QQ / 163 / 任意 SMTP(env:`IMHUB_SMTP_HOST/PORT/USER/PASS/FROM/SECURE`)
|
|
35
|
+
- `/remind email <addr> <time> <text>` 内联 / `/remind bindemail` 绑定默认 / `/remind email <time> <text>` 走默认
|
|
36
|
+
- 邮件 + 循环 + LLM 润色组合自由
|
|
37
|
+
|
|
38
|
+
### Added — WeChat context_token 持久化
|
|
39
|
+
- iLink Bot API 30 分钟 context_token TTL 之前只存内存,重启即丢,导致循环提醒在 wechat 端高概率失败
|
|
40
|
+
- 新 SQLite 表(`~/.im-hub/wechat-context.db`),收消息时写穿,启动时 warm 内存 Map,cleanup 同步清 DB
|
|
41
|
+
|
|
42
|
+
### Schema migrations
|
|
43
|
+
新建 / 扩展三张表(在 `reminders.db` 内):
|
|
44
|
+
- `reminders` 主表:`recurrence` `last_fired_at` `prompt_mode` 三列追加(idempotent ALTER)
|
|
45
|
+
- `user_email_bindings`
|
|
46
|
+
- `user_aiwatch_settings`
|
|
47
|
+
|
|
48
|
+
新独立 SQLite:`wechat-context.db`
|
|
49
|
+
|
|
50
|
+
### Tests
|
|
51
|
+
新增 7 个测试文件,~50 个新 case:`agent-helper`、`pending-reminder`、`remind-intent`、`reminder-rpc`、`email-adapter`、`wechat-context-store`、扩展的 `reminders.test.ts`。bun test 1033 pass / 34 skip / 0 new fail。
|
|
52
|
+
|
|
53
|
+
### Dependencies
|
|
54
|
+
- `nodemailer ^8.0.7`
|
|
55
|
+
|
|
56
|
+
### Known limitations
|
|
57
|
+
- opencode http 多用户场景不安全(`_im_context` 可伪造);多用户部署应选 stdio 或 claude-code
|
|
58
|
+
- `/api/reminders` 无 per-user owner-scoping(单运营者部署)
|
|
59
|
+
- codex 暂无 MCP reminder 工具(重写需 ~5h,下一轮专项)
|
|
60
|
+
- F1 detector 时区跟随 server `toLocaleString()`
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
5
64
|
## [0.2.35] - 2026-05-09
|
|
6
65
|
|
|
7
66
|
### Added — WeChat & Telegram rich media support
|
package/README.md
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
## Highlights
|
|
10
10
|
|
|
11
|
-
- **4 messengers, 4+ agents** — WeChat (image / file / voice), Feishu, Telegram, Discord; Claude Code, Codex, Copilot, OpenCode, plus any ACP endpoint
|
|
12
|
-
-
|
|
11
|
+
- **4 messengers + email, 4+ agents** — WeChat (image / file / voice), Feishu, Telegram, Discord, Email (SMTP); Claude Code, Codex, Copilot, OpenCode, plus any ACP endpoint
|
|
12
|
+
- **`/remind` reminders subsystem** — one-shot + recurring (`每天8点喝水`); LLM auto-detects reminder intent in casual chat; LLM polishes delivery; agent MCP tools; web `/reminders` page; email + IM delivery
|
|
13
|
+
- **Browser dashboard** — chat UI, tasks panel (jobs / schedules / approvals / health / files / audit), reminders panel, settings page with workspace CRUD
|
|
13
14
|
- **Human-in-the-loop tool approval** — Claude tool calls pause for `y`/`n` in IM or in-page card; works across all platforms
|
|
14
15
|
- **Rich media in WeChat & Telegram** — receive images, files, videos; voice messages transcribed via WeChat STT, OpenAI Whisper, or whisper.cpp
|
|
15
16
|
- **Smart routing** — intent classifier (CJK + ASCII), sticky sessions, circuit breaker, rate limiter
|
|
@@ -52,8 +53,9 @@ im-hub-pro start # config, env vars, headers all unchanged
|
|
|
52
53
|
|
|
53
54
|
| Category | Details |
|
|
54
55
|
|----------|---------|
|
|
55
|
-
| **Messengers** | WeChat (iLink — image / file / voice / video), Feishu (WebSocket), Telegram (grammy — photo / voice / audio), Discord (discord.js) |
|
|
56
|
+
| **Messengers** | WeChat (iLink — image / file / voice / video), Feishu (WebSocket), Telegram (grammy — photo / voice / audio), Discord (discord.js), Email (SMTP, push-only) |
|
|
56
57
|
| **Agents** | Claude Code, Codex, Copilot, OpenCode — all via shared `AgentBase`; any HTTP agent via ACP |
|
|
58
|
+
| **Reminders** | `/remind` slash, LLM intent detection, LLM-polished delivery, MCP tools for agents (claude-code + opencode), web `/reminders` UI |
|
|
57
59
|
| **Web UI** | Chat with streaming, three-state theme (light / dark / system), bilingual (EN / 中文), SSE dashboard |
|
|
58
60
|
| **Tool Approval** | Human-in-the-loop over IM + in-page cards; MCP sidecar for Claude |
|
|
59
61
|
| **Jobs** | Persistent SQLite job board + cron scheduler; batch ops; background task reader |
|
|
@@ -85,9 +87,10 @@ im-hub-pro messengers # List available messengers
|
|
|
85
87
|
| `/new` | New conversation (clear context) |
|
|
86
88
|
| `/model [provider/model]` | View or switch model |
|
|
87
89
|
| `/think on\|off` | Toggle extended thinking |
|
|
90
|
+
| `/remind …` | Reminders — see [Reminders](#reminders) below |
|
|
88
91
|
| `/job`, `/schedule`, `/audit`, `/stats` | Manage jobs, schedules, audit, stats |
|
|
89
92
|
| `/router status\|explain` | Inspect routing decisions |
|
|
90
|
-
| `y` / `n` / `批准` / `拒绝` | Approve / deny Claude tool call |
|
|
93
|
+
| `y` / `n` / `批准` / `拒绝` | Approve / deny Claude tool call (or reminder confirmation card) |
|
|
91
94
|
|
|
92
95
|
## Human-in-the-loop Tool Approval
|
|
93
96
|
|
|
@@ -102,6 +105,132 @@ Reply y to approve / n to deny (auto-deny in 5 min)
|
|
|
102
105
|
|
|
103
106
|
Reply `y` / `n` in IM, or click Allow / Deny in the web UI. Works identically across WeChat, Telegram, Feishu, and Discord. Disable with `IMHUB_APPROVAL_DISABLED=1`.
|
|
104
107
|
|
|
108
|
+
## Reminders
|
|
109
|
+
|
|
110
|
+
Built-in `/remind` subsystem — one-shot or recurring, with three creation paths and three delivery enhancements.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# One-shot
|
|
114
|
+
/remind 2m drink water
|
|
115
|
+
/remind 40秒喝水
|
|
116
|
+
/remind 下午6点下班
|
|
117
|
+
/remind 18:30 出门
|
|
118
|
+
|
|
119
|
+
# Recurring 🔁
|
|
120
|
+
/remind 每5分钟看屏幕外
|
|
121
|
+
/remind 每天早上8点喝水
|
|
122
|
+
/remind 每周一三五9点站会
|
|
123
|
+
/remind 每个工作日18:00下班
|
|
124
|
+
|
|
125
|
+
# Email delivery ✉️ (requires SMTP — see Configuration)
|
|
126
|
+
/remind email me@x.com 8:00 morning briefing
|
|
127
|
+
/remind bindemail me@x.com # bind default → /remind email 每天8点 早安
|
|
128
|
+
/remind unbindemail
|
|
129
|
+
|
|
130
|
+
# Manage
|
|
131
|
+
/remind list
|
|
132
|
+
/remind cancel <id>
|
|
133
|
+
/remind clear
|
|
134
|
+
/remind snooze <id> 5m
|
|
135
|
+
/remind aiwatch on|off # toggle the LLM intent detector
|
|
136
|
+
|
|
137
|
+
# Disable LLM polish for one reminder
|
|
138
|
+
/remind literal 每5分钟 喝水
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Three creation paths** (all land in the same `~/.im-hub/reminders.db`):
|
|
142
|
+
|
|
143
|
+
1. **`/remind` slash** — explicit, structured input
|
|
144
|
+
2. **LLM intent detection** (default-on) — say "明天早上8点提醒我开会" without `/remind`, the bot pops a confirmation card; reply `y` to create
|
|
145
|
+
3. **Agent MCP tools** — claude-code and opencode (stdio) auto-call `create_reminder` when you mention future commitments in chat. Works with opencode (http) too via single-user agent-asserted context
|
|
146
|
+
|
|
147
|
+
**Two delivery enhancements**:
|
|
148
|
+
|
|
149
|
+
- **LLM polish** (default-on): at fire time the active agent rewrites the literal seed text into a natural one-liner ("早上好,记得喝杯水"). Tone constrained: *no flattery, no over-humor, no exaggeration*. Falls back to literal text on agent failure / timeout
|
|
150
|
+
- **Late-delivery tag**: > 1 h overdue gets `⏰ 延迟投递` prefix so users know the bot was offline
|
|
151
|
+
|
|
152
|
+
Manage reminders in the web UI at `/reminders` (status filters, snooze, cancel).
|
|
153
|
+
|
|
154
|
+
Full design: [`docs/architecture/reminders.md`](docs/architecture/reminders.md).
|
|
155
|
+
|
|
156
|
+
## Configuration
|
|
157
|
+
|
|
158
|
+
### Config file
|
|
159
|
+
`~/.im-hub/config.json` (validated by zod at startup):
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"messengers": ["wechat-ilink", "telegram"],
|
|
164
|
+
"agents": ["claude-code", "opencode"],
|
|
165
|
+
"defaultAgent": "claude-code",
|
|
166
|
+
"telegram": { "botToken": "***" },
|
|
167
|
+
"acpAgents": [
|
|
168
|
+
{
|
|
169
|
+
"name": "my-agent",
|
|
170
|
+
"endpoint": "https://api.example.com",
|
|
171
|
+
"auth": { "type": "bearer", "token": "***" }
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"workspaces": [
|
|
175
|
+
{
|
|
176
|
+
"id": "team-data",
|
|
177
|
+
"name": "Data team",
|
|
178
|
+
"agents": ["opencode", "my-agent"],
|
|
179
|
+
"members": ["user-123"],
|
|
180
|
+
"rateLimit": { "rate": 30, "intervalSec": 60, "burst": 60 }
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Email reminders (SMTP)
|
|
187
|
+
|
|
188
|
+
To enable `/remind email …`, set these environment variables before starting:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Required
|
|
192
|
+
export IMHUB_SMTP_HOST=smtp.gmail.com
|
|
193
|
+
export IMHUB_SMTP_USER=you@gmail.com
|
|
194
|
+
export IMHUB_SMTP_PASS=<16-char-app-password> # NOT your normal password
|
|
195
|
+
|
|
196
|
+
# Optional
|
|
197
|
+
export IMHUB_SMTP_PORT=465 # default 465
|
|
198
|
+
export IMHUB_SMTP_FROM=you@gmail.com # default = USER
|
|
199
|
+
export IMHUB_SMTP_SECURE=auto # auto | true | false
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Provider quick reference**:
|
|
203
|
+
|
|
204
|
+
| Provider | HOST | PORT | Notes |
|
|
205
|
+
|----------|------|------|-------|
|
|
206
|
+
| Gmail | `smtp.gmail.com` | 465 | Use [App Password](https://myaccount.google.com/apppasswords) — 2-Step Verification required |
|
|
207
|
+
| QQ Mail | `smtp.qq.com` | 465 | "授权码" (account → POP3/SMTP service) |
|
|
208
|
+
| 163 Mail | `smtp.163.com` | 465 | "授权码" (account → POP3/SMTP/IMAP service) |
|
|
209
|
+
| Outlook | `smtp-mail.outlook.com` | 587 | Set `IMHUB_SMTP_SECURE=false` (STARTTLS) |
|
|
210
|
+
| Custom | any host | any port | Set `SECURE` per provider docs |
|
|
211
|
+
|
|
212
|
+
Without these env vars, the email adapter still loads but `/remind email …` returns "Email adapter not configured" — IM reminders keep working.
|
|
213
|
+
|
|
214
|
+
For systemd, put env in your unit file:
|
|
215
|
+
```ini
|
|
216
|
+
[Service]
|
|
217
|
+
Environment="IMHUB_SMTP_HOST=smtp.gmail.com"
|
|
218
|
+
Environment="IMHUB_SMTP_USER=you@gmail.com"
|
|
219
|
+
Environment="IMHUB_SMTP_PASS=xxxxxxxxxxxxxxxx"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Other env vars
|
|
223
|
+
|
|
224
|
+
| Env | Default | What it does |
|
|
225
|
+
|-----|---------|-------------|
|
|
226
|
+
| `IMHUB_WEB_BIND` | `127.0.0.1` | Web UI bind address (set `0.0.0.0` to expose; front with HTTPS reverse proxy) |
|
|
227
|
+
| `IMHUB_APPROVAL_DISABLED` | unset | Set `=1` to skip the human-in-the-loop tool approval gate |
|
|
228
|
+
| `IMHUB_OPENCODE_DRIVER` | `stdio` | `http` selects the HTTP driver (faster, but reminder MCP path uses single-user agent-asserted context) |
|
|
229
|
+
| `IMHUB_OPENCODE_GATE` | `medium` | `strict` / `loose` / `none` — opencode permission gate |
|
|
230
|
+
| `IM_HUB_LLM_JUDGE_AGENT` | unset | Agent name used as LLM router fallback judge |
|
|
231
|
+
| `OPENAI_API_KEY` | unset | Enables OpenAI Whisper for voice transcription |
|
|
232
|
+
| `IMHUB_WHISPERCPP_BIN` + `IMHUB_WHISPERCPP_MODEL` | unset | Local Whisper.cpp for voice transcription (no cloud) |
|
|
233
|
+
|
|
105
234
|
## Architecture
|
|
106
235
|
|
|
107
236
|
```
|
|
@@ -145,35 +274,6 @@ Single process, zero external dependencies — SQLite + session files are the en
|
|
|
145
274
|
|
|
146
275
|
For the full deep-dive see [`docs/architecture/current.md`](docs/architecture/current.md).
|
|
147
276
|
|
|
148
|
-
## Configuration
|
|
149
|
-
|
|
150
|
-
Config file: `~/.im-hub/config.json` (validated by zod at startup)
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
{
|
|
154
|
-
"messengers": ["wechat", "telegram"],
|
|
155
|
-
"agents": ["claude-code", "opencode"],
|
|
156
|
-
"defaultAgent": "claude-code",
|
|
157
|
-
"telegram": { "botToken": "***" },
|
|
158
|
-
"acpAgents": [
|
|
159
|
-
{
|
|
160
|
-
"name": "my-agent",
|
|
161
|
-
"endpoint": "https://api.example.com",
|
|
162
|
-
"auth": { "type": "bearer", "token": "***" }
|
|
163
|
-
}
|
|
164
|
-
],
|
|
165
|
-
"workspaces": [
|
|
166
|
-
{
|
|
167
|
-
"id": "team-data",
|
|
168
|
-
"name": "Data team",
|
|
169
|
-
"agents": ["opencode", "my-agent"],
|
|
170
|
-
"members": ["user-123"],
|
|
171
|
-
"rateLimit": { "rate": 30, "intervalSec": 60, "burst": 60 }
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
277
|
## Requirements
|
|
178
278
|
|
|
179
279
|
- **Node.js ≥ 18** (≥ 22 LTS recommended)
|
|
@@ -215,9 +315,11 @@ See [`docs/deployment.md`](docs/deployment.md) for systemd, Docker, nginx, monit
|
|
|
215
315
|
| v0.2.20–23 | Web console — theme, approvals, SSE, files, batch ops |
|
|
216
316
|
| v0.2.30 | Production hardening — session isolation, serial WS, loopback bind |
|
|
217
317
|
| v0.2.35 | WeChat & Telegram rich media — image / file / voice / video |
|
|
318
|
+
| v0.2.37 | Reminders subsystem — `/remind`, LLM intent + polish, agent MCP tools, email channel, web `/reminders`, WeChat context_token persistence |
|
|
218
319
|
|
|
219
320
|
### v0.3.0 (next)
|
|
220
321
|
|
|
322
|
+
- [ ] codex MCP-server mode (so codex can use reminder tools too)
|
|
221
323
|
- [ ] DingTalk adapter
|
|
222
324
|
- [ ] Slack adapter
|
|
223
325
|
- [ ] Feishu / Discord button-style approval cards
|
package/README.zh-CN.md
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
## 亮点
|
|
10
10
|
|
|
11
|
-
- **4 种 IM
|
|
12
|
-
-
|
|
11
|
+
- **4 种 IM + 邮件,4+ 种 Agent** — 微信(图片 / 文件 / 语音)、飞书、Telegram、Discord、Email(SMTP);Claude Code、Codex、Copilot、OpenCode,以及任意 ACP 端点
|
|
12
|
+
- **`/remind` 提醒子系统** — 一次性 + 定期(`每天8点喝水`);非 slash 消息 LLM 自动识别意图;LLM 润色投递文本;Agent MCP 工具直接创建;Web `/reminders` 管理页;邮件 + IM 双通道投递
|
|
13
|
+
- **浏览器控制台** — 对话界面、任务面板(Jobs / 调度 / 审批 / 健康 / 文件 / 审计)、提醒面板、设置页含工作区 CRUD
|
|
13
14
|
- **工具调用人审(HITL)** — Claude 调用工具时暂停,IM 回复 `y`/`n` 或在页面点卡片审批;全平台一致
|
|
14
15
|
- **微信和 Telegram 富媒体** — 接收图片、文件、视频;语音消息支持微信 STT、OpenAI Whisper、whisper.cpp 转写
|
|
15
16
|
- **智能路由** — 意图分类(中英文)、Sticky 会话、断路器、限流器
|
|
@@ -52,8 +53,9 @@ im-hub-pro start # 配置、环境变量、Header 全部不变
|
|
|
52
53
|
|
|
53
54
|
| 分类 | 内容 |
|
|
54
55
|
|------|------|
|
|
55
|
-
| **IM 通道** | 微信(iLink — 图片 / 文件 / 语音 / 视频)、飞书(WebSocket)、Telegram(grammy — 图片 / 语音 / 音频)、Discord(discord.js
|
|
56
|
+
| **IM 通道** | 微信(iLink — 图片 / 文件 / 语音 / 视频)、飞书(WebSocket)、Telegram(grammy — 图片 / 语音 / 音频)、Discord(discord.js)、Email(SMTP,仅推送) |
|
|
56
57
|
| **Agent** | Claude Code、Codex、Copilot、OpenCode(统一 `AgentBase`);任何 HTTP Agent 通过 ACP 接入 |
|
|
58
|
+
| **提醒** | `/remind` slash、LLM 意图识别、LLM 润色投递、Agent MCP 工具(claude-code + opencode)、Web `/reminders` 页面 |
|
|
57
59
|
| **Web 控制台** | 流式对话、三态主题(浅 / 深 / 跟随系统)、中英双语、SSE 实时仪表盘 |
|
|
58
60
|
| **工具人审** | IM 端 + 页面内审批卡;Claude MCP sidecar |
|
|
59
61
|
| **任务** | SQLite 持久化 Job Board + cron 调度;批量操作;后台任务读取 |
|
|
@@ -85,9 +87,10 @@ im-hub-pro messengers # 列出可用 IM
|
|
|
85
87
|
| `/new` | 开新会话(清空历史) |
|
|
86
88
|
| `/model [provider/model]` | 查看或切换模型 |
|
|
87
89
|
| `/think on\|off` | 切换深度思考模式 |
|
|
90
|
+
| `/remind …` | 提醒子系统 — 详见 [提醒](#提醒) |
|
|
88
91
|
| `/job`、`/schedule`、`/audit`、`/stats` | 管理任务、调度、审计、统计 |
|
|
89
92
|
| `/router status\|explain` | 查看路由策略 |
|
|
90
|
-
| `y` / `n` / `批准` / `拒绝` | 同意 /
|
|
93
|
+
| `y` / `n` / `批准` / `拒绝` | 同意 / 拒绝(工具调用 或 提醒确认卡片) |
|
|
91
94
|
|
|
92
95
|
## 工具调用人审
|
|
93
96
|
|
|
@@ -102,6 +105,132 @@ im-hub-pro messengers # 列出可用 IM
|
|
|
102
105
|
|
|
103
106
|
在 IM 回复 `y` / `n`,或在 Web 界面点 Allow / Deny。微信、Telegram、飞书、Discord 行为一致。`IMHUB_APPROVAL_DISABLED=1` 可关闭。
|
|
104
107
|
|
|
108
|
+
## 提醒
|
|
109
|
+
|
|
110
|
+
内置 `/remind` 子系统——一次性 / 定期,三种创建路径,三种投递增强。
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# 一次性
|
|
114
|
+
/remind 2m 喝水
|
|
115
|
+
/remind 40秒喝水
|
|
116
|
+
/remind 下午6点下班
|
|
117
|
+
/remind 18:30 出门
|
|
118
|
+
|
|
119
|
+
# 定期 🔁
|
|
120
|
+
/remind 每5分钟看屏幕外
|
|
121
|
+
/remind 每天早上8点喝水
|
|
122
|
+
/remind 每周一三五9点站会
|
|
123
|
+
/remind 每个工作日18:00下班
|
|
124
|
+
|
|
125
|
+
# 邮件 ✉️ (需要 SMTP 配置——见 [配置](#配置))
|
|
126
|
+
/remind email me@x.com 8:00 早安
|
|
127
|
+
/remind bindemail me@x.com # 绑定默认邮箱 → /remind email 每天8点 早安
|
|
128
|
+
/remind unbindemail
|
|
129
|
+
|
|
130
|
+
# 管理
|
|
131
|
+
/remind list
|
|
132
|
+
/remind cancel <id>
|
|
133
|
+
/remind clear
|
|
134
|
+
/remind snooze <id> 5m
|
|
135
|
+
/remind aiwatch on|off # 切换 LLM 意图识别
|
|
136
|
+
|
|
137
|
+
# 关掉某条提醒的 LLM 润色
|
|
138
|
+
/remind literal 每5分钟 喝水
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**三种创建路径**(最终都落到同一个 `~/.im-hub/reminders.db`):
|
|
142
|
+
|
|
143
|
+
1. **`/remind` slash** — 显式、结构化输入
|
|
144
|
+
2. **LLM 意图识别**(默认开启)— 直接说"明天早上8点提醒我开会",机器人弹"要不要为这件事设个提醒?"卡片,回复 `y` 创建
|
|
145
|
+
3. **Agent MCP 工具** — 在 claude-code / opencode (stdio) 会话里聊到未来事件时,agent 自动调 `create_reminder`。opencode (http) 走单用户 agent-asserted context 路径
|
|
146
|
+
|
|
147
|
+
**两种投递增强**:
|
|
148
|
+
|
|
149
|
+
- **LLM 润色**(默认开启):触发时由当前 agent 把字面文本重写成自然提醒("早上好,记得喝杯水")。系统约束:**不献媚、不过度幽默、不夸大**。Agent 失败 / 超时时回退字面文本
|
|
150
|
+
- **延迟投递标记**:超过 1 小时未投递的提醒前缀加 `⏰ 延迟投递`,让用户知道 bot 离线过
|
|
151
|
+
|
|
152
|
+
Web 界面 `/reminders` 可视化管理(状态筛选、延后、取消)。
|
|
153
|
+
|
|
154
|
+
完整设计:[`docs/architecture/reminders.md`](docs/architecture/reminders.md)。
|
|
155
|
+
|
|
156
|
+
## 配置
|
|
157
|
+
|
|
158
|
+
### 配置文件
|
|
159
|
+
`~/.im-hub/config.json`(启动时 zod 校验):
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"messengers": ["wechat-ilink", "telegram"],
|
|
164
|
+
"agents": ["claude-code", "opencode"],
|
|
165
|
+
"defaultAgent": "claude-code",
|
|
166
|
+
"telegram": { "botToken": "***" },
|
|
167
|
+
"acpAgents": [
|
|
168
|
+
{
|
|
169
|
+
"name": "my-agent",
|
|
170
|
+
"endpoint": "https://api.example.com",
|
|
171
|
+
"auth": { "type": "bearer", "token": "***" }
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"workspaces": [
|
|
175
|
+
{
|
|
176
|
+
"id": "team-data",
|
|
177
|
+
"name": "数据团队",
|
|
178
|
+
"agents": ["opencode", "my-agent"],
|
|
179
|
+
"members": ["user-123"],
|
|
180
|
+
"rateLimit": { "rate": 30, "intervalSec": 60, "burst": 60 }
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 邮件提醒(SMTP)
|
|
187
|
+
|
|
188
|
+
要启用 `/remind email …`,启动前设置以下环境变量:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# 必填
|
|
192
|
+
export IMHUB_SMTP_HOST=smtp.gmail.com
|
|
193
|
+
export IMHUB_SMTP_USER=you@gmail.com
|
|
194
|
+
export IMHUB_SMTP_PASS=<16位应用专用密码> # 不是登录密码
|
|
195
|
+
|
|
196
|
+
# 可选
|
|
197
|
+
export IMHUB_SMTP_PORT=465 # 默认 465
|
|
198
|
+
export IMHUB_SMTP_FROM=you@gmail.com # 默认 = USER
|
|
199
|
+
export IMHUB_SMTP_SECURE=auto # auto | true | false
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**常见邮箱配置参考**:
|
|
203
|
+
|
|
204
|
+
| 服务商 | HOST | PORT | 备注 |
|
|
205
|
+
|-------|------|------|------|
|
|
206
|
+
| Gmail | `smtp.gmail.com` | 465 | 用 [应用专用密码](https://myaccount.google.com/apppasswords),需先开两步验证 |
|
|
207
|
+
| QQ 邮箱 | `smtp.qq.com` | 465 | 设置 → 账号 → POP3/SMTP 服务 → 授权码 |
|
|
208
|
+
| 163 邮箱 | `smtp.163.com` | 465 | 设置 → POP3/SMTP/IMAP → 授权码 |
|
|
209
|
+
| Outlook | `smtp-mail.outlook.com` | 587 | 设 `IMHUB_SMTP_SECURE=false`(STARTTLS) |
|
|
210
|
+
| 企业邮箱 | 厂商提供 | 任意 | `SECURE` 按厂商文档设 |
|
|
211
|
+
|
|
212
|
+
不设这些环境变量也不会启动失败——邮件 adapter 仍会注册,但 `/remind email …` 会返回"Email adapter not configured",IM 提醒不受影响。
|
|
213
|
+
|
|
214
|
+
systemd 部署示例:
|
|
215
|
+
```ini
|
|
216
|
+
[Service]
|
|
217
|
+
Environment="IMHUB_SMTP_HOST=smtp.gmail.com"
|
|
218
|
+
Environment="IMHUB_SMTP_USER=you@gmail.com"
|
|
219
|
+
Environment="IMHUB_SMTP_PASS=xxxxxxxxxxxxxxxx"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 其他环境变量
|
|
223
|
+
|
|
224
|
+
| 变量 | 默认值 | 作用 |
|
|
225
|
+
|------|--------|------|
|
|
226
|
+
| `IMHUB_WEB_BIND` | `127.0.0.1` | Web UI 监听地址(设 `0.0.0.0` 对外暴露,建议前面挂 HTTPS 反代) |
|
|
227
|
+
| `IMHUB_APPROVAL_DISABLED` | 未设 | 设 `=1` 跳过工具调用人审 |
|
|
228
|
+
| `IMHUB_OPENCODE_DRIVER` | `stdio` | 设 `http` 启用 HTTP driver(更快,但 reminder MCP 路径会走单用户 agent-asserted context) |
|
|
229
|
+
| `IMHUB_OPENCODE_GATE` | `medium` | `strict` / `loose` / `none` — opencode 权限闸 |
|
|
230
|
+
| `IM_HUB_LLM_JUDGE_AGENT` | 未设 | 路由分类失败时用作兜底的 LLM judge agent 名 |
|
|
231
|
+
| `OPENAI_API_KEY` | 未设 | 启用 OpenAI Whisper 做语音转写 |
|
|
232
|
+
| `IMHUB_WHISPERCPP_BIN` + `IMHUB_WHISPERCPP_MODEL` | 未设 | 本地 Whisper.cpp 转写(不走云) |
|
|
233
|
+
|
|
105
234
|
## 架构
|
|
106
235
|
|
|
107
236
|
```
|
|
@@ -145,35 +274,6 @@ im-hub-pro messengers # 列出可用 IM
|
|
|
145
274
|
|
|
146
275
|
深入架构详见 [`docs/architecture/current.md`](docs/architecture/current.md)。
|
|
147
276
|
|
|
148
|
-
## 配置
|
|
149
|
-
|
|
150
|
-
配置文件:`~/.im-hub/config.json`(启动时 zod 校验)
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
{
|
|
154
|
-
"messengers": ["wechat", "telegram"],
|
|
155
|
-
"agents": ["claude-code", "opencode"],
|
|
156
|
-
"defaultAgent": "claude-code",
|
|
157
|
-
"telegram": { "botToken": "***" },
|
|
158
|
-
"acpAgents": [
|
|
159
|
-
{
|
|
160
|
-
"name": "my-agent",
|
|
161
|
-
"endpoint": "https://api.example.com",
|
|
162
|
-
"auth": { "type": "bearer", "token": "***" }
|
|
163
|
-
}
|
|
164
|
-
],
|
|
165
|
-
"workspaces": [
|
|
166
|
-
{
|
|
167
|
-
"id": "team-data",
|
|
168
|
-
"name": "数据团队",
|
|
169
|
-
"agents": ["opencode", "my-agent"],
|
|
170
|
-
"members": ["user-123"],
|
|
171
|
-
"rateLimit": { "rate": 30, "intervalSec": 60, "burst": 60 }
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
277
|
## 环境要求
|
|
178
278
|
|
|
179
279
|
- **Node.js ≥ 18**(推荐 ≥ 22 LTS)
|
|
@@ -215,9 +315,11 @@ systemd、Docker、nginx、监控与升级详见 [`docs/deployment.md`](docs/dep
|
|
|
215
315
|
| v0.2.20–23 | Web 控制台 — 主题、审批、SSE、文件、批量操作 |
|
|
216
316
|
| v0.2.30 | 生产硬化 — 会话隔离、WS 串行、回环监听 |
|
|
217
317
|
| v0.2.35 | 微信和 Telegram 富媒体 — 图片 / 文件 / 语音 / 视频 |
|
|
318
|
+
| v0.2.37 | 提醒子系统 — `/remind`、LLM 意图识别 + 润色、Agent MCP 工具、邮件通道、Web `/reminders`、微信 context_token 持久化 |
|
|
218
319
|
|
|
219
320
|
### v0.3.0(下一版)
|
|
220
321
|
|
|
322
|
+
- [ ] codex MCP-server 模式(让 codex 也能用提醒工具)
|
|
221
323
|
- [ ] 钉钉适配器
|
|
222
324
|
- [ ] Slack 适配器
|
|
223
325
|
- [ ] 飞书 / Discord 卡片按钮版审批
|
package/dist/cli.js
CHANGED
|
@@ -22,6 +22,10 @@ import { workspaceRegistry } from './core/workspace.js';
|
|
|
22
22
|
import { bootstrapAgentWorkspaces } from './core/agent-cwd.js';
|
|
23
23
|
import { approvalBus, threadKey } from './core/approval-bus.js';
|
|
24
24
|
import { install as installApprovalRouter, tryHandleApprovalReply, platformToMessengerName } from './core/approval-router.js';
|
|
25
|
+
import { tryConsumeReply as tryConsumePendingReminderReply } from './core/pending-reminder.js';
|
|
26
|
+
import { tryDetectReminderIntent } from './core/remind-intent.js';
|
|
27
|
+
import { createReminder } from './core/reminders.js';
|
|
28
|
+
import { setReminderConfirmNotifier } from './core/reminder-rpc.js';
|
|
25
29
|
import { checkMessengerConfig, checkAgentAvailability, runMessengerOnboarding, formatAgentInstallHint, formatMessengerStartError, loadConfig as loadOnboardingConfig, saveConfig as saveOnboardingConfig, } from './core/onboarding.js';
|
|
26
30
|
import { startWebServer } from './web/server.js';
|
|
27
31
|
import { startACPServer } from './core/acp-server.js';
|
|
@@ -89,6 +93,12 @@ program
|
|
|
89
93
|
try {
|
|
90
94
|
const sockPath = await approvalBus.start();
|
|
91
95
|
console.log(`✅ Approval bus listening on ${sockPath}`);
|
|
96
|
+
// Expose the path through env so long-lived child processes
|
|
97
|
+
// (notably `opencode serve`, which spawns the MCP sidecar with
|
|
98
|
+
// `env: process.env`) can connect without per-spawn config.
|
|
99
|
+
// claude-code and opencode stdio still set this per-spawn via
|
|
100
|
+
// extraEnv — that override wins inside their spawn scope.
|
|
101
|
+
process.env.IMHUB_APPROVAL_SOCK = sockPath;
|
|
92
102
|
}
|
|
93
103
|
catch (err) {
|
|
94
104
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -116,6 +126,20 @@ program
|
|
|
116
126
|
console.warn(`⚠️ Agent workspace bootstrap failed: ${msg}`);
|
|
117
127
|
console.warn(' Agents will fall back to im-hub-pro cwd ("/" under systemd)');
|
|
118
128
|
}
|
|
129
|
+
// Register the imhub MCP server in opencode's global config so opencode
|
|
130
|
+
// (stdio mode) can see the reminder tools. Idempotent; quietly skips if
|
|
131
|
+
// opencode isn't installed / no config dir / write fails. HTTP mode
|
|
132
|
+
// can't use this — see ensure-mcp-config.ts comments.
|
|
133
|
+
try {
|
|
134
|
+
const { ensureOpencodeMcpRegistered } = await import('./plugins/agents/opencode/ensure-mcp-config.js');
|
|
135
|
+
const wrote = await ensureOpencodeMcpRegistered();
|
|
136
|
+
if (wrote)
|
|
137
|
+
console.log('🔧 Registered imhub MCP in opencode global config');
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
141
|
+
console.warn(`⚠️ opencode MCP registration skipped: ${msg}`);
|
|
142
|
+
}
|
|
119
143
|
// Load ACP (remote) agents from config
|
|
120
144
|
if (config.acpAgents?.length) {
|
|
121
145
|
await registry.loadACPAgents(config.acpAgents);
|
|
@@ -128,6 +152,9 @@ program
|
|
|
128
152
|
// Start the scheduler (runs cron-due schedules every 30s)
|
|
129
153
|
const { startScheduler } = await import('./core/schedule.js');
|
|
130
154
|
startScheduler();
|
|
155
|
+
// Start the reminder engine (one-shot lightweight timers, 5s tick)
|
|
156
|
+
const { startReminderEngine } = await import('./core/reminders.js');
|
|
157
|
+
startReminderEngine();
|
|
131
158
|
// ============================================
|
|
132
159
|
// ONBOARDING CHECKS (before default fill!)
|
|
133
160
|
// ============================================
|
|
@@ -166,8 +193,15 @@ program
|
|
|
166
193
|
// ============================================
|
|
167
194
|
// Get messengers to start (now config.messengers is populated)
|
|
168
195
|
const messengersToStart = config.messengers.length > 0
|
|
169
|
-
? config.messengers
|
|
196
|
+
? [...config.messengers]
|
|
170
197
|
: ['wechat-ilink']; // Fallback default
|
|
198
|
+
// Auto-include the email adapter so /remind email ... works without
|
|
199
|
+
// explicit `im-hub-pro config email`. The adapter's start() is fail-soft
|
|
200
|
+
// when SMTP env vars are missing — it stays "registered but disabled"
|
|
201
|
+
// and any actual send call throws a clear "not configured" error.
|
|
202
|
+
if (!messengersToStart.includes('email')) {
|
|
203
|
+
messengersToStart.push('email');
|
|
204
|
+
}
|
|
171
205
|
// Start messenger adapters
|
|
172
206
|
for (const name of messengersToStart) {
|
|
173
207
|
const messenger = registry.getMessenger(name);
|
|
@@ -190,6 +224,67 @@ program
|
|
|
190
224
|
ctx.logger.info({ event: 'message.consumed_by_approval' });
|
|
191
225
|
return;
|
|
192
226
|
}
|
|
227
|
+
// Reminder-intent confirmation reply ("y"/"n" to a previously-
|
|
228
|
+
// proposed reminder card). Must come BEFORE the detector so a
|
|
229
|
+
// user replying "y" doesn't get re-detected as another reminder.
|
|
230
|
+
const tk = `${ctx.platform}:${ctx.channelId}:${ctx.message.threadId}`;
|
|
231
|
+
const replyDecision = tryConsumePendingReminderReply(tk, ctx.message.text);
|
|
232
|
+
if (replyDecision) {
|
|
233
|
+
if (replyDecision.decision === 'cancel') {
|
|
234
|
+
await messenger.sendMessage(ctx.message.threadId, '✅ 已忽略这条提醒建议');
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const p = replyDecision.pending;
|
|
238
|
+
try {
|
|
239
|
+
const id = createReminder({
|
|
240
|
+
fireAt: p.fireAt,
|
|
241
|
+
text: p.text,
|
|
242
|
+
platform: ctx.platform,
|
|
243
|
+
channelId: ctx.channelId,
|
|
244
|
+
threadId: ctx.message.threadId,
|
|
245
|
+
userId: ctx.message.userId || '',
|
|
246
|
+
source: 'slash',
|
|
247
|
+
recurrence: p.recurrence,
|
|
248
|
+
});
|
|
249
|
+
const recurLine = p.recurrence ? `\n 循环:${p.recurrence}` : '';
|
|
250
|
+
await messenger.sendMessage(ctx.message.threadId, `✅ 已创建提醒 #${id}\n 触发:${p.fireAt.toLocaleString()}${recurLine}\n 内容:${p.text}`);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
254
|
+
await messenger.sendMessage(ctx.message.threadId, `❌ 创建失败:${msg}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
ctx.logger.info({ event: 'message.consumed_by_pending_reminder', decision: replyDecision.decision });
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// Reminder-intent detector: only on non-slash messages and only
|
|
261
|
+
// when the user has aiwatch on. Cheap regex pre-filter inside the
|
|
262
|
+
// detector skips most messages without a time hint, so the LLM
|
|
263
|
+
// call only fires on plausible candidates.
|
|
264
|
+
if (!ctx.message.text.trim().startsWith('/')) {
|
|
265
|
+
try {
|
|
266
|
+
const detected = await tryDetectReminderIntent({
|
|
267
|
+
platform: ctx.platform,
|
|
268
|
+
channelId: ctx.channelId,
|
|
269
|
+
threadId: ctx.message.threadId,
|
|
270
|
+
userId: ctx.message.userId || '',
|
|
271
|
+
text: ctx.message.text,
|
|
272
|
+
});
|
|
273
|
+
if (detected.handled && detected.reply) {
|
|
274
|
+
await messenger.sendMessage(ctx.message.threadId, detected.reply);
|
|
275
|
+
ctx.logger.info({ event: 'message.proposed_reminder' });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
// Detector must never break the message pipeline. Log and
|
|
281
|
+
// fall through to normal agent dispatch.
|
|
282
|
+
ctx.logger.warn({
|
|
283
|
+
event: 'remind-intent.crashed',
|
|
284
|
+
err: err instanceof Error ? err.message : String(err),
|
|
285
|
+
}, 'detector crashed — falling through to normal agent flow');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
193
288
|
await handleMessage(ctx, config.defaultAgent);
|
|
194
289
|
});
|
|
195
290
|
try {
|
|
@@ -216,6 +311,19 @@ program
|
|
|
216
311
|
});
|
|
217
312
|
console.log('✅ Approval router wired to messengers');
|
|
218
313
|
}
|
|
314
|
+
// Reminder confirm notifier — used by reminder-rpc.ts when an agent
|
|
315
|
+
// creates a high-frequency + LLM-polish reminder (those go through a
|
|
316
|
+
// y/n card before landing in the DB). Plain message via the same
|
|
317
|
+
// messenger the agent is talking on.
|
|
318
|
+
setReminderConfirmNotifier(async (ctx, message) => {
|
|
319
|
+
const messenger = registry.getMessenger(platformToMessengerName(ctx.platform))
|
|
320
|
+
?? registry.getMessenger(ctx.platform);
|
|
321
|
+
if (!messenger) {
|
|
322
|
+
console.warn(`reminder-confirm: no messenger for platform "${ctx.platform}"`);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
await messenger.sendMessage(ctx.threadId, message);
|
|
326
|
+
});
|
|
219
327
|
// ============================================
|
|
220
328
|
// START WEB CHAT SERVER
|
|
221
329
|
// ============================================
|
|
@@ -293,6 +401,12 @@ program
|
|
|
293
401
|
closeScheduleDb();
|
|
294
402
|
}
|
|
295
403
|
catch { /* ignore */ }
|
|
404
|
+
try {
|
|
405
|
+
const { stopReminderEngine, closeReminderDb } = await import('./core/reminders.js');
|
|
406
|
+
stopReminderEngine();
|
|
407
|
+
closeReminderDb();
|
|
408
|
+
}
|
|
409
|
+
catch { /* ignore */ }
|
|
296
410
|
process.exit(0);
|
|
297
411
|
});
|
|
298
412
|
// Wait forever
|