# CHANGES — 本 fork 的改动 给接手这个 fork 的人。上游是 deepseek-ai/dsh-superagent-prototype(DeepSeek 团队的研究原型), 基线 = `6337fc8`(pristine);其上是本 fork 的 11 个提交(`4379973` → `cf40686`)。 ## 总述 本 fork 在上游研究原型之上做了三件事: 1. **新增 Gitea Source**:面向自建 Gitea 实例的纯拉取轮询——不用 webhook、不需要外网回调, 部署机器无需公网入口。设计决策见 [GITEA-DESIGN.md](GITEA-DESIGN.md)。 2. **Windows 全面移植**:上游原型只在 Linux/macOS 上跑通,本 fork 修复了四个卡死部署的 Windows 缺陷(都在 `4379973` 内,见下文)。 3. **部署 / 运维层若干改进**:面板头部一键切换部署默认模型、Source 活性与轮询停滞上报、 结束工单执行目录的磁盘回收、复用会话的角色模型重新指认等。 ## 提交清单 按时间序;每条的"改了哪里"都对照 `git show --stat` 核实过。 ### `4379973` Sources: add Gitea; fix Windows shims for shell tool, repo paths and stateDir 最大的一笔,Gitea Source 全家桶 + 四个 Windows 缺陷修复合在同一个提交里。 Gitea Source:新增 `src/sources/gitea.ts`(Source kind、轮询 round、`GiteaConfigSchema`、 插件入口)、`src/sources/gitea-api.ts`(最小 REST 客户端:凭据只从 `env[tokenEnv]` 读、zod 校验、分页、可注入 fetch)、`src/sources/gitea-trigger.ts`、`src/sources/gitea-channel.ts`; `src/kv.ts` 加 `gitea` 命名空间(since / retry / 每线程与每流游标);`src/deployment.ts` 加 `GiteaSetupSchema`、`giteaOnly()`、`resolveGiteaDeployment()`;`src/app/setup.ts` 加 `source:gitea` 分支与 `resolve()` 的 gitea-only 路径;`src/setup.ts` 加 gitea 的 setup problem 码;`src/infra/environment.ts` 加 Gitea 的 gh 封禁 shim 与 git 凭据 store;bundle 接线 (`cordis.patch.yml` 插件行默认 disabled、`package.json` 导出 `./gitea-watch`、 `tsdown.config.ts` 入口);`src/client/locales.ts` 文案;docs/007 模块与测试表; `GITEA-DESIGN.md`;`tests/gitea.spec.ts`。 四个 Windows 缺陷: - **stateDir**:`src/deployment.ts` 的 `absolute()` 校验正则 `/^\//` 拒绝 `D:\...` 盘符路径 → 扩为接受盘符与 UNC。 - **worker shim / git 凭据**:Gitea 的 git 凭据不走 POSIX shell 型 credential helper,改用 git 内置的 `store --file=...`(`src/infra/environment.ts`,代码注释:Windows git 行为一致), 写入 gitconfig 的路径反斜杠统一换成正斜杠。 - **仓库路径**:Gitea 的 API 不认 `%2F` 编码的 owner/repo,`resolveGiteaDeployment` 直接以 字面 `repos/${repository}` 请求(`src/deployment.ts`)。 - **shell 工具**:`src/app/sessions.ts` 给 master 会话的 `tools.restrict` 白名单硬编码 `'bash'`,Windows 上注册的 shell 工具是 `pwsh`,症状 = 既有会话 resume 必挂 unknown global tool → 新增 `SHELL_TOOL = process.platform === 'win32' ? 'pwsh' : 'bash'` 并让白名单与 WORK_TOOLS 都用它。 ### `e8b91ab` Infra: write gitea git-credentials line with the deployment's own scheme 动机:凭据行写死 `https://`,与 http 实例的 `credential..helper` 查找不匹配,git 取不到 凭据。改了哪里:`src/infra/environment.ts` 的 `writeGiteaCredentials()` 改按部署 URL 自身的 scheme/host 写这一行。 ### `589b5a6` Sources: accept login:password Basic credential for Gitea (API + git store) 动机:当时所用实例的 token 校验链损坏,需要以账号密码 Basic 顶替。改了哪里: `src/sources/gitea-api.ts` 新增 `giteaAuthorization()`——凭据含 `:` 走 HTTP Basic,否则按 `token `;`src/infra/environment.ts` 的 `writeGiteaCredentials()` 同一规则(Basic 时 store 只存冒号后的密码段)。API 与 git 两路一致。历史注记:上游 Gitea 1.26 收紧 Basic 后本部署 已改回细粒度 token,此提交保留为兜底能力(见"部署实战要点")。 ### `a662188` Panel: switch the deployment default model from the header 动机:面板只能显示 DSH 默认模型不能改,换 Executor / Assessor 模型要手工编辑 profile。 改了哪里:新增 `src/app/default-model.ts` 的 `switchDefaultModel()`——走 `sessionController.selectModel`(与 DSH 模型选择器同一路径,写 profile 的 `agent-default-model`),按 catalog 校验、值未变报 unchanged、后台保存靠轮询确认; `src/app/http.ts` 加 `PUT default-model` 路由;`src/client/default-model.tsx` 头部控件; `tests/default-model.spec.ts`。只影响新会话(既有会话见 `dee76b1` / `acacbb2`)。 ### `b348d69` Overview: report liveness and a Source whose poll loop stopped 动机:轮询循环死了的服务照常响应每个请求,面板看不出来。改了哪里: `src/app/views/overview.ts` 新增 `liveness`(startedAt / uptimeMs / lastPollAt / pollAgeMs / polling),Source 连续 3 个自己的轮询周期没有新动静(且不低于 180 s)报 `stalled` problem; `src/app/http.ts`、`src/app/runtime.ts` 传入进程 startedAt;`src/client/overview.tsx` 展示。 ### `3bee231` Execution: reclaim the directories of Ended Tickets after a retention 动机:每张工单留下 clone 与产物,长期吃磁盘。改了哪里:新增 `src/app/reclaim.ts`—— Reclaimer 每小时扫一次执行根,删超过保留期的 ended 工单目录;`src/policy.ts` 加 `executor.reclaimAfterDays`(默认 14,0 = 关闭,0–365);面板 overview / execution 显示回收 摘要。防误删四道闸:过了保留期、路径必须在执行根内、目录 `basename` 必须等于 `execution.name`、该目录不在任何 open 工单的目录集中。 ### `d527ad8` Sessions: account a restored Session to its Workspace 动机:数据迁移后面板看不到 worker 会话——`ensure()` 只在新建会话时登记 Workspace,从盘上 恢复的会话永远不在 Workspace 记录里。改了哪里:`src/app/sessions.ts` 改为对每次 ensure 都 幂等 `workspaceRegistry.create(cwd)`,未记账的既有会话补 `attachSession`(失败只 warn,不拦 启动)。 ### `dee76b1` + `acacbb2` Roles: re-point reused Sessions when a role model drifts 动机(`dee76b1`):角色模型只给新建会话播种,旧会话被唤醒后永远用旧路由——症状是改了 `executor.model` 之后老工单仍用 DeepSeek,新开任务才生效。改了哪里:`src/app/role-models.ts` 新增 `alignRoleModel()`,dispatch 时比对既有会话的持久 selection / header 与配置的角色模型, 漂移则 append `model/selection` 事件;`src/app/sessions.ts` 对非新建会话调用它。 关键修正(`acacbb2`):只 append 事件不刷内存缓存,真机复跑下一半秒的装配仍用旧模型—— prompt 装配读的是 Session Controller 的内存 selection。改为优先调用 `sessionController` 实例内部的 `agents.selectForNextRequest()`(append + 刷 Agent 缓存,且不动全局默认),该 内部面不存在时回退公开 `selectModel()`(会顺带保存默认,换正确性)。 `tests/role-models.spec.ts` 钉住快路径与回退路径两条。 ### `17be661` Default model: let the picker choose a reasoning effort 动机:头部快捷菜单只有 provider/model,catalog 声明的思考强度只能去 Eval / Execution 表单 里手打。改了哪里:`src/client/default-model.tsx` 为目录里带 efforts 的行展开二级菜单(模型 默认 + 各档位);`src/app/default-model.ts` 校验 effort 属于目录、只改强度也算真切换、转发 带 brand 的 id;docs/006、docs/007 同步。 ### `cf40686` Sources: carry the Gitea kanban convention in the Source's own instructions 动机:看板约定是部署侧约定,不该写进用户仓库的 AGENTS.md。改了哪里:`src/sources/gitea.ts` 注册环境变量 `DSH_SUPERAGENT_GITEA_KANBAN`,约定文本写进它自己的 description,经既有的环境 说明机制注入 worker 提示词。约定内容:Issue 挂在项目看板上,列名与标签同名,现共 9 列 = 文档性维护、已完成、进行中、下一顺位、排队、等外部资源、暂缓、观察、开放问题(首列原名 Uncategorized,已改名);该 Gitea 版本没有 Projects API,改标签或移卡后跑外部同步脚本 card-sync.mjs,只碰这个看板、不直接操作数据库。 ## 测试基线 上游原型在 Windows 本机跑 `pnpm test` 有约 38 个固有失败(tmux、lark-cli、POSIX shims、 deployment 相关、docs 测试的 path.sep 枚举、NTFS 语义等)。本 fork 的门禁口径 = **零回归**: 失败集合必须始终 ⊆ 基线,即 `pnpm typecheck` 全绿且失败集合不新增条目。新增测试文件: `tests/gitea.spec.ts`(21 例)、`tests/role-models.spec.ts`(14 例)、 `tests/default-model.spec.ts`(14 例)、`tests/reclaim.spec.ts`(5 例)。 ## Gitea 部署实战要点(运维备忘) - **1.25 / 1.26 / 1.27 均无 Projects REST API**,Projects API 首发于 1.28。判断以在跑的二进制 为准——main 分支的 swagger 是预览文档,≠ 已发布能力。看板同步只能靠外部脚本(见 `cf40686`)。 - **1.26 起密码 Basic 在 API 写路由被拒**(报 `token is required`),只读路由仍可用;必须用 细粒度 token。scope 按最小需要给,本部署用的例子:`write:issue` + `write:notification` + `read:user` + `write:repository`。 - **一个凭据两用**:同一个 token 既做 API 认证头(`token `)也做 git 的 HTTPS 密码——本 fork 的 `gitea-api.ts` 与 `environment.ts` 凭据 store 是同一规则,轮换时一处换新两处都要动。 - 实例若 token 校验链坏掉,可临时回退 `login:password` Basic(`589b5a6`);升级 1.26 后写 路径必须回到 token。 - **Windows git 兼容性**:MSYS bash 自带的 ucrt64 构建 git 2.56 与该 Gitea 实例的 http/2 协议交互挂起(clone 卡死);Git for Windows 2.39 正常。worker 用的是系统 PATH 里的 git, 不受 MSYS 那份影响,但手工在 MSYS 环境里跑长 clone 时注意。 - **插件不自动升级**:装时锁版本。升级纪律=一次升一个、升前看 changelog、升后跑门禁+验 overview;带本地热补丁的插件(如 `workbuddy-to-dsh` 桥脚本)升级会盖掉补丁,升完要重打补丁 并核对两侧桥脚本哈希一致(不一致时 buildDiff 判 stale,桥互杀重启);重度依赖在跑的插件 没有明确收益不升。详见 INSTALL.md「插件升级纪律」。 ## 已知限制 - 无 webhook,纯轮询(默认 60 s,可配 60–3600 s);响应速度换部署侧零入站端口。 - bot 不自动 merge PR,人工合并是设计(Gitea 侧 auto-merge 在 MVP 裁剪线之外,见 GITEA-DESIGN.md)。 - Gitea API 无 `is_bot` 字段,非 agent、不在名单里的 actor 一律按 human 处理;自动化账号要 手工维护进 Policy 的 `bots` 名单。 - 结束的工单可被 human 唤醒,所以执行目录回收留有保留期(默认 14 天,见 `3bee231`)。