8.3 KiB
8.3 KiB
@local/gitea-publisher
DeepSeek Harness 插件:在会话顶栏加一个「Gitea」按钮,把当前会话工作区的 最终项目源码做成一次快照提交,push 到本地部署的 Gitea。 一个工作区对应一个独立仓库:不同工作区各推各的仓库,互不混库。
工作方式
- 宿主半边(
index.js):giteaPublish服务,导出status/publish两个 Remote 方法 (typert SRC 回退模式,零依赖,只用 node: 内置模块 + 系统git)。 - 浏览器半边(
client.js):注册进conversation.session.header.actions槽位, 用ctx.remote.$mount挂载描述符;点按钮 → 宿主执行快照并推送。 - 一个工作区一个仓库:目标仓库名按工作区目录 basename 派生(非法字符换
-,截 60 字符); 目录名已被别的工作区绑定、或在 Gitea 上已存在(不许悄悄复用未知仓库)时,自动加-<工作区路径sha1前8位>后缀区分。首次发布成功后才写入绑定表~/.dsh/gitea-publisher-map.json(原子写;推失败不留脏绑定),此后该工作区永远推绑定到的仓库, 改名重绑用面板请求里的repo字段(也持久进绑定表)。repoFromWorkspace: false退回旧行为(所有会话共用配置里的固定repo)。 - 发布流程:
GET /user验证凭证 → 仓库不存在时自动建仓(owner 是组织就走POST /orgs/{owner}/repos,个人走POST /user/repos;可见性由private控制, 默认 false=完全公开)→ 工作区复制到临时目录(跳过.git/node_modules/dist等垃圾目录与超限大文件,.gitignore在git add阶段自然生效)→git init→ 先 fetch 远端目标分支并基于它提交(线性历史、永远可快进;内容没变就不产生新提交, 返回unchanged: true幂等成功)→git push(凭证走 URL 内嵌 basic auth, 对外报错文本会抹掉 token 及其 URL 转义形态)。 force: true改变语义:跳过 fetch,直接推「单提交快照历史」并--force覆盖远端分支。- 认证两种模式:
username+token(=密码)走 HTTP Basic(适配没开放令牌 API 的实例);username留空则按token <PAT>头。 - 按钮弹层显示目标仓库、发布进度、成功(commit/文件数/新建或无变更标注/仓库链接)或失败原因。
配置
写在 profile 的 ~/.dsh/profiles/web/cordis.patch.yml(gitea-publisher 行的
config.gitea;bundle 自带的 cordis.patch.yml 只是默认值副本)。本机已接入:
config:
gitea:
baseUrl: "http://10.0.0.131:3000" # 不带结尾斜杠
username: "dsh" # 填了就走 Basic 认证(账号密码)
token: "<账号密码或个人访问令牌>" # 该实例未开放令牌 API,用账号密码
owner: "XH" # 组织 XH:自动建仓建到组织下(个人用户名亦可)
branch: "main"
createIfMissing: true
private: false # 自动创建时可见性:false=完全公开(默认)
force: false
maxFileBytes: 31457280
# repoFromWorkspace: true # 默认开:一工作区一仓库;false = 旧的固定单仓库模式
# repo: "xxx" # 仅固定模式使用;per-workspace 模式下忽略
# mapFile: "~/.dsh/gitea-publisher-map.json" # 工作区→仓库 绑定表位置
注意:密码明文存在 profile 配置里,这台机器上等价于「本机可读」,介意的话 在 Gitea 侧给
dsh单独建低权限账号或恢复令牌 API 后换 PAT。
本机安装与热更新(实测结论)
这台机器 PATH 上没有 pnpm,手工接线:
~/.dsh/profiles/web/node_modules/@local/gitea-publisher/是一个 dev-link 中继目录:index.js是入口中继(每次 apply 带一次性 query 重新 import 工作区真实代码,绕开 ESM 模块缓存;.heartbeat.json为可观测证据),client.js是指向工作区的符号链接,package.json是内容拷贝(若改了包dsh.*字段需重新cp)。- profile
cordis.patch.yml末尾的- insert:行(idgitea-publisher)。 - profile 装载/配置变更需要重启 dsh-web 才在宿主生效(实测:这台机器的 systemd
单元用裸
node启动、没有--expose-internals,dsh-hmr 起不来 → 没有任何 patch/模块文件监视;此前「新行热挂上」的现象只是 Web 服务按请求重扫 client 模块清单, 宿主侧并没有装载)。浏览器端刷新页面后才能看到按钮。 可选改进:给 unit 的 ExecStart 换成node --expose-internals /usr/local/bin/dsh web …并重启一次,之后 HMR 链路(改代码热重载)就能用。
真机验证(2026-10-13,test/verify-real.mjs 全绿,每工作区模式)
对真实实例 http://10.0.0.131:3000(账号密码 Basic 认证)用临时合成工作区跑通
(绑定表放隔离临时文件,测试仓库结尾自动删除):
- status 配置校验 / API 可达(apiUser=dsh)、
repoPolicy=per-workspace、未绑定工作区预测派生名 - 提交:按目录名自动在组织 XH 下建公开仓库(如
XH/dsh-verify-ws)+ 推送(commit/files 回执正常) - 独立性:换另一个工作区目录 → 另建
XH/dsh-verify-other,两仓库互不共享;重发命中已绑定仓库(repoSource=bound) - (历史)旧固定模式曾用本项目工作区建
XH/gitea-publisher,验证条目同上;切换后本工作区首次点击会改推XH/gitea - 归属与可见性:API 匿名可读、
private=false、owner=XH,匿名 git 拉取 200 - 拉取:clone 回本地,文件树、HEAD 与回执一致
- 归档:
GET /api/v1/repos/XH/gitea-publisher/archive/main.zip下载校验,zip 内容有效 - 同内容重发 →
unchanged: true幂等;内容变化重发 → 基于远端线性提交可快进 force重发 → 单提交历史覆盖,远端 fetch 到预期 HEAD- 全程返回文本无凭证泄漏(含 URL 百分号转义形态)
另有离线套件:test/e2e-host.mjs(假 Gitea + 真 git http-backend 全链路)、
test/e2e-client.mjs(描述符校验/字典完备性/组件渲染)。
客户端接线的三个坑(真实浏览器里踩过,全是静默故障)
- 动态
$mount的命名空间不能静态 inject。remote.giteaPublish是本包 apply 里$mount之后才出现的,把它写进exports.inject会让 fiber 永远等待自己提供 → 整个 客户端 boot 报pending (waiting for service: remote.giteaPublish)(官方 typed client 能静态注入是因为挂载方是 boot 期的 dsh-api-remotes,不是同一个包)。 ctx.remote.<ns>属性糖受 inject 守卫,动态挂载后直接取属性会抛cannot get property "remote.giteaPublish" without inject;官方同款解法是ctx.get('remote.giteaPublish')(见 validateContribution 自身)。- 命名空间方法的返回值带
{ok, value}RPC 信封(src-json 的宿主返回值在value里)。不解包的话status.configured恒为 undefined——面板渲染成"目标"标签下空白、 发布按钮消失的半残状态,且不报任何错。解包在 inject 闭包里做:信封ok:false是协议层失败(撤回/断连),宿主业务失败在value.ok=false。
UI 侧兜底:status 加载失败/20 秒超时都会显示错误文本 + 重试按钮,不再有无限 "正在检查配置…"。宿主重启会让旧页面的连接失效,重启 dsh-web 后浏览器必须刷新一次。
已知边界
- 按钮推的是当前会话的工作区(lookup 由 sessionId 解析出 workspaceRoot)。
- 每工作区仓库的判定发生在每次 status/publish 时:绑定表被手工删掉后,若派生名对应仓库 已在远端存在,会被视为「占用」而加后缀,不会重新认领旧名(防止两个工作区抢同一仓库)。
- 工作区目录改名 = 换仓库(绑定按工作区路径记);换机器/换路径同理。
force: true会用单提交快照覆盖远端分支历史,确认后再开。- 会话工作区里若已有
.git,其历史不会被带进快照(只发源码现状)。 - 大文件阈值
maxFileBytes之外的文件跳过并在结果里报告数量。