From 526089a8992e8d9202dd95efebc16836753e9480 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Tue, 3 Nov 2026 19:48:13 +0800 Subject: [PATCH] Docs: translate GITEA-DESIGN.md to Chinese --- GITEA-DESIGN.md | 168 ++++++++++++++++++++++++------------------------ 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/GITEA-DESIGN.md b/GITEA-DESIGN.md index 2a6b013..b2d9434 100644 --- a/GITEA-DESIGN.md +++ b/GITEA-DESIGN.md @@ -1,112 +1,112 @@ -# Gitea Source — design & decisions (working doc) +# Gitea Source — 设计与决策(工作文档) -Goal per docs/008 Cookbook: new Source kind `gitea` (plugin `dsh-superagent-gitea`, files `src/sources/gitea*.ts`) -for the user's self-hosted **Gitea 1.25.4 at https://gitea.bitnasdaq.vip** (OpenAPI verified against the live -instance; dump at D:/Code/AI/gitea-spec.json). Gitea's REST API deliberately mirrors GitHub's shape -(`/notifications` + `X-Poll-Interval` header, Issue has `pull_request` meta, numbers shared Issue/PR sequence), -so `gitea.ts` mirrors `github.ts` structure with a direct HTTPS fetch client instead of the `gh` CLI. +目标按 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。 -## Domain map (cookbook step 1) +## 领域映射(手册第 1 步) -| Concept | Gitea mapping | +| 概念 | Gitea 对应 | | --- | --- | -| Trigger | Issue body `key('gitea-issue', n)` (same format for PRs — shared number sequence); comment/review `key('gitea-comment', url||thread, id, updated_at)` | -| Thread | `String(number)` primary; full key `gitea:` | -| Actor | `user.login` (allowed list names logins). Gitea 1.25.4 API has **no `is_bot` field** → `human: true` unless actor is the agent or on the Policy `bots` list (bots list is the lever). Muted/mirrored authors: `user` may be null → login `ghost`, id ''. | -| addressed | `mentions(body, login)` — same `@login` regex as github-trigger.ts | -| Channel address | `{ number, url }` — `url` = html_url of comment/issue, ALWAYS stored (cookbook: put every needed fact in the address) | -| Receiving scope rows | `issues` / `pulls` / `comments` (switches `observeIssues`/`observePulls`/`observeComments`, all default false). CI + inline review comments + PR-close binding = **M2, not MVP** | +| Trigger | Issue 正文 `key('gitea-issue', n)`(PR 同格式——共用编号序列);评论/评审 `key('gitea-comment', url||thread, id, updated_at)` | +| Thread | 主键 `String(number)`;完整 key `gitea:` | +| 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** | -## Config schema (stored in Settings.sources.gitea.config — generic record, zero core schema change) +## 配置 schema(存于 Settings.sources.gitea.config——通用 record,核心 schema 零改动) ```ts -{ url: '', // base, no trailing slash: ^https?://[^/]+$ +{ url: '', // base,不带末尾斜杠:^https?://[^/]+$ repository: '', // owner/name - tokenEnv: 'GITEA_TOKEN', // NAME of env var holding the API token; token NEVER persisted (pattern: gh keeps creds out of config) + tokenEnv: 'GITEA_TOKEN', // 存 API token 的环境变量「名字」;token 本身绝不落盘(模式:gh 的凭据也不进配置) pollSeconds: 60, failuresBeforeError: 3, observeIssues: false, observePulls: false, observeComments: false } ``` -Token source: `process.env[tokenEnv]` in the **orchestrator host process** (DSH injects `~/.dsh/.credentials.yaml` -keys into process env → add `GITEA_TOKEN` there). Note: DSH strips credential-shaped env names from *worker* -children (infra/environment.ts header comment) — irrelevant, workers never need the token in MVP. +Token 来源:orchestrator 宿主进程里的 `process.env[tokenEnv]`(DSH 把 `~/.dsh/.credentials.yaml` 的键注入 +进程环境 → 在那里加 `GITEA_TOKEN`)。注意:DSH 会从 *worker* 子进程剥掉凭据形状的环境变量名 +(infra/environment.ts 头注释)——无所谓,MVP 里 worker 从来不需要 token。 -## Verified live endpoints (1.25.4) +## 已验活的端点(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}; header `X-Poll-Interval`. -- `GET /user` → {login,id}; `GET /repos/{o}/{r}` → {full_name, permissions{pull,push}}. -- `GET /repos/{o}/{r}/issues` (issues only), `/pulls` (PRs only) — unlike GitHub where /issues includes PRs! - Client-side filter updated_at >= cursor (server `since` support not trusted). -- `GET /repos/{o}/{r}/issues/{index}` → Issue with `pull_request` non-null when PR (same dual read as GitHub). -- `GET|POST /repos/{o}/{r}/issues/{index}/comments` (no `since` param — client filter on updated_at; pagination per_page=100&page=N until short page). -- `GET /repos/{o}/{r}/pulls/{index}/reviews` → PullReview[] {id,state,body,commit_id,user,html_url,submitted_at,updated_at}. -- **No repo-wide review-comment stream** (`/pulls/comments` MISSING in 1.25.4) → MVP review intake only via per-PR reads. -- CI for M2: `/repos/{o}/{r}/branches/{branch}` (user_can_push!), `/commits/{ref}/status` (CombinedStatus), `/actions/runs`. + 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`。 -## Poll flow (mirrors github.ts; MVP subset) +## 轮询流(镜像 github.ts;MVP 子集) -`pollRound` = 1) `pollNotifications` (KV gitea-since + per-number cursor KV `gitea-cursor:` + retry map -`gitea-retry`; subject.url parsed by `giteaAddress` → read issue+comments[+reviews if PR]) → -2) `pollRepository` streams issues/pulls/comments (KV `gitea-stream:`, cursorAt −5 s overlap, stable ids dedup, -horizon POLL_HORIZON_MS, `started` per kind) → 3) `pollThreads` subscribed threads via poll.ts `pollThreads` -(skip PR-close observation). 5 s test-timer-friendly `pause`. 3 consecutive round failures → health error -(`failuresBeforeError`). Own messages never Triggers. Health entry: `['gitea']`. +`pollRound` = 1) `pollNotifications`(KV gitea-since + 按编号的游标 KV `gitea-cursor:` + 重试表 +`gitea-retry`;subject.url 由 `giteaAddress` 解析 → 读 issue+评论[PR 时加 reviews])→ +2) `pollRepository` 流式读 issues/pulls/comments(KV `gitea-stream:`,cursorAt −5 s 重叠,稳定 id 去重, +horizon POLL_HORIZON_MS,每 kind 记 `started`)→ 3) `pollThreads` 订阅的 thread 走 poll.ts 的 `pollThreads` +(跳过 PR 关闭观察)。带 5 s 测试友好的 `pause`。连续 3 轮失败 → 健康错误 +(`failuresBeforeError`)。自己的消息永远不是 Trigger。健康条目:`['gitea']`。 -## Channel send (idempotent, GitHub pattern) +## 通道发送(幂等,GitHub 模式) -Marker `` appended; before POST, list issue comments and if any comment by the -agent login contains the marker → 'sent'. POST `issues/{n}/comments` {body}. 'gone' when: GET issue → 404 (deleted) or -POST 403 `/locked/i`. Other throws → Outbox retries 5→300 s. `link()` = {url: address.url, label `Gitea #n`}. -`allowed(address, config)`: number parses && config.repository non-empty (address always has its own url). +marker `` 追加;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 (assessments) +## threadContext(研判上下文) -`messageId(trigger)`: issue body trigger → `${number}-body`; comment/review → `c${id}`/`r${id}`. -`read(part)`: fetch issue + all comments (page loop); messages = [body `${title}\n\n${body}`] + comments (+reviews -when PR and part allows), ContextMessage {id, from, at: updated_at??created_at, text, url}; 'root' → just body -message; flat forum — 'parent' returns the page containing the message (no threading in Gitea issue comments); -`next` = page cursor `p2` etc. body payload = the messages. +`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 part +## Policy 部分 -`allowed: [{id: login}]` regex `^[A-Za-z0-9._-]+$`; optional `bots` same shape (the substitute for missing is_bot — -documented hint: list automation accounts here). `explicit`: "名单里的账号在 Issue 或评论里 @ 智能体的账号。". -`rows`: issues/pulls/comments rows like github.ts (cells from observe* flags). No `sync` (self-hosted, no rosters). +`allowed: [{id: login}]` 正则 `^[A-Za-z0-9._-]+$`;可选 `bots` 同形状(缺 is_bot 的替代—— +文档提示:自动化账号列在这里)。`explicit`:"名单里的账号在 Issue 或评论里 @ 智能体的账号。" +`rows`:issues/pulls/comments 行,同 github.ts(单元格来自 observe* 开关)。无 `sync`(自建,没有名册)。 -## Environment variables (registered by this kind) +## 环境变量(本种类注册) -DSH_SUPERAGENT_GITEA_REPOSITORY (owner/name), DSH_SUPERAGENT_GITEA_URL (base url), DSH_SUPERAGENT_GITEA_ACCOUNT -(agent login — from deployment.gitea), DSH_SUPERAGENT_GITEA_NUMBER (issue/PR number that started ticket, else ''). -No token vars in worker env ever. +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 变量。 -## Core patch (required: `configured()` hard-requires gh — app/setup.ts resolve() → resolveDeployment throws) +## 核心补丁(必需:`configured()` 硬性要求 gh——app/setup.ts resolve() → resolveDeployment 抛错) -1. `deployment.ts`: add `GiteaSetupSchema = {repository,url,tokenEnv}`, `SETUP_FIELDS.gitea`, `Deployment.gitea?: - {url, login}`; `resolveGiteaDeployment(machine, config, fetchImpl, env)`: url→GET /user (token env), repo→GET - /repos (canonical full_name + permissions.push required). Gitea-only Deployment shape: - `{ ...machine, repository: , github: {cli:'',login:'',noreply:''}, gitea: {...} }` — github source - disabled in that profile, its readers never run (verified consumers: github.ts env/facts/poll only when enabled; - githubLink via registry per kind; writeEnvironment/verifyEnvironment patched next). -2. `app/setup.ts resolve()`: if github config empty (no repository && no cli) → gitea path; else GitHub path - unchanged. Gitea path: resolveGiteaDeployment; `writeEnvironment` branch when `!github.cli && gitea`: shims for - blocked commands (message "act as on Gitea"), NO gh shim, NO github.com credential section; - `verifyEnvironment` skipped for gitea-only (credential check is github.com-specific). -3. `checkAccounts`: block `source:gitea` → resolveGiteaDeployment (place 'gitea' so unrelated errors don't block saves). -4. `src/setup.ts` PROBLEMS: add gitea codes (place 'gitea'): giteaUrlUnset, giteaRepositoryUnset, giteaTokenMissing, - giteaTokenInvalid, giteaRepositoryUnreadable, giteaRepositoryReadOnly. -5. KV (`src/kv.ts`): `gitea: { since: record('gitea-since'), retries: record>('gitea-retry'), - cursor: family('gitea-cursor:'), stream: family('gitea-stream:') }` (tests/store.spec.ts requires key literals only there). -6. Build wiring: tsdown entry `'gitea-watch': 'src/sources/gitea.ts'`; package.json exports `"./gitea-watch"`; - cordis.patch.yml bundle insert `dsh-superagent-gitea` name `dsh-superagent/gitea-watch` **disabled: true** - (the router profile enables it; web profile must not). 007-architecture.md module+test tables (tests/docs.spec.ts - enforces completeness) + 001-domain "What is reusable" row. Tests: `tests/gitea.spec.ts` (fetch mocked via - injected fetchImpl / vi.stubGlobal), pattern of tests/sources.spec.ts. +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: , 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 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>('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-architecture.md 模块+测试表(tests/docs.spec.ts + 强制完整性)+ 001-domain "What is reusable" 行。测试:`tests/gitea.spec.ts`(fetch 用注入的 + fetchImpl / vi.stubGlobal mock),模式同 tests/sources.spec.ts。 -## MVP cut lines (M2 later): Gitea PR observation + threadClosed/bindThread + auto-merge; inline review comments; -CI (branch + PR checks) triggers; agent-credential git helper for the Gitea host (MVP workers use the machine's -ambient git auth = user's own account for pushes; agent identity applies to API writes); prompt seeds that mention gh. +## MVP 裁剪线(M2 在后):Gitea PR 观察 + threadClosed/bindThread + 自动合并;行内评审评论; +CI(branch + PR checks)trigger;Gitea 宿主的 agent 凭据 git helper(MVP worker 用机器的 +环境 git 认证 = 用户自己的账号做 push;agent 身份只作用于 API 写);提到 gh 的提示词种子。 -## Test plan order -translate → scope filtered → round (stored/filtered/cursor/dup) → mention explicit → send sent/gone/throw → -threadContext pages → apply registers + setupPending → policy rows → live smoke vs gitea.bitnasdaq.vip (needs user: -agent account + token + repo). +## 测试计划顺序 +翻译 → 范围过滤 → round(存储/过滤/游标/去重)→ mention explicit → send sent/gone/throw → +threadContext 翻页 → apply 注册 + setupPending → policy 行 → 对 gitea.bitnasdaq.vip 的实机冒烟(需要用户: +agent 账号 + token + 仓库)。