内容
# agentic-cal

**Agentic email + calendar hub on Cloudflare Workers.** 一个 Worker拥有您域名的电子邮件界面:一个完整的自我托管的电子邮件客户端,带有 AI 代理,以及一个统一的日历,聚合了 Proton / Outlook / iCloud 到一个忙/空模型,并使用符合标准的电子邮件邀请写回三个日历,使用标准的电子邮件邀请。无需 OAuth,无需 CalDAV,无需提供商 API。
[](https://deploy.workers.cloudflare.com/?url=https://github.com/talalakkari/agentic-cal)
从 [cloudflare/agentic-inbox](https://github.com/cloudflare/agentic-inbox) (Apache 2.0)分叉而来,作为底盘:克隆,重命名,拥有。未跟踪上游。
**一段话:** 读取路径每 10 分钟轮询每个提供商的秘密 ICS 发布链接(条件 GET 通过 KV,原始快照到 R2),并将所有内容(重复规则,例外,时区)扩展到一个统一的忙/空模型中,在 Durable Object 中。写入路径是 RFC 5546 over RFC 6047:阻止时间电子邮件发送符合标准的邀请到每个帐户,本地客户端渲染一个正常的会议邀请,接受(`METHOD:REPLY`)路由回同一个 Worker 的 `email()` 处理程序,其中 Cloudflare 工作流跟踪每个帐户的接受。客户端接受;提供商进行写入。
## 功能
- **完整的电子邮件客户端。** 通过 Cloudflare 电子邮件路由发送和接收;富文本编辑器,线程,文件夹,搜索,附件。每个邮箱都是一个隔离的 Durable Object(SQLite),带有 R2 用于附件。
- **统一日历(读取)。** 每个提供商发布一个只读 ICS 源(Proton “通过链接共享”,Outlook “发布日历”,iCloud “公共日历”)。cron 轮询器将所有这些规范化(RRULE 扩展,EXDATE,RECURRENCE-ID 覆盖,时区)成一个可用性模型,带有每个源的过期警告。
- **日历写入路径(iMIP)。** `block_time` 电子邮件发送 RFC 5546/6047 邀请到每个帐户;本地客户端渲染一个正常的会议邀请;接受路由回同一个 Worker 和 Cloudflare 工作流跟踪生命周期(发送,等待,提醒,确定)每个块。
- **MCP 服务器。** `/mcp` 在 Cloudflare Access 后面暴露 **20 个工具** (13 个电子邮件 + 7 个日历:`get_availability`,`find_free_slots`,`block_time`,`get_block_status`,`cancel_block`,`list_blocks`,`list_calendars`)用于外部代理,如 Claude Code,Hermes 或任何支持 MCP 的代理,通过 Access 服务令牌进行身份验证。
- **内置 AI 代理。** 聊天面板,带有 16 个工具界面(9 个电子邮件 + 7 个日历,与通过 `/mcp` 暴露的 20 个工具重叠但不相同),由 Workers AI 提供支持(默认 `@cf/google/gemma-4-26b-a4b-it`,可通过 `AGENT_MODEL` 变量交换),所有模型流量通过 **AI Gateway**。自动草拟回复 inbound 电子邮件,并在回答调度电子邮件之前检查实际可用性。草拟始终需要显式确认后才能发送。
- **日历 UI。** `/calendar` 页面,用于粘贴链接的源注册,源健康和每个块的接受状态(✓ ✗ ~ …)一键取消。
## 工作原理
三个流程承载整个系统。**读取路径** 将三个提供商折叠成一个忙/空模型;**写入路径** 使用电子邮件阻止时间;**inbound dispatch** 根据消息内容路由每个消息,而不是根据地址。
**读取路径:三个日历到一个忙/空模型**

**写入路径:通过电子邮件阻止时间(iMIP,RFC 5546 over RFC 6047)**

**inbound dispatch:根据消息路由,而不是地址**

## 技术栈
- **前端:** React 19,React Router v7,Tailwind CSS,Zustand,TipTap,`@cloudflare/kumo`
- **后端:** Hono,Workers,Durable Objects(SQLite),Workflows,R2,KV,Email Routing + Email Service,cron triggers
- **日历:** `ical.js`(解析/扩展),手工滚动 RFC 5545 编写,`mimetext` 信封
- **AI:** Cloudflare Agents SDK(`AIChatAgent`),AI SDK v6,Workers AI 通过 AI Gateway(`AGENT_MODEL` / `AI_GATEWAY_ID` 变量)
- **身份验证:** Cloudflare Access JWT 验证(在生产环境中关闭失败)
## 部署
### 一键部署
上面的 **部署到 Cloudflare** 按钮在您的帐户上配置 Worker 及其资源(Durable Objects,`block-time` 工作流,R2 存储桶,KV 命名空间,cron 触发器,Workers AI),并提示您输入域名值。
**按钮无法完成所有操作。** 按照以下手动步骤完成:
1. **Cloudflare Access:** 在 Worker 上启用一键 Access;从模态框设置 `POLICY_AUD` + `TEAM_DOMAIN` 机密。应用程序故意返回 500,直到完成。 为任何 MCP 代理创建服务令牌。
2. **AI Gateway:** 创建一个与 `AI_GATEWAY_ID` 变量(默认 `t9-agentic-cal`)匹配的网关,或将变量设置为 `""` 直接调用 Workers AI。
3. **电子邮件路由:** 在您的域名上,添加一个 catch-all 规则 → 这个 Worker;启用发送电子邮件服务;将您的日历邀请地址添加为 **已验证的目标地址**。
4. **变量:** 将 `DOMAINS` 设置为您的域名,将 `ORGANIZER_ADDR` 设置为 `calendar@<your-domain>`。这个单一地址既是会议组织者,也是正常的受监控的收件箱:调度将 RSVP 路由到日历路径,其他所有内容通过有效载荷 + UID(而非地址)路由到收件箱,因此普通的邮件不会被吞噬。
5. **源:** 打开 `/calendar` 并粘贴您的三个 ICS 发布链接(下表)。
### 手动 / 本地
```bash
npm install
npm run dev # 本地开发(使用.dev.vars,参见.dev.vars.example)
npm run typecheck # cf-typegen + tsc,在每次推送前运行
npx tsx scripts/smoke-ics.ts && npx tsx scripts/smoke-availability.ts && npx tsx scripts/smoke-imip.ts
```
手动克隆而不是使用按钮?创建您自己的 KV 命名空间(`wrangler kv namespace create calendar-feed-cache`)和 R2 存储桶(`agentic-inbox`,`calendar-snapshots`),并将您的 KV ID 放入 `wrangler.jsonc`。然后使用 `wrangler deploy` 部署。
## 注册日历源
打开 **`/calendar`** (收件箱页面上的按钮)。每个提供商一个卡片:粘贴发布链接,设置邀请地址,保存,点击 **立即轮询**:
| 提供商 | 生成链接的位置 | 推荐详情 |
|---|---|---|
| Proton | Proton 日历 → 设置 → **通过链接共享日历** | 完整 |
| Outlook (M365) | 设置 → 日历 → 共享日历 → **发布日历** → ICS | 仅忙碌(Microsoft 缓存;源延迟数小时) |
| iCloud | Apple 日历 → 日历信息 → **公共日历** → 复制 `webcal://` 链接 | 完整 |
- `webcal://` URL 会自动转换;存储的发布 URL 是只写的机密(仅回显截断形式)。
- 每个源的 **邀请电子邮件** 是发送 iMIP 邀请的地址(通常是帐户自己的地址)。
- cron 每 10 分钟重新轮询一次;失败的源保留其最后一个好数据,并在卡片和 `feed_warnings` 中显示错误。
API 等价项(在 Access 后面):`GET/POST /api/v1/calendar/feeds`,`DELETE /api/v1/calendar/feeds/:id`,`POST /api/v1/calendar/poll`,`GET /api/v1/calendar/stats`,`GET /api/v1/calendar/events`,`GET /api/v1/calendar/blocks`,`DELETE /api/v1/calendar/blocks/:uid`。
## 通过 MCP 连接代理
```
端点:https://<your-worker>/mcp
身份验证:CF-Access-Client-Id / CF-Access-Client-Secret(Access 服务令牌)
```
所有 20 个工具都通过 MCP 自我描述(`list_tools` 返回完整的模式和用法指南)。值得提前了解的语义:`block_time` 是 **异步** 的;它立即返回 `pending` (时间段立即视为忙碌),除非设置 `force`,否则拒绝冲突列表;每个帐户的接受由工作流跟踪,并通过 `get_block_status` 读取。工具响应在日历源过期或出错时包含 `feed_warnings`,因此代理应相应地权衡可用性声明,并且在同一回合内从未调用工具的情况下绝不应声明可用性。
## 信任模型
任何通过共享的 Cloudflare Access 策略的 Principal 都可以访问 **所有** 收件箱和日历,包括通过 `/mcp` (工具接受 `mailboxId` 参数)。没有每个收件箱的授权;Access 策略是单一的信任边界。相同的授权包括设置每个收件箱的 **代理系统** (设置 → 将原样提供给模型),因此 Access 边界也控制代理被指示执行的操作。在将应用程序置于多用户 Access 策略后面之前,请记住这一点。
## 故障排除 Access
- `Invalid or expired Access token` → `POLICY_AUD` 或 `TEAM_DOMAIN` 机密已过期。切换 Access 关闭/打开 Worker 以再次获取模态框,并重置两个机密。
- `Cloudflare Access must be configured in production` → 故意关闭行为;启用 Access 并设置机密。
## 架构

## 许可证
Apache 2.0。参见 [LICENSE](LICENSE)。源自 [cloudflare/agentic-inbox](https://github.com/cloudflare/agentic-inbox), Cloudflare, Inc. 版权所有。