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.
Files changed (76) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +135 -33
  3. package/README.zh-CN.md +135 -33
  4. package/dist/cli.js +115 -1
  5. package/dist/cli.js.map +1 -1
  6. package/dist/core/agent-helper.d.ts +45 -0
  7. package/dist/core/agent-helper.d.ts.map +1 -0
  8. package/dist/core/agent-helper.js +124 -0
  9. package/dist/core/agent-helper.js.map +1 -0
  10. package/dist/core/approval-bus.d.ts +18 -0
  11. package/dist/core/approval-bus.d.ts.map +1 -1
  12. package/dist/core/approval-bus.js +89 -0
  13. package/dist/core/approval-bus.js.map +1 -1
  14. package/dist/core/approval-router.d.ts +2 -2
  15. package/dist/core/approval-router.d.ts.map +1 -1
  16. package/dist/core/approval-router.js +117 -24
  17. package/dist/core/approval-router.js.map +1 -1
  18. package/dist/core/commands/remind.d.ts +3 -0
  19. package/dist/core/commands/remind.d.ts.map +1 -0
  20. package/dist/core/commands/remind.js +271 -0
  21. package/dist/core/commands/remind.js.map +1 -0
  22. package/dist/core/pending-reminder.d.ts +25 -0
  23. package/dist/core/pending-reminder.d.ts.map +1 -0
  24. package/dist/core/pending-reminder.js +53 -0
  25. package/dist/core/pending-reminder.js.map +1 -0
  26. package/dist/core/registry.d.ts.map +1 -1
  27. package/dist/core/registry.js +2 -0
  28. package/dist/core/registry.js.map +1 -1
  29. package/dist/core/remind-intent.d.ts +25 -0
  30. package/dist/core/remind-intent.d.ts.map +1 -0
  31. package/dist/core/remind-intent.js +185 -0
  32. package/dist/core/remind-intent.js.map +1 -0
  33. package/dist/core/reminder-rpc.d.ts +17 -0
  34. package/dist/core/reminder-rpc.d.ts.map +1 -0
  35. package/dist/core/reminder-rpc.js +169 -0
  36. package/dist/core/reminder-rpc.js.map +1 -0
  37. package/dist/core/reminders.d.ts +159 -0
  38. package/dist/core/reminders.d.ts.map +1 -0
  39. package/dist/core/reminders.js +977 -0
  40. package/dist/core/reminders.js.map +1 -0
  41. package/dist/core/router.d.ts.map +1 -1
  42. package/dist/core/router.js +14 -1
  43. package/dist/core/router.js.map +1 -1
  44. package/dist/core/types.d.ts +3 -0
  45. package/dist/core/types.d.ts.map +1 -1
  46. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts +12 -0
  47. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts.map +1 -1
  48. package/dist/plugins/agents/claude-code/mcp-approval-server.js +165 -18
  49. package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -1
  50. package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts +11 -0
  51. package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts.map +1 -0
  52. package/dist/plugins/agents/opencode/ensure-mcp-config.js +100 -0
  53. package/dist/plugins/agents/opencode/ensure-mcp-config.js.map +1 -0
  54. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts.map +1 -1
  55. package/dist/plugins/agents/opencode/opencode-http-adapter.js +44 -1
  56. package/dist/plugins/agents/opencode/opencode-http-adapter.js.map +1 -1
  57. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts +8 -0
  58. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts.map +1 -1
  59. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js +36 -2
  60. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js.map +1 -1
  61. package/dist/plugins/messengers/email/email-adapter.d.ts +34 -0
  62. package/dist/plugins/messengers/email/email-adapter.d.ts.map +1 -0
  63. package/dist/plugins/messengers/email/email-adapter.js +137 -0
  64. package/dist/plugins/messengers/email/email-adapter.js.map +1 -0
  65. package/dist/plugins/messengers/wechat/context-store.d.ts +18 -0
  66. package/dist/plugins/messengers/wechat/context-store.d.ts.map +1 -0
  67. package/dist/plugins/messengers/wechat/context-store.js +105 -0
  68. package/dist/plugins/messengers/wechat/context-store.js.map +1 -0
  69. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -1
  70. package/dist/plugins/messengers/wechat/ilink-adapter.js +46 -8
  71. package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -1
  72. package/dist/web/public/reminders.html +233 -0
  73. package/dist/web/server.d.ts.map +1 -1
  74. package/dist/web/server.js +93 -1
  75. package/dist/web/server.js.map +1 -1
  76. 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
- - **Browser dashboard** — chat UI, tasks panel (jobs / schedules / approvals / health / files / audit), settings page with workspace CRUD
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 通道,4+ 种 Agent** — 微信(图片 / 文件 / 语音)、飞书、Telegram、DiscordClaude Code、Codex、Copilot、OpenCode,以及任意 ACP 端点
12
- - **浏览器控制台**对话界面、任务面板(Jobs / 调度 / 审批 / 健康 / 文件 / 审计)、设置页含工作区 CRUD
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` / `批准` / `拒绝` | 同意 / 拒绝 Claude 工具调用 |
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