feat: 添加日历批量操作与客户端时区感知功能,优化前端 UI 交互体验

This commit is contained in:
zl-q
2026-03-17 00:13:41 +08:00
parent d3783522e6
commit c26cdbbc27
27 changed files with 1532 additions and 412 deletions
+75
View File
@@ -185,6 +185,44 @@ interface Context {
---
## forwardedProps.client_time Schema
`RunAgentInput.forwardedProps` 支持透传客户端时间上下文。日历相关能力必须使用以下结构:
```typescript
interface ForwardedProps {
client_time?: {
device_timezone: string; // IANA 时区,例如 "America/Los_Angeles"
client_now_iso: string; // RFC3339 带偏移时间,例如 "2026-03-16T09:12:33-07:00"
client_epoch_ms: number; // Unix epoch 毫秒
};
}
```
### 时间来源优先级(固定)
后端在运行时按以下顺序解析事件时区:
1. `event_timezone`(工具调用显式传参)
2. `forwardedProps.client_time.device_timezone`
3. `users.profile.settings.timezone`
4. `UTC`
### 约束
- `device_timezone` 必须是有效 IANA 时区。
- `client_now_iso` 必须是 RFC3339 且包含时区偏移。
- `client_epoch_ms` 必须是整数毫秒时间戳。
- 业务代码不得使用服务器本地时区作为事件语义时区。
### 说明
- `forwardedProps` 是透传字段,不改变 AG-UI 主体协议结构。
-`forwardedProps.client_time` 缺失或非法时,运行时回退到 `users.profile.settings.timezone`
- 日历写入必须在最终工具调用中带上 `event_timezone`,不得依赖工具默认值。
---
## Validation Rules
Backend 实现了以下验证规则:
@@ -203,6 +241,16 @@ Backend 实现了以下验证规则:
| binary 不允许使用 data | `binary content data is not allowed` |
| 单条消息最多 3 张附件 | `Too many attachments` |
### forwardedProps.client_time Validation
建议在后端校验层返回以下错误(按业务实现映射到 4xx):
| Rule | Error Message |
|------|---------------|
| `device_timezone` 非 IANA 时区 | `invalid client_time.device_timezone` |
| `client_now_iso` 无法解析或缺少时区 | `invalid client_time.client_now_iso` |
| `client_epoch_ms` 非整数毫秒值 | `invalid client_time.client_epoch_ms` |
---
## Request Example
@@ -292,6 +340,32 @@ Backend 实现了以下验证规则:
}
```
### 带 forwardedProps.client_time 的请求
```json
{
"threadId": "550e8400-e29b-41d4-a716-446655440000",
"runId": "run-004",
"state": {},
"messages": [
{
"id": "msg-001",
"role": "user",
"content": "帮我明天早上9点创建一个日历"
}
],
"tools": [],
"context": [],
"forwardedProps": {
"client_time": {
"device_timezone": "America/Los_Angeles",
"client_now_iso": "2026-03-16T09:12:33-07:00",
"client_epoch_ms": 1773658353000
}
}
}
```
---
## Response
@@ -454,3 +528,4 @@ interface UiSchemaRenderer {
- backend 验证通过后,会将 binary url 转换为内部存储路径
- `tools` 是前端工具通道字段;当前后端运行时不基于该字段构造后端工具 prompt
- `RunAgentInput` 同时接受 camelCase 与 snake_case 别名输入(推荐统一使用 camelCase)
- 日历能力依赖 `forwardedProps.client_time` 透传设备时间上下文;缺失时回退用户 profile 时区
@@ -0,0 +1,87 @@
# Calendar Timezone Policy Protocol
## Version
- Current: `1.0`
- Status: Active
---
## Goal
统一日历事件在 App、Agent、工具、数据库之间的时间语义,消除时区不一致导致的显示和落库偏差。
---
## Canonical Rules
1. 数据库存储基准为 UTC。
2. 事件语义时区使用 IANA 时区字符串(`event_timezone` / `timezone`)。
3. 禁止无时区时间(naive datetime)进入日历写入链路。
4. 日历写入必须显式确定事件时区,不允许工具层硬编码默认时区。
---
## Timezone Resolution Priority
运行时事件时区解析顺序固定如下:
1. `event_timezone`(工具调用显式传参)
2. `forwardedProps.client_time.device_timezone`
3. `users.profile.settings.timezone`
4. `UTC`
---
## Write Contract
### Required fields
- `start_at`: RFC3339 且必须包含时区偏移
- `timezone`: IANA 时区
### Optional fields
- `end_at`: RFC3339 且必须包含时区偏移(若提供)
### Validation
- `timezone` 非法 -> 拒绝请求
- `start_at`/`end_at` 无时区 -> 拒绝请求
- `end_at < start_at` -> 拒绝请求
---
## Read Contract
读接口最小语义要求:
- 返回 UTC 时间字段(`start_at`, `end_at`
- 返回事件时区字段(`timezone`
- 前端展示必须以 `timezone` 作为事件本地时间转换基准,不允许直接按设备本地时区隐式渲染
---
## Error Codes
推荐错误码(由后端映射为 4xx):
- `INVALID_DATETIME_FORMAT`
- `NAIVE_DATETIME_FORBIDDEN`
- `INVALID_TIMEZONE`
- `TIMEZONE_REQUIRED`
- `INVALID_TIME_RANGE`
---
## DST Rule
- 夏令时切换期间,时间解释以 IANA 时区数据库为准。
- 对于歧义本地时间(例如回拨重复小时),由后端统一按标准库解析策略处理并返回确定 UTC 结果。
---
## Non-Goals
- 本协议不引入新的数据库时间列。
- 本协议不改变 `schedule_items` 现有 `TIMESTAMPTZ + timezone` 存储结构。