Skip to content

feat: 支持共享 Skill 编辑并收敛安装服务边界 - #1088

Merged
xerrors merged 6 commits into
mainfrom
feat/shared-skill-edit
Sep 29, 2026
Merged

xerrors merged 6 commits into
mainfrom
feat/shared-skill-edit

Conversation

@xerrors

@xerrors xerrors commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

变更说明

共享 Skill 详情页支持在线编辑文件及运行依赖。保存根级 SKILL.md 同步 PostgreSQL 的名称、描述与依赖索引;过期修订值返回 409,页面保留草稿。安装流程区分获取/解析失败与安装失败,部分成功后可以只重试失败项,用户移除的条目不会被重新选中。

  • 任务类型:feature、architecture、simplification。
  • 目标与非目标:统一共享 Skill 编辑、安装草稿和运行时来源边界;保持个人同名覆盖,不引入通用 provider 框架或目录级发布。
  • substantial:涉及权限、文件、PostgreSQL 行锁、运行时投影和前端交互。

工程主张与 Owner

  • services/skills/edit.py 校验管理权限、内置来源、文件路径和 SHA-256 修订值;共享行锁覆盖读取/写入与根文件索引更新。文件发布后提交数据库,普通异常恢复旧文件。
  • repositories/skill_repository.py 拥有可见性查询和行锁;agents/skills/runtime.py 与 services/skills/projection.py 在锁后重查权限,并只读取对应来源。
  • services/skills/personal.py 拥有个人 Skill 文件及安装用例;draft.py 拥有安装快照、选择和成功条目消费;package.py 统一包格式及相对路径校验。路由与工具只调用对应用例。
  • SkillDetailView.vue、AgentFilePreview.vue 拥有编辑草稿与冲突交互;SkillInstallFlowModal.vue 按实际可复用失败项选择重试路径。
  • 决策记录:共享编辑、模块边界、草稿契约、个人来源、文件一致性。

验证情况

提交 79b071ba 的远端 CI 已全部通过:后端单测、PowerShell 安全契约、Web lint/unit/build、工程契约、Ruff、文档构建,以及 Durable Task / PostgreSQL / readiness / 确定性 Agent 与沙盒链路。PR 环境的 Pages 部署按 workflow 条件跳过。

保存、授权与运行时加载

  • 失败面:过期写入覆盖新文件、文件与索引分叉、不可见 Skill 阻塞读取、artifact 来源混淆、worker 加载旧内容。
  • 语义 Owner:编辑 service、repository、projection、runtime。
  • 直接证据 / 命令:
    • docker compose exec -T api env SANDBOX_RUNTIME_PROFILE=core uv run --no-sync --group test pytest test/unit -m 'not slow' -q -p no:cacheprovider --timeout=60:2454 passed、58 skipped。
    • docker compose exec -T api uv run --no-sync --group test pytest test/integration/api/test_shared_skill_edit_router.py test/integration/api/test_skill_artifact_authorization.py test/integration/services/test_user_skill_projection.py -q -p no:cacheprovider --timeout=120:7 passed,真实 HTTP/PostgreSQL。
    • docker compose exec -T api uv run --no-sync --group test pytest test/e2e/test_shared_skill_edit_e2e.py -q -p no:cacheprovider --timeout=360:1 passed,确定性 replay provider,经实际 API/worker 回读文件投影和 Run manifest。
    • 修正投影测试 mock Owner 后,docker compose exec -T api uv run --no-sync --group test pytest test/unit/services/skills/test_skill_service.py::test_unchanged_skill_projection_does_not_create_staging -q -p no:cacheprovider:1 passed。
  • 负向案例:旧修订值、越权、symlink/路径穿越、提交失败恢复、个人覆盖及不可见共享行。
  • 结果:Passed;skip 不计入通过结果。

草稿保存与安装失败重试

  • 失败面:切页或 409 丢草稿,重试重新选择已移除项,草稿只剩未选项时远程失败项无法重试。
  • 语义 Owner:SkillDetailView、AgentFilePreview、SkillInstallFlowModal。
  • 直接证据 / 命令:docker compose exec -T web pnpm run lint:check、docker compose exec -T web pnpm run test:unit(394 passed)、docker compose exec -T web pnpm run build;真实浏览器验证保存/HTTP 回读、取消切页保留草稿、真实 409 后保留编辑内容;重试组件在真实 Vue DOM 配合模拟远程响应中验证部分成功后的重新获取、确认按钮可用与已移除项不重新选中。
  • 负向案例:新增“部分成功、解析失败且保留用户移除项”的重试用例,旧实现因未发起重新获取而失败,修复后通过;其余重试用例保留混合失败与移除选择约束。
  • 结果:Passed。

