Pin the extension roadmap and downstream spike #132

Merged
weichen merged 6 commits from feature/base-extension-roadmap into main 2026-10-01 10:47:14 +00:00
21 changed files with 203 additions and 6 deletions
+27
View File
@@ -0,0 +1,27 @@
# feature/base-extension-roadmap 任务卡
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/132 ;状态:进行中;owner:weichen
## 基线与授权
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= f8fefa0f888e8f5fd5207c9e97099e59935ff69b
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-base-extension-roadmap(worktree);分支:feature/base-extension-roadmap
- 授权与交付终点:在本 worktree 落地路线图提案与基座实现,并在各子仓与父仓本地提交。不推送、不打版本 tag、不新建下游远程仓。
## 目标与非目标
- 目标:公开扩展面(`pkg/extension`)、插件契约 v2 四种形态、生命周期治理、质量回路与工程基座接线;下游薄切片只 import 公开 SDK。
- 非目标:打 `v0.2.0-experimental`、发布 npm、改 Go module path、把目标系统(两树一网)做进基座、容器编排、旧资产导入。
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | f8fefa0f888e8f5fd5207c9e97099e59935ff69b | f8fefa0f888e8f5fd5207c9e97099e59935ff69b | 2026-10-01T10:10:35Z |
| 同步前验收 | 26dfea7ed62575097f14fedf1b7713a77dedb66f | 6d496139b45bbe0d32818e56ed6c5fbba649d777 | 2026-10-01T10:39:24Z |
## 结果与验证(完成后填写)
- 结果:documents 三份提案与开发指南;core manifest v2;backend 声明/sidecar/进程内/Host API/迁移 v13/质量回路/审计与命令对拍;ui SDK 与 frontend ESM 加载;examples 与 `make plugin-new`、`make test-boundaries`。
- 验证:`cd modules/core && go test ./...`;`cd modules/backend && go test ./...`;`bash scripts/lint-boundaries.sh`;`cd examples/downstream-rdm && go test .`;UI `tsc`+vitest 71;frontend vitest。
- 影响与剩余项:apiVersion `2.0-experimental`。版本 tag 与 npm 发布命令留在兼容提案,由用户确认后执行。下游远程仓由用户另建。Playwright 对拍文件在 `modules/frontend/e2e/`,未安装 `@playwright/test`,不进 vitest。合入顺序:Core #14、Documents #31、UI #25、Frontend #31,然后 Backend #41,最后 Dev #132。子仓不要 squash,否则 Backend / Dev 的 gitlink 会指向不存在的提交。
+8 -1
View File
@@ -96,7 +96,14 @@ test-ui: contracts
test-agentcmd:
cd deps/agent-command && go vet ./... && go test -count=1 ./...
test: test-core test-backend test-frontend test-ui test-agentcmd
test-boundaries:
bash scripts/lint-boundaries.sh
plugin-new:
@test -n "$(NAME)" || (echo "用法:make plugin-new NAME=<id>"; exit 2)
bash scripts/plugin-new.sh "$(NAME)"
test: test-boundaries test-core test-backend test-frontend test-ui test-agentcmd
# 运行演示(mock 模式):单二进制 + 示例插件 + 嵌入式存储,浏览器 http://127.0.0.1:8787
# 环境服务管理(scripts/svc.sh):deploy = 停旧 → 构建 → 启动;dev/prod 相互隔离。
+9
View File
@@ -0,0 +1,9 @@
# 下游薄切片
本目录模拟独立仓库 `Agent-System-Plugin-RDM`。它只 import `agent-system-backend/pkg/extension`,不改基座。
把本目录搬到新仓库时,由 owner 创建 Git 仓库。`replace` 改成基座检出路径或已发布版本。需要拉起完整二进制时再空白导入 `agent-system-backend/pkg/extension/link`,并复制基座 `go.mod` 的 replace(下游模块不会继承它们)。
```
go test .
```
+7
View File
@@ -0,0 +1,7 @@
module example.com/agent-system-plugin-rdm
go 1.27
require agent-system-backend v0.0.0
replace agent-system-backend => ../../modules/backend
+12
View File
@@ -0,0 +1,12 @@
package rdm
import "agent-system-backend/pkg/extension"
func init() {
extension.RegisterCompiled(extension.InProc{
ID: "rdm",
Register: func(reg extension.Registry) error {
return reg.AddSkill("rdm-note", "# 研发记忆\n\n下游切片技能,不修改基座。\n")
},
})
}
+19
View File
@@ -0,0 +1,19 @@
package rdm
import (
"testing"
"agent-system-backend/pkg/extension"
)
func TestCompiledSkillRegistration(t *testing.T) {
extension.ResetCompiled()
extension.RegisterCompiled(extension.InProc{ID: "rdm"})
got := extension.Compiled()
if len(got) != 1 || got[0].ID != "rdm" {
t.Fatalf("%+v", got)
}
if extension.SurfaceVersion == "" {
t.Fatal("surface version required")
}
}
+8
View File
@@ -0,0 +1,8 @@
export function mount(el, bridge) {
const btn = document.createElement('button')
btn.type = 'button'
btn.textContent = '走命令'
btn.addEventListener('click', () => bridge.execCommand('builtin.help', {}))
el.replaceChildren(btn)
}
export function unmount() {}
@@ -0,0 +1 @@
{ "id": "feishu", "kind": "feishu", "title": "飞书" }
@@ -0,0 +1 @@
{ "id": "notes", "title": "笔记", "description": "数据集声明,检索引擎在基座" }
+5
View File
@@ -0,0 +1,5 @@
export function mount(el, bridge) {
el.textContent = '声明示例页面'
el.dataset.bridge = bridge ? 'ready' : ''
}
export function unmount() {}
@@ -0,0 +1 @@
{ "id": "home", "title": "声明示例", "module": "./page.mjs" }
+13
View File
@@ -0,0 +1,13 @@
{
"id": "decl-demo",
"name": "声明示例",
"version": "0.1.0",
"apiVersion": "2.0-experimental",
"requires": { "network": ["api.example.com"] },
"provides": {
"skills": ["welcome"],
"pages": ["home"],
"channels": ["feishu"],
"knowledge": ["notes"]
}
}
@@ -0,0 +1,3 @@
# welcome
声明式技能示例。描述任务步骤,不携带可执行代码。
+17
View File
@@ -0,0 +1,17 @@
// sidecar 示例:只通过 Host API 客户端说话,不导入 internal。
package main
import (
"context"
"log"
"os"
"agent-system-backend/pkg/extension"
)
func main() {
c := extension.NewHostClient(os.Getenv("AGENT_HOST_API"), os.Getenv("AGENT_HOST_TOKEN"), os.Getenv("AGENT_PLUGIN_ID"), "local")
if err := c.Audit(context.Background(), "sidecar.ready", os.Getenv("AGENT_PLUGIN_ID"), []byte(`{}`)); err != nil {
log.Fatal(err)
}
}
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# 依赖边界:公开扩展 SDK 与示例不得导入后端 internal。
# pkg/extension/link 是唯一例外:下游空白导入它才能把 extension.Main 接到产品组合根。
set -euo pipefail
root=$(cd "$(dirname "$0")/.." && pwd)
fail=0
check() {
local path="$1"
if [ -d "$path" ] && grep -R -n --include='*.go' --include='*.ts' --include='*.tsx' 'agent-system-backend/internal' "$path" >/dev/null; then
echo "boundary: $path imports agent-system-backend/internal" >&2
grep -R -n --include='*.go' --include='*.ts' --include='*.tsx' 'agent-system-backend/internal' "$path" >&2
fail=1
fi
}
sdk="$root/modules/backend/pkg/extension"
if [ -d "$sdk" ]; then
hits=$(grep -R -n --include='*.go' 'agent-system-backend/internal' "$sdk" | grep -v '/link/' || true)
if [ -n "$hits" ]; then
echo "boundary: pkg/extension imports agent-system-backend/internal" >&2
echo "$hits" >&2
fail=1
fi
fi
check "$root/examples"
if [ "$fail" -ne 0 ]; then
exit 1
fi
echo "boundaries ok"
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# 生成声明式插件骨架:make plugin-new NAME=<id>
set -euo pipefail
name="${1:-}"
if [ -z "$name" ]; then
echo "usage: plugin-new.sh <id>" >&2
exit 2
fi
root=$(cd "$(dirname "$0")/.." && pwd)
dest="$root/examples/plugins/$name"
if [ -e "$dest" ]; then
echo "exists: $dest" >&2
exit 1
fi
mkdir -p "$dest/tools" "$dest/skills" "$dest/pages"
cat > "$dest/plugin.json" <<EOF
{
"id": "$name",
"name": "$name",
"version": "0.1.0",
"apiVersion": "2.0-experimental",
"requires": { "network": [] },
"provides": { "skills": ["welcome"], "pages": ["home"] }
}
EOF
printf '# %s\n\n描述这个技能做什么、何时使用。\n' "$name" > "$dest/skills/welcome.md"
cat > "$dest/pages/home.json" <<EOF
{ "id": "home", "title": "$name", "module": "./page.mjs" }
EOF
cat > "$dest/page.mjs" <<'EOF'
export function mount(el, bridge) {
el.textContent = '插件页面'
el.dataset.bridge = bridge ? 'ready' : ''
}
export function unmount() {}
EOF
echo "wrote $dest"