workspace-cli AI Native Web 技术实现方案

本文给出 AI Native Web 操作需求方案 的实现设计。面向使用者的启动、配置和端到端操作说明见 AI Native Web 功能与使用指南。现有 AI Native Core 的状态机、数据模型、Provider、MCP、Worker 和发布安全语义继续以 AI Native 研发平台技术实现方案 为准。

1. 技术目标

在现有 Go 单二进制中增加可由 CLI 启动的 Web 控制台,同时满足:

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 当前可复用能力

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 关键边界

  1. HTTP handler 只做认证、解析、DTO 转换和调用 application service。
  2. Web 专属会话、chat 和 action 状态通过 application service 操作 store;handler 不直接执行 SQL。
  3. Requirement/release 等业务写入必须调用 workspace.WebCommandService 的类型化命令;它与 CLI/MCP 复用同一组 transaction-aware 业务原语,不能在 Web package 重建状态迁移。
  4. 数据库事务在调用 Git、Provider 或等待网络之前结束;长操作分成“记录 action → 执行外部工作 → 原子提交结果”。
  5. Web 返回显式 DTO,不直接 JSON encode store struct 或 sql.Null*。
  6. 前端只保存页面状态和缓存;服务端数据变化始终可通过重新查询恢复。

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/

职责:

生成后的 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 脱敏诊断
  1. 父进程加载配置、migration 并读取当前 Web instance。
  2. 若数据库记录为 running、heartbeat 新鲜且进程 identity 匹配,不重复启动;调用 PairingService 撤销该实例尚未使用的旧 code、原子签发一个新 code,并返回当前 URL/code。
  3. 若记录 stale,先完成安全 reconciliation;不能仅凭旧 PID kill 进程。
  4. 父进程执行当前二进制的隐藏 child 模式:

    workspace --home <home> web start --foreground --web-child ...
    
  5. child 获取 Web 单实例 lease,记录 PID、可执行文件 canonical path 和操作系统进程启动时间。
  6. child 完成 listener、TLS、数据库、assets 和 API health 初始化后写 running heartbeat。
  7. 父进程最多等待五秒;成功后打印 URL、pairing code、日志和停止命令,失败时返回日志路径。
  8. 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

4.4 Stop 和 restart

stop:

  1. 校验 instance identity 和 heartbeat。
  2. 把状态改为 stopping。
  3. 向已验证的 PID 发送正常终止信号。
  4. Web 停止接受新请求,关闭 SSE,等待当前短请求完成。
  5. 不停止 coordinator daemon,不取消 Agent Job,也不撤销 daemon 持有的 operator action lease。
  6. 正在运行和 queued 的 operator action 继续执行并写数据库;Web 再次启动后恢复展示。
  7. 完成当前短 HTTP 请求后关闭 listener 和 Web instance。

--force 只在 identity 校验成功且超时后使用。restart 先 stop Web,再按新参数 start;TLS/loopback session 保存在数据库中并继续有效,非 loopback insecure HTTP session 在 restart transaction 中撤销。

4.5 Daemon 协同

5. 网络、TLS 与浏览器打开

5.1 监听规则

5.2 TLS

5.3 Browser opener

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。构建要求:

6.2 Go embed

internal/web/assets.go 使用:

//go:embed assets
var assets embed.FS

静态 handler:

7. HTTP Server 与中间件

7.1 Server

使用显式 http.Server:

7.2 中间件顺序

  1. Request ID。
  2. Panic recovery。
  3. 安全响应头。
  4. Host allow-list。
  5. 结构化访问日志和 secret redaction。
  6. 请求体大小与 content type。
  7. Session authentication。
  8. Origin/CSRF。
  9. Per-session rate limit。
  10. 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"
}

流程在同一事务中:

  1. 按 hash 查找未使用、未过期、未锁定 code。
  2. 失败时原子增加 attempts;响应始终使用通用 pairing_failed。
  3. 成功时标记 code used,创建 session,并设置 session 与 CSRF 两个 Cookie。
  4. Pairing code 不得被两个并发请求重复使用。

登录接口按来源 IP 和实例做速率限制;连续失败写安全审计事件,但不写原始 code。

8.3 CSRF

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 基础规则

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-..."
}

稳定错误码至少包含:

9.3 Revision 与并发

9.4 Idempotency

以下请求必须带 Idempotency-Key:

唯一键为 session_key + idempotency_key,因此浏览器必须为每次用户意图生成新的随机 UUID,不能在不同 route 复用。重复请求:

同步写操作使用 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。实现要求:

  1. 将现有业务状态迁移下沉成接收 store.Tx 的 transaction-aware core;CLI/MCP 和 Web 均调用这些 core,避免嵌套事务或复制规则。
  2. 每条 Web 命令只开一个 Store transaction,依次执行 idempotency lookup、expected revision 条件更新、业务 mutation、业务 event/Web audit、所有受影响 resource revision/hash 更新、显式 response DTO 生成和 completed idempotency record 写入;任一步失败全部回滚。
  3. 业务表本身的 event/status 是权威状态;Web audit 不替代它。response DTO 只包含返回给该 route 的脱敏字段和新 revision。
  4. 长 action 的入队 transaction 使用同一 CommandMeta 语义,原子写 action、resource ownership/locks、revision 和 idempotency 响应;后续 checkpoint/terminal 是 daemon 拥有的独立 fenced transaction。
  5. 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

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 生产与轮询

13. Durable Operator Action

13.1 适用操作

以下动作不在 HTTP handler 中同步执行:

纯数据库短操作如字段更新、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。

唯一约束:

13.3 Executor

Coordinator daemon 启动独立于 Agent Worker pool 的有界 action executor,默认最多并行两个 lock set 不相交的 action。Web 只创建、查询或请求取消 action:

  1. 入队事务验证 precondition、写 action、预留完整资源锁,并记录 expected revision。
  2. daemon claim 时原子写 lease、增加 action/resource fence,并取得所有路径资源的 OS advisory lock。
  3. 任一 OS lock 已被旧进程持有,或无法证明旧 owner 已终止时,不接管外部操作;action 进入 manual 并保留诊断。
  4. 重新读取全部资源 revision 和 Git probe;状态不满足时写 state_conflict,不执行外部操作。
  5. 按 action type 执行显式 step state machine,每一步前后验证 lease owner、fence、cancel request 和资源 revision。
  6. 每个外部副作用完成后持久化 checkpoint/event,再进入下一步;数据库事务不能跨外部命令。
  7. 完成时在同一事务复核 fence,提交业务 terminal state、action terminal state/event 并释放数据库锁;随后释放 OS lock。
  8. 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:

取消只在 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:

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 必须在同一事务中完成:

  1. 校验 chat session active、Coordinator Agent available 和 Idempotency-Key。
  2. 调用唯一 ChatScopeResolver 规范化所有 context root,校验存在性和用户显式选择,按版本化 relation allow-list 物化允许的 entity type + ID/key 集合,计算 scope hash。空 root 产生空业务 scope;只有明确的 workspace_read root 可生成全 workspace 只读 scope。
  3. 创建 user message。
  4. 创建 role=coordinator、job_type=coordinator_chat、status=chat_queued Job,并在 input 中绑定 chat session/message、scope hash 和 policy version。
  5. 将 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:

上下文总大小设上限;较旧消息先生成/更新 summary,再截断。summary 只辅助对话,任何状态查询和动作前仍实时调用 Service/MCP。

14.4 Provider 与结果

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
}

14.4.2 持久化取消

CLI/MCP 的 Job cancel 和 Web Chat cancel 共用同一 JobCancellationService:

  1. 在 Store transaction 内校验 Job/message 状态,幂等写入 agent_job_cancellation_requests,并设置 web_chat_messages.cancel_requested_at。
  2. queued/chat_queued Job 可在该 transaction 中直接收敛为 cancelled,同时终结 message/event/capability;已 running Job 只记录 request,不由 Web 进程伪造 Provider 已终止。
  3. daemon 维护仅属于自身进程的 job_id -> cancel function registry,同时运行 DB cancellation watcher。Watcher 发现 request 后标记 acknowledged,调用 registry cancel;claim Provider 前和每次 recovery 也必须检查 request,避免“请求先于 registry 注册”的窗口。
  4. Provider context 结束后仍由 ChatJobFinalizer 收敛。终态 transaction 若看到已提交但未完成的 cancellation request,选择 cancelled 而不是 succeeded,并填写 request completed_at;若自然完成已先提交,后来的 cancel 返回已有终态。
  5. 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
    }
  ]
}

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:

聊天文本中的“同意”“发布吧”等不直接映射为结构化响应。Assistant 返回 action card,用户在受控表单提交后才调用 Service。

15. Artifact 与只读 Diff

15.1 Artifact 内容

ReadArtifactContent:

  1. 从 artifact ID 读取 DB metadata。
  2. 根据 config ArtifactDir 和数据库登记 path 计算 canonical path;兼容现有 Artifact 以绝对路径入库,也兼容未来相对路径。
  3. 浏览器不得提交 path。数据库登记绝对路径只有在 canonical 后严格位于 ArtifactDir 内才允许;相对路径拒绝 ..,两类路径都拒绝根目录逃逸和业务 symlink。
  4. Lstat 每个业务路径组件。
  5. 限制允许扩展名和 MIME。
  6. 限制单次读取字节数,返回 truncated flag。
  7. 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
}

实现规则:

Release workspace 只提供快照和测试所需的只读 diff,绝不提供修改动作。

16. 本地查询与远程 Git

现有部分 release 查询会执行 remote fetch。Web 高频接口必须新增无副作用查询:

这些方法只读取 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 兼容

18. 前端架构

18.1 基础设施

具体库版本由 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 模块

18.4 响应式

18.5 本地草稿

Artifact 修订、Requirement 表单和聊天未发送文本可以保存在浏览器 local storage,但:

19. 安全加固

19.1 文件和控制面

19.2 HTTP

19.3 审计

新增 web_audit_events:

字段包括 actor session、action、entity、result、request ID、来源摘要和时间;不保存 request body secret。业务状态变化继续由原有 requirement/release/run/approval event 记录,Web audit 不替代它们。

20. SQLite Migration

新增 additive migration,创建:

Migration 要求:

关键索引:

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

新增或补齐:

现有自行开 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

22.2 Action

22.3 Chat

23. 测试策略

23.1 Store

23.2 Go 单元测试

23.3 HTTP 集成测试

使用 httptest 和临时 home:

23.4 Coordinator/Action 集成

23.5 前端

23.6 端到端

Playwright 覆盖:

  1. CLI 启动 Web,配对浏览器。
  2. 提交需求、分配 Agent、观察 analysis event。
  3. 比较 Artifact 并选择两类终稿。
  4. 观察开发 step/review 和只读 diff。
  5. 创建 Release、集成、缺失测试命令 Approval。
  6. 测试发现 Bug、feature 修复、stale、重新集成和测试。
  7. 确认 snapshot 并发布。
  8. Chat 查询状态、创建 action card,但不能绕过 publish Approval。
  9. 浏览器断线/刷新/Web restart 后恢复 action 和 chat。
  10. 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 运行时

完成条件:可通过 CLI 安全启动、配对、查看本地状态并可靠停止。

24.2 阶段 2:核心读写与实时

完成条件:除 Git 长操作和 Chat 外,Web 可完成主要短操作并实时观察。

24.3 阶段 3:Action 与研发/发布

完成条件:浏览器断线/重启不破坏从研发到发布的长操作。

24.4 阶段 4:主 Agent Chat

完成条件:聊天可恢复、可审计,并且不能绕过任何人工门禁。

24.5 阶段 5:加固与发布

完成条件:全部验收场景通过,旧 CLI/manual workflow 回归通过,单二进制包含可用 Web UI。

25. 兼容、升级与回滚

26. 技术验收标准