Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions .agents/plugins/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
{
"id": "contentcloud-video-production",
"kind": "scene_plugin",
"version": "0.8.0",
"version": "0.9.0",
"source": {
"repository": "https://github.com/limecloud/contentcloud",
"ref": "v0.8.0"
"ref": "v0.9.0"
},
"license": "Apache-2.0",
"digest": "sha256:0bb239fa608f20638f9540cc41c4de9291662565de234e1ee758bb99903c5aeb",
"digest": "sha256:30a5a5c5c3109d86aa272451a8df0a30c269df9a91fdad9c8e7cdce93f0e8bdc",
"signature": {
"status": "verified",
"algorithm": "ed25519",
"key_id": "contentcloud-plugin-release-2026-07",
"value": "Z0neUpfzYjOxXTcuP6ocANIK/n4J9EhkVxgARo5xn2oRE3p5vFjQnhCKqLp9MutoAPcv+GLHun0rqa2LNWPECg=="
"value": "mTdUw7bh2wmkEdVIWdjGMxfBgvzGiIl3fl8uwRG9d05h1J+7WbEAAUQyAmgD5O4HnUbHAM82xFpmm5I/vvWfBw=="
},
"compatible_profiles": [
"contentcloud.video-production"
Expand Down Expand Up @@ -54,8 +54,8 @@
],
"evaluation": {
"status": "passed",
"report": ".agents/plugins/evaluations/contentcloud-video-production-0.8.0.json",
"digest": "sha256:313c4d68c8a4f39d359270eb47c88668f5567740499fcc5a4d5123ffff82ae9c",
"report": ".agents/plugins/evaluations/contentcloud-video-production-0.9.0.json",
"digest": "sha256:93dacddb4ea9a69a3a02ec0b914821694b2e022beed3e497590c7b58b3da348e",
"evidence": [
"codex-plugin-transaction",
"bootstrap-confirmation",
Expand All @@ -66,7 +66,8 @@
"content-contract",
"v5-local-production-boundary",
"browser-navigation-safety",
"environment-control-plane"
"environment-control-plane",
"agent-client-governance"
]
},
"lifecycle": "published",
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

ContentCloud 的重要变更记录在此文件中。

## [0.9.0] - 2026-07-29

### Added

- 增加统一 Agent Client Registry,集中登记 Codex、Claude Code、WorkBuddy、Cursor、Hermes 和 OpenClaw,并按客户端显式公布自动化、Workspace 注册、初始化、交接和创作环境能力状态。
- 增加通用 Agent Handoff API 和 Web 客户端选择器,支持从项目与审核反馈页面生成绑定精确项目、Revision 与 digest 的受验证交接。

### Changed

- Workspace 注册、bootstrap、Automation Adapter 与 Environment Manifest/Profile/Lock 统一使用 Agent Client Registry,并将未实现的客户端能力明确标记为 `planned`。
- Codex 交接收敛为通用 Handoff 策略的已发布实现,旧 Codex 响应保留兼容层;CLI、Web、npm 安装器、Plugin、MCP、Environment Profile 和 bootstrap 固定版本统一升级到 `0.9.0`。
- bootstrap 增加同源 Marketplace/Plugin 版本检测、显式升级计划与 `resume` 恢复;旧 Marketplace 与 Plugin 必须作为同一可回滚快照更新,异常的 Plugin-only 版本漂移会 fail closed。

### Fixed

- 拒绝未知 Agent 客户端和尚未提供的能力,避免将保留客户端误报为可初始化或可交接。
- 收紧 Agent Handoff Web 校验,要求 Codex Prompt 同时绑定 Plugin、Workspace 上下文、项目以及审核反馈的 Revision/digest。

## [0.8.0] - 2026-07-29

### Added
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.0
0.9.0
2 changes: 1 addition & 1 deletion contracts/creative-environment-manifest-1.0.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"profile_id": {"type": "string", "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*$"},
"profile_version": {"$ref": "#/$defs/version"},
"environment_version": {"$ref": "#/$defs/version"},
"harness": {"const": "codex"},
"harness": {"enum": ["codex", "claude-code", "workbuddy", "cursor", "hermes", "openclaw"]},
"distribution": {
"type": "object",
"additionalProperties": false,
Expand Down
2 changes: 1 addition & 1 deletion contracts/creative-environment-profile-1.0.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"id": {"type": "string", "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*$"},
"version": {"$ref": "#/$defs/version"},
"environment_version": {"$ref": "#/$defs/version"},
"harness": {"const": "codex"},
"harness": {"enum": ["codex", "claude-code", "workbuddy", "cursor", "hermes", "openclaw"]},
"marketplace": {"type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"},
"plugins": {
"type": "array",
Expand Down
29 changes: 29 additions & 0 deletions contracts/embed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package contracts

import (
"encoding/json"
"reflect"
"sort"
"testing"

"github.com/limecloud/contentcloud/internal/agentadapter"
)

func TestEmbeddedSchemasAreValidJSON(t *testing.T) {
Expand Down Expand Up @@ -41,6 +45,31 @@ func TestEmbeddedSchemasAreValidJSON(t *testing.T) {
}
}

func TestEnvironmentSchemasReserveRegisteredAgentClients(t *testing.T) {
want := agentadapter.ClientIDs()
for name, body := range map[string][]byte{
"profile": CreativeEnvironmentProfileSchema,
"manifest": CreativeEnvironmentManifestSchema,
"lock": EnvironmentLockSchema,
} {
var schema struct {
Properties struct {
Harness struct {
Enum []string `json:"enum"`
} `json:"harness"`
} `json:"properties"`
}
if err := json.Unmarshal(body, &schema); err != nil {
t.Fatal(err)
}
got := schema.Properties.Harness.Enum
sort.Strings(got)
if !reflect.DeepEqual(got, want) {
t.Fatalf("%s harness enum=%v, want registry IDs %v", name, got, want)
}
}
}

func TestEmbeddedProjectPageContractIsValidJSON(t *testing.T) {
var contract struct {
SchemaVersion string `json:"schema_version"`
Expand Down
2 changes: 1 addition & 1 deletion contracts/environment-lock-1.0.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"profile_id": {"type": "string", "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*$"},
"profile_version": {"$ref": "#/$defs/version"},
"environment_version": {"$ref": "#/$defs/version"},
"harness": {"const": "codex"},
"harness": {"enum": ["codex", "claude-code", "workbuddy", "cursor", "hermes", "openclaw"]},
"manifest_digest": {"$ref": "#/$defs/digest"},
"plugins": {
"type": "array",
Expand Down
2 changes: 1 addition & 1 deletion contracts/project-pages-1.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"label": "接入与初始化",
"eyebrow": "Workspace",
"title": "连接本地创作 Workspace",
"description": "检查项目绑定、Codex 创作环境和 V3 Workspace 是否已经就绪。",
"description": "检查项目绑定、Agent 创作环境和 V3 Workspace 是否已经就绪。",
"section": "onboarding",
"submission_types": [],
"snapshot_types": [],
Expand Down
2 changes: 1 addition & 1 deletion deploy/systemd/contentcloud.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ CONTENTCLOUD_PLUGIN_TRUST_FILE=/etc/contentcloud/plugin-trusted-keys.json
CONTENTCLOUD_ENVIRONMENT_TRUST_FILE=/etc/contentcloud/environment-trusted-keys.json
CONTENTCLOUD_ENVIRONMENT_SIGNING_KEY_FILE=/etc/contentcloud/secrets/environment-ed25519.key
CONTENTCLOUD_ENVIRONMENT_SIGNING_KEY_ID=contentcloud-environment-2026-07
CONTENTCLOUD_CAPABILITY_RELEASE_VERSION=0.8.0
CONTENTCLOUD_CAPABILITY_RELEASE_VERSION=0.9.0
CONTENTCLOUD_ENVIRONMENT_MANIFEST_TTL=24h
2 changes: 1 addition & 1 deletion deploy/systemd/environment-profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
"id": "contentcloud-video-production",
"kind": "scene_plugin",
"version": "0.8.0",
"version": "0.9.0",
"required": true,
"scope": "environment",
"capabilities": [
Expand Down
47 changes: 30 additions & 17 deletions docs/roadmap/v4/02-contracts-and-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,32 +181,43 @@ type ProjectViewContract = {

Projection 返回类型化目标,不返回任意绝对 URL。Web 和 MCP 分别通过同一 Page Contract 构造适合当前宿主的链接。

### 5.1 Web 到 Codex 恢复契约
### 5.1 Web 到 Agent 恢复契约

已实现两个受会话保护的 BFF 读取入口
客户端目录和通用恢复入口均受会话保护

```text
GET /api/bff/projects/{projectID}/codex-handoff
GET /api/bff/projects/{projectID}/submission-revisions/{revisionID}/codex-handoff
GET /api/bff/agent-clients
GET /api/bff/projects/{projectID}/agent-handoff?client={clientID}
GET /api/bff/projects/{projectID}/submission-revisions/{revisionID}/agent-handoff?client={clientID}
```

统一响应:
`agent-clients` 是客户端 ID、显示名和逐项能力状态的唯一事实源。固定客户端 ID 为 `codex`、`claude-code`、`workbuddy`、`cursor`、`hermes`、`openclaw`;能力状态只有 `available` 和 `planned`。`planned` 只允许 UI 展示“即将支持”,服务端不得生成恢复入口。

通用恢复响应:

```json
{
"schema_version": "contentcloud.codex-handoff/1.0",
"schema_version": "contentcloud.agent-handoff/1.0",
"client": {
"id": "codex",
"display_name": "Codex",
"capabilities": [{"id": "interactive_handoff", "status": "available"}]
},
"kind": "review_feedback",
"project_id": "prj_01H...",
"target": {
"kind": "submission_revision",
"id": "rev_01H...",
"digest": "sha256:..."
},
"plugin_id": "contentcloud-video-production@contentcloud",
"plugin_version": "0.6.0",
"requires_new_chat": true,
"integration": {
"kind": "plugin",
"id": "contentcloud-video-production@contentcloud",
"version": "0.9.0"
},
"requires_new_session": true,
"requires_workspace_selection": true,
"launch_url": "codex://new?prompt=...",
"launch": {"mode": "deep_link", "url": "codex://new?prompt=..."},
"prompt": "...",
"steps": ["..."],
"fallback_url": "/codex"
Expand All @@ -215,13 +226,15 @@ GET /api/bff/projects/{projectID}/submission-revisions/{revisionID}/codex-handof

约束:

1. Project 必须存在且至少已有一个 Workspace/Device 绑定;服务端仍不持有本机 Workspace 路径。
2. review feedback 入口先验证 Revision 属于 URL 中的 project,再要求存在审核评论或 `changes_requested` 状态;跨 project/tenant 统一返回 404。
3. `launch_url` 固定为 `codex://new?prompt=...`,只有一个 `prompt` query;禁止 `path`、`originUrl`、token、客户正文、评论正文和本机路径。
4. Prompt 固定引用 Plugin ID、project ID;feedback 额外包含 Revision ID 与完整 digest。它先调用 `workspace_context` 并验证 `project_id`,不匹配则停止。
5. feedback Prompt 只先调用 `review_feedback_list`;pull、claim、本地写入和新修订 Run 都需要用户后续明确要求。
6. Web 在打开自定义协议前再次校验 schema、Plugin 版本、new-chat/workspace gate、当前页面 project/revision/digest、query allowlist 和 Prompt 一致性。校验失败只显示错误,不导航。
7. Assignment handoff 尚未实现,等待 V3 W3-01 的 WorkAssignment 与 pull 契约,不能复用 project target 冒充精确 Assignment。
1. `internal/agentadapter` 使用 Registry + Strategy:Registry 声明能力成熟度,各能力的 Strategy factory 必须与 `available` 状态一一对应。
2. Project 必须存在且至少已有一个 Workspace/Device 绑定;服务端仍不持有本机 Workspace 路径。
3. review feedback 入口先验证 Revision 属于 URL 中的 project,再要求存在审核评论或 `changes_requested` 状态;跨 project/tenant 统一返回 404。
4. Codex Strategy 的 URL 固定为 `codex://new?prompt=...`,只有一个 `prompt` query;禁止 `path`、`originUrl`、token、客户正文、评论正文和本机路径。其他客户端必须提供自己的独立 URL 校验 Strategy。
5. Prompt 固定引用 Integration ID、project ID;feedback 额外包含 Revision ID 与完整 digest。它先调用 `workspace_context` 并验证 `project_id`,不匹配则停止。
6. feedback Prompt 只先调用 `review_feedback_list`;pull、claim、本地写入和新修订 Run 都需要用户后续明确要求。
7. Web 在打开自定义协议前按客户端再次校验 schema、Integration 版本、new-session/workspace gate、当前页面 project/revision/digest、query allowlist 和 Prompt 一致性。校验失败只显示错误,不导航。
8. 原 `/codex-handoff` 与 `contentcloud.codex-handoff/1.0` 保留为兼容层,内部复用 Codex Strategy,不再承载新 UI。
9. Assignment handoff 尚未实现,等待 V3 W3-01 的 WorkAssignment 与 pull 契约,不能复用 project target 冒充精确 Assignment。

## 6. 既有 Tool 的链接复用

Expand Down
14 changes: 14 additions & 0 deletions docs/roadmap/v5/05-execution-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ Codex + 本机 Workspace ContentCloud 服务端 外部

本文中的 `Codex` 特指运行在用户机器、绑定本机 Workspace 的 Codex/Plugin Agent,不包括服务端 LLM worker。首版不设置服务端创意生成 worker。

## 1.1 版本与升级边界

ContentCloud 的 CLI、Codex Marketplace/Plugin、Workspace 模板、Environment
Manifest/Registry 和 Seedance provider profile 都是独立版本事实,不能因为 CLI
升级就静默替换其他对象。安装器必须先生成确定性的只读计划,再由用户确认同一个
`plan_id`。

- 同源且版本相同:Plugin plan 为 `noop`,不产生安装副作用。
- 同源但 ref 或 Plugin 版本较旧:生成显式升级动作;必要时先移除旧 Plugin/Marketplace,再按固定 source/ref 安装新版本,并在失败时局部回滚。
- 同名异源:保持 `blocked`,由用户人工处理,禁止覆盖未知 Marketplace。
- 已有 Workspace:使用 `bootstrap plan` 查看 `resume_required` 和 Plugin 升级计划,再用 `bootstrap resume --accept` 复用绑定、刷新签名 Environment、运行 doctor 并重新注册;业务文件不上传、不静默覆盖。
- 模板或 schema 迁移:与 Plugin 升级分开,必须提供独立、可审计的 Workspace migration;旧的已批准剧本、分镜和 Seedance 交付包保持不可变。
- Plugin/Skill/路由更新后:结果标记为需要新 Codex 会话;旧会话不假定会热加载新能力。

执行位置按四条规则确定:

1. 需要未披露原始素材、频繁交互或可恢复生成的工作放在 Codex 本机。
Expand Down
24 changes: 16 additions & 8 deletions internal/agentadapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,30 @@ type Adapter interface {
}

func Select(kind string) (Adapter, error) {
switch kind {
case "codex":
return Codex{}, nil
case "claude", "claude-code":
return Claude{}, nil
case "", "auto":
normalized := strings.ToLower(strings.TrimSpace(kind))
if normalized == "" || normalized == "auto" {
if err := (Codex{}).Detect(); err == nil {
return Codex{}, nil
}
if err := (Claude{}).Detect(); err == nil {
return Claude{}, nil
}
return nil, domain.Policy("AGENT_ADAPTER_REQUIRED", "未检测到可用的 Codex 或 Claude Code", "在本机安装并登录其中一个 Agent,或显式使用 --fixture 进行开发验证")
default:
return nil, domain.Invalid("AGENT_ADAPTER_INVALID", "--adapter 必须为 auto、codex 或 claude")
}
client, err := RequireCapability(normalized, CapabilityLocalAutomation)
if err != nil {
return nil, err
}
factory, ok := automationFactories[client.ID]
if !ok {
return nil, domain.Policy("AGENT_ADAPTER_NOT_IMPLEMENTED", client.DisplayName+" 的本地 Automation Adapter 尚未实现", "选择已实现的客户端 Adapter")
}
return factory(), nil
}

var automationFactories = map[ClientID]func() Adapter{
ClientCodex: func() Adapter { return Codex{} },
ClientClaudeCode: func() Adapter { return Claude{} },
}

type Codex struct{}
Expand Down
Loading
Loading