diff --git a/.task/feature-base-extension-roadmap.md b/.task/feature-base-extension-roadmap.md index 8199caaf..d4469686 100644 --- a/.task/feature-base-extension-roadmap.md +++ b/.task/feature-base-extension-roadmap.md @@ -6,11 +6,12 @@ - 权威仓: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、把目标系统(两树一网)做进基座、容器编排、旧资产导入。 ## 同步记录 @@ -20,6 +21,6 @@ ## 结果与验证(完成后填写) -- 结果: -- 验证: -- 影响与剩余项: +- 结果: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。 diff --git a/Makefile b/Makefile index f59eb58d..2f1e7325 100644 --- a/Makefile +++ b/Makefile @@ -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="; 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 相互隔离。 diff --git a/examples/downstream-rdm/README.md b/examples/downstream-rdm/README.md new file mode 100644 index 00000000..cb3f4347 --- /dev/null +++ b/examples/downstream-rdm/README.md @@ -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 . +``` diff --git a/examples/downstream-rdm/go.mod b/examples/downstream-rdm/go.mod new file mode 100644 index 00000000..4865a243 --- /dev/null +++ b/examples/downstream-rdm/go.mod @@ -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 diff --git a/examples/downstream-rdm/plugin.go b/examples/downstream-rdm/plugin.go new file mode 100644 index 00000000..db286a89 --- /dev/null +++ b/examples/downstream-rdm/plugin.go @@ -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") + }, + }) +} diff --git a/examples/downstream-rdm/plugin_test.go b/examples/downstream-rdm/plugin_test.go new file mode 100644 index 00000000..a76969de --- /dev/null +++ b/examples/downstream-rdm/plugin_test.go @@ -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") + } +} diff --git a/examples/page-demo/page.mjs b/examples/page-demo/page.mjs new file mode 100644 index 00000000..04643e0d --- /dev/null +++ b/examples/page-demo/page.mjs @@ -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() {} diff --git a/examples/plugins/decl-demo/channels/feishu.json b/examples/plugins/decl-demo/channels/feishu.json new file mode 100644 index 00000000..fc349275 --- /dev/null +++ b/examples/plugins/decl-demo/channels/feishu.json @@ -0,0 +1 @@ +{ "id": "feishu", "kind": "feishu", "title": "飞书" } diff --git a/examples/plugins/decl-demo/knowledge/notes.json b/examples/plugins/decl-demo/knowledge/notes.json new file mode 100644 index 00000000..777c970e --- /dev/null +++ b/examples/plugins/decl-demo/knowledge/notes.json @@ -0,0 +1 @@ +{ "id": "notes", "title": "笔记", "description": "数据集声明,检索引擎在基座" } diff --git a/examples/plugins/decl-demo/page.mjs b/examples/plugins/decl-demo/page.mjs new file mode 100644 index 00000000..c2d8aca8 --- /dev/null +++ b/examples/plugins/decl-demo/page.mjs @@ -0,0 +1,5 @@ +export function mount(el, bridge) { + el.textContent = '声明示例页面' + el.dataset.bridge = bridge ? 'ready' : '' +} +export function unmount() {} diff --git a/examples/plugins/decl-demo/pages/home.json b/examples/plugins/decl-demo/pages/home.json new file mode 100644 index 00000000..a245c840 --- /dev/null +++ b/examples/plugins/decl-demo/pages/home.json @@ -0,0 +1 @@ +{ "id": "home", "title": "声明示例", "module": "./page.mjs" } diff --git a/examples/plugins/decl-demo/plugin.json b/examples/plugins/decl-demo/plugin.json new file mode 100644 index 00000000..60ecf7a5 --- /dev/null +++ b/examples/plugins/decl-demo/plugin.json @@ -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"] + } +} diff --git a/examples/plugins/decl-demo/skills/welcome.md b/examples/plugins/decl-demo/skills/welcome.md new file mode 100644 index 00000000..977c7261 --- /dev/null +++ b/examples/plugins/decl-demo/skills/welcome.md @@ -0,0 +1,3 @@ +# welcome + +声明式技能示例。描述任务步骤,不携带可执行代码。 diff --git a/examples/sidecar-demo/main.go b/examples/sidecar-demo/main.go new file mode 100644 index 00000000..74440bf1 --- /dev/null +++ b/examples/sidecar-demo/main.go @@ -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) + } +} diff --git a/modules/backend b/modules/backend index 268f4a9a..cd5b8218 160000 --- a/modules/backend +++ b/modules/backend @@ -1 +1 @@ -Subproject commit 268f4a9abf9fdf23de487cb7a2edbfbaa60ff6af +Subproject commit cd5b82186d0425d6c8fcbce9ad9850b35ae61833 diff --git a/modules/core b/modules/core index d4375300..fdc0c1b9 160000 --- a/modules/core +++ b/modules/core @@ -1 +1 @@ -Subproject commit d4375300cde71320cdd2a012304603efe9eb5cf9 +Subproject commit fdc0c1b933abc0f2025b71fa9da33625be288fde diff --git a/modules/documents b/modules/documents index 8ee5d5c4..a0da588f 160000 --- a/modules/documents +++ b/modules/documents @@ -1 +1 @@ -Subproject commit 8ee5d5c467c364664d5c450976c51dd453a45e37 +Subproject commit a0da588f9035e132bcfd1dcc0ac1dacb62924776 diff --git a/modules/frontend b/modules/frontend index 0ed8ad32..22f7d845 160000 --- a/modules/frontend +++ b/modules/frontend @@ -1 +1 @@ -Subproject commit 0ed8ad32f1614e4425c17616573ede582794a426 +Subproject commit 22f7d84560f593fbda75d6ef87e9c919456ab38b diff --git a/modules/ui b/modules/ui index 8bfe8a89..edc1f3b9 160000 --- a/modules/ui +++ b/modules/ui @@ -1 +1 @@ -Subproject commit 8bfe8a89dbe40d91fc9b42837270bcb29044e903 +Subproject commit edc1f3b9829071cc0a18eccbd8facebcf0f9f654 diff --git a/scripts/lint-boundaries.sh b/scripts/lint-boundaries.sh new file mode 100755 index 00000000..e00d3e7b --- /dev/null +++ b/scripts/lint-boundaries.sh @@ -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" diff --git a/scripts/plugin-new.sh b/scripts/plugin-new.sh new file mode 100755 index 00000000..2526aad0 --- /dev/null +++ b/scripts/plugin-new.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# 生成声明式插件骨架:make plugin-new NAME= +set -euo pipefail +name="${1:-}" +if [ -z "$name" ]; then + echo "usage: plugin-new.sh " >&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" < "$dest/skills/welcome.md" +cat > "$dest/pages/home.json" < "$dest/page.mjs" <<'EOF' +export function mount(el, bridge) { + el.textContent = '插件页面' + el.dataset.bridge = bridge ? 'ready' : '' +} +export function unmount() {} +EOF +echo "wrote $dest"