结构、文档与静态门禁

  • 失败面:旧导入残留、跨层 Owner 失效、错误测试接线、文档链接断裂。
  • 语义 Owner:实际模块、工程契约 verifier、CI 配置与决策文档。
  • 直接证据 / 命令:python3 scripts/verify_engineering_contracts.py、python3 -m unittest scripts.test_verify_engineering_contracts(63 passed)、uvx ruff==0.16.4 check backend/package、uvx ruff==0.16.4 format backend/package --check、uvx ruff==0.16.4 check --select I backend/package、pnpm --dir docs run build、git diff --check。
  • 负向案例:个人 Skill Owner 的宿主根访问例外不放行其他 service;旧模块引用搜索。
  • 结果:Passed。

简化 / 删除验收

移除原 agents/skills/service.py、旧 repository/remote 模块路径和重复准备逻辑,实际调用方迁至 service/repository Owner,不提供无 consumer 的转发层。保留共享行锁、文件补偿、个人覆盖和草稿消费语义。旧模块导入在源码、测试和当前文档中的搜索无残留;HTTP、持久数据和部署配置无需迁移。只有出现明确兼容消费者时才重新评估旧 Python 路径兼容。

独立语义 Review

全新上下文 Reviewer 审查完整需求、70 文件 diff、规范、验证结果及远端既有修复。发现安装重试死路,补先红后绿回归并修复;投影测试 mock 同步到真实 Owner。复审独立执行 4 项重试测试并核对追加差异,批准提交,无剩余阻塞。对接远端历史后核对 Git tree 与已审查快照完全一致。

未验证范围与风险

  • 文件系统与 PostgreSQL 不具备跨资源原子事务,进程在文件发布与数据库提交间崩溃仍可能留下不一致。
  • 未执行完整 integration/E2E 套件、真实外部模型 provider 或真实远程下载故障恢复 E2E。
  • 原样 uv run --group test 因挂载目录不可写而无法构建 editable 包,使用 --no-sync 验证当前挂载源码。文档原跨工作树 node_modules 链接缺依赖,已在本工作树按 frozen lockfile 安装后构建通过,未更改依赖清单或锁文件。
  • 扩大范围的 Ruff 检查发现未改动的 test_model_provider_uid_header.py 长行,以及 scripts 文件已有的隐式字符串拼接告警;本 PR 不顺手修改,实际 CI package 门禁通过本地同版本检查。

事故反馈

新功能及提交前发现的问题,不涉及已逃逸高影响事故。

界面变更

已完成实际页面交互检查并保存浅色、深色、移动端及 409 冲突截图,位于本地验证产物,未提交到仓库或上传到 PR。

关联事项

无。

补充说明

旧客户端保存文件需先读取修订值;无数据库 Schema 迁移。远端原有三个提交历史通过合并保留,未强推分支;同步 main 的文档重组后重新通过工程契约和文档构建,应用源码不变。

@xerrors
xerrors marked this pull request as ready for review September 29, 2026 08:32
Copilot AI balanced review requested due to automatic review settings September 29, 2026 08:32
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T08:42:19.957670Z be2e27a Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@xerrors xerrors changed the title 支持共享 Skill 在线编辑 feat: 支持共享 Skill 编辑并收敛安装服务边界 Sep 29, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be2e27ac0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

os.close(child_fd)
elif stat.S_ISREG(mode):
with open_regular_file_fd(directory_fd, (name,)) as (file_fd, _file_stat):
with archive.open(path, "w") as output:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve executable bits in exported ZIP files

When a Skill contains an executable script, streaming it through archive.open() creates a ZIP entry with the default Unix mode (typically 0600) rather than the source file's mode. The previous ZipFile.write() path preserved 0755, so users who export and extract such a Skill now receive non-executable scripts; construct each ZipInfo from file_stat or explicitly copy its permission bits into external_attr.

Useful? React with 👍 / 👎.

Comment on lines +357 to +358
temporary = f".{filename}.{uuid.uuid4().hex}.tmp"
file_fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, mode, dir_fd=staging_fd)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use filename-independent staging names for edits

For an existing valid file whose basename is near the filesystem's 255-byte limit, prefixing and suffixing that basename with a UUID makes temporary exceed NAME_MAX, so os.open() raises ENAMETOOLONG and the online editor returns a 500 even though the target itself is valid. Use a short UUID-only staging name so every file accepted by the owning filesystem remains editable.

Useful? React with 👍 / 👎.

Comment on lines +291 to +295
while pending:
slug = pending.pop(0)
if slug in shadowed or slug not in visible or slug in locked:
continue
item = await repo.get_by_slug_for_read(slug)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Acquire multi-Skill row locks in a consistent order

During a rolling startup, a run selecting built-in Skills in B, A order can retain a FOR SHARE lock on B here while waiting for A, while init_builtin_skills() retains its FOR UPDATE lock on A and proceeds to update B before its final commit. PostgreSQL then detects a deadlock and aborts either run preparation or the required startup component; derive the closure before locking and coordinate every multi-row reader and initializer through one deterministic row order or common advisory lock, with real PostgreSQL concurrency coverage.

AGENTS.md reference: backend/AGENTS.md:L37-L37

Useful? React with 👍 / 👎.

@xerrors
xerrors merged commit 031e2c7 into main Sep 29, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants