Files

113 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gitea Source — 设计与决策(工作文档)
目标按 docs/008 手册:新的 Source 种类 `gitea`(插件 `dsh-superagent-gitea`,文件 `src/sources/gitea*.ts`),
服务于用户自建的 **Gitea 1.25.4 @ https://gitea.bitnasdaq.vip**(OpenAPI 已对照实机核验;dump 在
D:/Code/AI/gitea-spec.json)。Gitea 的 REST API 刻意镜像 GitHub 的形状(`/notifications` + `X-Poll-Interval`
头,Issue 带 `pull_request` 元数据,Issue/PR 共用编号序列),所以 `gitea.ts` 镜像 `github.ts` 的结构,
用直接 HTTPS fetch 客户端代替 `gh` CLI。
## 领域映射(手册第 1 步)
| 概念 | Gitea 对应 |
| --- | --- |
| Trigger | Issue 正文 `key('gitea-issue', n)`(PR 同格式——共用编号序列);评论/评审 `key('gitea-comment', url||thread, id, updated_at)` |
| Thread | 主键 `String(number)`;完整 key `gitea:<number>` |
| Actor | `user.login`(允许名单按 login 匹配)。Gitea 1.25.4 API **没有 `is_bot` 字段** → 除非 actor 是 agent 自己在 Policy `bots` 名单里,否则 `human: true`(bots 名单就是补偿手段)。静音/镜像作者:`user` 可能为 null → login `ghost`,id ''。 |
| addressed | `mentions(body, login)`——与 github-trigger.ts 相同的 `@login` 正则 |
| 通道地址 | `{ number, url }`——`url` = 评论/issue 的 html_url,**永远存下来**(手册:地址里放齐所有需要的事实) |
| 接收流 | `issues` / `pulls` / `comments`(开关 `observeIssues`/`observePulls`/`observeComments`,全默认 false)。CI + 行内评审评论 + PR 关闭绑定 = **M2,不在 MVP** |
## 配置 schema(存于 Settings.sources.gitea.config——通用 record,核心 schema 零改动)
```ts
{ url: '', // base,不带末尾斜杠:^https?://[^/]+$
repository: '', // owner/name
tokenEnv: 'GITEA_TOKEN', // 存 API token 的环境变量「名字」;token 本身绝不落盘(模式:gh 的凭据也不进配置)
pollSeconds: 60, failuresBeforeError: 3,
observeIssues: false, observePulls: false, observeComments: false }
```
Token 来源:orchestrator 宿主进程里的 `process.env[tokenEnv]`(DSH 把 `~/.dsh/.credentials.yaml` 的键注入
进程环境 → 在那里加 `GITEA_TOKEN`)。注意:DSH 会从 *worker* 子进程剥掉凭据形状的环境变量名
(infra/environment.ts 头注释)——无所谓,MVP 里 worker 从来不需要 token。
## 已验活的端点(1.25.4)
- `GET /api/v1/notifications?all&since&limit&page` → `NotificationThread[]` {id, unread, repository{full_name},
subject{type,url,latest_comment_url,html_url,title,state}, updated_at};头 `X-Poll-Interval`。
- `GET /user` → {login,id};`GET /repos/{o}/{r}` → {full_name, permissions{pull,push}}。
- `GET /repos/{o}/{r}/issues`(只有 issue)、`/pulls`(只有 PR)——与 GitHub 不同(GitHub 的 /issues 包含 PR)!
客户端过滤 updated_at >= cursor(不信任服务端 `since`)。
- `GET /repos/{o}/{r}/issues/{index}` → PR 时 `pull_request` 非 null 的 Issue(与 GitHub 同一种双读)。
- `GET|POST /repos/{o}/{r}/issues/{index}/comments`(无 `since` 参数——客户端按 updated_at 过滤;分页 per_page=100&page=N 直到短页)。
- `GET /repos/{o}/{r}/pulls/{index}/reviews` → PullReview[] {id,state,body,commit_id,user,html_url,submitted_at,updated_at}。
- **没有仓库级评审评论流**(1.25.4 缺 `/pulls/comments`)→ MVP 的评审接收只走逐 PR 读取。
- M2 的 CI:`/repos/{o}/{r}/branches/{branch}`(有 user_can_push!)、`/commits/{ref}/status`(CombinedStatus)、`/actions/runs`。
## 轮询流(镜像 github.ts;MVP 子集)
`pollRound` = 1) `pollNotifications`(KV gitea-since + 按编号的游标 KV `gitea-cursor:<n>` + 重试表
`gitea-retry`;subject.url 由 `giteaAddress` 解析 → 读 issue+评论[PR 时加 reviews])→
2) `pollRepository` 流式读 issues/pulls/comments(KV `gitea-stream:<id>`,cursorAt −5 s 重叠,稳定 id 去重,
horizon POLL_HORIZON_MS,每 kind 记 `started`)→ 3) `pollThreads` 订阅的 thread 走 poll.ts 的 `pollThreads`
(跳过 PR 关闭观察)。带 5 s 测试友好的 `pause`。连续 3 轮失败 → 健康错误
(`failuresBeforeError`)。自己的消息永远不是 Trigger。健康条目:`['gitea']`。
## 通道发送(幂等,GitHub 模式)
marker `<!-- dsh-superagent:<message.id> -->` 追加;POST 前列出 issue 评论,若 agent login 的某条评论已含
marker → 'sent'。POST `issues/{n}/comments` {body}。'gone' 的情形:GET issue → 404(已删)或
POST 403 `/locked/i`。其它抛错 → Outbox 5→300 s 重试。`link()` = {url: address.url, label `Gitea #n`}。
`allowed(address, config)`:number 可解析 && config.repository 非空(地址永远有自己的 url)。
## threadContext(研判上下文)
`messageId(trigger)`:issue 正文 trigger → `${number}-body`;评论/评审 → `c${id}`/`r${id}`。
`read(part)`:fetch issue + 全部评论(翻页循环);messages = [正文 `${title}\\n\\n${body}`] + 评论(PR 且 part 允许时
加 reviews),ContextMessage {id, from, at: updated_at??created_at, text, url};'root' → 只有正文
消息;论坛是平的——'parent' 返回包含该消息的那一页(Gitea issue 评论没有线程结构);
`next` = 页游标 `p2` 等。body payload = 这些消息。
## Policy 部分
`allowed: [{id: login}]` 正则 `^[A-Za-z0-9._-]+$`;可选 `bots` 同形状(缺 is_bot 的替代——
文档提示:自动化账号列在这里)。`explicit`:"名单里的账号在 Issue 或评论里 @ 智能体的账号。"
`rows`:issues/pulls/comments 行,同 github.ts(单元格来自 observe* 开关)。无 `sync`(自建,没有名册)。
## 环境变量(本种类注册)
DSH_SUPERAGENT_GITEA_REPOSITORY (owner/name)、DSH_SUPERAGENT_GITEA_URL (base url)、DSH_SUPERAGENT_GITEA_ACCOUNT
(agent login——来自 deployment.gitea)、DSH_SUPERAGENT_GITEA_NUMBER (开出工单的 issue/PR 编号,没有则 '')。
worker 环境里永远没有 token 变量。
## 核心补丁(必需:`configured()` 硬性要求 gh——app/setup.ts resolve() → resolveDeployment 抛错)
1. `deployment.ts`:加 `GiteaSetupSchema = {repository,url,tokenEnv}`、`SETUP_FIELDS.gitea`、`Deployment.gitea?:
{url, login}`;`resolveGiteaDeployment(machine, config, fetchImpl, env)`:url→GET /user(token env)、repo→GET
/repos(规范 full_name + 要求 permissions.push)。Gitea-only 的 Deployment 形状:
`{ ...machine, repository: <gitea 仓库>, github: {cli:'',login:'',noreply:''}, gitea: {...} }`——该 profile 里
github source 禁用,其读者永不运行(已验消费方:github.ts 的 env/facts/poll 只在 enabled 时跑;
githubLink 走 registry 按 kind;writeEnvironment/verifyEnvironment 随后补)。
2. `app/setup.ts resolve()`:github 配置为空(无 repository 且无 cli)→ gitea 路径;否则 GitHub 路径
不变。Gitea 路径:resolveGiteaDeployment;`!github.cli && gitea` 时 `writeEnvironment` 分支:被禁命令的 shims
(消息 "act as <gitea login> on Gitea")、无 gh shim、无 github.com 凭据段;
`verifyEnvironment` 对 gitea-only 跳过(凭据检查是 github.com 特有的)。
3. `checkAccounts`:`source:gitea` 阻塞 → resolveGiteaDeployment(位置 'gitea',让无关错误不阻塞保存)。
4. `src/setup.ts` PROBLEMS:加 gitea 码(位置 'gitea'):giteaUrlUnset、giteaRepositoryUnset、giteaTokenMissing、
giteaTokenInvalid、giteaRepositoryUnreadable、giteaRepositoryReadOnly。
5. KV(`src/kv.ts`):`gitea: { since: record('gitea-since'), retries: record<Record<string,Retry>>('gitea-retry'),
cursor: family('gitea-cursor:'), stream: family('gitea-stream:') }`(tests/store.spec.ts 只在那里要求 key 字面量)。
6. 构建接线:tsdown entry `'gitea-watch': 'src/sources/gitea.ts'`;package.json exports `"./gitea-watch"`;
cordis.patch.yml bundle 插入 `dsh-superagent-gitea` 名 `dsh-superagent/gitea-watch` **disabled: true**
(router profile 启用它;web profile 必须不启)。007-架构.md 模块+测试表(tests/docs.spec.ts
强制完整性)+ 001-领域模型 "哪些是可复用的" 行。测试:`tests/gitea.spec.ts`(fetch 用注入的
fetchImpl / vi.stubGlobal mock),模式同 tests/sources.spec.ts。
## MVP 裁剪线(M2 在后):Gitea PR 观察 + threadClosed/bindThread + 自动合并;行内评审评论;
CI(branch + PR checks)trigger;Gitea 宿主的 agent 凭据 git helper(MVP worker 用机器的
环境 git 认证 = 用户自己的账号做 push;agent 身份只作用于 API 写);提到 gh 的提示词种子。
## 测试计划顺序
翻译 → 范围过滤 → round(存储/过滤/游标/去重)→ mention explicit → send sent/gone/throw →
threadContext 翻页 → apply 注册 + setupPending → policy 行 → 对 gitea.bitnasdaq.vip 的实机冒烟(需要用户:
agent 账号 + token + 仓库)。