workspace-cli AI Native Web 技术实现方案
本文给出 AI Native Web 操作需求方案 的实现设计。面向使用者的启动、配置和端到端操作说明见 AI Native Web 功能与使用指南。现有 AI Native Core 的状态机、数据模型、Provider、MCP、Worker 和发布安全语义继续以 AI Native 研发平台技术实现方案 为准。
1. 技术目标
在现有 Go 单二进制中增加可由 CLI 启动的 Web 控制台,同时满足:
- React + TypeScript + Vite SPA 编译后嵌入
workspace二进制。 - Web handler、CLI 和 MCP 复用同一个
internal/workspace.Service,不复制业务规则。 - 本机默认零配置启动,局域网访问具备 TLS、一次性配对和可撤销会话。
- Requirement、Release、Job、Run、Approval 和聊天通过 SSE 增量更新并支持断线续传。
- Git、测试和发布等长操作持久化,浏览器断线或 Web 重启后可恢复。
- 主 Agent Web 聊天使用可审计的 coordinator Job,而不是转发 PTY 或直接调用模型 API。
- 保持已有 CLI、manual workflow、数据库和发布流程向后兼容。
1.1 固定技术决策
| 领域 | 决策 |
|---|---|
| 后端 | Go 标准库 net/http、embed、现有 SQLite/Service |
| 前端 | React、TypeScript、Vite,单页应用 |
| 交付 | 前端构建产物嵌入 Go 单二进制 |
| API | 同源 /api/v1 REST JSON |
| 实时 | SSE;写操作仍使用 HTTP POST/PATCH/DELETE |
| 认证 | 五分钟单次配对码 + 二十四小时可撤销浏览器会话 |
| 网络 | loopback 可用 HTTP;非 loopback 默认强制用户提供 TLS 证书 |
| Chat | 每条消息一个 coordinator_chat Job,结果和事件持久化 |
| 长操作 | SQLite durable operator_actions 队列和独立执行器 |
| 代码查看 | 受控只读 Git summary/diff;不提供编辑器或 PTY |
不使用 MCP stdio 作为浏览器协议,不让 Web 后端通过 shell 调用 workspace req ... 等 CLI 命令,也不引入独立远程数据库或消息队列。
2. 当前架构与目标架构
2.1 当前可复用能力
internal/cli注册所有 Cobra 命令并加载 config、store 和 workspace service。internal/workspace.Service封装 requirement、release、Agent、Worker、Artifact、Job、Run、Approval、Bug 和 Git 业务规则。internal/coordinator管理 daemon、Worker、lease、retry 和各阶段 workflow。internal/store提供 SQLite migration、事务和持久化对象。internal/mcp把同一 Service 暴露给 Agent,并执行 capability scope 校验。- Requirement/Release watch snapshot 和 Job/Run event 已提供 Web 所需的大部分聚合数据。
2.2 目标组件
Browser
│ HTTPS / REST / SSE
▼
workspace web server
├── auth/session/csrf/origin middleware
├── explicit API DTO + validation
├── SSE multiplexer
├── typed Web command/query adapter
├── operator action/chat submission and query
└── embedded React SPA
│
├───────────────┐
▼ ▼
internal/workspace internal/coordinator
└── Service ├── Controller
├── coordinator lane
└── operator action executor
│ │
└───────┬───────┘
▼
internal/store
SQLite + WAL
│
┌───────┴────────┐
▼ ▼
Artifact files Git worktrees
2.3 关键边界
- HTTP handler 只做认证、解析、DTO 转换和调用 application service。
- Web 专属会话、chat 和 action 状态通过 application service 操作 store;handler 不直接执行 SQL。
- Requirement/release 等业务写入必须调用
workspace.WebCommandService的类型化命令;它与 CLI/MCP 复用同一组 transaction-aware 业务原语,不能在 Web package 重建状态迁移。 - 数据库事务在调用 Git、Provider 或等待网络之前结束;长操作分成“记录 action → 执行外部工作 → 原子提交结果”。
- Web 返回显式 DTO,不直接 JSON encode store struct 或
sql.Null*。 - 前端只保存页面状态和缓存;服务端数据变化始终可通过重新查询恢复。
3. 代码和包布局
计划新增:
internal/
web/
server.go
lifecycle.go
routes.go
middleware.go
auth.go
dto.go
errors.go
events.go
actions.go
chat.go
assets.go
assets/
index.html
assets/...
workspace/
web_queries.go
web_commands.go
chat_scope.go
artifact_content.go
diff.go
coordinator/
controller.go
operator_actions.go
chat_jobs.go
job_cancellation.go
operationlock/
lock_unix.go
lock_windows.go
web/
package.json
package-lock.json
tsconfig.json
vite.config.ts
src/
app/
api/
components/
features/
routes/
styles/
职责:
internal/web:Web 进程生命周期、HTTP API、认证会话、SSE、Web 专属读模型和静态资源;不组合多个业务事务。internal/workspace/web_queries.go:不触发远程 fetch 的 Dashboard/列表聚合和 Web 所需读取 facade。internal/workspace/web_commands.go:Web 类型化 mutation 的唯一入口,在同一 Store transaction 内完成业务变更、revision、event/audit 和 idempotency response。internal/workspace/chat_scope.go:Chat root 规范化、版本化关联图、物化 scope 和无副作用ChatLocalQueryResolver。internal/workspace/artifact_content.go:Artifact 内容安全读取、创建修订版本和比较。internal/workspace/diff.go:仅对登记 worktree 生成 Git summary/diff。internal/coordinator/controller.go:抽出 CLI 和 Web 都能复用的 daemon ensure/status/stop 控制器。internal/coordinator/operator_actions.go:在 daemon 中执行 Web 提交的 durable action;Web 进程退出时继续运行。internal/coordinator/chat_jobs.go:chat lane、Chat terminal finalizer 和结果 contract 校验。internal/coordinator/job_cancellation.go:轮询持久化取消请求,并把它转换为 daemon 进程内 Provider context cancellation。internal/operationlock:Unixflock和 WindowsLockFileEx的同语义 advisory lock,实现 CLI/daemon 跨进程互斥。web/src:前端源代码;不得包含服务端 secret 或环境特有配置。internal/web/assets:Vite 生成并由 Go embed 的产物。
生成后的 assets 纳入版本控制,以保证 go build 和 go install 不要求 Node.js。CI 运行前端构建后必须验证 internal/web/assets 没有未提交差异,避免源代码与嵌入产物漂移。
4. CLI 与 Web 进程生命周期
4.1 Cobra 命令
根命令新增:
workspace web start
workspace web status [--json]
workspace web stop [--timeout 10s] [--force]
workspace web restart [start flags...]
workspace web pair
workspace web sessions list
workspace web sessions revoke <session-id>
workspace web sessions revoke --all
start 参数:
--host 127.0.0.1
--port 8080
--tls-cert <path>
--tls-key <path>
--allow-insecure-http
--foreground
--no-open
--no-daemon
全局 --home 继续按 --home > WORKSPACE_CLI_HOME > ~/.workspace-cli 解析。
4.1.1 配置
config.Config 增加:
type WebConfig struct {
Host string
Port int
TLSCertPath string
TLSKeyPath string
OperatorActionParallelism int
}
config.yaml 使用现有 flat key 解析方式:
web_host: "127.0.0.1"
web_port: 8080
web_tls_cert: ""
web_tls_key: ""
web_operator_action_parallelism: 2
显式 CLI flag 覆盖 config;--allow-insecure-http、--foreground、--no-open 和 --no-daemon 只允许命令行指定,避免把危险/临时运行模式永久写入配置。Pairing code 五分钟、正常 session 二十四小时和 insecure LAN session 十五分钟是安全常量,不在 V1 开放配置。
4.2 后台启动
后台启动沿用 daemon 的可靠模式,但使用独立 Web instance:
web_server_instances 至少包含:
| 字段 | 含义 |
|---|---|
active_slot |
固定为 1,用 partial unique index 保证单实例 |
instance_key |
每次启动的随机唯一标识 |
pid / executable_path / process_started_at |
防止 PID 复用时停止错误进程 |
status |
starting/running/stopping/stopped/stale |
scheme / listen_host / listen_port |
实际监听配置 |
public_urls_json |
已校验的本机/局域网访问 URL |
tls_fingerprint / tls_expires_at |
TLS 诊断,可空 |
heartbeat_at / started_at / stopped_at |
生命周期 |
message |
脱敏诊断 |
- 父进程加载配置、migration 并读取当前 Web instance。
- 若数据库记录为 running、heartbeat 新鲜且进程 identity 匹配,不重复启动;调用 PairingService 撤销该实例尚未使用的旧 code、原子签发一个新 code,并返回当前 URL/code。
- 若记录 stale,先完成安全 reconciliation;不能仅凭旧 PID kill 进程。
-
父进程执行当前二进制的隐藏 child 模式:
workspace --home <home> web start --foreground --web-child ... - child 获取 Web 单实例 lease,记录 PID、可执行文件 canonical path 和操作系统进程启动时间。
- child 完成 listener、TLS、数据库、assets 和 API health 初始化后写 running heartbeat。
- 父进程最多等待五秒;成功后打印 URL、pairing code、日志和停止命令,失败时返回日志路径。
- child 每十秒刷新 heartbeat;正常退出写 stopped。
Store 提供原子 AcquireWebInstance、HeartbeatWebInstance、RequestWebInstanceStop、CompleteWebInstance 和 ReconcileWebInstance。Heartbeat 超过三十秒视为 stale;Acquire 只有在旧 identity 不存活或已完成 reconciliation 后才能取得 active_slot=1。所有更新同时匹配 instance key、PID identity 和预期 status。
隐藏 --web-child 只能由内部 detached 路径使用。
workspace web pair 使用同一 PairingService:先验证 active Web instance 的 heartbeat 和进程 identity,再在单一事务撤销其他未使用 code、写入一个五分钟 code,原文只输出到当前终端。没有健康实例时命令失败并提示先执行 workspace web start。因此每次 start 或 pair 都恰好产生一个当前可用 code,不在日志中重放历史 code。
4.3 Runtime 文件
<runtime_dir>/web/
web.log
web.pid # 仅诊断,数据库 instance 才是权威
effective-config.json # 不含 token、pairing code 或 TLS key
- 目录权限 0700,日志和配置 0600。
- TLS key 使用用户提供的原路径,只读取不复制。
- pairing code 只在创建时输出一次,不写入日志或 effective config。
- 日志必须对 Cookie、Authorization、CSRF、pairing code 和 capability token 脱敏。
4.4 Stop 和 restart
stop:
- 校验 instance identity 和 heartbeat。
- 把状态改为 stopping。
- 向已验证的 PID 发送正常终止信号。
- Web 停止接受新请求,关闭 SSE,等待当前短请求完成。
- 不停止 coordinator daemon,不取消 Agent Job,也不撤销 daemon 持有的 operator action lease。
- 正在运行和 queued 的 operator action 继续执行并写数据库;Web 再次启动后恢复展示。
- 完成当前短 HTTP 请求后关闭 listener 和 Web instance。
--force 只在 identity 校验成功且超时后使用。restart 先 stop Web,再按新参数 start;TLS/loopback session 保存在数据库中并继续有效,非 loopback insecure HTTP session 在 restart transaction 中撤销。
4.5 Daemon 协同
- 新增
daemon_protocol_attestations,以每次启动新生成、不得复用且至少 128 bit 随机的instance_key为主键,保存 PID/process-start identity digest、canonical executable digest/path、binary version、protocol version、feature bitmap 和 attested time。新 daemon 的 instance acquire 与 attestation 在同一事务写入,不只上报“进程存活”。 - V1 Web 需求的 feature 至少是
operator_actions_v1、coordinator_chat_v1、persistent_job_cancel_v1和resource_revisions_v1。未知 protocol major 或缺少任一 feature 都视为不兼容,不能把旧 daemon 的新鲜 heartbeat 当成可用。 coordinator_chatJob 使用新的内部可调度状态chat_queued(API 统一显示 queued),只有支持coordinator_chat_v1的ClaimCoordinatorChatJob才能原子 claim;旧 daemon 只查询status='queued',因此即使被误启动也不会把 Chat Job 当作普通 coordinator Job 执行。- 默认调用共享
coordinator.Controller.EnsureRunning(ctx, RequiredFeatures)。它必须同时校验当前daemon_instances.instance_key、PID/process-start/executable identity 与 attestation 完全一致,再校验 protocol/feature;不允许从同一 singleton row 中继承上一个 daemon 的能力声明。Controller 只会使用当前workspace可执行文件启动 daemon。 - 已有 daemon 不兼容但没有 running Job/operator action/leased Worker 时,Controller 执行有界的 stop 和当前二进制 restart;存在运行中工作时拒绝自动重启,返回
daemon_upgrade_required和可操作诊断。 - 默认
workspace web start在无法取得兼容 daemon 时失败,不启动一个表面 ready 但永远无法消费任务的 UI。 --no-daemon允许 Web 以降级模式启动,页面明确显示 Agent 调度暂停。当 daemon 仅是未启动时,action/chat 可持久化为 queued;当存在不兼容 daemon 时,新建 action/chat 直接返回503 daemon_upgrade_required,避免被旧协议错误 claim 或永久排队。- Web status 同时返回 Web 和 daemon 状态,但两者生命周期独立。
- Web server 不内嵌 daemon loop,避免一个进程故障同时停止 UI 和 Worker 调度。
- 因 daemon 承载 operator action executor,
workspace agent daemon stop和 Web daemon-stop API 都通过同一 Controller:存在 running action 时默认拒绝;--force必须把未完成 action 按 checkpoint/fence 收敛为 interrupted/manual 后才能退出,不能留下伪 running lease。 - Web 启动时和每次提交 daemon-backed mutation 前都重新校验 feature handshake,因为 Web 运行期间 daemon 可能被外部 CLI 替换。System health 显示二进制版本、protocol 和 feature,但未认证
/readyz不暴露这些信息。
5. 网络、TLS 与浏览器打开
5.1 监听规则
- 默认
127.0.0.1:8080。 localhost、127.0.0.0/8和::1视为 loopback。- 非 loopback host 必须同时提供可读的
--tls-cert和--tls-key。 --allow-insecure-http可以显式覆盖 TLS 要求,但启动日志、status API 和所有页面持续标记 insecure。0.0.0.0/::启动后打印检测到的局域网地址;不把 wildcard 地址直接作为浏览器 URL。- 端口被占用时启动失败并明确提示
--port,不自动漂移到其他端口。
5.2 TLS
- 使用 Go
http.Server.ServeTLS,最低 TLS 1.2,优先 TLS 1.3。 - 启动时校验证书有效期、私钥匹配和至少一个可用于访问地址的 SAN;不满足时失败。
- status 输出证书指纹和到期时间,不输出私钥路径之外的敏感信息。
- 证书临近到期时页面健康中心告警,但当前连接继续工作。
5.3 Browser opener
- macOS 使用
open,Linux 使用xdg-open,Windows 使用rundll32 url.dll,FileProtocolHandler。 - opener 失败只产生提示,不导致已健康的 Web 服务退出。
- 非 loopback 绑定时仍优先打开 loopback 或明确可访问的宿主地址。
- URL 不包含 pairing code、session token 或其他 secret。
6. 前端构建与嵌入
6.1 构建
npm --prefix web ci
npm --prefix web test
npm --prefix web run build
go test ./...
go build ./cmd/workspace
Vite 输出固定到 internal/web/assets。构建要求:
- 文件名包含内容 hash。
index.html不缓存;hashed assets 使用长期 immutable cache。- production source map 不嵌入 release,或仅生成不含源码内容的受控 map。
- base path 固定为
/;当前版本不支持部署到反向代理子路径。 - package-lock 必须提交,依赖版本通过 lockfile 固定。
6.2 Go embed
internal/web/assets.go 使用:
//go:embed assets
var assets embed.FS
静态 handler:
/api/*、/healthz和/readyz不进入 SPA fallback。- 已存在的静态 asset 使用正确 Content-Type、ETag 和缓存头。
- 未匹配的非 API GET 返回
index.html,支持前端路由刷新。 - 非 GET/HEAD 的未知路径返回 404,不返回 SPA。
- 禁止目录列表。
7. HTTP Server 与中间件
7.1 Server
使用显式 http.Server:
ReadHeaderTimeout5 秒。ReadTimeout30 秒;上传 Artifact 使用独立大小和超时控制。WriteTimeout对普通请求 60 秒;SSE 路由不使用普通 WriteTimeout。IdleTimeout120 秒。- Header 总大小限制。
- graceful shutdown 默认 10 秒。
7.2 中间件顺序
- Request ID。
- Panic recovery。
- 安全响应头。
- Host allow-list。
- 结构化访问日志和 secret redaction。
- 请求体大小与 content type。
- Session authentication。
- Origin/CSRF。
- Per-session rate limit。
- Handler。
安全响应头至少包含:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cache-Control: no-store # API 和认证页面
Markdown 渲染必须禁用 raw HTML 或经过严格 sanitizer;外部链接增加 rel="noreferrer noopener"。
7.3 Health
| 路径 | 认证 | 含义 |
|---|---|---|
GET /healthz |
无 | 进程和 listener 存活,只返回 {"status":"ok"} |
GET /readyz |
无 | migration、DB、assets 和实例 lease 正常,不暴露路径/版本细节 |
GET /api/v1/system/health |
有 | 完整 daemon、MCP、Provider、Worker、TLS 和路径诊断 |
MCP health 复用 workspace mcp health 的同一 stdio Serve 边界和临时 coordinator capability,返回 transport、tool count 和 tool names;Web 不通过 shell 执行 CLI,也不把临时 token 返回浏览器。
8. 认证、会话与 CSRF
8.1 数据模型
web_pairing_codes
| 字段 | 含义 |
|---|---|
id |
SQLite ID |
code_hash |
随机 pairing code 的 SHA-256 hash,唯一 |
expires_at |
创建后五分钟 |
attempts |
当前失败次数 |
max_attempts |
固定 5 |
used_at |
成功配对时间 |
created_by_instance_key |
签发 Web instance |
created_at |
创建时间 |
原始 code 使用加密安全随机数并包含至少 80 bit 熵,显示为便于输入的分组字符;数据库和日志只保存 SHA-256 hash。Session token 和 CSRF token 分别使用至少 256 bit 随机数。
web_sessions
| 字段 | 含义 |
|---|---|
session_key |
可公开的审计标识 |
token_hash |
Cookie bearer token hash |
csrf_secret_hash |
CSRF secret hash |
device_label |
用户填写的设备名 |
client_ip / user_agent_summary |
审计摘要 |
created_at / last_seen_at |
生命周期 |
expires_at |
TLS/loopback 创建后二十四小时;非 loopback insecure HTTP 最多十五分钟 |
revoked_at / revoke_reason |
撤销记录 |
会话 Cookie 名为 workspace_session,Path=/、HttpOnly、SameSite=Strict;TLS 请求设置 Secure。CSRF token 使用独立的 workspace_csrf Cookie,Path=/、SameSite=Strict、允许同源 SPA 读取,TLS 下同样设置 Secure。两个原始 token 都只在配对成功时写 Cookie,服务端只保存 hash。
--allow-insecure-http 不能保护 bearer Cookie 免受同网段监听,只用于用户明确接受风险的隔离网络。该模式强制十五分钟 session TTL、Web restart 时撤销这些 session、持续 UI/CLI 告警并写安全审计;不因 CSRF/Origin 校验而宣称传输安全。
8.2 Pair API
POST /api/v1/auth/pair 是未认证的唯一写 API:
{
"code": "ABCD-EFGH-JKLM-NPQR",
"device_label": "MacBook Safari"
}
流程在同一事务中:
- 按 hash 查找未使用、未过期、未锁定 code。
- 失败时原子增加 attempts;响应始终使用通用
pairing_failed。 - 成功时标记 code used,创建 session,并设置 session 与 CSRF 两个 Cookie。
- Pairing code 不得被两个并发请求重复使用。
登录接口按来源 IP 和实例做速率限制;连续失败写安全审计事件,但不写原始 code。
8.3 CSRF
- 配对成功后设置随机 CSRF Cookie,并把其 hash 关联 session。
- SPA 从同源
workspace_csrfCookie 读取值并写入X-CSRF-Token;多个 Tab 共享同一值,不因刷新互相失效。 - 所有 POST/PATCH/DELETE 同时校验 header 值、CSRF Cookie 值和数据库 hash,并校验 Origin 与当前 scheme/host。
- SSE 和 GET 只读接口不要求 CSRF,但仍要求 session。
- session revoke 后清除两个 Cookie;现有连接在下一次认证检查或心跳时终止。
8.4 Session 管理
GET /api/v1/auth/session
DELETE /api/v1/auth/session
GET /api/v1/system/sessions
DELETE /api/v1/system/sessions/{session_key}
POST /api/v1/system/sessions/revoke-all
“revoke all”默认保留当前 session,除非请求明确 include_current=true。CLI revoke 不需要浏览器 session,但必须在本机读取同一 home。
撤销 Web session 会阻止新请求并关闭其 SSE,但不自动取消该 session 已经成功提交的 Agent Job/operator action;这些操作保留原审计并继续执行,必须由仍有效的操作者显式取消。
9. API 约定
9.1 基础规则
- Base path:
/api/v1。 - Content-Type:
application/json; charset=utf-8。 - 时间:UTC RFC3339Nano;前端负责显示本地时区。
- SQLite int64 ID 在 JSON 中使用十进制字符串,避免 JavaScript 精度问题。
- Null 使用 JSON
null,不暴露sql.Null*。 - Enum 返回存储值和可本地化 display label。
- 列表默认 50 条,最大 200,使用 opaque cursor 分页。
- 所有响应包含
request_id;日志使用同一 ID。 - API 是本地控制台接口,不承诺跨 major 版本的第三方集成兼容;
v1内保持向后兼容。
9.2 成功和错误
短写操作成功返回更新后的 DTO。长操作返回 202:
{
"data": {
"action_key": "action-...",
"status": "queued",
"resource": {"type": "release", "id": "42"},
"status_url": "/api/v1/actions/action-..."
},
"request_id": "req-..."
}
统一错误:
{
"error": {
"code": "state_conflict",
"message": "release snapshot changed; integrate and test again",
"details": {},
"current_revision": "opaque-revision",
"allowed_actions": ["refresh", "integrate"]
},
"request_id": "req-..."
}
稳定错误码至少包含:
validation_failed:400。authentication_required:401。session_expired:401。csrf_failed:403。forbidden:403。not_found:404。state_conflict/revision_conflict/action_already_running:409。payload_too_large:413。rate_limited:429。provider_unavailable:503。daemon_unavailable:503。daemon_upgrade_required:503。internal_error:500,响应不得含 stack 或 secret。
9.3 Revision 与并发
- 所有可通过 Web 修改的实体 DTO 返回 opaque
revision,至少包括 Requirement、Release、Release Test Run、Repo、Agent、Worker pool、Review policy、Approval、Artifact 和 Bug;不能遗漏 Repo 等配置页面资源。 resource_revisions为每个可变资源保存version INTEGER、state_hash和writer_epoch。state_hash是稳定字段顺序的 canonical semantic projection hash,不包含 last-seen 等挥发性诊断字段;它用于发现绕过 revision 的旧二进制写入,不是客户端 revision 本身。- 创建资源时写 version 1、当前 hash 和 writer epoch;当前版本 CLI、MCP、daemon 和 Web mutation 都必须在原业务事务中单调增加 version 并更新 hash/epoch,不能依赖低精度
updated_at。 - API 把 resource type、ID 和 version 编码为不可解析的 revision;客户端不能自行构造。
- 单资源 GET 同时设置与 DTO
revision一致的ETag。 - 修改请求发送
If-Match: "<revision>"。 - Web 把 expected version 传入 Service/Store;事务使用
UPDATE resource_revisions ... WHERE version = ?条件更新,影响行数不是 1 时整笔业务写回滚并返回 409。不能只在 handler 预检后调用不带条件的旧 Service。 - 同一事务修改多个 requirement/release 等资源时,按稳定顺序校验并增加全部 revision,任一冲突全部回滚。
- CLI/MCP 调用可以不带 expected version,但成功 mutation 仍必须增加 revision。
- 长 action 保存创建时 revision;每个 checkpoint 和 terminal transaction 都重新验证相关 resource revision 与 fence。
- 状态机自身的前置条件始终优先于前端缓存,不允许通过省略 If-Match 绕过。
- migration 后、Web ready 前必须运行
ReconcileResourceRevisions:枚举 canonical resource registry,缺失行以 version 1 回填,hash 不同时在单一事务中增加而不重置 version,并写当前 writer epoch。这一步专门覆盖回滚到旧二进制修改数据后再升级的场景。 - 单资源 authoritative GET 在返回前使用同一 registry 校验 canonical hash,发现偏移时先事务性增加 revision 再返回;从而即使用户在 Web 运行期间误用旧 CLI,下一次读取也不会继续发放过期 ETag。新旧二进制并发写仍不在支持范围,health 需要告警 writer epoch 偏移。
9.4 Idempotency
以下请求必须带 Idempotency-Key:
- 长 operator action。
- Chat message。
- 创建 requirement、release 或用户修订 Artifact。
- Approval response。
唯一键为 session_key + idempotency_key,因此浏览器必须为每次用户意图生成新的随机 UUID,不能在不同 route 复用。重复请求:
- 请求 hash 相同:返回原响应/action/message。
- 请求 hash 不同:返回
409 idempotency_conflict。
同步写操作使用 web_idempotency_records,保存 session、key、canonical route、request hash、HTTP status、结果资源引用、显式脱敏 DTO 的可重放 response_json 和完成时间;不保存 secret、Cookie 或内部 store struct。重放返回原 HTTP status 和原 response snapshot,不用已经变化的实体重新组装“同一”响应。业务 mutation 与 completed idempotency record 必须在同一数据库事务提交。长 action 和 chat message 分别以 operator_actions、web_chat_messages 自身的唯一键实现相同语义。
9.5 Web mutation Unit of Work
Web 同步 mutation 不能在 handler 中先调一个自带 transaction 的 workspace.Service,再另开 transaction 写 revision/idempotency/audit。新增类型化命令层:
type CommandMeta struct {
WebSessionKey string
RequestID string
Route string
IdempotencyKey string
RequestHash string
Expected map[ResourceRef]int64
}
type WebCommandService struct {
DB *store.DB
}
WebCommandService 显式提供 SubmitRequirement、UpdateRequirement、UpdateRepo、UpdateAgent、UpdateWorkerPool、UpdateReviewPolicy、RespondApproval、CreateArtifactRevision 和其他受支持 Web mutation,不提供任意 SQL/JSON passthrough。实现要求:
- 将现有业务状态迁移下沉成接收
store.Tx的 transaction-aware core;CLI/MCP 和 Web 均调用这些 core,避免嵌套事务或复制规则。 - 每条 Web 命令只开一个 Store transaction,依次执行 idempotency lookup、expected revision 条件更新、业务 mutation、业务 event/Web audit、所有受影响 resource revision/hash 更新、显式 response DTO 生成和 completed idempotency record 写入;任一步失败全部回滚。
- 业务表本身的 event/status 是权威状态;Web audit 不替代它。response DTO 只包含返回给该 route 的脱敏字段和新 revision。
- 长 action 的入队 transaction 使用同一 CommandMeta 语义,原子写 action、resource ownership/locks、revision 和 idempotency 响应;后续 checkpoint/terminal 是 daemon 拥有的独立 fenced transaction。
- Handler 只能调用一个 typed command 并转换结果,代码审查/架构测试禁止
Workspace.Mutate(); WebStore.Write()这种跨 transaction 组合。
10. API 资源
10.1 Dashboard 与系统
GET /api/v1/dashboard
GET /api/v1/system/health
GET /api/v1/system/config
GET /api/v1/system/sessions
POST /api/v1/system/daemon/start
POST /api/v1/system/daemon/stop
Dashboard 使用纯本地 query,不调用 remote fetch。返回阶段计数、pending approval、running/blocked job、Release 风险、Worker capacity 和最近事件。
Daemon start/stop 通过共享 coordinator.Controller 执行,不进入由 daemon 自身消费的 operator action 队列。Web 使用独立于 request cancellation 的短期控制 context,写 Web audit,并以 authoritative daemon instance/status 作为恢复依据;stop 在有 running Job/operator action 时默认拒绝,只有专用危险确认才允许 force。
10.2 Requirement
GET /api/v1/requirements
POST /api/v1/requirements
GET /api/v1/requirements/{key}
PATCH /api/v1/requirements/{key}
POST /api/v1/requirements/{key}/actions/assign-agents
POST /api/v1/requirements/{key}/actions/prepare-workspace
POST /api/v1/requirements/{key}/actions/finish
POST /api/v1/requirements/{key}/actions/reopen
POST /api/v1/requirements/{key}/actions/archive
GET /api/v1/requirements/{key}/activity
GET /api/v1/requirements/{key}/diff
GET /api/v1/requirements/{key}/bugs
不提供通用 PATCH workflow_status。每个业务动作映射到明确 Service 方法和前置检查。
创建 body 支持 mode=ai_native|manual。AI Native 使用 submit 语义并要求 description、acceptance criteria 和 repo;manual 使用现有 create 语义。
10.3 Artifact
GET /api/v1/artifacts/{id}
GET /api/v1/artifacts/{id}/content
GET /api/v1/artifacts/compare?left={id}&right={id}
POST /api/v1/requirements/{key}/artifacts
POST /api/v1/requirements/{key}/actions/finalize-artifacts
- content endpoint 只允许数据库登记的 Markdown/JSON/text 类型,设置大小上限和
nosniff。 - compare 要求两个 Artifact 同 entity、同 type;服务端生成结构化 diff。
- create 只写新版本,验证 title、stage、type 和内容大小。
- finalize 仍通过 Approval response transaction;快捷 action 只允许定位并响应已有 pending
artifact_finalization。
10.4 Release、test run 与 Bug
GET /api/v1/releases
POST /api/v1/releases
GET /api/v1/releases/{key}
POST /api/v1/releases/{key}/actions/add-requirement
POST /api/v1/releases/{key}/actions/remove-requirement
POST /api/v1/releases/{key}/actions/integrate
POST /api/v1/releases/{key}/actions/start-test
POST /api/v1/releases/{key}/actions/publish
GET /api/v1/releases/{key}/test-runs
GET /api/v1/test-runs/{id}
GET /api/v1/test-runs/{id}/bugs
GET /api/v1/bugs/{id}
POST /api/v1/bugs/{id}/actions/retry-fix
Release GET 默认使用本地 snapshot。远端刷新使用显式 action:
POST /api/v1/releases/{key}/actions/refresh-remote-status
run-scoped DTO 只包含该 release_test_run_id 的 membership、jobs、approvals、bugs、status/run events 和 Artifact。release ID 不能扩大读取范围。
start-test action 在创建 run/snapshot 前必须验证 Release 已成功 integrate、release workspace 与 ArtifactDir 存在且为目录、全部 release worktree clean、active scope/SHA 完整且测试 Agent 可用;任一失败不创建部分 test run。缺命令路径继续使用现有原子 transaction 创建 waiting run、immutable snapshots、test_command_missing Approval 和可见 event。
10.5 Approval
GET /api/v1/approvals
GET /api/v1/approvals/{approval_key}
POST /api/v1/approvals/{approval_key}/response
Response 使用 discriminated union:
type ApprovalResponse =
| { type: "requirement_clarification"; choice: "approve"; message: string; updates?: RequirementClarificationUpdates }
| { type: "requirement_clarification"; choice: "reject" | "cancel"; message?: string }
| { type: "artifact_finalization"; choice: "approve"; finalRequirementDesignArtifactId: string; finalTechnicalDesignArtifactId: string }
| { type: "artifact_finalization"; choice: "reject" | "cancel"; message?: string }
| { type: "implementation_blocker"; choice: "continue" | "replan" | "block"; message?: string }
| { type: "test_command_missing"; choice: "approve"; testCommand: string }
| { type: "test_command_missing"; choice: "block" }
| { type: "publish_confirmation"; choice: "approve"; confirm: true }
| { type: "publish_confirmation"; choice: "reject" | "cancel"; confirm: false }
| { type: "provider_permission"; choice: "approve"; scope: string; expiresAt: string; sandbox: string; approvalPolicy: string }
| { type: "provider_permission"; choice: "reject" | "cancel"; message?: string };
服务器按数据库 ApprovalType 选择 schema,拒绝类型不匹配或缺字段,不把任意 response_json 透传给 store。implementation_blocker=replan 只记录重新规划决策;新的技术方案通过后续 Artifact 创建/定稿事务选择,不在该 response 塞入当前 Service 不消费的 artifact ID。
10.6 Job、Run、Agent、Worker 与 Policy
GET /api/v1/jobs
GET /api/v1/jobs/{job_key}
POST /api/v1/jobs/{job_key}/actions/cancel
POST /api/v1/jobs/{job_key}/actions/retry
GET /api/v1/runs/{id}
GET /api/v1/runs/{id}/events
GET /api/v1/runs/{id}/raw-tail
GET|POST /api/v1/agents
GET|PATCH|DELETE /api/v1/agents/{name}
POST /api/v1/agents/{name}/actions/health
GET|POST /api/v1/worker-pools
GET|PATCH /api/v1/worker-pools/{name}
POST /api/v1/worker-pools/{name}/actions/enable
POST /api/v1/worker-pools/{name}/actions/disable
GET|POST /api/v1/review-policies
GET|PATCH|DELETE /api/v1/review-policies/{name}
GET|POST /api/v1/repos
GET|PATCH|DELETE /api/v1/repos/{name}
POST /api/v1/repos/{name}/actions/sync
raw-tail 始终使用现有 redactor,限制字节数和速率,不提供 unsafe_unredacted 参数。Web facade 不能直接信任历史 DB path:必须从 Run → Job attempt 派生允许的 RuntimeDir,canonicalize stdout/stderr path,逐组件拒绝 symlink,并验证目标严格位于该 attempt runtime 下;损坏或越界的历史记录只返回诊断,不读取文件。
Agent DTO 按 provider schema allow-list 投影 metadata;未知 key、环境变量和疑似 credential 不返回浏览器。更新 metadata 也只接受 allow-list 字段,不能把 raw metadata_json 当作通用编辑器。
11. Web DTO
11.1 公共 DTO 约束
后端定义 Web 专用 DTO,例如:
type RequirementDTO struct {
ID string `json:"id"`
Key string `json:"key"`
Title string `json:"title"`
Mode string `json:"mode"`
Stage string `json:"stage"`
WorkflowStatus *string `json:"workflow_status"`
ExecutionHealth ExecutionHealthDTO `json:"execution_health"`
Priority int `json:"priority"`
Repos []RepoRefDTO `json:"repos"`
Agents RequirementAgentsDTO `json:"agents"`
PendingApprovals int `json:"pending_approvals"`
OpenBugs int `json:"open_bugs"`
NextActions []ActionDescriptorDTO `json:"next_actions"`
Revision string `json:"revision"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
ExecutionHealthDTO 单独聚合:
type ExecutionHealthDTO struct {
Status string `json:"status"`
ActiveJobCount int `json:"active_job_count"`
BlockedCount int `json:"blocked_count"`
FailedCount int `json:"failed_count"`
WaitingCount int `json:"waiting_user_input_count"`
LastHeartbeatAt *time.Time `json:"last_heartbeat_at"`
Reason *string `json:"reason"`
}
前端必须使用 OpenAPI 或同一 schema 生成/校验 TypeScript 类型,避免 Go DTO 与手写 TS interface 漂移。OpenAPI 文件由后端路由/schema 生成或在 CI 做双向一致性检查,但不作为未经认证的运行时公开文档。
11.2 Next actions
服务器根据 Service 状态返回可用操作描述:
{
"id": "integrate",
"label": "重新集成",
"enabled": true,
"danger": "medium",
"reason_disabled": null,
"requires_confirmation": true
}
前端可以据此禁用和解释操作,但服务器仍必须重新执行完整校验。NextActions 不能替代权限或状态机。
12. SSE 实时模型
12.1 Endpoint
GET /api/v1/events?scope_type=dashboard
GET /api/v1/events?scope_type=requirement&scope_id=REQ-001
GET /api/v1/events?scope_type=release&scope_id=2026-07-15
GET /api/v1/events?scope_type=job&scope_id=<job-key>
GET /api/v1/events?scope_type=run&scope_id=<run-id>
GET /api/v1/events?scope_type=chat&scope_id=<chat-session-key>
同一浏览器最多保持有限数量 SSE;页面切换时关闭不需要的实体流,保留一个全局待办/健康流。
12.2 Event 格式
id: <opaque-cursor>
event: requirement.updated
data: {"entity":{"type":"requirement","id":"42","key":"REQ-001"},"reason":"status_event","revision":"...","occurred_at":"..."}
事件只携带最小失效信息和必要的实时摘要;复杂对象由前端 Query cache 重新读取,避免把 store payload 或 secret 直接广播。
12.3 Cursor
现有事件分布在 requirement status、release status、agent run、approval、artifact、bug、operator action 和 chat message 多个表。V1 不为已有业务事务强制增加统一 outbox,而使用 opaque 多源 cursor:
{
"requirement_status_event_id": 120,
"release_status_event_id": 85,
"agent_run_event_id": 930,
"artifact_id": 64,
"approval_cursor": {"updated_at": "...", "id": 41},
"bug_cursor": {"updated_at": "...", "id": 27},
"operator_action_event_id": 44,
"chat_message_id": 18
}
cursor 以 versioned base64url 编码,客户端不得解析。每个 SSE id 是消费该事件后的新 cursor。重连时优先使用浏览器自动发送的 Last-Event-ID,也接受 after query 作为兼容。
多源 cursor 只保证各来源内的增量高水位和 best-effort replay,不承诺跨表事务的全局总序或绝对无遗漏。Approval/Bug 等可更新行使用 (updated_at, id) tuple;发现时间倒退、相同 tuple 内容 revision 变化、查询窗口溢出或任何不确定性时立即发送 stream.reset。页面正确性由 authoritative snapshot + resource revision 保证。
12.4 生产与轮询
- Web 发起的写入通过进程内 notifier 立即唤醒相关 scope。
- CLI、daemon、MCP 或其他 Web instance 的写入由数据库增量查询发现。
- Active entity 默认一秒轮询,Dashboard/全局流两秒;无订阅时不轮询。
- SSE 只负责低延迟失效通知,不是唯一一致性来源;可见页面至少每三十秒以及浏览器恢复前台时重新校验 snapshot revision。
- 每十五秒发送 comment heartbeat。
- cursor 太旧且历史已清理时发送
stream.reset,前端清空相关 cache 并重新获取 snapshot。 - 每个 session 的连接数、scope 数和发送速率设上限;慢客户端只保留最新 invalidation,不能阻塞业务写入。
13. Durable Operator Action
13.1 适用操作
以下动作不在 HTTP handler 中同步执行:
- Repo add/sync/update/remove 的远端或文件操作。
- Requirement prepare workspace、finish、reopen。
- Release create 中的 workspace 初始化、integrate、remote refresh、test start、publish。
- Agent health 或其他可能启动外部进程的诊断。
纯数据库短操作如字段更新、Agent/Policy 配置和 Approval response 可以同步返回。
13.2 数据模型
operator_actions
| 字段 | 含义 |
|---|---|
action_key |
用户可见唯一键 |
action_type |
allow-list 枚举 |
primary_resource_type/primary_resource_id |
页面主要关联对象;create 前可空 |
requested_by_session_key |
Web 审计来源 |
idempotency_key/request_hash |
防重复 |
expected_revision |
创建时的状态 revision |
request_json |
schema 校验后的结构化参数 |
status |
queued/running/succeeded/failed/interrupted/manual/cancelled |
lease_owner/lease_expires_at |
coordinator daemon action executor lease |
fence_epoch |
每次合法执行所有权变化时单调增加 |
checkpoint_json |
当前 typed step 和已完成外部副作用 |
cancel_requested_at |
用户取消请求,可空 |
attempts/max_attempts |
恢复控制 |
result_json/error_code/error_message |
脱敏结果 |
created_at/started_at/completed_at/updated_at |
生命周期 |
operator_action_resources
把 action 与它创建或操作的实体绑定:action_id、resource_type、resource_id、natural_key、relation 和 created_at。Release create 和 Repo add 在开始前预分配稳定 action/natural key;插入业务 row 时必须在同一事务写 relation=created 映射。恢复时先按 action ownership 查找实体,不能把“duplicate key”直接当作失败或再次创建目录。纯数据库 Requirement submit 则由同步业务事务与 web_idempotency_records 保证创建和重放原子性。
operation_resource_locks
| 字段 | 含义 |
|---|---|
resource_key |
requirement、release、repo、worktree、target branch 等 canonical key |
owner_type/owner_key |
operator_action 或直接 CLI operation |
fence_epoch |
单调 fencing token |
lease_owner/lease_expires_at |
当前执行者 |
created_at/updated_at |
生命周期 |
每个 action 在入队事务中计算并原子预留完整 lock set,而不是只锁 primary entity。例如 Release publish 同时锁定 release、所有 active requirement、相关 repo、release/publish worktree 和目标 branch;requirement finish/reopen 锁定 requirement、绑定 repo/worktree 和受影响 release。Repo sync/update 与所有引用该 repo 的运行中 action 冲突。
CLI 和 Web action 必须共用 workspace.OperationLockManager。直接 CLI 操作也先获取相同数据库锁和 runtime 下的 OS advisory lock,避免 CLI 绕开 Web 锁。
OS lock 文件位于 <runtime_dir>/locks/<sha256(resource_key)>.lock,文件内容只写脱敏 owner/fence 诊断。Unix 和 Windows 使用各自 build-tag 实现;不支持可靠 advisory lock 的平台对 Git 写 action fail closed。
operator_action_events
id、action_id、event_seq、event_type、severity、message、payload_json 和 created_at。payload 使用显式 schema,不保存原始 Git stderr 或 secret。
唯一约束:
- 同一
session + idempotency key唯一。 - 同一
resource_key最多一个有效 lock owner;一个 action 的全部 lock row 必须一次成功或全部回滚。 - 同一 action 的
event_seq唯一。
13.3 Executor
Coordinator daemon 启动独立于 Agent Worker pool 的有界 action executor,默认最多并行两个 lock set 不相交的 action。Web 只创建、查询或请求取消 action:
- 入队事务验证 precondition、写 action、预留完整资源锁,并记录 expected revision。
- daemon claim 时原子写 lease、增加 action/resource fence,并取得所有路径资源的 OS advisory lock。
- 任一 OS lock 已被旧进程持有,或无法证明旧 owner 已终止时,不接管外部操作;action 进入
manual并保留诊断。 - 重新读取全部资源 revision 和 Git probe;状态不满足时写
state_conflict,不执行外部操作。 - 按 action type 执行显式 step state machine,每一步前后验证 lease owner、fence、cancel request 和资源 revision。
- 每个外部副作用完成后持久化 checkpoint/event,再进入下一步;数据库事务不能跨外部命令。
- 完成时在同一事务复核 fence,提交业务 terminal state、action terminal state/event 并释放数据库锁;随后释放 OS lock。
- daemon heartbeat 同时续期 action 和 resource lock lease;Web server 不拥有 lease。
Git manager 必须增加 context-aware runner,所有 action 路径使用 exec.CommandContext 或等价实现。现有单体 prepare/finish/reopen/integrate/publish Service 要拆成 typed steps 或暴露带 checkpoint/fence callback 的 operation;不能仅在原同步方法外包一层队列并声称支持取消/恢复。
最低 checkpoint:
- Repo add/sync/update/remove:reserve/probe、clone/fetch、validate、persist/cleanup。
- Requirement prepare:plan、逐 repo create/reuse、persist result。
- Finish:validate、逐 repo commit、push、cleanup、ready transition。
- Reopen:stale transaction、逐 repo restore worktree、persist。
- Release create:reserve natural key、create entity+ownership mapping、workspace initialization。
- Integrate:snapshot scope、逐 repo rebuild/integrate、persist SHA/status。
- Test start:resolve command、create run+immutable snapshots+Approval/job。
- Publish:freshness check、逐 repo publish、persist published SHA、final transition。
取消只在 step boundary 或 context-aware external command 中生效。已经完成的 Git 副作用不能回滚伪装成未执行;取消结果必须根据 checkpoint 显示 cancelled、interrupted 或 manual。
Web 使用 --no-daemon 且当前确实没有 daemon 时仍可创建 action,但 action 保持 queued,并在界面明确显示“等待 coordinator daemon”;兼容 daemon 恢复后再执行。如果存在不兼容 daemon,入队前的 feature gate 返回 daemon_upgrade_required。
每个 action type 定义 retry policy:
- read/sync/prepare/integrate 只有在 crash probe 与 checkpoint 能证明重入安全、旧 OS lock 已释放且新 fence 生效时才自动恢复一次。
- finish/reopen 依赖现有幂等与 cleanup 状态决定是否重试。
- publish 中断后不自动从头发布;先读取 release diagnostics。
publish_in_progress只允许人工确认后幂等 retry。 - create action 恢复时先按
operator_action_resources/natural key 查找已创建实体和目录,再决定继续或清理 orphan。 - 状态不明、旧进程仍可能运行或 fence 无法证明时标记 manual 并给出人工恢复,不推断成功、不自动 steal lease。
13.4 API
GET /api/v1/actions
GET /api/v1/actions/{action_key}
GET /api/v1/actions/{action_key}/events
POST /api/v1/actions/{action_key}/cancel
POST /api/v1/actions/{action_key}/retry
取消是 best effort。已经进入不可安全取消的 publish 子阶段时返回 409,并继续展示进度。
14. 主 Agent Web Chat
14.1 持久化模型
web_chat_sessions
| 字段 | 含义 |
|---|---|
session_key |
稳定 URL key |
title |
用户或首条消息生成 |
status |
active/archived |
coordinator_agent_id |
使用的 coordinator Agent |
summary |
长会话滚动摘要,不能作为业务状态 |
created_by_web_session_key |
审计 |
created_at/updated_at/archived_at |
生命周期 |
web_chat_messages
| 字段 | 含义 |
|---|---|
chat_session_id / sequence |
会话内稳定顺序 |
role |
user/assistant/system_summary |
content |
用户可见 Markdown |
status |
queued/running/completed/failed/cancelled |
reply_to_message_id |
assistant 对应 user message |
job_id/run_id |
coordinator 执行审计 |
context_json |
用户显式选择的 root ref 或 workspace_read,不含完整快照或 secret |
context_scope_json/scope_hash |
创建时物化的可读实体 ID 集合、relation policy version 和完整性 hash |
requested_by_web_session_key |
发起消息的浏览器会话 |
idempotency_key |
防重复发送 |
error_code/error_message |
脱敏失败 |
cancel_requested_at |
用户已提交取消,可空 |
created_at/completed_at |
生命周期 |
同一 chat session 最多一个 user message 处于 running;后续消息 queued。
agent_job_cancellation_requests
| 字段 | 含义 |
|---|---|
job_id |
唯一目标 Job,外键 |
requested_at/requested_by |
取消发起时间和 CLI/MCP/Web actor |
reason |
脱敏枚举或可选短文本 |
acknowledged_at |
daemon 已观察并请求终止 Provider |
completed_at |
Job 已收敛终态 |
这是 Web 与 daemon 之间的持久化取消协议,不把进程内 cancel function 暴露给 Web 进程。
14.2 API
GET /api/v1/chat/sessions
POST /api/v1/chat/sessions
GET /api/v1/chat/sessions/{session_key}
PATCH /api/v1/chat/sessions/{session_key}
DELETE /api/v1/chat/sessions/{session_key} # archive
GET /api/v1/chat/sessions/{session_key}/messages
POST /api/v1/chat/sessions/{session_key}/messages
POST /api/v1/chat/messages/{message_id}/actions/cancel
POST /api/v1/chat/messages/{message_id}/actions/retry
14.3 Message 执行
创建用户消息和 coordinator_chat Agent Job 必须在同一事务中完成:
- 校验 chat session active、Coordinator Agent available 和 Idempotency-Key。
- 调用唯一
ChatScopeResolver规范化所有 context root,校验存在性和用户显式选择,按版本化 relation allow-list 物化允许的 entity type + ID/key 集合,计算 scope hash。空 root 产生空业务 scope;只有明确的workspace_readroot 可生成全 workspace 只读 scope。 - 创建 user message。
- 创建
role=coordinator、job_type=coordinator_chat、status=chat_queuedJob,并在 input 中绑定 chat session/message、scope hash 和 policy version。 - 将 message 标记 queued,提交后只能由已完成 feature handshake 的 daemon 专用 chat lane 通过
ClaimCoordinatorChatJob执行。
V1 relation policy 显式枚举单向边:Requirement 可带出自身 Artifact、development cycle/step、Job/Run、Approval、Bug 和所属 Release;Release 可带出成员 Requirement、test run、Job/Run、Approval、Bug 和 Artifact;Job 可带出它的 Run/Event/Approval/Artifact;Run、Approval、Artifact 和 test run 可回到其唯一直接父实体。解析不做未声明的递归闭包;例如从 Requirement 带出的 Release 关联不再扩张到该 Release 的其他 Requirement。物化后新增的关联对象不自动进入该消息 scope,用户需发新消息刷新范围。
coordinator_chat 不进入当前 RunOnce 中同步执行的 coordinator decision 路径。Daemon 新增有界异步 chat lane,默认并行两个不同会话,同一 chat session 严格串行;普通 workflow decision、analysis/development/test Job 创建和 Worker 调度不能等待 Chat Provider 返回。
daemon 构造 prompt:
- 固定 coordinator system prompt 和 Agent 配置。
- 会话滚动 summary。
- 受大小限制的最近消息。
- 由
ChatLocalQueryResolver通过 message 冻结 scope 读取的最新无副作用 snapshot;prompt builder 不能绕过 resolver 直接调用无 scope 的 Service query。 - Chat 专用工具 allow-list、action-card result contract 和必须使用类型化 Approval 的约束。
上下文总大小设上限;较旧消息先生成/更新 summary,再截断。summary 只辅助对话,任何状态查询和动作前仍实时调用 Service/MCP。
14.4 Provider 与结果
- 继续使用 Codex/Claude 非交互 provider runner,不使用交互 PTY。
- Job 获取短期、job/run-bound coordinator capability,且
allowed_tools必须非空并使用 Chat 专用 allow-list。Capability 同时持久化 message scope hash、policy version 和规范化 resource scope;MCP 每次调用同时验证 job/run binding、tool 和 entity scope。 - Chat Provider 使用专用
chat_readonlyexecution profile,不能仅依赖 Codexread-only:- Provider 配置禁用原生 shell、filesystem、web/search 和其他非 MCP tool。
- 外层 OS allow-list 只开放 session runtime、必需 Provider 可执行文件/系统库、只读 Provider 认证配置和 MCP proxy/socket;拒绝其他 home、workspace、Artifact、DB、runtime 和子进程执行。
- Provider 进程可使用模型服务所需 transport,但禁用模型可调用的任意网络工具;无法区分或限制时 Chat health 为 unavailable。
- 不接受 Provider approval/grant 或 sandbox elevation;隔离 backend 不可证明可用的平台 fail closed,不启动 Chat Provider。
- 业务内容只能经 Chat local MCP read tools 返回,不能靠 Provider 直接读取 worktree/Artifact 文件。
- 需要写操作时只返回 action card,由 Web API 在用户确认后执行。
- Provider 流事件写入现有 AgentRunEvent,SSE 转成 chat delta/进度。
- Provider 退出、协议错误、多个 Approval 或非法输出按现有终态优先级失败。
- retry 创建新的 Job/run attempt,保留原失败消息和审计。
14.4.1 Provider 执行与终态所有权
现有 ProcessRunner.Run 会在 Provider 返回后直接调用 FinalizeAgentExecution,无法再与 chat message 原子收敛。实现 Web Chat 前必须拆分执行与终态:
type TerminalFinalizer interface {
Finalize(ctx context.Context, in TerminalInput) (RunResult, error)
}
type ProcessRunner struct {
DB *store.DB
Builder ProviderRunner
Finalizer TerminalFinalizer
}
- ProcessRunner 负责构建命令、启动 Job/Run、流式事件、等待进程和产生非终态
TerminalInput;从 build/isolation/start 失败到 success/cancel 的每条路径都必须经过注入的 finalizer,ProcessRunner 本身不再直接完成 Job/Run 或撤销 capability。 TerminalInput包含 Job ID、可空 Run ID、attempt、候选 terminal status、exit code、脱敏 error、result JSON/path 和可选 Approval;因此 Provider 未成功启动的 job-only 失败也使用同一终态边界。DefaultJobFinalizer调用 store transaction-aware 终态原语,保持现有 analysis/development/test 等 Job 的 status、Approval、event、Worker token 和 capability 语义。ChatJobFinalizer先校验版本化结果 contract,再在单一 Store transaction 内条件更新 user message、创建 assistant message/action-card payload、收敛 Job/Run、写 terminal event 并撤销 capability。它不允许 Provider Approval;任何 Approval 输出转成 failed。- finalizer 在更新时同时匹配 job/run/message 的非终态和 attempt;影响行数不是预期值时读取已有终态并执行幂等 reconciliation,不覆盖竞争者结果。
- interface 和 execution data 定义在
internal/agent,transaction 原语在internal/store,coordinator 只组装对应 finalizer;internal/agent不引用internal/web或internal/coordinator,避免 package cycle。
14.4.2 持久化取消
CLI/MCP 的 Job cancel 和 Web Chat cancel 共用同一 JobCancellationService:
- 在 Store transaction 内校验 Job/message 状态,幂等写入
agent_job_cancellation_requests,并设置web_chat_messages.cancel_requested_at。 - queued/
chat_queuedJob 可在该 transaction 中直接收敛为 cancelled,同时终结 message/event/capability;已 running Job 只记录 request,不由 Web 进程伪造 Provider 已终止。 - daemon 维护仅属于自身进程的
job_id -> cancel functionregistry,同时运行 DB cancellation watcher。Watcher 发现 request 后标记 acknowledged,调用 registry cancel;claim Provider 前和每次 recovery 也必须检查 request,避免“请求先于 registry 注册”的窗口。 - Provider context 结束后仍由 ChatJobFinalizer 收敛。终态 transaction 若看到已提交但未完成的 cancellation request,选择 cancelled 而不是 succeeded,并填写 request
completed_at;若自然完成已先提交,后来的 cancel 返回已有终态。 - daemon crash 后新 owner 依据 Job/Run 和 request 持久化状态完成 reconciliation;Web 重启不会丢失取消意图。
Provider 最终输出使用版本化 contract:
{
"schema_version": 1,
"assistant_markdown": "当前 Release 因测试命令变化已 stale。",
"context_refs": [
{"type": "release", "id": "42", "key": "2026-07-15"}
],
"proposed_actions": [
{
"kind": "release_integrate",
"resource": {"type": "release", "id": "42"},
"parameters": {},
"risk": "medium",
"requires_confirmation": true
}
]
}
assistant_markdown必填;其余字段可为空数组。proposed_actions.kind使用服务端固定 allow-list,不接受 URL、shell command、MCP tool name 或任意 JSON passthrough。对已有实体的 Action resource 必须位于当前 message 的冻结 scope;越界 card 使整个 Chat result fail closed。requirement_create等 create-only card 只能指向固定的当前-home pseudo resource,不得携带伪造 entity ID/路径,点击后仍只打开结构化创建表单,不因此获得 workspace-wide 读取权限。- 未知字段、未知 action、越界 entity、非法参数、多个 Approval 或 schema 解析失败时 Job fail closed,不创建 action card、不执行任何副作用。
- Action card 点击后由 Web 重新读取 entity/revision、渲染对应结构化表单并调用普通 API;Provider 返回的参数只能作为预填建议。
14.5 Chat 操作门禁
coordinator_chat capability 只允许 req_list_local/show_local/watch_local、release_status_local/watch_local、job_list/show/watch、脱敏 run_tail、artifact_list/show、approval_list/show、bug_list 和只读 Agent/Worker/Policy 查询。*_local 是新增无副作用 MCP tools,强制调用同一 ChatLocalQueryResolver,不执行 fetch、不写 stale/status event。所有 list 在 SQL/query 层按 scope 过滤,show/watch/tail 和关联展开在读取前校验目标 ID;不允许“先读全部再在 Provider prompt 中过滤”。它不允许直接调用任何状态迁移、Approval 响应、Artifact finalize、Git action 或发布工具。
MCP 授权除 tool name allow-list 外再执行 Chat 参数和 scope 策略;即使 registry 配置错误,也必须拒绝越界 entity、approval_respond、release_publish、artifact_finalize、req_update_workflow_status、Provider grant 和任何 force=true。创建需求、分配 Agent、重试、集成、测试和发布都由 proposed_actions 生成结构化卡片,用户确认后由 Web API 调用 Service。
以下行为必须转为专用结构化 UI/Approval:
artifact_finalization。implementation_blocker决策。test_command_missing。provider_permission。publish_confirmation和 publish。- 强制丢弃脏 worktree。
聊天文本中的“同意”“发布吧”等不直接映射为结构化响应。Assistant 返回 action card,用户在受控表单提交后才调用 Service。
15. Artifact 与只读 Diff
15.1 Artifact 内容
ReadArtifactContent:
- 从 artifact ID 读取 DB metadata。
- 根据 config ArtifactDir 和数据库登记 path 计算 canonical path;兼容现有 Artifact 以绝对路径入库,也兼容未来相对路径。
- 浏览器不得提交 path。数据库登记绝对路径只有在 canonical 后严格位于 ArtifactDir 内才允许;相对路径拒绝
..,两类路径都拒绝根目录逃逸和业务 symlink。 Lstat每个业务路径组件。- 限制允许扩展名和 MIME。
- 限制单次读取字节数,返回 truncated flag。
- Markdown/JSON/text 使用 UTF-8 校验;非法编码不内联展示。
创建用户修订 Artifact 继续调用 internal/artifact 的安全写入和 SHA-256 逻辑,不能让浏览器指定最终存储 path 或 version。
15.2 Diff
新增 workspace service:
type RequirementDiffParams struct {
RequirementKey string
RepoName string
Baseline string
Path string
MaxBytes int64
}
type RequirementDiffView struct {
RepoName string
Branch string
HeadSHA string
BaseSHA string
Files []DiffFileSummary
Patch string
Truncated bool
}
实现规则:
- Requirement 必须处于允许读取的非归档状态,repo 必须绑定该 requirement。
- worktree path 从数据库派生并执行同一 canonical/symlink 校验。
- Git 命令使用参数数组和
--分隔 path,不拼接 shell。 - Baseline 只接受
base_branch或release_snapshot等服务端 allow-list 枚举;实际 Git ref/SHA 始终从登记 branch/snapshot 派生,HTTP DTO 不接受 raw ref。 - Path 必须来自同一响应的相对 changed file,服务端仍重新验证。
- 二进制、超大 patch、submodule 和敏感文件只返回 summary。
- 输出再次经过通用 redactor。
Release workspace 只提供快照和测试所需的只读 diff,绝不提供修改动作。
16. 本地查询与远程 Git
现有部分 release 查询会执行 remote fetch。Web 高频接口必须新增无副作用查询:
ListRequirementsLocal。ListReleasesLocal。RequirementWatchSnapshotLocal。ReleaseWatchSnapshotLocal。DashboardSnapshot。
这些方法只读取 SQLite 和本地文件元数据,不调用 remote。显式 refresh-remote-status action 执行 fetch 后写可见诊断/时间;页面展示:
- 本地状态更新时间。
- 上次远端检查时间。
- 是否正在刷新。
- 远端检查失败信息。
这些字段不放在前端内存,也不新增容易漂移的 Release 列:从该 Release 最新的 typed refresh_remote_status operator action 及其 terminal event 派生。running 表示正在刷新;succeeded/failed 的 completed_at 是上次检查时间,脱敏 result/error 是结果。Action 审计记录保留后,Web 重启仍能恢复该视图。
不能把远端检查失败误报为本地业务状态改变。
17. Manual 兼容
- Requirement DTO 增加
mode=manual|ai_native,从 workflow row/status 推导。 - Release DTO 增加相同 mode。
- manual API action 只调用已有 manual service 方法。
- AI Native-only route 对 manual 对象返回
409 unsupported_workflow_mode和允许操作。 - Web 不创建隐式 workflow row、不自动生成 Artifact/Job,也不转换历史状态。
dev/ideAPI 不从局域网请求直接启动宿主应用;返回可复制命令。只有明确的本机 loopback/browser 场景才允许受控 opener,失败不写业务状态。
18. 前端架构
18.1 基础设施
- React Router:URL 与页面层级。
- Query cache:REST server state、分页、mutation 和 revision 冲突恢复。
- 原生 EventSource 包装:SSE scope、重连、cursor 和 cache invalidation。
- 轻量表单/schema 校验:与 API discriminated union 对齐。
- Markdown renderer + sanitizer。
- 自有 CSS variables 和可访问基础组件;不依赖需要运行时外部 CDN 的资源。
具体库版本由 lockfile 固定,文档不依赖某个瞬时版本号。
18.2 Route
/
/requirements
/requirements/:key
/releases
/releases/:key
/approvals
/approvals/:approvalKey
/jobs
/jobs/:jobKey
/runs/:runId
/chat
/chat/:sessionKey
/settings/repos
/settings/agents
/settings/workers
/settings/review-policies
/settings/system
18.3 Feature 模块
dashboard:卡片、待办、健康、活动。requirements:list/board、submit、detail tabs、analysis、development、diff。releases:scope、integrate、test runs、bugs、publish。approvals:类型化 schema 和 action cards。execution:Job/Run/Event/raw tail。chat:session、message、context picker 和 action card。settings:Repo、Agent、Worker、Policy、daemon、sessions。shared:status badge、timeline、Artifact viewer、error recovery、confirmation dialog。
18.4 响应式
- 桌面三栏,右侧 Chat/待办可收起。
- 中等宽度改为两栏,Chat 使用抽屉。
- 手机只加载摘要、时间线、聊天和 Approval;复杂 diff、配置表格显示“请在桌面打开”并提供只读概要。
- 前端 route 和 API 权限一致;隐藏组件不能作为安全控制。
18.5 本地草稿
Artifact 修订、Requirement 表单和聊天未发送文本可以保存在浏览器 local storage,但:
- key 必须包含 Web instance/home 的不可逆标识,避免不同 home 混用。
- 不保存 session token、CSRF、capability、raw output 或 Provider secret。
- 提交成功后清理对应草稿。
- 服务端 revision 变化时提示用户合并,不自动覆盖。
19. 安全加固
19.1 文件和控制面
- Web Provider/chat CWD 继续使用 session/job scoped runtime,不以 workspace home 为可写根。
- Web 不提供任意文件 API;所有文件读取从 DB entity 派生。
- Worker permission grant 不能开放 DB、WAL/SHM、config、runtime socket、Web session 表或其他 worktree。
- TLS key、session token、CSRF 和 pairing code 不进入 Artifact、AgentRun command 或 event。
- Web 日志使用与 Provider 一致的 exact-token 和通用 secret redactor。
19.2 HTTP
- Host allow-list 来自有效 listen addresses 和显式配置。
- 不发送
Access-Control-Allow-Origin: *;V1 不支持跨域 SPA。 - JSON 写请求拒绝 form-urlencoded 和 text/plain,减少 simple-request CSRF。
- Pair、Artifact content、diff、raw tail 和 chat 分别设置速率/大小限制。
- CSP 禁止 inline script、外部 script、frame 和 object。
- API 返回路径时使用用户可读的缩略/相对形式;完整本机路径只在必要诊断中展示。
- Session last_seen 更新限频,避免每个请求都写数据库。
19.3 审计
新增 web_audit_events:
- session paired/revoked。
- pairing failures/lockout。
- Web instance start/stop/restart。
- operator action request/cancel/retry。
- chat session/message lifecycle。
- 敏感读取:raw tail、Artifact download、diff。
字段包括 actor session、action、entity、result、request ID、来源摘要和时间;不保存 request body secret。业务状态变化继续由原有 requirement/release/run/approval event 记录,Web audit 不替代它们。
20. SQLite Migration
新增 additive migration,创建:
daemon_protocol_attestations。web_server_instances。web_pairing_codes。web_sessions。web_idempotency_records。web_chat_sessions。web_chat_messages。capability_resource_scopes。agent_job_cancellation_requests。operator_actions。operator_action_resources。operation_resource_locks。operator_action_events。resource_revisions。web_audit_events。
Migration 要求:
- 不修改已有业务状态默认值。
- 具有具体父表的字段使用外键,
resource_type/resource_id等多态引用由 Service/Store 强校验;同时建立必要的 unique/partial indexes。store.Open必须对每个 SQLite connection 执行PRAGMA foreign_keys=ON并验证结果,否则不能宣称 foreign key 生效。 - 时间全部保存 UTC。
- pairing/session 只保存 token hash。
- migration 在旧数据库上可重复启动并保持幂等。
- upgrade 后旧 CLI 不认识新表但仍能操作原有流程;Web 不依赖旧 CLI 二进制写新表。
daemon_protocol_attestations.instance_key是每次启动的不可变快照主键,不对会被下一次 acquire 覆写的 singleton row 建立级联外键。Controller 只读取与当前 instance key 以及 PID/process-start/executable identity 全部匹配的 attestation;旧 daemon 新增/接管 singleton row 但没有自己 attestation 时必须被视为 incompatible。- Agent Job 状态读写层增加
chat_queued;通用 List/DTO 将它归一为 queued,普通ClaimCoordinatorJob显式不 claim,专用ClaimCoordinatorChatJob同时匹配status=chat_queued和job_type=coordinator_chat。 web_chat_messages包含cancel_requested_at、context_scope_json、scope_hash和scope_policy_version;capability_tokensadditive 增加 scope hash/policy version,并用capability_resource_scopes按 capability ID 保存物化 entity 范围。web_idempotency_records保存脱敏response_json;resource_revisions包含state_hash和writer_epoch。这些列的回填/默认不能伪造业务 mutation。- DDL migration 完成后单独运行可重试的 revision reconciliation;在扫描成功前
/readyz不返回 ready,扫描失败不能留下部分降低的 version。
关键索引:
CREATE UNIQUE INDEX web_server_single_active
ON web_server_instances(active_slot)
WHERE status IN ('starting', 'running', 'stopping');
CREATE UNIQUE INDEX web_pairing_code_hash_unique
ON web_pairing_codes(code_hash);
CREATE UNIQUE INDEX web_session_token_hash_unique
ON web_sessions(token_hash);
CREATE UNIQUE INDEX web_idempotency_key_unique
ON web_idempotency_records(session_key, idempotency_key);
CREATE UNIQUE INDEX web_chat_message_sequence_unique
ON web_chat_messages(chat_session_id, sequence);
CREATE UNIQUE INDEX web_chat_message_idempotency_unique
ON web_chat_messages(requested_by_web_session_key, idempotency_key)
WHERE idempotency_key IS NOT NULL;
CREATE UNIQUE INDEX capability_resource_scope_unique
ON capability_resource_scopes(capability_token_id, resource_type, resource_id);
CREATE UNIQUE INDEX agent_job_cancel_request_job_unique
ON agent_job_cancellation_requests(job_id);
CREATE UNIQUE INDEX operator_action_idempotency_unique
ON operator_actions(requested_by_session_key, idempotency_key);
CREATE UNIQUE INDEX operator_action_resource_unique
ON operator_action_resources(action_id, resource_type, resource_id, relation);
CREATE UNIQUE INDEX resource_revision_unique
ON resource_revisions(resource_type, resource_id);
web_server_instances.active_slot 定义为 INTEGER NOT NULL DEFAULT 1 CHECK(active_slot = 1),用 partial unique index 保证同一 home 只有一个 active Web instance。operation_resource_locks.resource_key 是主键;锁只能通过带 owner/fence 条件的 Acquire/Renew/Release transaction 改变。
21. Service 扩展
21.1 Workspace Service
新增或补齐:
- 本地 Dashboard、requirement 和 release snapshot。
- Artifact 内容安全读取、比较和用户修订创建。
- Requirement/release 只读 diff。
- 显式业务 action facade,返回稳定语义错误而不是 CLI 文本。
- Web 所需 Agent/Worker/Policy/Repo CRUD view。
- revision 计算和 expected revision 校验。
- 适用于 Web 的 publish/test 快照摘要。
WebCommandService与CommandMeta,覆盖全部 Web 可写资源。- 共享的 transaction-aware 业务 core 和 canonical resource registry/hash,供 CLI、MCP、daemon 和 Web 共用。
现有自行开 transaction 的写 facade 需拆为“对外便捷包装 + 接收 store.Tx 的 core”;WebCommandService 直接在自己的 Unit of Work 中调用 core。不能先写 Web action succeeded 再补业务状态,也不能在业务 commit 后再补 revision/idempotency。
21.2 Coordinator Controller
从 CLI 私有函数抽出:
type DaemonStatus struct {
Instance store.DaemonInstance
Attestation store.DaemonAttestation
}
type Controller interface {
EnsureRunning(ctx context.Context, required FeatureSet) (DaemonStatus, error)
Status(ctx context.Context) (DaemonStatus, error)
Stop(ctx context.Context, timeout time.Duration, force bool) error
}
CLI agent daemon 和 Web lifecycle 共用该实现,避免两套进程 identity/heartbeat 规则。Controller 返回已验证当前 instance identity 的 DaemonAttestation(binary/protocol/features),负责安全升级判断,handler 不自行 join 历史 attestation,也不用字符串比较版本。
21.3 Web Application Service
internal/web 内定义:
type App struct {
Queries *workspace.Service
Commands *workspace.WebCommandService
WebStore WebStore
Coordinator coordinator.Controller
Actions *ActionService
Chat *ChatService
Events *EventBroker
Clock Clock
Random io.Reader
}
Queries 只读。Commands 是业务 mutation 和同步 idempotency/revision 的唯一入口。WebStore 只提供 auth/session、pairing、Web instance 和其他纯 Web 读模型,不暴露 Requirement/Release 业务写方法。Clock/Random 接口只用于可测的过期、token 和 pairing 逻辑。生产使用 UTC system clock 和 crypto/rand。
22. 失败恢复
22.1 Web instance
- heartbeat 过期但 PID identity 不匹配:记录 stale,不发送信号。
- listener 已启动但数据库未 ready:readyz 失败,父进程不报告成功。
- DB 暂时 busy:使用现有 busy timeout,短暂重试;不把 HTTP 请求无限排队。
- assets 缺失:启动失败,不提供只有 API 的半成品 UI。
22.2 Action
- daemon/action executor crash:lease expiry 后 reconciliation。
- 外部 Git 命令中断:根据 action type 和当前 Service/Git 状态决定 retry、failed 或 manual。
- publish 中断:读取 authoritative release status;可能进入 publish-in-progress,禁止自动重置。
- action terminal 状态和 terminal event 同一事务提交。
22.3 Chat
- daemon 未启动且 Web 为
--no-daemon:消息保持 queued 并显示调度暂停;兼容 daemon 恢复后正常 claim。daemon 存活但 protocol/feature 不兼容时不接受新消息。 - Provider unavailable:Job fail/retry,消息显示错误和 health 链接。
- Web 断线:Run 继续,重连后按 message/Run event cursor 恢复。
- Web server 在消息执行中退出:daemon 不受影响,并继续完成 chat completion transaction;Web 重启后直接读取结果。
- daemon/Web 崩溃不丢失
agent_job_cancellation_requests;recovery 先处理取消意图,再考虑重试 Provider。 - 新版本由 ChatJobFinalizer 保证不产生 split terminal state。daemon 在启动和每轮 recovery 中仍检查历史数据;若升级前已存在 Job terminal 但 assistant message 未终结,按 Job result 幂等修复并写审计事件。
23. 测试策略
23.1 Store
- Migration 从空库和现有 AI Native 库升级。
- Daemon instance+attestation 原子 acquire,attestation 必须绑定 instance/process/executable identity,历史行不会被新 instance 继承。
- Web 单实例 partial unique。
- Pairing code 单次使用、过期、五次锁定和并发竞争。
- Session hash、过期、撤销和 revoke all。
- Chat sequence、同会话单 active message。
- Chat scope root 规范化、关联物化、scope hash/policy version 与 capability scope 原子绑定。
- Job cancellation request 幂等、queued 立即取消、running acknowledge 和 completion race。
- Operator action idempotency、完整 resource lock set、OS lock、fence、lease、checkpoint、recovery 和 event sequence。
- WebCommandService 的业务/revision/event/audit/idempotency 全成功与任一步失败全回滚;重放使用原响应 snapshot。
- Repo 等全部 resource revision registry、canonical hash drift、旧二进制回滚后再升级 reseed 和 version 不倒退。
- Web audit 不保存 secret。
23.2 Go 单元测试
- CLI flags、默认值、非 loopback TLS guard、后台 child argv。
- PID/process identity 防止停止错误进程。
- Daemon protocol/feature handshake、旧 daemon 自动安全 restart、有 running work 时拒绝升级和
--no-daemonmutation gate;特别覆盖“新 daemon 写过 attestation → 旧 daemon 用新 instance key 接管同一 singleton row”仍判定 incompatible。 - Host/Origin/CSRF/session middleware。
- DTO 的 null、int64 string、revision 和 error envelope。
- Artifact canonical path、symlink、大小和 MIME 限制。
- Diff path/ref 注入、二进制和截断。
- SSE cursor encode/decode、Last-Event-ID、reset、慢客户端。
- Action retry policy,尤其 publish-in-progress。
- ProcessRunner 每个 success/failure/start-error/cancel 路径只经过一个 TerminalFinalizer;DefaultJobFinalizer 保持现有语义。
- Chat message/job 原子创建、持久化 cancel、retry、ChatJobFinalizer 原子终态和历史 split-state reconciliation。
- ChatLocalQueryResolver 对 list/show/watch/tail/relation 和 prompt snapshot 使用同一冻结 scope;空 scope 拒绝业务读取,越界参数/action card fail closed,显式
workspace_read才可全局只读。 - Secret redaction 覆盖 Cookie、pairing、CSRF 和 capability。
23.3 HTTP 集成测试
使用 httptest 和临时 home:
- 未认证只允许 health/pair。
- Pair 后 Cookie/CSRF 可用,错误 Origin 和无 CSRF 被拒绝。
- Session revoke 终止后续 API/SSE。
- Requirement submit/assign/Artifact/finalize。
- Approval 六类 schema。
- Release integrate/test/bug/retest/publish action。
- If-Match 冲突、Repo revision 和 Idempotency-Key 原 status/body 重放。
- Manual 与 AI Native route 隔离。
- raw tail 永不接受 unsafe unredacted。
23.4 Coordinator/Action 集成
- Web start 自动 ensure daemon。
- Web start 对 daemon protocol/features 做 handshake,不兼容且有运行工作时给出可恢复错误。
- 浏览器请求结束后 action 继续。
- Web crash 后 prepare/integrate action 恢复。
- Publish crash 后进入正确 manual/publish-in-progress 路径,不重复发布成功 repo。
- Coordinator chat 使用 fake Codex/Claude runner,产生 Run events、工具结果和 assistant message。
- Chat 分别绑定单实体、关联实体、空 scope 和显式 workspace scope,验证 MCP/prompt/action card 无范围泄漏。
- 独立 Web 进程提交 cancel request,daemon watcher 终止 fake Provider;模拟 watcher 前后、自然完成和 daemon crash 竞争。
- Web 停止不取消普通 Agent Job。
23.5 前端
- 组件测试:状态徽标、Approval union form、Artifact compare、action progress、错误恢复。
- Query/SSE 测试:局部 cache invalidation、断线续传、stream reset。
- 表单测试:revision conflict 保留草稿,重复点击只提交一个 idempotency key。
- 响应式测试:桌面完整流程、手机聊天和六类 Approval。
- 无障碍测试:键盘、焦点、ARIA、对比度、错误关联。
- Markdown XSS、恶意链接和超长事件渲染。
23.6 端到端
Playwright 覆盖:
- CLI 启动 Web,配对浏览器。
- 提交需求、分配 Agent、观察 analysis event。
- 比较 Artifact 并选择两类终稿。
- 观察开发 step/review 和只读 diff。
- 创建 Release、集成、缺失测试命令 Approval。
- 测试发现 Bug、feature 修复、stale、重新集成和测试。
- 确认 snapshot 并发布。
- Chat 查询状态、创建 action card,但不能绕过 publish Approval。
- 浏览器断线/刷新/Web restart 后恢复 action 和 chat。
- CLI 修改同一对象后 Web 陈旧写入返回 409。
23.7 构建验证
npm --prefix web ci
npm --prefix web run lint
npm --prefix web test
npm --prefix web run build
git diff --exit-code -- internal/web/assets
GOCACHE=/private/tmp/workspace-cli-gocache \
GOMODCACHE=/private/tmp/workspace-cli-gomodcache \
GOSUMDB=off \
go test -count=1 ./...
GOCACHE=/private/tmp/workspace-cli-gocache \
GOMODCACHE=/private/tmp/workspace-cli-gomodcache \
GOSUMDB=off \
go vet ./...
GOCACHE=/private/tmp/workspace-cli-gocache \
GOMODCACHE=/private/tmp/workspace-cli-gomodcache \
GOSUMDB=off \
go build ./cmd/workspace
git diff --check
24. 分阶段实施
24.1 阶段 1:基础 Web 运行时
- Migration、Web instance、CLI start/status/stop/restart。
- 与 instance/process identity 原子绑定的 Daemon protocol/feature attestation、安全自动 restart 和降级模式门禁。
- TLS guard、pair/session/CSRF/Origin。
- React/Vite embed、基础 layout、health。
- 只读 Dashboard 和本地 Requirement/Release 列表。
完成条件:可通过 CLI 安全启动、配对、查看本地状态并可靠停止。
24.2 阶段 2:核心读写与实时
- Requirement/Release 详情 DTO。
- Transaction-aware 业务 core、WebCommandService Unit of Work、全资源 revision registry/hash 和升级 reconciliation。
- SSE broker 和 opaque cursor。
- Agent、Worker、Policy、Repo 配置。
- Artifact 内容、版本创建和比较。
- Approval 类型化响应。
完成条件:除 Git 长操作和 Chat 外,Web 可完成主要短操作并实时观察。
24.3 阶段 3:Action 与研发/发布
- Operator action store/executor/events/recovery。
- Workspace prepare、finish/reopen、repo sync、integrate、test、publish。
- Bug 循环、snapshot freshness、publish-in-progress。
- 只读 diff。
完成条件:浏览器断线/重启不破坏从研发到发布的长操作。
24.4 阶段 4:主 Agent Chat
- Chat session/message persistence。
chat_queuedJob 和 feature-gated coordinator chat lane 接入。- ProcessRunner/TerminalFinalizer 拆分与 Chat 原子终态。
- SSE message/run event、持久化 cross-process cancel、retry/reconciliation。
- Chat readonly OS isolation 和无副作用 local MCP allow-list。
- ChatScopeResolver、物化 capability entity scope 和 scope-aware prompt/MCP/action-card 校验。
- 上下文选择和结构化 action card。
完成条件:聊天可恢复、可审计,并且不能绕过任何人工门禁。
24.5 阶段 5:加固与发布
- 完整 E2E、XSS/CSRF/path/secret tests。
- 响应式、可访问性、性能和大数据分页。
- Release CI 前端构建、assets drift、跨平台 browser opener。
- 文档、help、升级/回滚说明。
完成条件:全部验收场景通过,旧 CLI/manual workflow 回归通过,单二进制包含可用 Web UI。
25. 兼容、升级与回滚
- 新 migration 只加表/索引,原有 CLI 数据和状态机不变。
- 已有
workspace ai start交互式终端会话继续可用;Web Chat 是新的 job-based 入口。 - 已有 CLI/MCP 输出格式不因 Web DTO 改变。
- Web UI 与后端由同一二进制交付,避免跨版本前后端;API 返回 build version 供诊断。
- 回滚到不含 Web/operation lock 的旧二进制前,必须停止 Web 和 daemon,并确认没有 queued/running/manual action 或 resource lock;否则旧 CLI 会绕过新锁。满足该前置后,新表可保留不读,原有 CLI 继续工作。
resource_revisions是新增旁表,不要求在每个旧业务表增加列;首次升级为现有可变资源回填 version/hash/epoch,并通过回归测试确保当前 CLI/MCP/daemon mutation 同事务递增。- 如果曾回滚并用旧二进制修改过业务数据,再升级时 Web/daemon 必须使用当前二进制启动,完成 canonical hash reconciliation 后才 ready;偏移资源增加 version 而不沿用旧 ETag。不支持新旧二进制同时对同一 home 写入。
- daemon protocol/feature 是数据兼容之外的运行时门禁;新 Web 不会复用没有 handshake 的旧 daemon。
- Web 启动失败不得影响 daemon 或 CLI。
26. 技术验收标准
workspace web start默认后台启动单实例,并在 ready 后输出 URL 和一次性 pairing code。- 非 loopback 无 TLS 时 fail closed,显式 insecure 模式有持续告警。
- Session、CSRF、Origin、CSP、rate limit 和 secret redaction 测试通过。
- SPA 完整嵌入二进制,离线于外部 CDN 运行,前端源与 generated assets 不漂移。
- 所有业务写操作通过 workspace service;Web handler 不直接写业务表或 shell 执行 CLI。
- Web 同步 mutation 通过单一 typed Unit of Work 原子提交业务状态、revision/hash、event/audit 和可重放 idempotency response;Repo 等所有可写资源均覆盖。
- Dashboard/实时刷新不触发高频 remote Git fetch。
- SSE 支持多源 cursor、断线续传、慢客户端和 reset。
- Git/Provider 长操作由 durable action/Job 承载,HTTP 断线和 Web restart 后可恢复。
- Web 与 daemon 通过绑定当前 instance/process/executable identity 的版本化 protocol/feature attestation 协同,历史 attestation 不会被旧 daemon 继承,旧 daemon 不会误 claim Web action/chat。
- Chat 每条消息都有持久化 message、Job、Run 和 Event;TerminalFinalizer 原子收敛 assistant message 和 Job/Run,capability 在终态撤销。
- Chat/Job 取消意图持久化,Web 和 daemon 分进程或任一方重启都不丢失,与自然完成竞争时只有一个终态。
- Chat prompt、MCP list/show/watch/tail、关联展开和 action card 全部受每条消息的冻结 entity scope 约束;未显式选择
workspace_read时不会扩大为全局读取。 - 六类 Approval 使用严格 schema,聊天不能直接提交危险确认。
- Artifact/diff/raw tail 通过 canonical scope、大小限制和脱敏测试。
- Manual 与 AI Native 对象正确分流,现有 CLI、MCP、daemon、Provider 和发布测试全部回归通过。