v2 platform runtime (docs 22 Phase 2/4/7 + 17): Project/Environment + Config + Storage + Observability

- Project/Environment model with default environments (dev/staging/prod)
- Config Runtime: cascade resolution (platform→org→project→env), secrets via vault
- Storage Runtime: local provider, sha256 dedup, integrity check, file list/upload/delete
- Observability: execution query, per-action summary (success rate, avg, p95), health
- Realtime SSE: workspace event stream endpoint (GET /api/v1/v2/realtime/events)
- Phase 1 acceptance: core.health.check, core.user.create, core.user.list
- Actions: core.project.*, core.config.*, core.file.*, core.user.* (62 total)
This commit is contained in:
phptits
2026-09-11 21:11:07 +00:00
parent 72012a7da3
commit 851ccf1d7f
28 changed files with 87763 additions and 0 deletions
@@ -0,0 +1,761 @@
# 动态 FPS 截图推送模块
## 1. 目标
现有 Browser SSE 已经支持 Screenshot 推送,本次只增加**动态 Screenshot 推送频率**。
核心规则:
- 无操作时:每 **2 秒** 推送一张 Screenshot
- 用户发生浏览器操作后:进入高频模式
- 高频模式:每 **500ms** 推送一张 Screenshot
- 高频模式持续 **5 秒**
- 5 秒的计算基于**最后一次浏览器操作**
- 新操作发生时,只延长高频模式时间
- 操作本身**不立即触发 Screenshot**
- 不使用真正的视频流
- 不使用 WebRTC
- 不使用 VNC
- Screenshot 仍然是独立 JPEG 图片
- Screenshot 仍然通过现有 SSE 推送
---
# 2. 配置
默认配置:
```ts
const screenshotConfig = {
quality: 30,
normalInterval: 2000,
activeInterval: 500,
activeDuration: 5000
}
```
参数:
| 参数 | 默认值 | 说明 |
|---|---:|---|
| `quality` | `30` | JPEG 质量 |
| `normalInterval` | `2000` | 普通模式截图间隔,单位 ms |
| `activeInterval` | `500` | 活跃模式截图间隔,单位 ms |
| `activeDuration` | `5000` | 最后一次操作后保持活跃模式的时间,单位 ms |
所有时间参数统一使用 `ms`。
---
# 3. Screenshot Mode
只有两种状态:
```ts
type ScreenshotMode = 'normal' | 'active'
```
### Normal
```text
interval = 2000ms
```
即:
```text
Screenshot
↓
等待 2 秒
↓
Screenshot
↓
等待 2 秒
↓
...
```
### Active
```text
interval = 500ms
```
即:
```text
Screenshot
↓
等待 500ms
↓
Screenshot
↓
等待 500ms
↓
...
```
---
# 4. 状态
Scheduler 至少维护:
```ts
interface ScreenshotSchedulerState {
mode: 'normal' | 'active'
lastActivityAt: number
activeUntil: number
running: boolean
}
```
说明:
### `mode`
当前截图模式:
```text
normal
active
```
### `lastActivityAt`
最后一次成功 Browser Action 的时间。
### `activeUntil`
Active Mode 的结束时间。
例如:
```text
当前时间:10:00:00
用户操作
↓
activeUntil = 10:00:05
```
---
# 5. 操作触发 Active Mode
任何**成功的浏览器操作**完成后调用:
```ts
notifyActivity()
```
实现:
```ts
function notifyActivity() {
const now = Date.now()
lastActivityAt = now
activeUntil = now + activeDuration
}
```
同时:
```ts
mode = 'active'
```
---
# 6. 操作不立即 Screenshot
这是必须遵守的规则。
错误:
```text
Browser Action
↓
Screenshot
↓
SSE
```
正确:
```text
Browser Action
↓
Action 执行成功
↓
notifyActivity()
↓
切换 Active Mode
↓
Scheduler 按 500ms 推送 Screenshot
```
也就是说:
> **Browser Action 只修改 Screenshot Scheduler 状态,不主动调用 Screenshot。**
---
# 7. Active Mode 生命周期
例如:
```text
00:00
用户 click
↓
Active Until = 00:05
00:00.5 Screenshot
00:01.0 Screenshot
00:01.5 Screenshot
00:02.0 Screenshot
...
00:05.0
↓
恢复 Normal
```
恢复后:
```text
每 2000ms Screenshot
```
---
# 8. 连续操作
如果 Active Mode 中再次发生操作:
```text
00:00 click
↓
activeUntil = 00:05
00:02 fill
↓
activeUntil = 00:07
00:04 scroll
↓
activeUntil = 00:09
```
因此:
> **Active Mode 的 5 秒永远从最后一次操作重新计算。**
最终:
```text
00:04 最后一次操作
↓
保持 Active 到 00:09
↓
恢复 Normal
```
---
# 9. 多次操作不会创建多个 Timer
禁止:
```ts
setInterval(...)
setInterval(...)
setInterval(...)
```
也禁止每次操作创建一个新的 Screenshot Loop。
整个 Browser Session 只允许存在:
```text
1 个 Screenshot Scheduler
```
所有操作统一调用:
```ts
notifyActivity()
```
---
# 10. Scheduler
推荐采用单一循环。
示例:
```ts
class ScreenshotScheduler {
private running = false
private mode: 'normal' | 'active' = 'normal'
private activeUntil = 0
async start() {
if (this.running) {
return
}
this.running = true
while (this.running) {
const now = Date.now()
this.mode =
now < this.activeUntil
? 'active'
: 'normal'
await this.capture()
const interval =
this.mode === 'active'
? this.config.activeInterval
: this.config.normalInterval
await sleep(interval)
}
}
notifyActivity() {
const now = Date.now()
this.mode = 'active'
this.activeUntil =
now + this.config.activeDuration
}
stop() {
this.running = false
}
}
```
实际代码可以根据项目结构调整,但必须保持相同的行为。
---
# 11. Screenshot 执行顺序
Scheduler 每次执行:
```text
检查当前 Mode
↓
Screenshot
↓
SSE 推送
↓
根据当前 Mode 等待
↓
进入下一轮
```
例如 Normal:
```text
Screenshot
↓
wait 2000ms
↓
Screenshot
↓
wait 2000ms
```
Active:
```text
Screenshot
↓
wait 500ms
↓
Screenshot
↓
wait 500ms
```
---
# 12. Screenshot 不允许并发
必须保证同一 Browser Session:
```text
同时最多 1 个 Screenshot
```
禁止:
```text
Screenshot A
↓
还没完成
Screenshot B
↓
还没完成
Screenshot C
```
正确:
```text
Screenshot A
↓
完成
↓
等待 interval
↓
Screenshot B
```
原因:
- 避免 CPU 突增
- 避免内存占用增加
- 避免多个 Screenshot 同时进入 SSE
- 避免页面状态竞争
---
# 13. Browser Action 与 Scheduler 的关系
Browser Controller 在 Action 成功后:
```ts
await executeAction(action)
scheduler.notifyActivity()
```
不要:
```ts
await executeAction(action)
await screenshot()
```
也不要:
```ts
scheduler.start()
```
Browser Session 创建时只启动一次 Scheduler。
---
# 14. Action 失败
Action 失败:
```text
Browser Action
↓
失败
↓
不调用 notifyActivity()
```
因此不会进入 Active Mode。
例如:
```text
click element
↓
Element Not Found
↓
Action Failed
↓
保持 Normal Mode
```
---
# 15. Navigation Action
以下操作成功后同样进入 Active Mode:
```text
goto
reload
back
forward
```
例如:
```text
goto
↓
页面加载完成
↓
notifyActivity()
↓
Active Mode
↓
500ms Screenshot
```
---
# 16. Batch Action
Batch Action 只调用一次:
```ts
scheduler.notifyActivity()
```
例如:
```text
Action 1
↓
Action 2
↓
Action 3
↓
全部成功
↓
notifyActivity()
```
不要:
```text
Action 1 → notifyActivity()
Action 2 → notifyActivity()
Action 3 → notifyActivity()
```
Batch 本身只需要最终刷新一次 Active Mode 时间。
---
# 17. SSE
动态 FPS 不改变现有 SSE API。
仍然:
```http
GET /profiles/:profileId/browser/events
```
Screenshot Event 保持现有格式。
Scheduler 只决定:
> **什么时候生成下一张 Screenshot。**
SSE 只负责:
> **把 Screenshot 推送给前端。**
两者职责保持分离。
---
# 18. SSE State
建议在 `state` event 中增加:
```json
{
"mode": "active",
"normalInterval": 2000,
"activeInterval": 500,
"activeUntil": 1780000000000
}
```
前端可以知道当前 Screenshot Scheduler 状态。
如果现有 `state` 已经定义完成,也可以不修改协议,仅在服务端内部维护状态。
---
# 19. SSE Client 数量
如果当前没有 SSE Client:
```text
SSE Client = 0
```
可以暂停 Screenshot Scheduler。
重新建立 SSE:
```text
SSE Connected
↓
启动 Screenshot Scheduler
↓
发送当前 Screenshot
↓
进入 Normal Mode
```
如果当前项目已经有 Scheduler 生命周期管理,则只增加动态 interval,不修改现有连接管理。
---
# 20. Active Mode 不等于视频流
必须保持:
```text
Active Mode
↓
每 500ms
↓
生成一张 JPEG
```
而不是:
```text
Active Mode
↓
视频编码
↓
持续视频流
```
所以本功能仍然是:
> **Adaptive Screenshot Streaming**
而不是 Live View / Video Streaming。
---
# 21. 推荐默认参数
第一版固定默认:
```ts
{
quality: 30,
normalInterval: 2000,
activeInterval: 500,
activeDuration: 5000
}
```
即:
```text
无操作:
2 秒 / 张
操作后:
500ms / 张
最后一次操作后:
5 秒恢复正常
JPEG:
quality 30
```
后续如果需要,再将这些参数开放为系统配置。
---
# 22. 核心流程
```text
Browser Action
│
▼
Playwright
│
操作成功
│
▼
notifyActivity()
│
▼
Active Mode
interval=500ms
│
▼
Screenshot
│
▼
SSE
│
▼
Console
│
│
500ms 后
│
▼
Screenshot
│
...
│
最后操作 + 5 秒
│
▼
Normal Mode
interval=2000ms
│
▼
Screenshot
│
2 秒后
│
▼
Screenshot
```
---
# 23. 验收标准
- [ ] Normal Mode 默认 2000ms
- [ ] Active Mode 默认 500ms
- [ ] Active Duration 默认 5000ms
- [ ] Browser Action 成功后进入 Active Mode
- [ ] Browser Action 不立即触发 Screenshot
- [ ] Action 失败不进入 Active Mode
- [ ] Active Mode 从最后一次操作重新计时
- [ ] 连续操作不会创建多个 Timer
- [ ] 每个 Browser Session 只有一个 Screenshot Scheduler
- [ ] Screenshot 不允许并发执行
- [ ] Active Mode 结束后自动恢复 Normal Mode
- [ ] SSE 接口保持不变
- [ ] Screenshot 仍然使用 JPEG
- [ ] Screenshot quality 默认 30
- [ ] 不引入 WebSocket
- [ ] 不引入 WebRTC
- [ ] 不引入 VNC
- [ ] 不引入 CDP
- [ ] 不实现视频流