baa-conductor


commit
d8e883d
parent
8a4a3ee
author
codex@macbookpro
date
2026-04-01 20:05:10 +0800 CST
feat: remove stagit repo route
8 files changed,  +45, -164
Raw patch view.
  1diff --git a/PROGRESS/2026-03-29-current-code-progress.md b/PROGRESS/2026-03-29-current-code-progress.md
  2index dc08be404ccb06b927a5462714b7e1b218376665..d0007234afd74ad3f71c0648e64643960084662c 100644
  3--- a/PROGRESS/2026-03-29-current-code-progress.md
  4+++ b/PROGRESS/2026-03-29-current-code-progress.md
  5@@ -52,7 +52,7 @@
  6 - `/artifact/` 静态文件服务
  7 - `/v1/messages`、`/v1/executions`、`/v1/sessions`、`/v1/sessions/latest`
  8 - `/describe` 返回 `recent_sessions_url`
  9-- stagit 仓库静态页面
 10+- repo 静态页浏览能力后来已迁到 `baa-pgit`,当前 `conductor` 不再提供 `/artifact/repo/*`
 11 
 12 因此,`ARTIFACT_STATIC_SERVICE.md` 现在更适合作为已完成主线的实现参考,而不是当前活跃任务入口。
 13 
 14@@ -80,11 +80,10 @@
 15 
 16 ## 当前 open bug / 风险
 17 
 18-### BUG-026
 19+### BUG-026(历史背景)
 20 
 21-- `/artifact/repo/:repo_name` 根路径不会落到默认 `log.html`
 22-- 显式 `/artifact/repo/<repo>/log.html` 可用
 23-- 影响 repo 首页入口和 URL 语义一致性
 24+- 该问题对应的 repo 静态页路由已在后续任务中整体删除
 25+- `/artifact/repo/*` 不再是当前仓库能力;相关描述只保留为历史排障背景
 26 
 27 ### BUG-027
 28 
 29diff --git a/apps/conductor-daemon/src/index.test.js b/apps/conductor-daemon/src/index.test.js
 30index eef834882b46e5d18c389835464ce25e7b45e5b0..a8427b699caeadba57c7b0c83c6e47d9c1ace747 100644
 31--- a/apps/conductor-daemon/src/index.test.js
 32+++ b/apps/conductor-daemon/src/index.test.js
 33@@ -7846,10 +7846,6 @@ test("handleConductorHttpRequest serves artifact files and robots.txt", async ()
 34   const { repository, snapshot } = await createLocalApiFixture();
 35 
 36   await withArtifactStoreFixture(async ({ artifactStore }) => {
 37-    const repoDir = join(artifactStore.getArtifactsDir(), "repo", "demo-repo");
 38-    mkdirSync(repoDir, { recursive: true });
 39-    writeFileSync(join(repoDir, "log.html"), "<html><body>demo repo home</body></html>\n");
 40-
 41     await artifactStore.insertMessage({
 42       conversationId: "conv_artifact",
 43       id: "msg_artifact",
 44@@ -7935,27 +7931,25 @@ test("handleConductorHttpRequest serves artifact files and robots.txt", async ()
 45     assert.equal(sessionLatestResponse.status, 200);
 46     assert.match(Buffer.from(sessionLatestResponse.body).toString("utf8"), /session_artifact/u);
 47 
 48-    const repoIndexResponse = await handleConductorHttpRequest(
 49+    const removedRepoRootResponse = await handleConductorHttpRequest(
 50       {
 51         method: "GET",
 52         path: "/artifact/repo/demo-repo"
 53       },
 54       context
 55     );
 56-    assert.equal(repoIndexResponse.status, 200);
 57-    assert.match(repoIndexResponse.headers["content-type"], /^text\/html\b/u);
 58-    assert.match(Buffer.from(repoIndexResponse.body).toString("utf8"), /demo repo home/u);
 59+    assert.equal(removedRepoRootResponse.status, 404);
 60+    assert.equal(parseJsonBody(removedRepoRootResponse).error, "not_found");
 61 
 62-    const repoLogResponse = await handleConductorHttpRequest(
 63+    const removedRepoFileResponse = await handleConductorHttpRequest(
 64       {
 65         method: "GET",
 66         path: "/artifact/repo/demo-repo/log.html"
 67       },
 68       context
 69     );
 70-    assert.equal(repoLogResponse.status, 200);
 71-    assert.match(repoLogResponse.headers["content-type"], /^text\/html\b/u);
 72-    assert.match(Buffer.from(repoLogResponse.body).toString("utf8"), /demo repo home/u);
 73+    assert.equal(removedRepoFileResponse.status, 404);
 74+    assert.equal(parseJsonBody(removedRepoFileResponse).error, "not_found");
 75 
 76     const missingResponse = await handleConductorHttpRequest(
 77       {
 78diff --git a/apps/conductor-daemon/src/local-api.ts b/apps/conductor-daemon/src/local-api.ts
 79index fb8af94b1e6d72cc91dfb4b9ea6e3b35593da64e..58aa47a437480f6c8fcfce89c1d832f279c41a6e 100644
 80--- a/apps/conductor-daemon/src/local-api.ts
 81+++ b/apps/conductor-daemon/src/local-api.ts
 82@@ -437,16 +437,6 @@ const LOCAL_API_ROUTES: LocalApiRouteDefinition[] = [
 83     pathPattern: "/robots.txt",
 84     summary: "返回允许 AI 访问 /artifact/ 的 robots.txt"
 85   },
 86-  // Keep the repo route ahead of the generic artifact route so repo root URLs
 87-  // can fall back to log.html instead of being claimed by the generic matcher.
 88-  {
 89-    id: "service.artifact.repo",
 90-    exposeInDescribe: false,
 91-    kind: "read",
 92-    method: "GET",
 93-    pathPattern: "/artifact/repo/:repo_name/*",
 94-    summary: "读取 stagit 生成的 git 仓库静态页面"
 95-  },
 96   {
 97     id: "service.artifact.read",
 98     exposeInDescribe: false,
 99@@ -6576,67 +6566,6 @@ async function handleArtifactRead(context: LocalApiRequestContext): Promise<Cond
100   }
101 }
102 
103-const REPO_STATIC_CONTENT_TYPES: Record<string, string> = {
104-  ".html": "text/html; charset=utf-8",
105-  ".css":  "text/css; charset=utf-8",
106-  ".xml":  "application/xml",
107-  ".atom": "application/atom+xml",
108-  ".json": "application/json",
109-  ".txt":  "text/plain; charset=utf-8",
110-  ".png":  "image/png",
111-  ".ico":  "image/x-icon",
112-  ".svg":  "image/svg+xml"
113-};
114-
115-function getRepoStaticContentType(filePath: string): string {
116-  const dot = filePath.lastIndexOf(".");
117-
118-  if (dot !== -1) {
119-    const ext = filePath.slice(dot).toLowerCase();
120-    const ct = REPO_STATIC_CONTENT_TYPES[ext];
121-
122-    if (ct) {
123-      return ct;
124-    }
125-  }
126-
127-  return "text/plain; charset=utf-8";
128-}
129-
130-const REPO_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
131-
132-async function handleArtifactRepoRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
133-  const artifactStore = requireArtifactStore(context.artifactStore);
134-  const repoName = context.params.repo_name;
135-  const wildcard = context.params["*"] || "log.html";
136-
137-  if (!repoName || !REPO_NAME_PATTERN.test(repoName) || wildcard.includes("..")) {
138-    throw new LocalApiHttpError(
139-      404,
140-      "not_found",
141-      `No conductor route matches "${normalizePathname(context.url.pathname)}".`
142-    );
143-  }
144-
145-  const filePath = join(artifactStore.getArtifactsDir(), "repo", repoName, wildcard);
146-
147-  try {
148-    return binaryResponse(200, readFileSync(filePath), {
149-      "content-type": getRepoStaticContentType(wildcard)
150-    });
151-  } catch (error) {
152-    if (isMissingFileError(error)) {
153-      throw new LocalApiHttpError(
154-        404,
155-        "not_found",
156-        `Artifact "${normalizePathname(context.url.pathname)}" was not found.`
157-      );
158-    }
159-
160-    throw error;
161-  }
162-}
163-
164 function buildPlainTextBinaryResponse(status: number, body: string): ConductorHttpResponse {
165   return binaryResponse(status, Buffer.from(body, "utf8"), {
166     "content-type": CODE_ROUTE_CONTENT_TYPE
167@@ -7754,8 +7683,6 @@ async function dispatchBusinessRoute(
168       return handleRobotsRead();
169     case "service.artifact.read":
170       return handleArtifactRead(context);
171-    case "service.artifact.repo":
172-      return handleArtifactRepoRead(context);
173     case "service.code.read":
174       return handleCodeRead(context);
175     case "service.health":
176diff --git a/plans/ARTIFACT_STATIC_SERVICE.md b/plans/ARTIFACT_STATIC_SERVICE.md
177index ccc30cef41eb87c43889c00034202dc9b54e7850..d9460a038a5e05a8812f90c3946eb8fe5d2bad61 100644
178--- a/plans/ARTIFACT_STATIC_SERVICE.md
179+++ b/plans/ARTIFACT_STATIC_SERVICE.md
180@@ -379,7 +379,7 @@ D1 同步细节:
181 7. **D1 异步适配器**(TypeScript async 重写)
182 8. **D1 同步队列**(后台推送 + 重试)
183 9. **会话索引自动更新**(事件触发)
184-10. **stagit 集成**(后续)
185+10. **repo 静态页浏览能力迁出**(当前仓库不做)
186 
187 ## 11. 不做的事
188 
189diff --git a/plans/STATUS_SUMMARY.md b/plans/STATUS_SUMMARY.md
190index 523052b961d34a775874bf3a3919790b703d3c2f..655c6e8b4fe57224e73f0685c48a6dcbdf6c9917 100644
191--- a/plans/STATUS_SUMMARY.md
192+++ b/plans/STATUS_SUMMARY.md
193@@ -17,7 +17,7 @@
194   - 本地 SQLite + D1 异步同步
195   - `/artifact/` HTTP serve
196   - recent sessions 入口
197-  - 历史上曾包含 stagit 仓库浏览;当前已拆出 `T-S073` 准备从本仓库删除
198+  - repo 静态页能力已迁到 `baa-pgit`;当前仓库不再提供 `/artifact/repo/*`
199 - 插件诊断日志链路已经完成:
200   - 插件 → WS → conductor → `logs/baa-plugin/YYYY-MM-DD.jsonl`
201   - `browser.final_message` ingest → `logs/baa-ingest/YYYY-MM-DD.jsonl`
202@@ -72,21 +72,18 @@
203 - `OPT-002`、`OPT-007` 已分别随 `889f746`、`b8d69c8` 合入 `main`,旧汇总中的 open 状态已改正
204 - `T-BUG-029` / `T-BUG-031` 的任务卡已完成,但旧汇总文档仍把它们写成 pending manual verification;现统一改为“建议补做浏览器复核”
205 - Artifact 静态服务已经完成,不再把它写成“下一阶段主线”
206-- repo 静态页能力已迁到 `baa-pgit`;当前仓库内残留的 stagit 路由与脚本已拆为 `T-S073`
207+- `T-S050` 曾引入 stagit repo 静态页,但该能力已迁到 `baa-pgit`,当前仓库内残留实现已随 `T-S073` 删除
208 
209 ## 当前最高优先级
210 
211 **当前下一波任务:**
212 
213-当前最高优先级任务:
214-
215-- [`../tasks/T-S073.md`](../tasks/T-S073.md):移除 conductor 内的 stagit 仓库静态页能力
216 - [`../tasks/T-S074.md`](../tasks/T-S074.md):删除旧版 watchdog 与 Safari a11y 续命方案
217 - Web UI 主线 `T-S070 -> T-S071 -> T-S072` 保持下一顺位
218 
219 其余没有 open bug / open opt。
220 
221-如继续推进,建议先完成 `T-S073`,再完成 `T-S074`,之后回到 Web UI 主线,再根据新需求或代码审查结果继续拆任务。
222+如继续推进,建议先完成 `T-S074`,之后回到 Web UI 主线,再根据新需求或代码审查结果继续拆任务。
223 
224 **并行优化项:**
225 
226diff --git a/scripts/git-snapshot.sh b/scripts/git-snapshot.sh
227deleted file mode 100755
228index 6ef396d92a9e39b161c97802a4e83cff37c64c0f..0000000000000000000000000000000000000000
229--- a/scripts/git-snapshot.sh
230+++ /dev/null
231@@ -1,49 +0,0 @@
232-#!/usr/bin/env bash
233-# git-snapshot.sh — generate static HTML for a git repo using stagit.
234-# Usage: git-snapshot.sh <repo_path> <output_dir>
235-#
236-# Supports incremental generation via stagit -c (cachefile).
237-
238-set -euo pipefail
239-
240-REPO_PATH="${1:?Usage: git-snapshot.sh <repo_path> <output_dir>}"
241-OUTPUT_DIR="${2:?Usage: git-snapshot.sh <repo_path> <output_dir>}"
242-
243-# Resolve to absolute paths.
244-REPO_PATH="$(cd "$REPO_PATH" && pwd)"
245-
246-# Ensure output directory exists.
247-mkdir -p "$OUTPUT_DIR"
248-OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
249-
250-CACHEFILE="$OUTPUT_DIR/.stagit-cache"
251-
252-# stagit must be run from the output directory.
253-cd "$OUTPUT_DIR"
254-
255-# Generate static HTML (with incremental cache).
256-stagit -c "$CACHEFILE" "$REPO_PATH"
257-
258-# Provide a minimal style.css if one doesn't exist yet.
259-if [ ! -f "$OUTPUT_DIR/style.css" ]; then
260-  cat > "$OUTPUT_DIR/style.css" <<'CSSEOF'
261-body { font-family: monospace; margin: 1em; background: #fff; color: #222; }
262-table { border-collapse: collapse; }
263-td, th { padding: 2px 6px; }
264-a { color: #005f87; }
265-pre { overflow-x: auto; }
266-hr { border: 0; border-top: 1px solid #ccc; }
267-#content { overflow-x: auto; }
268-.num { text-align: right; }
269-CSSEOF
270-fi
271-
272-# Create a 1x1 transparent PNG for logo/favicon if missing.
273-if [ ! -f "$OUTPUT_DIR/logo.png" ]; then
274-  printf '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01\r\n\xb4\x00\x00\x00\x00IEND\xaeB`\x82' > "$OUTPUT_DIR/logo.png"
275-fi
276-if [ ! -f "$OUTPUT_DIR/favicon.png" ]; then
277-  cp "$OUTPUT_DIR/logo.png" "$OUTPUT_DIR/favicon.png"
278-fi
279-
280-echo "stagit: generated static HTML in $OUTPUT_DIR"
281diff --git a/tasks/T-S073.md b/tasks/T-S073.md
282index 41f9c32868d0e4bd8113a14da997a5d1e2d8fb3a..bf63e3fb7f06d8eb5db92c56ce7f8035ece845b4 100644
283--- a/tasks/T-S073.md
284+++ b/tasks/T-S073.md
285@@ -2,7 +2,7 @@
286 
287 ## 状态
288 
289-- 当前状态:`待开始`
290+- 当前状态:`已完成`
291 - 规模预估:`M`
292 - 依赖任务:无
293 - 建议执行者:`Codex`
294@@ -21,7 +21,7 @@
295 
296 - 仓库:`/Users/george/code/baa-conductor`
297 - 分支基线:`main`
298-- 提交:`9674c30`
299+- 提交:`4aea341`
300 
301 ## 分支与 worktree(强制)
302 
303@@ -159,26 +159,39 @@
304 
305 ## 执行记录
306 
307-> 以下内容由执行任务的 AI 填写,创建任务时留空。
308-
309 ### 开始执行
310 
311-- 执行者:
312-- 开始时间:
313-- 状态变更:
314+- 执行者:`Codex`
315+- 开始时间:`2026-04-01 19:57:00 CST`
316+- 状态变更:`待开始` → `进行中`
317 
318 ### 完成摘要
319 
320-- 完成时间:
321-- 状态变更:
322+- 完成时间:`2026-04-01 20:04:52 CST`
323+- 状态变更:`进行中` → `已完成`
324 - 修改了哪些文件:
325+  - `apps/conductor-daemon/src/local-api.ts`
326+  - `apps/conductor-daemon/src/index.test.js`
327+  - `scripts/git-snapshot.sh`
328+  - `tasks/TASK_OVERVIEW.md`
329+  - `plans/STATUS_SUMMARY.md`
330+  - `plans/ARTIFACT_STATIC_SERVICE.md`
331+  - `PROGRESS/2026-03-29-current-code-progress.md`
332+  - `tasks/T-S073.md`
333 - 核心实现思路:
334+  - 直接删除 `service.artifact.repo` 路由、`handleArtifactRepoRead` 和 repo 静态页专用 content-type/路径校验逻辑,不保留跳转或兼容层
335+  - 把 repo 静态页测试改成负向断言,确认 `/artifact/repo/*` 现在返回 `404`,同时保留普通 artifact 路径回归覆盖
336+  - 删除 `scripts/git-snapshot.sh`,并把当前文档口径统一改成“repo 静态页能力已迁到 `baa-pgit`”
337 - 跑了哪些测试:
338+  - `pnpm -C /Users/george/code/baa-conductor-remove-stagit-repo-route/apps/conductor-daemon test`
339+  - `pnpm -C /Users/george/code/baa-conductor-remove-stagit-repo-route/apps/conductor-daemon build`
340+  - `rg -n --glob '!tasks/archive/**' --glob '!plans/archive/**' --glob '!bugs/archive/**' --glob '!node_modules/**' "artifact/repo|stagit|git-snapshot\\.sh" /Users/george/code/baa-conductor-remove-stagit-repo-route`
341 
342 ### 执行过程中遇到的问题
343 
344-- 
345+- 主仓库 worktree 里已有未提交的文档改动,因此先确认 `main` 与 `origin/main` 一致,再从 `main` 新建独立 worktree,避免污染主仓库工作区
346+- 新建 worktree 默认没有 `node_modules`,首次执行 `pnpm test` 时 `pnpm exec tsc` 缺失;在 worktree 根目录执行一次 `pnpm install` 后恢复正常
347 
348 ### 剩余风险
349 
350-- 
351+- 历史任务卡、archive 文档和历史需求文档仍保留 stagit 描述;这是按任务约束刻意保留的历史记录,不代表当前主线能力
352diff --git a/tasks/TASK_OVERVIEW.md b/tasks/TASK_OVERVIEW.md
353index b1ccb5084eec4a5322ccb24cdcc0a4767a7620c8..f2756edcf887b3b0c862114003bcd8652b87d620 100644
354--- a/tasks/TASK_OVERVIEW.md
355+++ b/tasks/TASK_OVERVIEW.md
356@@ -16,7 +16,7 @@
357   - `artifact-db` 持久化 messages / executions / sessions
358   - `conductor` HTTP serve `/artifact/`
359   - D1 异步同步队列
360-  - 历史上曾提供 stagit 仓库浏览;当前已拆出 `T-S073` 准备从本仓库删除这部分能力
361+  - repo 静态页能力已迁到 `baa-pgit`;当前仓库不再提供 `/artifact/repo/*`
362 - Firefox 插件诊断日志链路已落地:
363   - `page-interceptor -> content-script -> controller -> WS -> conductor`
364   - conductor 写 `logs/baa-plugin/YYYY-MM-DD.jsonl`
365@@ -72,7 +72,7 @@
366 - `OPT-002`、`OPT-007` 已分别随 `889f746`、`b8d69c8` 合入 `main`,旧总览中的 open 状态已改正
367 - `T-BUG-029`、`T-BUG-031` 的任务卡已是 `已完成`,但旧文档仍把它们写成 pending manual verification;现统一改为“建议补做浏览器复核”
368 - Artifact 静态服务已经完成,不再把 `T-S039`~`T-S045` 写成“当前活跃主线”
369-- `T-S050` 虽已完成,但 repo 静态页已迁到 `baa-pgit`;现新增 `T-S073` 从当前仓库删除残留的 stagit 能力
370+- `T-S050` 曾引入 stagit repo 静态页,但该能力已迁到 `baa-pgit`,当前仓库内残留实现已随 `T-S073` 删除
371 
372 ## 当前活跃任务与优先级
373 
374@@ -95,6 +95,7 @@
375 | [`T-S068`](./T-S068.md) | ChatGPT proxy send 冷启动降级保护 | S | 无 | Codex | 已完成 |
376 | [`T-S069`](./T-S069.md) | proxy_delivery 成功语义增强 | L | T-S060 | Codex | 已完成 |
377 | [`T-S065`](./T-S065.md) | policy 配置化 | M | 无 | Codex | 已完成 |
378+| [`T-S073`](./T-S073.md) | 移除 conductor 内的 stagit 仓库静态页能力 | M | 无 | Codex | 已完成 |
379 
380 ### 当前下一波任务
381 
382@@ -102,7 +103,6 @@
383 
384 | 任务 | 标题 | 规模 | 依赖 | 建议 AI | 状态 |
385 |---|---|---|---|---|---|
386-| [`T-S073`](./T-S073.md) | 移除 conductor 内的 stagit 仓库静态页能力 | M | 无 | Codex | 待开始 |
387 | [`T-S074`](./T-S074.md) | 删除旧版 watchdog 与 Safari a11y 续命方案 | S | 无 | Codex | 待开始 |
388 | [`T-S070`](./T-S070.md) | Conductor UI 基础设施:Vue 3 脚手架与 `/app` 静态托管 | M | 无 | Codex | 待开始 |
389 | [`T-S071`](./T-S071.md) | Conductor UI 会话鉴权:登录页与浏览器 session | M | T-S070 | Codex | 待开始 |
390@@ -110,11 +110,11 @@
391 
392 当前没有 open bug / open opt。
393 
394-如继续推进,建议先按 `T-S073 -> T-S074` 收缩旧实现,再回到 `T-S070 -> T-S071 -> T-S072` 的 Web UI 主线。
395+如继续推进,建议先完成 `T-S074`,再回到 `T-S070 -> T-S071 -> T-S072` 的 Web UI 主线。
396 
397 说明:
398 
399-- `T-S073` / `T-S074` 是当前仓库边界收缩任务
400+- `T-S073` 已完成;`T-S074` 是剩余的仓库边界收缩任务
401 - `T-S070` / `T-S071` / `T-S072` 仍是后续正式 Web UI 主线
402 - `Channels` 工作区和正式 `channel` 域模型继续留在 `T-S072` 之后再拆下一轮
403 
404@@ -140,6 +140,7 @@
405 | T-S048 | Gemini 投递适配器 | ✅ |
406 | T-S049 | 开放 chatgpt/gemini target | ✅ |
407 | T-S050 | stagit git 静态页面 | ✅ |
408+| T-S070 | 移除 conductor 内的 stagit 仓库静态页能力 | ✅ |
409 | T-S052 | D1 数据库初始化 | ✅ |
410 | T-S053 | 插件诊断日志 | ✅ |
411 | T-S054 | 插件日志 WS 转发 | ✅ |
412@@ -200,12 +201,11 @@
413 
414 ## 当前主线判断
415 
416-Phase 1(浏览器主链)、Artifact 静态服务,以及 timed-jobs + 续命主线都已完成收口。`T-S060`、`T-S061`、`T-S062`、`T-S063`、`T-S064`、`T-S065`、`T-S066`、`T-S067`、`T-S068`、`T-S069` 已全部落地;当前主线没有 open bug blocker,也没有 open opt,但已新增 `T-S073` / `T-S074` 用于收缩不再需要的 stagit 与 watchdog 旧能力。
417+Phase 1(浏览器主链)、Artifact 静态服务,以及 timed-jobs + 续命主线都已完成收口。`T-S060`、`T-S061`、`T-S062`、`T-S063`、`T-S064`、`T-S065`、`T-S066`、`T-S067`、`T-S068`、`T-S069` 已全部落地;当前主线没有 open bug blocker,也没有 open opt,`T-S073` 已完成,剩余待开始任务是 `T-S074` 与后续 Web UI 主线。
418 
419 如果继续推进,建议:
420 
421 - 远端 `baa-conductor-artifact` 已在 `2026-04-01` 应用 [`packages/d1-client/src/d1-setup.sql`](../packages/d1-client/src/d1-setup.sql) 的最新 schema;后续如新建或重置 D1 环境,仍需先执行同一脚本
422-- 先完成 `T-S073`
423 - 再完成 `T-S074`
424 - 之后回到 `T-S070 -> T-S071 -> T-S072` 的正式 Web UI 工作台主线
425