From 6535e6d6a68e8b4057460fbf32c46f2b0b532173 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 26 Jun 2026 07:52:14 +0000 Subject: [PATCH] feat: add skill health size check --- .agents/skills/unidesk-cicd/SKILL.md | 319 +------------ .../skills/unidesk-cicd/references/full.md | 317 +++++++++++++ .agents/skills/unidesk-code-queue/SKILL.md | 239 +--------- .../unidesk-code-queue/references/full.md | 237 ++++++++++ .agents/skills/unidesk-gh/SKILL.md | 439 +----------------- .agents/skills/unidesk-gh/references/full.md | 434 +++++++++++++++++ .agents/skills/unidesk-oa/SKILL.md | 80 +--- .agents/skills/unidesk-oa/references/full.md | 78 ++++ .agents/skills/unidesk-ops/SKILL.md | 275 +---------- .agents/skills/unidesk-ops/references/full.md | 276 +++++++++++ .agents/skills/unidesk-otel/SKILL.md | 174 +------ .../skills/unidesk-otel/references/full.md | 176 +++++++ .agents/skills/unidesk-sub2api/SKILL.md | 288 +----------- .../skills/unidesk-sub2api/references/full.md | 287 ++++++++++++ .agents/skills/unidesk-trans/SKILL.md | 352 +------------- .../skills/unidesk-trans/references/full.md | 353 ++++++++++++++ .agents/skills/unidesk-webdev/SKILL.md | 271 +---------- .../skills/unidesk-webdev/references/full.md | 268 +++++++++++ scripts/cli.ts | 19 + scripts/src/check.ts | 1 + scripts/src/health.ts | 243 ++++++++++ scripts/src/help.ts | 3 + 22 files changed, 2849 insertions(+), 2280 deletions(-) create mode 100644 .agents/skills/unidesk-cicd/references/full.md create mode 100644 .agents/skills/unidesk-code-queue/references/full.md create mode 100644 .agents/skills/unidesk-gh/references/full.md create mode 100644 .agents/skills/unidesk-oa/references/full.md create mode 100644 .agents/skills/unidesk-ops/references/full.md create mode 100644 .agents/skills/unidesk-otel/references/full.md create mode 100644 .agents/skills/unidesk-sub2api/references/full.md create mode 100644 .agents/skills/unidesk-trans/references/full.md create mode 100644 .agents/skills/unidesk-webdev/references/full.md create mode 100644 scripts/src/health.ts diff --git a/.agents/skills/unidesk-cicd/SKILL.md b/.agents/skills/unidesk-cicd/SKILL.md index 582124fb..39399707 100644 --- a/.agents/skills/unidesk-cicd/SKILL.md +++ b/.agents/skills/unidesk-cicd/SKILL.md @@ -3,315 +3,32 @@ name: unidesk-cicd description: UniDesk CI/CD 控制面 — `hwlab g14` 和 `agentrun` 子命令,覆盖 PR 监控自动合并、Tekton/Argo 控制面、git-mirror、Secret、observability、CI tools image、PipelineRun 清理、AgentRun v0.1 部署和 AgentRun YAML-only lane 部署。用户提到 CI/CD、deploy、rollout、PipelineRun、trigger、git-mirror、control-plane、k3s 部署、agentrun 部署、hwlab g14、monitor-prs、trigger-current 时使用。任何需要把代码变更推送部署到 G14 k3s 的操作都必须走本 skill。 --- -# UniDesk HWLAB G14 CI/CD CLI +# UniDesk CI/CD -HWLAB G14 的 PR → CI → CD 控制面和运维入口,统一通过 `bun scripts/cli.ts hwlab g14 ...` 管理。 +HWLAB G14 和 AgentRun CI/CD 的受控入口。任何 PR 监控、Tekton/Argo、git-mirror、Secret、observability、CI tools image、PipelineRun 清理或 AgentRun 部署都必须走 `bun scripts/cli.ts`。 -**固定入口前缀**: `cd /root/unidesk && bun scripts/cli.ts hwlab g14 ...` - ---- - -## PR 监控与自动合并 - -### G14 主线 +## 高频入口 ```bash -bun scripts/cli.ts hwlab g14 monitor-prs \ - [--lane g14|v02] [--once] [--dry-run] \ - [--interval-seconds N] [--max-cycles N] [--timeout-seconds N] -``` - -后台 worker 监控 `pikasTech/HWLAB` 的 open PR → preflight → 自动合并 → 观察 CI/CD 直到 DEV `Synced/Healthy`。成功 rollout 后自动追加指挥简报。状态指针按用途分离(`latest-monitor-job.json` / `latest-once-job.json` 等)。 - -### v0.2 lane - -```bash -bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 [--once] [--dry-run] -``` - -只监控 base=`v0.2` 的 PR。CD 采用 latest-only:旧 PipelineRun 不取消不等待,stale commit 以 superseded/no-op 收口。合并后在原 PR 下追加语义化状态评论(含起止时间、source commit、PipelineRun、targetValidation、git mirror 状态)。 - -### v0.3 lane - -```bash -bun scripts/cli.ts hwlab g14 monitor-prs --lane v03 [--once] [--dry-run] -``` - -只监控 base=`v0.3` 的 PR。ready PR 经 UniDesk `gh pr merge` 合并后触发 runtime lane CD,检查 PipelineRun、Argo、`hwlab-v03` runtime public probes 和 Git mirror flush,并对失败 check、冲突、CD failure/timeout 创建或更新 failure issue。public probe 必须使用 `config/hwlab-node-lanes.yaml` 选中 node/lane 的 formal public URL;D601 `v0.3` 当前是 `https://hwlab.pikapython.com`,裸 IP、FRP 端口和 legacy `20666/20667` 只作为边缘诊断证据,不能作为用户入口验收口径。 - ---- - -## 控制面(Tekton/Argo) - -### 状态查询 - -```bash -# 最新 head +bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --once --dry-run bun scripts/cli.ts hwlab g14 control-plane status --lane v02 - -# 定点 PipelineRun -bun scripts/cli.ts hwlab g14 control-plane status \ - --lane v02 --pipeline-run hwlab-v02-ci-poll- - -# 定点 source commit -bun scripts/cli.ts hwlab g14 control-plane status \ - --lane v02 --source-commit +bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm --wait +bun scripts/cli.ts hwlab g14 git-mirror status --lane v02 +bun scripts/cli.ts agentrun control-plane status ``` -定点 status 输出 `targetValidation.state=passed|superseded`,只检查指定 target 的证据。 +完整 PR monitor、control-plane、git-mirror、Secret、observability、platform-infra、CI tools image、PipelineRun 清理、rollout 补记和 AgentRun v0.1 部署矩阵见 [references/full.md](references/full.md)。 -### 手动触发 +## P0 边界 -```bash -bun scripts/cli.ts hwlab g14 control-plane trigger-current \ - --lane v02|v03 [--dry-run|--confirm] -``` +- CI/CD、GitOps、rollout、PipelineRun、Argo、git-mirror 和 AgentRun 部署必须走受控 CLI;不要用裸 `kubectl`、`argo`、`tkn`、`curl` 当正式控制入口。 +- 触发或验收 rollout 时必须绑定 lane、source commit、PipelineRun/GitOps revision 和用户入口验证结果。 +- Secret 只通过 YAML sourceRef/targetKey 和受控 CLI 下发;输出只披露 presence/fingerprint。 +- 长命令用异步 job 或短轮询;不要长时间挂住 trans/ssh。 -从 `/root/hwlab-v02-cicd.git` 解析当前 `origin/v0.2` full SHA,创建 commit-pinned PipelineRun。confirmed trigger 创建异步 job 并立即返回 `job.id`。 +## 何时读取 reference -### 应用 RBAC/Pipeline/Argo - -```bash -bun scripts/cli.ts hwlab g14 control-plane apply --lane v02 [--dry-run|--confirm] -``` - -server-side apply v02 的 Tekton RBAC、Pipeline 和 Argo Application。 - -### D601 节点本地 infra bootstrap - -```bash -bun scripts/cli.ts hwlab nodes control-plane infra plan --node D601 --lane v03 -bun scripts/cli.ts hwlab nodes control-plane infra status --node D601 --lane v03 -bun scripts/cli.ts hwlab nodes control-plane infra apply --node D601 --lane v03 --dry-run -bun scripts/cli.ts hwlab nodes control-plane infra apply --node D601 --lane v03 --confirm -bun scripts/cli.ts hwlab nodes control-plane infra tools-image status --node D601 --lane v03 -bun scripts/cli.ts hwlab nodes control-plane infra tools-image build --node D601 --lane v03 --confirm -bun scripts/cli.ts hwlab nodes control-plane infra runtime-image status --node D601 --lane v03 -bun scripts/cli.ts hwlab nodes control-plane infra runtime-image preload --node D601 --lane v03 --confirm -bun scripts/cli.ts hwlab nodes control-plane infra runtime-image logs --node D601 --lane v03 -bun scripts/cli.ts hwlab nodes control-plane infra argo status --node D601 --lane v03 -bun scripts/cli.ts hwlab nodes control-plane infra argo apply --node D601 --lane v03 --confirm -bun scripts/cli.ts hwlab nodes control-plane status --node D601 --lane v03 [--pipeline-run |--source-commit ] [--full|--raw] -bun scripts/cli.ts hwlab nodes control-plane trigger-current --node D601 --lane v03 --confirm --wait -bun scripts/cli.ts hwlab nodes control-plane sync --node D601 --lane v03 --confirm -``` - -从 `config/hwlab-node-control-plane.yaml` 渲染 D601 HWLAB v03 的节点本地 CI/CD、git-mirror、Tekton、runtime dependency image preload 和 Argo 前置对象。confirmed apply 只做 control-plane bootstrap,不触发 runtime rollout,不创建 PK01 DB,也不修改 Caddy/FRP。node-local registry 镜像只能作为 tools image 或 runtime dependency 的输出 artifact;输入 base/pull image 必须是 YAML 中声明的公开 registry 来源,缺失 output image 时通过 `status.next.blockers` 或 `runtime-image status` 暴露。D601 Argo CD 安装也必须由 YAML 声明:官方 manifest URL、版本、镜像 rewrite/preload、CRD、期望 workload 和 AppProject/Application 都来自 YAML,不能使用手工 kubectl/argo CLI 作为正式安装路径。 - -`hwlab nodes control-plane status` 默认返回 compact commander summary,只保留 source commit、PipelineRun、Argo、runtime readiness、public probe 和 next action;完整 expected YAML/render target、kubectl result tail、Secret/sourceRef 详情和 probe 原始结果只在 `--full` 或 `--raw` 下展开。 - -`hwlab nodes control-plane sync --confirm` 是 Argo runtime 收敛修复入口:会先按 YAML `runtimeStore.postgres.mode=local-k3s` 同步本地 postgres bootstrap Secret,再终止卡住的 running Argo operation、删除失败 hook Job,并在 StatefulSet template 已更新但旧 controller-revision pod 因 `ImagePullBackOff` / `ErrImagePull` / `CrashLoopBackOff` 卡住时受控删除该旧 pod,让 StatefulSet 按最新 revision 重建。不要手工裸删 pod;需要解除这类死锁时走该入口。 - -`hwlab nodes control-plane trigger-current --node --lane --confirm --wait` 是 node/lane CI/CD 一键入口:按 YAML 解析 source head,执行 git-mirror pre-sync/pre-flush,刷新 control-plane,创建或复用 commit-pinned PipelineRun,等待 PipelineRun 终态,并在终态成功后执行 post-flush。默认输出必须是低噪声 CICD 表格摘要;完整 JSON 只能通过 `--full` 或 `--raw` 展开。120 秒是严重超时阈值:PipelineRun wait 或 `trigger-current` total elapsed 超过 120 秒时,即使最终 status=ok/completed,也必须输出并在 closeout 中记录 `node-runtime-trigger-over-120s` warning、total elapsed、pipeline wait、git mirror status,并从 env-reuse 和 git-mirror/control-plane path 着手排查;未到终态时 CLI 返回 `pending` warning,不继续长时间阻塞,也不把仍在运行误报为构建失败。小范围 PR 触发 120s 时必须看 `plan-artifacts` 的 `affectedServices/buildServices/reusedServices`:如果 source diff 很小却出现所有 envreuse 服务都在 `buildServices` 且 `reusedServices=[]`,优先怀疑 current GitOps artifact catalog 没有 hydrate 到 source plan 阶段,而不是继续盲目重跑 PipelineRun。 - -### G14 v0.3 runtime base image - -```bash -bun scripts/cli.ts hwlab nodes control-plane runtime-image status --node G14 --lane v03 -bun scripts/cli.ts hwlab nodes control-plane runtime-image preload --node G14 --lane v03 --confirm -``` - -G14 v0.3 的 Tekton/BuildKit base image 也走 `config/hwlab-node-lanes.yaml`:`baseImageSource` 是公开来源,`baseImage` 是 node-local registry 目标。缺失 base image 时先用 `runtime-image status` 判断 `registryTagPresent`,再用 `preload --confirm` seed;不要手工 `docker tag/push`。`trigger-current` 后若 PipelineRun 已越过 base image 阶段但卡在某个 service build task,按 TaskRun 单独提 issue/修复,不把它并回 base-image preload 问题。长期边界见 `docs/reference/g14.md`。 - -D601/v03 env-reuse service build task 失败时,先看 `build-` TaskRun 的 `step-publish` 日志;Debian apt、npm、Go module 等外部依赖下载通过 lane YAML 注入 egress proxy 后可能出现 502、reset 或超时。先用 `platform-infra sub2api status|validate` 区分 proxy 整体故障和单个上游 transient;proxy 健康但单次下载 transient 时可以受控 `trigger-current --rerun`,重复失败应修 HWLAB `scripts/artifact-publish.mjs` / envRecipe 的有限 retry 后合并发布,不手工 patch pod 或裸删 PipelineRun。若 Pod 内 unset `HTTP_PROXY/HTTPS_PROXY/ALL_PROXY` 后外部 registry/DNS 不可达,说明该 lane 的外部依赖下载依赖 egress proxy;此时 npm/Bun retry 只能降噪,根因仍是 proxy upstream 或 catalog/plan 误触发的过量 build。 - ---- - -## Git Mirror - -```bash -bun scripts/cli.ts hwlab g14 git-mirror status -bun scripts/cli.ts hwlab g14 git-mirror apply [--dry-run|--confirm] -bun scripts/cli.ts hwlab g14 git-mirror sync [--dry-run|--confirm] -bun scripts/cli.ts hwlab g14 git-mirror flush [--dry-run|--confirm] - -# D601 node-local v0.3 lane -bun scripts/cli.ts hwlab nodes git-mirror status --node D601 --lane v03 -bun scripts/cli.ts hwlab nodes git-mirror sync --node D601 --lane v03 --confirm --wait -bun scripts/cli.ts hwlab nodes git-mirror flush --node D601 --lane v03 --confirm --wait -``` - -- `apply`: 渲染并 apply `devops-infra/git-mirror.yaml` -- `sync`: 把当前配置声明的 GitHub refs 拉入本地 mirror -- `flush`: 把本地 lane GitOps ref 快进推回 GitHub - -PipelineRun `gitops-promote` 如果报 git mirror 控制面漂移、refs 不一致或 flush/publish 未完成,优先按当前 `devops-infra/git-mirror.yaml` 收敛:先 `git-mirror apply --confirm`,再 `git-mirror sync --confirm --wait`,然后用 `control-plane cleanup-runs --pipeline-run --confirm` 受控清理失败 PipelineRun 后重试。旧 branch/path allowlist gate 已删除,不要恢复旧 hook、直接 `kubectl delete`、手工 patch pod 内 hook 或绕过 `flush`。 - -手动 trigger closeout 不能只看 PipelineRun `Completed`。必须继续查 `control-plane status --pipeline-run ` 和 `git-mirror status`;node-scoped `trigger-current --confirm --wait` 会自动做必要的 mirror pre/post flush,但 closeout 仍要确认最终 `pendingFlush=false`、`githubInSync=true`。如果 lower-level 手工路径或旧 job 留下 `pendingFlush=true`,执行 `git-mirror flush --confirm --wait` 到 `githubInSync=true`。 - -node-scoped lane 可能在本次 PR 合并后又被后续 PR 推进。`control-plane status --pipeline-run ` 是定点观察某个 PipelineRun,但输出里的当前 `sourceHead` / `summary.sourceCommit` 可能已经是最新 branch tip,而不是该 PipelineRun 名称对应的 merge commit。closeout 必须同时记录 PR merge commit、PipelineRun 名称/状态、Argo sync revision、当前 branch tip,并用 `git merge-base --is-ancestor HEAD` 或等价证据说明最新 tip 包含本次 PR;不要只凭当前 source head 判断本次 rollout。 - -`trigger-current --node D601 --lane v03 --confirm --wait` 可能先解析到一个 source commit,随后在 control-plane apply 的 `source-render` / `local-git-clone-worktree` 阶段因 `v0.3` 被并行 PR 推进而 clone 到更新 head,触发 `rev-parse HEAD == source_commit` 校验失败。这不是业务代码构建失败;先执行 `hwlab nodes git-mirror sync --node D601 --lane v03 --confirm --wait`,再快进固定 worktree,确认原 PR merge commit 是最新 `origin/v0.3` 的祖先,然后按最新 branch tip 重新运行 `trigger-current`。收口时同时记录原 PR merge commit、最新 source head 和 ancestor 证据。 - -D601 `v0.3` 固定 worktree 的 fetch remote 是 node-local git mirror。GitHub PR 合并后,如果 `D601:/home/ubuntu/workspace/hwlab-v03` 中 `git fetch origin v0.3` 仍看不到最新 merge commit,先执行 `hwlab nodes git-mirror sync --node D601 --lane v03 --confirm --wait`,再在固定 worktree `git fetch origin v0.3 && git pull --ff-only origin v0.3`。D601/v03 `git-mirror` 的 GitHub upstream 标准传输固定为 YAML 声明的 SSH:`githubTransport.mode=ssh`,脚本通过 `GIT_SSH` wrapper 访问 `ssh://git@ssh.github.com:443/...`;node-global HTTP proxy 只作为 SSH CONNECT tunnel,不是 GitHub HTTPS auth/token transport。若 CLI 输出 `transport=https`、`GITHUB_TOKEN`、`git-mirror-github-token` 或 HTTPS token sourceRef,按 control-plane drift/配置回归处理:先修 `config/hwlab-node-control-plane.yaml` 并执行 `hwlab nodes control-plane apply --node D601 --lane v03 --confirm`,不要改走 HTTPS、不要增加 fallback、不要用 host workspace repair。`sync/flush` 的 retry 只消费 SSH upstream transient,并在耗尽后输出 stopped/exhausted;`trigger-current --lane v03` 会为 PipelineRun 做 mirror pre-sync,但不替代固定 worktree 的 fetch hygiene。promotion 后若 node-local `git-mirror status` 显示 `pendingFlush=true`,执行 node-local flush 并等到 `pendingFlush=false`、`githubInSync=true`。 - -D601/node-scoped mirror status 的 `githubGitops` 来自本地 mirror cache 的 `refs/mirror-stage/...`;`status` 输出应通过 `refSources.githubFieldsAreMirrorStageCache=true` 显示这一点。`hwlab nodes git-mirror flush --node D601 --lane v03 --confirm --wait` 如果已经显示 `v0.3-gitops -> v0.3-gitops` 推送成功,但随后因 GitHub SSH `kex_exchange_identification` 或 fetch 确认失败导致命令非零退出,会标记 `partialSuccess=push-succeeded-fetch-failed`。当前 CLI 会自动做一次受控 sync/recheck;恢复后输出 `partialSuccessRecovered=true`、`postPushRecovery` 且整体 `ok=true`,未恢复时才把下一步指向 `hwlab nodes git-mirror sync --node D601 --lane v03 --confirm --wait`。不要连续盲目 flush;先刷新 mirror-stage,再用 status 确认 `localGitops=githubGitops`、`pendingFlush=false`、`githubInSync=true`。 - ---- - -## Secret 管理 - -```bash -# 查看 -bun scripts/cli.ts hwlab g14 secret status --lane v02 \ - --name hwlab-v02-openfga|hwlab-v02-master-server-admin-api-key - -# 确保 -bun scripts/cli.ts hwlab g14 secret ensure --lane v02 \ - --name hwlab-v02-master-server-admin-api-key [--dry-run|--confirm] - -# 删除废弃 Secret -bun scripts/cli.ts hwlab g14 secret delete --lane v02 \ - --name [--dry-run|--confirm] -``` - ---- - -## 运行时迁移 - -```bash -bun scripts/cli.ts hwlab g14 control-plane runtime-migration \ - --lane v02 [--dry-run|--confirm] -``` - -通过 `deployment/hwlab-cloud-api` 容器内 migration CLI 执行。 - ---- - -## Observability - -```bash -bun scripts/cli.ts hwlab g14 observability status|apply|query|targets|boundary|closeout \ - [--lane v02] [--promql ] [--expect-count N] [--expect-value V] [--dry-run|--confirm] -``` - -管理 G14 Prometheus 基础设施和 HWLAB v0.2 监控 closeout。 - ---- - -## Platform Infra - -```bash -bun scripts/cli.ts platform-infra sub2api plan|apply|status|validate -bun scripts/cli.ts platform-infra sub2api codex-pool plan|sync|validate|expose|configure-local -bun scripts/cli.ts platform-infra wechat-archive plan|apply|status|validate|pull -bun scripts/cli.ts platform-infra wechat-archive wcf-host-status|collector-plan|collector-apply|collector-status -``` - -- `platform-infra` 是 G14 k3s 上 UniDesk 运维的平台基础设施 namespace;新增平台服务优先进入该 namespace,旧 `devops-infra` 只作为渐进迁移来源。 -- Sub2API 的日常部署、Codex pool、FRP 暴露、master `~/.codex` 配置、验收和排障统一使用 `$unidesk-sub2api`(UniDesk 仓库 `.agents/skills/unidesk-sub2api/SKILL.md`)。 -- WeChat archive 是 platform-infra 的 YAML-first 工作流入口;D601 personal WeChat upstream 必须复用既有 D601 `platform-infra` namespace,`createNamespace=false`,只读 collector 的副本、镜像、WCF host、端口和版本 pin 都以 `config/platform-infra/wechat-archive.yaml` 为准。 -- 如果 WeChatFerry 配套的 PC 微信版本被微信服务端拒绝登录,按上游兼容阻塞处理:把 collector 的 YAML 副本数调为 `0` 并通过 `collector-apply --confirm --wait` 同步,保留 Secret/ConfigMap/PVC 和 Windows 准备态;不要手工 `kubectl scale`、新建 namespace 或采用版本检查绕过工具作为长期路径。 -- UniDesk 仓库 `docs/reference/platform-infra.md` 只保留开发边界、YAML-first 真相和探针口径,不重复日常操作手册。 - ---- - -## CI Tools Image - -```bash -bun scripts/cli.ts hwlab g14 tools-image status -bun scripts/cli.ts hwlab g14 tools-image build \ - --name ci-node-tools --tag \ - [--dockerfile deploy/ci/hwlab-ci-node-tools.Dockerfile] [--dry-run|--confirm] -``` - -在 G14 host 构建并 push 到本地 registry。 - ---- - -## PipelineRun 清理 - -```bash -# 清理已完成 PipelineRun -bun scripts/cli.ts hwlab g14 control-plane cleanup-runs \ - --lane v02|g14|all [--min-age-minutes N] [--limit N] [--dry-run|--confirm] - -# D601/G14 node-scoped runtime lane retention -bun scripts/cli.ts hwlab nodes control-plane cleanup-runs \ - --node D601 --lane v03 [--min-age-minutes N] [--limit N] [--dry-run|--confirm --wait] - -# 补充清理 Released PV -bun scripts/cli.ts hwlab g14 control-plane cleanup-released-pvs \ - --lane all [--limit N] [--dry-run|--confirm] -``` - ---- - -## 手动补记 rollout - -```bash -bun scripts/cli.ts hwlab g14 record-rollout --pr --source-commit -``` - -手动补记 CI/CD 耗时、TaskRun 指标和语义化 changelog 到指挥简报。 - ---- - -## AgentRun 控制面 - -YAML-only lane 以 `config/agentrun.yaml` 为部署真相;node/lane、source workspace/branch、image build、GitOps branch/path、runtime namespace、Secret、外置数据库、manager env、git-mirror 和 edge 暴露都从 YAML 进入 CLI。AgentRun service repo 的 `deploy/deploy.json` 不能作为 UniDesk deployment truth;新 lane 不再维护该文件。 - -```bash -bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02 -bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane cleanup-runners --node D601 --lane v02 [--force-active] [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02 [--pipeline-run |--source-commit ] [--full|--raw] -``` - -- `plan`: 只读解析 YAML,输出控制面、source、image build、GitOps、runtime 和 Secret plan,不打印 Secret value -- `apply`: 按 YAML 渲染并 apply Tekton RBAC/Pipeline、Argo AppProject/Application 和 runtime namespace -- `secret-sync`: 按 YAML 的 Secret sourceRef/keyMapping 同步 runtime Secret 和外置 DB Secret,只输出 fingerprint -- `restart`: patch manager Deployment 的 restart annotation 并等待 rollout,用于 Secret export/DB 连接串变化后让 workload 读取新 Secret;不要手工删除 Pod -- `trigger-current`: 确保 source branch/workspace,删除新 lane source branch 的 `deploy/deploy.json`,构建并推送 YAML 声明的 image,渲染 GitOps/artifact catalog,触发 git-mirror sync 和 provenance PipelineRun;confirmed 运行可返回异步 job,必须用 `job status --tail-bytes 12000` 看 `agentrun-yaml-lane-trigger` progress,再用 `status --pipeline-run ` 轮询收口;confirmed 收尾会尝试恢复 fixed source workspace 到 lane branch,恢复失败只作为 warning 披露 -- `cleanup-runners`: 只清 YAML 选中 lane runtime namespace 中匹配 `deployment.runner.retention.selectors` 的 runner Job/Pod;runner 上限、最后活跃排序、active heartbeat 窗口、age-based cleanup 开关和 selector 都以 YAML 为准。`dry-run` 必须先看 manager facts、inactive candidates、selected 和 `activeRunRisk`;普通 `--confirm` 只删除 selected inactive runner,不替代 CI PipelineRun 清理。`--force-active` 只用于 operator 明确决定强杀所有匹配 runner pod/job 的资源恢复场景,必须先 dry-run 确认 `criteria.forceActive=true` 和 selection 范围;它会中断 active run/command/session,但仍优先于裸 `kubectl delete pod/job`。 -- `status`: 默认返回 compact commander JSON,关键结论在 `.data.summary` 和 `.data.alignment`,完整 YAML target、原始 source/runtime/gitMirror payload 和成功 probe tail 只在 `--full|--raw` 展开 - -YAML-only lane 的长步骤必须由 CLI 拆成短提交和状态轮询:source bootstrap、image build、GitOps publish、git-mirror sync 和 PipelineRun 创建不得塞进一个顶层 `trans` 长连接。GitOps publish 必须使用隔离临时 clone/worktree,不能切换或污染 YAML 声明的固定 source workspace;如果历史失败发布留下 dirty/detached/GitOps branch 状态,只清理已知发布残留并恢复到 lane source branch 后再重试。后台步骤的 `status` 和 `ok` 要共同判定,`status=succeeded` 但 `ok=false` 是终态失败,不继续轮询到超时。 - -AgentRun YAML-only lane closeout 必须同时看当前 source branch tip、目标 PipelineRun、GitOps revision、Argo revision 和 manager source commit。发布过程中如果 source branch 被并行 PR 推进,`status --pipeline-run ` 会通过 `summary.branchDrift` / `alignment.branchDrift` 标记目标 PipelineRun 是否已被当前 branch tip supersede;先确认最新 tip 包含本次修复,再按最新 tip 重新 `trigger-current`。最终只用最新 PipelineRun 的 `status` 中 `aligned=true`、`blockers=[]`、`argoSyncedToGitops=true` 和 `managerSourceMatchesExpected=true` 收口。`trigger-current` 会尝试恢复 YAML 声明的固定 source workspace 到 lane branch;若返回 `source-worktree-restore-failed` warning、workspace dirty,或后续 `status` 仍显示 `summary.source.workspaceDetached=true`,先按 `sourceWorkspaceRestore.failureKind` 修复 fixed workspace,再继续下一轮开发或部署。 - -Runner egress proxy 只从 `config/agentrun.yaml` 的 `deployment.runner.egressProxyUrl` 与 `deployment.runner.noProxyExtra` 进入部署;manager Deployment 必须带 `AGENTRUN_RUNNER_EGRESS_PROXY_URL` 与 `AGENTRUN_RUNNER_NO_PROXY_EXTRA`,验收时还要用真实 `create/apply/send` 触发 runner Job,检查 Pod env、event/trace 和 final response。GitOps 已更新但 Argo 仍在旧 revision 时,走 `agentrun control-plane refresh --node --lane --confirm`,不要手工 patch runtime。 - -Runner 持久化、idle timeout 和 runner retention 只从 `config/agentrun.yaml` 的 `deployment.runner.*` / `deployment.runner.retention` 进入部署,不在 HWLAB 仓库放运维 YAML。验收不能只看 manager Deployment/Pod env;必须用 HWLAB/AgentRun 原入口创建新 turn 或 runner Job,并检查新 runner Job env、session PVC、`AGENTRUN_SOURCE_COMMIT` 和 trace/result 是否复用同一 run 且没有 `reuse-blocked`;runner retention closeout 还要用 `cleanup-runners --dry-run` 证明 over-limit selection 不触碰 active runner。 - -Provider credential 的 `config.toml` 变更同样走 YAML `sourceRef`、`secret-sync` 和 `restart`;lane config 只声明该 lane 需要的 Codex CLI runtime options。不要复制指挥机全局 `~/.codex/config.toml` 作为长期事实,也不要在没有同 lane `auth.json` / API key source 验证的情况下覆盖 provider endpoint。 - -### AgentRun v0.1 兼容入口 - -```bash -bun scripts/cli.ts agentrun control-plane status \ - [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane trigger-current \ - [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane refresh \ - [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane cleanup-runs \ - [--min-age-minutes N] [--limit N] [--dry-run|--confirm] -bun scripts/cli.ts agentrun control-plane cleanup-released-pvs \ - [--limit N] [--dry-run|--confirm] -``` - -- `status`: 只读汇总 source commit、PipelineRun、Argo、manager image、git mirror 和 `aligned` 结论 -- `trigger-current`: 快进 `G14:/root/agentrun-v01` → mirror sync → 创建 `agentrun-v01-ci-` PipelineRun -- `refresh`: Argo hard refresh(不 patch runtime workload) -- `cleanup-runs`: 只清理 `agentrun-ci` 中已完成 PipelineRun + 临时 PVC;不清理 `agentrun-v01` runtime runner Job/Pod/Secret -- `cleanup-released-pvs`: 回收 Released PV - -AgentRun `control-plane status` 的 compact JSON 关键字段在 `.data.summary.sourceCommit`、`.data.summary.expectedPipelineRun`、`.data.summary.runtimeAlignment`、`.data.summary.gitMirror`、`.data.summary.ci.pipelineRun`、`.data.summary.argo` 和 `.data.alignment`,不要假设存在 `.data.status`。触发部署后如果 GitOps 已 promotion 但 git mirror `pendingFlush=true`,先执行 `bun scripts/cli.ts agentrun git-mirror flush --confirm --wait`,再 `control-plane refresh --confirm`,最后用 `control-plane status --full` 证明 `.data.summary.runtimeAlignment.argoSyncedToGitops=true`、`.data.summary.runtimeAlignment.managerSourceMatchesExpected=true` 且 `.data.summary.ci.pipelineRun.status=True`。 - -## AgentRun v0.1 Git Mirror - -```bash -bun scripts/cli.ts agentrun git-mirror status [--full|--raw] -bun scripts/cli.ts agentrun git-mirror sync [--dry-run|--confirm] [--wait] -bun scripts/cli.ts agentrun git-mirror flush [--dry-run|--confirm] [--wait] -``` - -- `status`: 返回 `localV01`/`githubV01`/`localGitops`/`githubGitops`/`pendingFlush`/`githubInSync` -- `sync`: 拉取 GitHub `v0.1` + `v0.1-gitops` refs -- `flush`: 推送本地 `v0.1-gitops` → GitHub - -与 HWLAB v0.2 mirror 共用 `devops-infra` 服务和 cache PVC。 +- PR 自动合并、v0.2/v0.3 lane 差异:读 [references/full.md](references/full.md) 的 PR 监控段。 +- 手动触发、定点 PipelineRun/source commit、RBAC/Pipeline/Argo:读控制面段。 +- git-mirror、Secret、observability、CI tools image、PipelineRun/PV 清理:读对应段。 +- AgentRun v0.1 或 YAML-only lane 部署:读 AgentRun 控制面段。 diff --git a/.agents/skills/unidesk-cicd/references/full.md b/.agents/skills/unidesk-cicd/references/full.md new file mode 100644 index 00000000..582124fb --- /dev/null +++ b/.agents/skills/unidesk-cicd/references/full.md @@ -0,0 +1,317 @@ +--- +name: unidesk-cicd +description: UniDesk CI/CD 控制面 — `hwlab g14` 和 `agentrun` 子命令,覆盖 PR 监控自动合并、Tekton/Argo 控制面、git-mirror、Secret、observability、CI tools image、PipelineRun 清理、AgentRun v0.1 部署和 AgentRun YAML-only lane 部署。用户提到 CI/CD、deploy、rollout、PipelineRun、trigger、git-mirror、control-plane、k3s 部署、agentrun 部署、hwlab g14、monitor-prs、trigger-current 时使用。任何需要把代码变更推送部署到 G14 k3s 的操作都必须走本 skill。 +--- + +# UniDesk HWLAB G14 CI/CD CLI + +HWLAB G14 的 PR → CI → CD 控制面和运维入口,统一通过 `bun scripts/cli.ts hwlab g14 ...` 管理。 + +**固定入口前缀**: `cd /root/unidesk && bun scripts/cli.ts hwlab g14 ...` + +--- + +## PR 监控与自动合并 + +### G14 主线 + +```bash +bun scripts/cli.ts hwlab g14 monitor-prs \ + [--lane g14|v02] [--once] [--dry-run] \ + [--interval-seconds N] [--max-cycles N] [--timeout-seconds N] +``` + +后台 worker 监控 `pikasTech/HWLAB` 的 open PR → preflight → 自动合并 → 观察 CI/CD 直到 DEV `Synced/Healthy`。成功 rollout 后自动追加指挥简报。状态指针按用途分离(`latest-monitor-job.json` / `latest-once-job.json` 等)。 + +### v0.2 lane + +```bash +bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 [--once] [--dry-run] +``` + +只监控 base=`v0.2` 的 PR。CD 采用 latest-only:旧 PipelineRun 不取消不等待,stale commit 以 superseded/no-op 收口。合并后在原 PR 下追加语义化状态评论(含起止时间、source commit、PipelineRun、targetValidation、git mirror 状态)。 + +### v0.3 lane + +```bash +bun scripts/cli.ts hwlab g14 monitor-prs --lane v03 [--once] [--dry-run] +``` + +只监控 base=`v0.3` 的 PR。ready PR 经 UniDesk `gh pr merge` 合并后触发 runtime lane CD,检查 PipelineRun、Argo、`hwlab-v03` runtime public probes 和 Git mirror flush,并对失败 check、冲突、CD failure/timeout 创建或更新 failure issue。public probe 必须使用 `config/hwlab-node-lanes.yaml` 选中 node/lane 的 formal public URL;D601 `v0.3` 当前是 `https://hwlab.pikapython.com`,裸 IP、FRP 端口和 legacy `20666/20667` 只作为边缘诊断证据,不能作为用户入口验收口径。 + +--- + +## 控制面(Tekton/Argo) + +### 状态查询 + +```bash +# 最新 head +bun scripts/cli.ts hwlab g14 control-plane status --lane v02 + +# 定点 PipelineRun +bun scripts/cli.ts hwlab g14 control-plane status \ + --lane v02 --pipeline-run hwlab-v02-ci-poll- + +# 定点 source commit +bun scripts/cli.ts hwlab g14 control-plane status \ + --lane v02 --source-commit +``` + +定点 status 输出 `targetValidation.state=passed|superseded`,只检查指定 target 的证据。 + +### 手动触发 + +```bash +bun scripts/cli.ts hwlab g14 control-plane trigger-current \ + --lane v02|v03 [--dry-run|--confirm] +``` + +从 `/root/hwlab-v02-cicd.git` 解析当前 `origin/v0.2` full SHA,创建 commit-pinned PipelineRun。confirmed trigger 创建异步 job 并立即返回 `job.id`。 + +### 应用 RBAC/Pipeline/Argo + +```bash +bun scripts/cli.ts hwlab g14 control-plane apply --lane v02 [--dry-run|--confirm] +``` + +server-side apply v02 的 Tekton RBAC、Pipeline 和 Argo Application。 + +### D601 节点本地 infra bootstrap + +```bash +bun scripts/cli.ts hwlab nodes control-plane infra plan --node D601 --lane v03 +bun scripts/cli.ts hwlab nodes control-plane infra status --node D601 --lane v03 +bun scripts/cli.ts hwlab nodes control-plane infra apply --node D601 --lane v03 --dry-run +bun scripts/cli.ts hwlab nodes control-plane infra apply --node D601 --lane v03 --confirm +bun scripts/cli.ts hwlab nodes control-plane infra tools-image status --node D601 --lane v03 +bun scripts/cli.ts hwlab nodes control-plane infra tools-image build --node D601 --lane v03 --confirm +bun scripts/cli.ts hwlab nodes control-plane infra runtime-image status --node D601 --lane v03 +bun scripts/cli.ts hwlab nodes control-plane infra runtime-image preload --node D601 --lane v03 --confirm +bun scripts/cli.ts hwlab nodes control-plane infra runtime-image logs --node D601 --lane v03 +bun scripts/cli.ts hwlab nodes control-plane infra argo status --node D601 --lane v03 +bun scripts/cli.ts hwlab nodes control-plane infra argo apply --node D601 --lane v03 --confirm +bun scripts/cli.ts hwlab nodes control-plane status --node D601 --lane v03 [--pipeline-run |--source-commit ] [--full|--raw] +bun scripts/cli.ts hwlab nodes control-plane trigger-current --node D601 --lane v03 --confirm --wait +bun scripts/cli.ts hwlab nodes control-plane sync --node D601 --lane v03 --confirm +``` + +从 `config/hwlab-node-control-plane.yaml` 渲染 D601 HWLAB v03 的节点本地 CI/CD、git-mirror、Tekton、runtime dependency image preload 和 Argo 前置对象。confirmed apply 只做 control-plane bootstrap,不触发 runtime rollout,不创建 PK01 DB,也不修改 Caddy/FRP。node-local registry 镜像只能作为 tools image 或 runtime dependency 的输出 artifact;输入 base/pull image 必须是 YAML 中声明的公开 registry 来源,缺失 output image 时通过 `status.next.blockers` 或 `runtime-image status` 暴露。D601 Argo CD 安装也必须由 YAML 声明:官方 manifest URL、版本、镜像 rewrite/preload、CRD、期望 workload 和 AppProject/Application 都来自 YAML,不能使用手工 kubectl/argo CLI 作为正式安装路径。 + +`hwlab nodes control-plane status` 默认返回 compact commander summary,只保留 source commit、PipelineRun、Argo、runtime readiness、public probe 和 next action;完整 expected YAML/render target、kubectl result tail、Secret/sourceRef 详情和 probe 原始结果只在 `--full` 或 `--raw` 下展开。 + +`hwlab nodes control-plane sync --confirm` 是 Argo runtime 收敛修复入口:会先按 YAML `runtimeStore.postgres.mode=local-k3s` 同步本地 postgres bootstrap Secret,再终止卡住的 running Argo operation、删除失败 hook Job,并在 StatefulSet template 已更新但旧 controller-revision pod 因 `ImagePullBackOff` / `ErrImagePull` / `CrashLoopBackOff` 卡住时受控删除该旧 pod,让 StatefulSet 按最新 revision 重建。不要手工裸删 pod;需要解除这类死锁时走该入口。 + +`hwlab nodes control-plane trigger-current --node --lane --confirm --wait` 是 node/lane CI/CD 一键入口:按 YAML 解析 source head,执行 git-mirror pre-sync/pre-flush,刷新 control-plane,创建或复用 commit-pinned PipelineRun,等待 PipelineRun 终态,并在终态成功后执行 post-flush。默认输出必须是低噪声 CICD 表格摘要;完整 JSON 只能通过 `--full` 或 `--raw` 展开。120 秒是严重超时阈值:PipelineRun wait 或 `trigger-current` total elapsed 超过 120 秒时,即使最终 status=ok/completed,也必须输出并在 closeout 中记录 `node-runtime-trigger-over-120s` warning、total elapsed、pipeline wait、git mirror status,并从 env-reuse 和 git-mirror/control-plane path 着手排查;未到终态时 CLI 返回 `pending` warning,不继续长时间阻塞,也不把仍在运行误报为构建失败。小范围 PR 触发 120s 时必须看 `plan-artifacts` 的 `affectedServices/buildServices/reusedServices`:如果 source diff 很小却出现所有 envreuse 服务都在 `buildServices` 且 `reusedServices=[]`,优先怀疑 current GitOps artifact catalog 没有 hydrate 到 source plan 阶段,而不是继续盲目重跑 PipelineRun。 + +### G14 v0.3 runtime base image + +```bash +bun scripts/cli.ts hwlab nodes control-plane runtime-image status --node G14 --lane v03 +bun scripts/cli.ts hwlab nodes control-plane runtime-image preload --node G14 --lane v03 --confirm +``` + +G14 v0.3 的 Tekton/BuildKit base image 也走 `config/hwlab-node-lanes.yaml`:`baseImageSource` 是公开来源,`baseImage` 是 node-local registry 目标。缺失 base image 时先用 `runtime-image status` 判断 `registryTagPresent`,再用 `preload --confirm` seed;不要手工 `docker tag/push`。`trigger-current` 后若 PipelineRun 已越过 base image 阶段但卡在某个 service build task,按 TaskRun 单独提 issue/修复,不把它并回 base-image preload 问题。长期边界见 `docs/reference/g14.md`。 + +D601/v03 env-reuse service build task 失败时,先看 `build-` TaskRun 的 `step-publish` 日志;Debian apt、npm、Go module 等外部依赖下载通过 lane YAML 注入 egress proxy 后可能出现 502、reset 或超时。先用 `platform-infra sub2api status|validate` 区分 proxy 整体故障和单个上游 transient;proxy 健康但单次下载 transient 时可以受控 `trigger-current --rerun`,重复失败应修 HWLAB `scripts/artifact-publish.mjs` / envRecipe 的有限 retry 后合并发布,不手工 patch pod 或裸删 PipelineRun。若 Pod 内 unset `HTTP_PROXY/HTTPS_PROXY/ALL_PROXY` 后外部 registry/DNS 不可达,说明该 lane 的外部依赖下载依赖 egress proxy;此时 npm/Bun retry 只能降噪,根因仍是 proxy upstream 或 catalog/plan 误触发的过量 build。 + +--- + +## Git Mirror + +```bash +bun scripts/cli.ts hwlab g14 git-mirror status +bun scripts/cli.ts hwlab g14 git-mirror apply [--dry-run|--confirm] +bun scripts/cli.ts hwlab g14 git-mirror sync [--dry-run|--confirm] +bun scripts/cli.ts hwlab g14 git-mirror flush [--dry-run|--confirm] + +# D601 node-local v0.3 lane +bun scripts/cli.ts hwlab nodes git-mirror status --node D601 --lane v03 +bun scripts/cli.ts hwlab nodes git-mirror sync --node D601 --lane v03 --confirm --wait +bun scripts/cli.ts hwlab nodes git-mirror flush --node D601 --lane v03 --confirm --wait +``` + +- `apply`: 渲染并 apply `devops-infra/git-mirror.yaml` +- `sync`: 把当前配置声明的 GitHub refs 拉入本地 mirror +- `flush`: 把本地 lane GitOps ref 快进推回 GitHub + +PipelineRun `gitops-promote` 如果报 git mirror 控制面漂移、refs 不一致或 flush/publish 未完成,优先按当前 `devops-infra/git-mirror.yaml` 收敛:先 `git-mirror apply --confirm`,再 `git-mirror sync --confirm --wait`,然后用 `control-plane cleanup-runs --pipeline-run --confirm` 受控清理失败 PipelineRun 后重试。旧 branch/path allowlist gate 已删除,不要恢复旧 hook、直接 `kubectl delete`、手工 patch pod 内 hook 或绕过 `flush`。 + +手动 trigger closeout 不能只看 PipelineRun `Completed`。必须继续查 `control-plane status --pipeline-run ` 和 `git-mirror status`;node-scoped `trigger-current --confirm --wait` 会自动做必要的 mirror pre/post flush,但 closeout 仍要确认最终 `pendingFlush=false`、`githubInSync=true`。如果 lower-level 手工路径或旧 job 留下 `pendingFlush=true`,执行 `git-mirror flush --confirm --wait` 到 `githubInSync=true`。 + +node-scoped lane 可能在本次 PR 合并后又被后续 PR 推进。`control-plane status --pipeline-run ` 是定点观察某个 PipelineRun,但输出里的当前 `sourceHead` / `summary.sourceCommit` 可能已经是最新 branch tip,而不是该 PipelineRun 名称对应的 merge commit。closeout 必须同时记录 PR merge commit、PipelineRun 名称/状态、Argo sync revision、当前 branch tip,并用 `git merge-base --is-ancestor HEAD` 或等价证据说明最新 tip 包含本次 PR;不要只凭当前 source head 判断本次 rollout。 + +`trigger-current --node D601 --lane v03 --confirm --wait` 可能先解析到一个 source commit,随后在 control-plane apply 的 `source-render` / `local-git-clone-worktree` 阶段因 `v0.3` 被并行 PR 推进而 clone 到更新 head,触发 `rev-parse HEAD == source_commit` 校验失败。这不是业务代码构建失败;先执行 `hwlab nodes git-mirror sync --node D601 --lane v03 --confirm --wait`,再快进固定 worktree,确认原 PR merge commit 是最新 `origin/v0.3` 的祖先,然后按最新 branch tip 重新运行 `trigger-current`。收口时同时记录原 PR merge commit、最新 source head 和 ancestor 证据。 + +D601 `v0.3` 固定 worktree 的 fetch remote 是 node-local git mirror。GitHub PR 合并后,如果 `D601:/home/ubuntu/workspace/hwlab-v03` 中 `git fetch origin v0.3` 仍看不到最新 merge commit,先执行 `hwlab nodes git-mirror sync --node D601 --lane v03 --confirm --wait`,再在固定 worktree `git fetch origin v0.3 && git pull --ff-only origin v0.3`。D601/v03 `git-mirror` 的 GitHub upstream 标准传输固定为 YAML 声明的 SSH:`githubTransport.mode=ssh`,脚本通过 `GIT_SSH` wrapper 访问 `ssh://git@ssh.github.com:443/...`;node-global HTTP proxy 只作为 SSH CONNECT tunnel,不是 GitHub HTTPS auth/token transport。若 CLI 输出 `transport=https`、`GITHUB_TOKEN`、`git-mirror-github-token` 或 HTTPS token sourceRef,按 control-plane drift/配置回归处理:先修 `config/hwlab-node-control-plane.yaml` 并执行 `hwlab nodes control-plane apply --node D601 --lane v03 --confirm`,不要改走 HTTPS、不要增加 fallback、不要用 host workspace repair。`sync/flush` 的 retry 只消费 SSH upstream transient,并在耗尽后输出 stopped/exhausted;`trigger-current --lane v03` 会为 PipelineRun 做 mirror pre-sync,但不替代固定 worktree 的 fetch hygiene。promotion 后若 node-local `git-mirror status` 显示 `pendingFlush=true`,执行 node-local flush 并等到 `pendingFlush=false`、`githubInSync=true`。 + +D601/node-scoped mirror status 的 `githubGitops` 来自本地 mirror cache 的 `refs/mirror-stage/...`;`status` 输出应通过 `refSources.githubFieldsAreMirrorStageCache=true` 显示这一点。`hwlab nodes git-mirror flush --node D601 --lane v03 --confirm --wait` 如果已经显示 `v0.3-gitops -> v0.3-gitops` 推送成功,但随后因 GitHub SSH `kex_exchange_identification` 或 fetch 确认失败导致命令非零退出,会标记 `partialSuccess=push-succeeded-fetch-failed`。当前 CLI 会自动做一次受控 sync/recheck;恢复后输出 `partialSuccessRecovered=true`、`postPushRecovery` 且整体 `ok=true`,未恢复时才把下一步指向 `hwlab nodes git-mirror sync --node D601 --lane v03 --confirm --wait`。不要连续盲目 flush;先刷新 mirror-stage,再用 status 确认 `localGitops=githubGitops`、`pendingFlush=false`、`githubInSync=true`。 + +--- + +## Secret 管理 + +```bash +# 查看 +bun scripts/cli.ts hwlab g14 secret status --lane v02 \ + --name hwlab-v02-openfga|hwlab-v02-master-server-admin-api-key + +# 确保 +bun scripts/cli.ts hwlab g14 secret ensure --lane v02 \ + --name hwlab-v02-master-server-admin-api-key [--dry-run|--confirm] + +# 删除废弃 Secret +bun scripts/cli.ts hwlab g14 secret delete --lane v02 \ + --name [--dry-run|--confirm] +``` + +--- + +## 运行时迁移 + +```bash +bun scripts/cli.ts hwlab g14 control-plane runtime-migration \ + --lane v02 [--dry-run|--confirm] +``` + +通过 `deployment/hwlab-cloud-api` 容器内 migration CLI 执行。 + +--- + +## Observability + +```bash +bun scripts/cli.ts hwlab g14 observability status|apply|query|targets|boundary|closeout \ + [--lane v02] [--promql ] [--expect-count N] [--expect-value V] [--dry-run|--confirm] +``` + +管理 G14 Prometheus 基础设施和 HWLAB v0.2 监控 closeout。 + +--- + +## Platform Infra + +```bash +bun scripts/cli.ts platform-infra sub2api plan|apply|status|validate +bun scripts/cli.ts platform-infra sub2api codex-pool plan|sync|validate|expose|configure-local +bun scripts/cli.ts platform-infra wechat-archive plan|apply|status|validate|pull +bun scripts/cli.ts platform-infra wechat-archive wcf-host-status|collector-plan|collector-apply|collector-status +``` + +- `platform-infra` 是 G14 k3s 上 UniDesk 运维的平台基础设施 namespace;新增平台服务优先进入该 namespace,旧 `devops-infra` 只作为渐进迁移来源。 +- Sub2API 的日常部署、Codex pool、FRP 暴露、master `~/.codex` 配置、验收和排障统一使用 `$unidesk-sub2api`(UniDesk 仓库 `.agents/skills/unidesk-sub2api/SKILL.md`)。 +- WeChat archive 是 platform-infra 的 YAML-first 工作流入口;D601 personal WeChat upstream 必须复用既有 D601 `platform-infra` namespace,`createNamespace=false`,只读 collector 的副本、镜像、WCF host、端口和版本 pin 都以 `config/platform-infra/wechat-archive.yaml` 为准。 +- 如果 WeChatFerry 配套的 PC 微信版本被微信服务端拒绝登录,按上游兼容阻塞处理:把 collector 的 YAML 副本数调为 `0` 并通过 `collector-apply --confirm --wait` 同步,保留 Secret/ConfigMap/PVC 和 Windows 准备态;不要手工 `kubectl scale`、新建 namespace 或采用版本检查绕过工具作为长期路径。 +- UniDesk 仓库 `docs/reference/platform-infra.md` 只保留开发边界、YAML-first 真相和探针口径,不重复日常操作手册。 + +--- + +## CI Tools Image + +```bash +bun scripts/cli.ts hwlab g14 tools-image status +bun scripts/cli.ts hwlab g14 tools-image build \ + --name ci-node-tools --tag \ + [--dockerfile deploy/ci/hwlab-ci-node-tools.Dockerfile] [--dry-run|--confirm] +``` + +在 G14 host 构建并 push 到本地 registry。 + +--- + +## PipelineRun 清理 + +```bash +# 清理已完成 PipelineRun +bun scripts/cli.ts hwlab g14 control-plane cleanup-runs \ + --lane v02|g14|all [--min-age-minutes N] [--limit N] [--dry-run|--confirm] + +# D601/G14 node-scoped runtime lane retention +bun scripts/cli.ts hwlab nodes control-plane cleanup-runs \ + --node D601 --lane v03 [--min-age-minutes N] [--limit N] [--dry-run|--confirm --wait] + +# 补充清理 Released PV +bun scripts/cli.ts hwlab g14 control-plane cleanup-released-pvs \ + --lane all [--limit N] [--dry-run|--confirm] +``` + +--- + +## 手动补记 rollout + +```bash +bun scripts/cli.ts hwlab g14 record-rollout --pr --source-commit +``` + +手动补记 CI/CD 耗时、TaskRun 指标和语义化 changelog 到指挥简报。 + +--- + +## AgentRun 控制面 + +YAML-only lane 以 `config/agentrun.yaml` 为部署真相;node/lane、source workspace/branch、image build、GitOps branch/path、runtime namespace、Secret、外置数据库、manager env、git-mirror 和 edge 暴露都从 YAML 进入 CLI。AgentRun service repo 的 `deploy/deploy.json` 不能作为 UniDesk deployment truth;新 lane 不再维护该文件。 + +```bash +bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02 +bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane cleanup-runners --node D601 --lane v02 [--force-active] [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02 [--pipeline-run |--source-commit ] [--full|--raw] +``` + +- `plan`: 只读解析 YAML,输出控制面、source、image build、GitOps、runtime 和 Secret plan,不打印 Secret value +- `apply`: 按 YAML 渲染并 apply Tekton RBAC/Pipeline、Argo AppProject/Application 和 runtime namespace +- `secret-sync`: 按 YAML 的 Secret sourceRef/keyMapping 同步 runtime Secret 和外置 DB Secret,只输出 fingerprint +- `restart`: patch manager Deployment 的 restart annotation 并等待 rollout,用于 Secret export/DB 连接串变化后让 workload 读取新 Secret;不要手工删除 Pod +- `trigger-current`: 确保 source branch/workspace,删除新 lane source branch 的 `deploy/deploy.json`,构建并推送 YAML 声明的 image,渲染 GitOps/artifact catalog,触发 git-mirror sync 和 provenance PipelineRun;confirmed 运行可返回异步 job,必须用 `job status --tail-bytes 12000` 看 `agentrun-yaml-lane-trigger` progress,再用 `status --pipeline-run ` 轮询收口;confirmed 收尾会尝试恢复 fixed source workspace 到 lane branch,恢复失败只作为 warning 披露 +- `cleanup-runners`: 只清 YAML 选中 lane runtime namespace 中匹配 `deployment.runner.retention.selectors` 的 runner Job/Pod;runner 上限、最后活跃排序、active heartbeat 窗口、age-based cleanup 开关和 selector 都以 YAML 为准。`dry-run` 必须先看 manager facts、inactive candidates、selected 和 `activeRunRisk`;普通 `--confirm` 只删除 selected inactive runner,不替代 CI PipelineRun 清理。`--force-active` 只用于 operator 明确决定强杀所有匹配 runner pod/job 的资源恢复场景,必须先 dry-run 确认 `criteria.forceActive=true` 和 selection 范围;它会中断 active run/command/session,但仍优先于裸 `kubectl delete pod/job`。 +- `status`: 默认返回 compact commander JSON,关键结论在 `.data.summary` 和 `.data.alignment`,完整 YAML target、原始 source/runtime/gitMirror payload 和成功 probe tail 只在 `--full|--raw` 展开 + +YAML-only lane 的长步骤必须由 CLI 拆成短提交和状态轮询:source bootstrap、image build、GitOps publish、git-mirror sync 和 PipelineRun 创建不得塞进一个顶层 `trans` 长连接。GitOps publish 必须使用隔离临时 clone/worktree,不能切换或污染 YAML 声明的固定 source workspace;如果历史失败发布留下 dirty/detached/GitOps branch 状态,只清理已知发布残留并恢复到 lane source branch 后再重试。后台步骤的 `status` 和 `ok` 要共同判定,`status=succeeded` 但 `ok=false` 是终态失败,不继续轮询到超时。 + +AgentRun YAML-only lane closeout 必须同时看当前 source branch tip、目标 PipelineRun、GitOps revision、Argo revision 和 manager source commit。发布过程中如果 source branch 被并行 PR 推进,`status --pipeline-run ` 会通过 `summary.branchDrift` / `alignment.branchDrift` 标记目标 PipelineRun 是否已被当前 branch tip supersede;先确认最新 tip 包含本次修复,再按最新 tip 重新 `trigger-current`。最终只用最新 PipelineRun 的 `status` 中 `aligned=true`、`blockers=[]`、`argoSyncedToGitops=true` 和 `managerSourceMatchesExpected=true` 收口。`trigger-current` 会尝试恢复 YAML 声明的固定 source workspace 到 lane branch;若返回 `source-worktree-restore-failed` warning、workspace dirty,或后续 `status` 仍显示 `summary.source.workspaceDetached=true`,先按 `sourceWorkspaceRestore.failureKind` 修复 fixed workspace,再继续下一轮开发或部署。 + +Runner egress proxy 只从 `config/agentrun.yaml` 的 `deployment.runner.egressProxyUrl` 与 `deployment.runner.noProxyExtra` 进入部署;manager Deployment 必须带 `AGENTRUN_RUNNER_EGRESS_PROXY_URL` 与 `AGENTRUN_RUNNER_NO_PROXY_EXTRA`,验收时还要用真实 `create/apply/send` 触发 runner Job,检查 Pod env、event/trace 和 final response。GitOps 已更新但 Argo 仍在旧 revision 时,走 `agentrun control-plane refresh --node --lane --confirm`,不要手工 patch runtime。 + +Runner 持久化、idle timeout 和 runner retention 只从 `config/agentrun.yaml` 的 `deployment.runner.*` / `deployment.runner.retention` 进入部署,不在 HWLAB 仓库放运维 YAML。验收不能只看 manager Deployment/Pod env;必须用 HWLAB/AgentRun 原入口创建新 turn 或 runner Job,并检查新 runner Job env、session PVC、`AGENTRUN_SOURCE_COMMIT` 和 trace/result 是否复用同一 run 且没有 `reuse-blocked`;runner retention closeout 还要用 `cleanup-runners --dry-run` 证明 over-limit selection 不触碰 active runner。 + +Provider credential 的 `config.toml` 变更同样走 YAML `sourceRef`、`secret-sync` 和 `restart`;lane config 只声明该 lane 需要的 Codex CLI runtime options。不要复制指挥机全局 `~/.codex/config.toml` 作为长期事实,也不要在没有同 lane `auth.json` / API key source 验证的情况下覆盖 provider endpoint。 + +### AgentRun v0.1 兼容入口 + +```bash +bun scripts/cli.ts agentrun control-plane status \ + [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane trigger-current \ + [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane refresh \ + [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane cleanup-runs \ + [--min-age-minutes N] [--limit N] [--dry-run|--confirm] +bun scripts/cli.ts agentrun control-plane cleanup-released-pvs \ + [--limit N] [--dry-run|--confirm] +``` + +- `status`: 只读汇总 source commit、PipelineRun、Argo、manager image、git mirror 和 `aligned` 结论 +- `trigger-current`: 快进 `G14:/root/agentrun-v01` → mirror sync → 创建 `agentrun-v01-ci-` PipelineRun +- `refresh`: Argo hard refresh(不 patch runtime workload) +- `cleanup-runs`: 只清理 `agentrun-ci` 中已完成 PipelineRun + 临时 PVC;不清理 `agentrun-v01` runtime runner Job/Pod/Secret +- `cleanup-released-pvs`: 回收 Released PV + +AgentRun `control-plane status` 的 compact JSON 关键字段在 `.data.summary.sourceCommit`、`.data.summary.expectedPipelineRun`、`.data.summary.runtimeAlignment`、`.data.summary.gitMirror`、`.data.summary.ci.pipelineRun`、`.data.summary.argo` 和 `.data.alignment`,不要假设存在 `.data.status`。触发部署后如果 GitOps 已 promotion 但 git mirror `pendingFlush=true`,先执行 `bun scripts/cli.ts agentrun git-mirror flush --confirm --wait`,再 `control-plane refresh --confirm`,最后用 `control-plane status --full` 证明 `.data.summary.runtimeAlignment.argoSyncedToGitops=true`、`.data.summary.runtimeAlignment.managerSourceMatchesExpected=true` 且 `.data.summary.ci.pipelineRun.status=True`。 + +## AgentRun v0.1 Git Mirror + +```bash +bun scripts/cli.ts agentrun git-mirror status [--full|--raw] +bun scripts/cli.ts agentrun git-mirror sync [--dry-run|--confirm] [--wait] +bun scripts/cli.ts agentrun git-mirror flush [--dry-run|--confirm] [--wait] +``` + +- `status`: 返回 `localV01`/`githubV01`/`localGitops`/`githubGitops`/`pendingFlush`/`githubInSync` +- `sync`: 拉取 GitHub `v0.1` + `v0.1-gitops` refs +- `flush`: 推送本地 `v0.1-gitops` → GitHub + +与 HWLAB v0.2 mirror 共用 `devops-infra` 服务和 cache PVC。 diff --git a/.agents/skills/unidesk-code-queue/SKILL.md b/.agents/skills/unidesk-code-queue/SKILL.md index 2e93d0c8..a2e0c6d6 100644 --- a/.agents/skills/unidesk-code-queue/SKILL.md +++ b/.agents/skills/unidesk-code-queue/SKILL.md @@ -5,233 +5,30 @@ description: UniDesk AgentRun-backed Code Queue CLI — Skill(cli-spec)。legacy # UniDesk Code Queue / AgentRun CLI -旧 Code Queue 已冻结新任务和写入口。`bun scripts/cli.ts codex ...` 现在只作为历史归档、只读排障和残留任务停止入口;新的指挥官派单、Aipod/Artificer 执行、events/logs/result、ack/cancel、dispatch、session follow-up 必须走 AgentRun 资源原语,并按 cli-spec 渐进披露。UniDesk 是 render-only client:默认输出是低噪声 human 表格/摘要,脚本读取显式使用 `-o json|yaml` 的稳定客户端 schema,`--raw` 只用于查看直连 AgentRun REST envelope。 +新任务、Aipod/Artificer 派单、session follow-up、events/logs/result、ack/cancel、dispatch 和 run/command/runner drill-down 走 AgentRun 资源原语。legacy `codex` 只保留历史只读、未读积压和残留任务停止。 -**固定入口前缀**: `cd /root/unidesk && bun scripts/cli.ts agentrun ...` - ---- - -## 新任务入口 +## 高频入口 ```bash -# 查看 AgentRun 指挥官队列 -bun scripts/cli.ts agentrun get tasks --queue commander --limit 20 -bun scripts/cli.ts agentrun get tasks --queue commander -o wide - -# 查看一个 task/run/command/runnerjob/session 生命周期 -bun scripts/cli.ts agentrun describe task/ -bun scripts/cli.ts agentrun events run/ --after-seq --limit 100 -bun scripts/cli.ts agentrun logs session/ --tail 100 -bun scripts/cli.ts agentrun result run/ --command -bun scripts/cli.ts agentrun describe command/ --run -bun scripts/cli.ts agentrun describe runnerjob/ --run - -# 查看 / 渲染 AipodSpec(Artificer 是默认分布式开发 agent) -bun scripts/cli.ts agentrun get aipodspecs -bun scripts/cli.ts agentrun describe aipodspec/Artificer -bun scripts/cli.ts agentrun aipod-specs render Artificer --prompt-stdin - -# AipodSpec apply/delete 仍由客户端资源原语发起,服务端只返回 REST 业务事实 -bun scripts/cli.ts agentrun aipod-specs apply --yaml-stdin --dry-run - -# 提交 AgentRun task manifest -bun scripts/cli.ts agentrun apply -f - --dry-run <<'YAML' -kind: Task -spec: - tenantId: unidesk - projectId: example - queue: commander - title: 任务标题 - payload: - prompt: 任务说明 -YAML - -# 用 AipodSpec 提交,优先用于新任务和 Artificer -bun scripts/cli.ts agentrun create task --aipod Artificer \ - --prompt-stdin --idempotency-key - -# 查看/控制 AgentRun session -bun scripts/cli.ts agentrun logs session/ --tail 100 -bun scripts/cli.ts agentrun ack session/ -bun scripts/cli.ts agentrun dispatch task/ -bun scripts/cli.ts agentrun send session/ --aipod Artificer --prompt-stdin -bun scripts/cli.ts agentrun cancel session/ --reason --dry-run -bun scripts/cli.ts agentrun cancel command/ --run --reason --dry-run +bun scripts/cli.ts agentrun get task --limit 20 +bun scripts/cli.ts agentrun describe task +bun scripts/cli.ts agentrun events --limit 80 +bun scripts/cli.ts agentrun logs --tail 120 +bun scripts/cli.ts agentrun result +bun scripts/cli.ts agentrun send --prompt-stdin ``` -日常 task manifest 优先使用 YAML heredoc:`agentrun apply -f -`;单 prompt 派单优先 `agentrun create task --aipod Artificer --prompt-stdin`;同 session 续跑只使用 `agentrun send session/`。UniDesk 客户端按 `config/agentrun.yaml` 直连 AgentRun REST API,不经过 HWLAB runtime、SSH official CLI 或旧 bridge wrapper;`send` 是唯一用户级 session follow-up 写入口,服务端按 durable session/run/command 状态自动决定内部 `steer` 或新 `turn`,旧 CLI `turn/steer` 路径不保留兼容。`--json-file`、`--prompt-file` 和 `--runner-json-file` 只是客户端输入来源,用于已审阅且可复用的受控文件。它不是旧 Code Queue adapter,不双写,也不迁移旧历史。 +AipodSpec/Artificer、task manifest、session 控制、queue 渐进披露、HWLAB Code Agent 整合、trace/output 分页、judge、中断/取消和旧队列归档见 [references/full.md](references/full.md)。 -`AipodSpec` 是 AgentRun v0.1 的声明式 agent 装配:模型 profile、gitbundle、skills/tools、SecretRef 和 tool credential 都从 YAML 规格渲染。`Artificer` 默认用于 UniDesk 分布式开发任务,使用 `sub2api` provider、`gpt-5.5`、`reasoningEffort=xhigh`,并通过 SecretRef 注入 GitHub PR token、GitHub SSH 和 UniDesk SSH 透传能力。更新规格时使用 `agentrun aipod-specs apply --yaml-stdin --dry-run` 先看计划,确认后再去掉 `--dry-run`;不得把 API key、SSH key 或 token 写入 prompt、payload、YAML 或 issue。 +## P0 边界 -AgentRun task payload 需要 runner 内使用 UniDesk SSH 透传时,只通过 `executionPolicy.secretScope.toolCredentials[].tool=unidesk-ssh` 请求 `agentrun-v01-tool-unidesk-ssh/UNIDESK_SSH_CLIENT_TOKEN` SecretRef;不要把 token 写入 prompt、payload 或 `transientEnv`。非敏感 endpoint 使用 `UNIDESK_MAIN_SERVER_IP`、`UNIDESK_MAIN_SERVER_HOST` 或 `UNIDESK_FRONTEND_URL`,可由 runner-job `transientEnv` 显式提供;G14 `agentrun-v01` manager 也会在缺省时按受控默认值自动补齐,并在 trace 中只显示 env name/count/hash 与 `valuesPrinted=false`。 +- 新写入口不要用 `codex submit/steer/resume`;这些 legacy mutation 已冻结。 +- 默认输出保持低噪声表格/摘要;机器消费显式 `-o json|yaml` 或 `--raw`。 +- Session follow-up 使用 `agentrun send`,由服务端决定内部 steer vs turn。 +- HWLAB Code Agent/CaseRun follow-up 也优先落到 AgentRun resource primitive,不绕过队列状态。 -## Queue 渐进披露 +## 何时读取 reference -AgentRun queue 生命周期不是一个单独的 `queue lifecycle` 命令,而是一组资源原语的渐进披露组合: - -1. 默认总览用 `get tasks --queue commander --limit 20`,只看 task state、queue/lane、run/cmd/rjob/session ref、age 和 attention。 -2. 单任务用 `describe task/`,读取 `latestAttempt.runId`、`commandId`、`runnerJobId`、`sessionId/sessionPath` 和少量 `Next:`。 -3. Run 级状态用 `events run/` 和 `result run/ --command `,判断 terminalClassification、failureKind、provider interruption、timeoutBudget 和 recoveryActions。 -4. Command 级状态用 `describe command/ --run ` 和 `result command/ --run `,确认 command state、ack、terminal status 和结果摘要;确认为单个 active command 卡住时,先用 `cancel command/ --run --reason --dry-run` 核对 `CancelLifecycle` 的 authority、cascade、runner abort 和 fencing,再去掉 `--dry-run` 清理该 command,保留同一个 session 后再用 `send session/` 续跑。 -5. Runner job 只读状态用 `describe runnerjob/ --run `,确认 env image reuse、jobName、namespace、phase、exitCode、retention 和 `valuesPrinted=false`。不要为了这些字段手动调用 `trans G14:k3s kubectl ...`。 -6. Runtime runner Job/Pod retention 或 operator 明确要求强杀 runner 时,不属于单个 task/session 资源原语;使用 `bun scripts/cli.ts agentrun control-plane cleanup-runners --node --lane [--force-active] --dry-run|--confirm`。普通 cleanup 只删 inactive selected runner;`--force-active` 会中断 active run/command/session,必须先 dry-run 确认 selection,并且仍应优先于裸 `kubectl delete pod/job`。 -7. Session trace/output 只在 `describe task` 或 result 里有实际 `sessionId` 时使用 `logs|ack|send|cancel session/`;`sessionRef=null` 时不要猜 session 命令。用户级 follow-up 一律使用 `send session/`,不要回到旧 `turn/steer` 或 `sessions ...` 兼容路径。 -8. 已创建但尚未运行的 task 使用 `dispatch task/` 派发,不再退回旧 bridge `queue dispatch`。 - -默认视图必须低噪声且不是 JSON envelope,`-o json|yaml` 才输出稳定机器结构,`--raw` 才保留直连 AgentRun REST envelope;命令返回里的下一步应优先是 `bun scripts/cli.ts agentrun ...` 资源原语,不得把人工 k8s 查询作为日常下一步。 - -AgentRun cancel 策略由 `config/agentrun.yaml` 的 lane 级 `deployment.runner.cancelLifecycle` 管理;操作 D601、G14 或其他非默认 lane 时必须带 `--node/--lane --dry-run` 先确认 YAML policy,不要依赖全局默认或手动 k8s 强杀来替代资源原语。 - -## HWLAB Code Agent 入口整合 - -HWLAB Code Agent / CaseRun follow-up 的日常派单也归入 AgentRun 资源原语:新任务用 `create task --aipod Artificer` 或包含 HWLAB gitbundle 的 `apply -f -`;运行中纠偏用 `send session/ --aipod Artificer`。需要验证 HWLAB Web/Cloud API 原入口时,仍按 `$hwlab-code-agent` 使用 G14 `/root/hwlab-v02` 的 `hwlab-cli client agent ...` 拉取同一 trace/result/inspect;不要回到旧 `codex submit/resume/steer`。 - -排查 HWLAB v0.3 / D601 Code Agent trace 时,HWLAB `client agent result|trace|inspect` 仍是跨层事实锚点:它会暴露 AgentRun namespace、runId、commandId、jobName 和 providerProfile。若 `bun scripts/cli.ts agentrun events|result run/` 在默认 direct-http 面返回 `run not found`,不要据此判定 run 已丢失;先核对该 run 是否属于 D601 `agentrun-v02` 或其他非默认 lane,并继续用 HWLAB trace/result 或对应 `agentrun control-plane ... --node --lane ` 查询运行面。Codex provider config 的 context/auto-compact 修复属于 AgentRun lane Secret `sourceRef` 问题,应回到 UniDesk `config/agentrun.yaml` 的 `secrets[].sourceRef`、`.state/secrets/agentrun/*config.toml` 和 `agentrun control-plane secret-sync|restart` 收敛,不要在 prompt、HWLAB adapter 或 Kubernetes Secret 里手补。 - ---- - -## 本地 Codex Trace 采集 - -`bun scripts/cli.ts codex trace ...` 是本机 Codex 运行痕迹的只读诊断入口,默认扫描 `~/.codex`,也可以用 `--root ` 指向其他 Codex home 或已拷贝的 trace 目录。它不提交新任务、不续跑 session,也不恢复旧 Code Queue 写入口。 - -```bash -bun scripts/cli.ts codex trace list [--root ~/.codex] [--limit 30] -bun scripts/cli.ts codex trace active [--root ~/.codex] -bun scripts/cli.ts codex trace grep --session --pattern 'playwright|auth-login-failed' [--since ISO] -bun scripts/cli.ts codex trace grep --session --messages --pattern 'Playwright|web-probe' -bun scripts/cli.ts codex trace grep --session --failed-only [--tool exec_command] -bun scripts/cli.ts codex trace grep --pattern 'playwright|auth-login-failed' [--file sessions/...jsonl] [--since ISO] -bun scripts/cli.ts codex trace collect [--root ~/.codex] [--output .state/codex-trace/] [--limit 30] -bun scripts/cli.ts codex trace show --session [--root ~/.codex] [--tail-bytes 12000] -``` - -默认只收集 `sessions/*.jsonl`、`history.jsonl`、`shell_snapshots`、`*.log` 和 trace 命名文本;`auth.json`、`config.toml`、sqlite、cache、`.tmp`、generated images、plugins 和 skills 默认跳过。`active` 从 `/proc` 找正在被 Codex 进程打开的 session JSONL,不依赖外部 `lsof`,并输出可复制的 `SESSION-ID`。`grep/show` 优先用 `--session ` 定位,避免复制长 JSONL 路径;session id 支持完整 UUID、短前缀和文件名片段,歧义时会提示候选。`grep` 默认只扫 active/recent session,并用 `rg`/raw-line prefilter 先定位候选行再解析 JSONL;需要全量文件域时显式加 `--all-files`,需要跳过 raw 预筛、逐行深度解析 decoded 字段时显式加 `--deep`。排查对话脉络优先 `--messages --pattern ...`,排查工具链优先 `--tools --tool `,只看失败调用用 `--failed-only [--tool ]` 且可以不带 pattern。默认输出优先 message 和 tool-call input;tool output 默认折叠,只给失败状态、错误摘要和对应输入,不打印整段 transcript;需要查看命中输出片段时显式加 `--include-output` 或 `-o wide`。它优先用于排查 `playwright`、`auth-login-failed`、`UNIDESK_*HINT` 等高噪声 trace。默认输出是类似 k8s 的简洁 text/table;脚本消费时显式加 `-o json|yaml|name|wide`。需要本地审阅敏感/数据库文件时必须显式加 `--include-sensitive` 或 `--include-sqlite`;`collect` 只复制有界文件并写 manifest,不压缩、不上传、不删除源文件。 - ---- - -## 冻结的旧写入口 - -以下命令必须返回 `ok=false`、`frozen=true`、`degradedReason=legacy-code-queue-frozen`,并提示 AgentRun 替代命令: - -```bash -bun scripts/cli.ts codex submit ... -bun scripts/cli.ts codex enqueue ... -bun scripts/cli.ts codex steer ... -bun scripts/cli.ts codex resume ... -bun scripts/cli.ts codex queue create -bun scripts/cli.ts codex queue merge --into -bun scripts/cli.ts codex move --queue -``` - -不要用兼容开关、legacy mode、adapter、双写或 fallback 绕开冻结边界。 - ---- - -## 历史任务视图 - -### Commander(低噪声轮询) - -```bash -bun scripts/cli.ts codex tasks --view commander [--limit N] -``` - -返回旧 Code Queue 历史/残留任务的有界 action map:active runners 计数、少量 active item、queued/retry_wait 计数、terminal-unread 总数、关键风险计数、分类计数和 drill-down 命令。新任务队列状态用 `agentrun get tasks --queue commander`。 - -### Supervisor(分区视图) - -```bash -bun scripts/cli.ts codex tasks --view supervisor \ - [--status succeeded|running|queued|failed|canceled|judging|retry_wait] \ - [--unread] [--limit N] [--before-id id] -``` - -返回 `activeRunning`、`running`、`completedUnread`、`recentCompleted`、`queued` 分区。状态 alias:`completed|successful → succeeded`、`cancelled → canceled`、`pending → queued`。 - -### 单任务 - -```bash -bun scripts/cli.ts codex task [--detail|--full] [--trace] -``` - ---- - -## 未读积压 - -```bash -# 摘要 -bun scripts/cli.ts codex unread [--queue id] [--repo owner/name] [--issue N] - -# 详细列表 -bun scripts/cli.ts codex unread list [--view full] [--limit N] - -# 标记已读 -bun scripts/cli.ts codex unread mark-read --confirm [--queue id] -``` - -`mark-read` 必须 `--confirm`,单任务审阅优先用 `codex read `。 - ---- - -## Trace / Output 分页 - -```bash -# 逻辑 trace(分页) -bun scripts/cli.ts codex task --trace \ - [--tail|--from-start|--after-seq N|--before-seq N] [--limit N] [--full] - -# 原始 output(分页补取) -bun scripts/cli.ts codex output \ - [--tail|--from-start|--after-seq N|--before-seq N] [--limit N] [--full-text] -``` - ---- - -## 标记已读 - -```bash -bun scripts/cli.ts codex read -``` - -标记单个终态任务已读,同时返回任务身份、终态 attempt 摘要和最终 response。 - ---- - -## Judge(复现评判) - -```bash -bun scripts/cli.ts codex judge --attempt N [--dry-run] [--include-prompt] -``` - -按指定 attempt 单步复现 judge,诊断入口。 - ---- - -## 中断/取消 - -```bash -bun scripts/cli.ts codex interrupt -bun scripts/cli.ts codex cancel -``` - -仅用于停止旧 Code Queue 残留任务;新 AgentRun session 使用 `bun scripts/cli.ts agentrun cancel session/`,单个卡住 command 使用 `bun scripts/cli.ts agentrun cancel command/ --run `。 - ---- - -## 旧队列归档 - -```bash -bun scripts/cli.ts codex queues [--full|--all] [--limit N] [--page N] -``` - -只读查看旧队列摘要。旧 queue create/merge 和 move 已冻结。 - ---- - -## Dev Ready / PR Preflight - -```bash -bun scripts/cli.ts codex dev-ready -bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run ...] [--pr-create-dry-run ...] [--issue N] [--full|--raw] -``` - ---- +- 创建/应用 AgentRun task 或 AipodSpec:读 [references/full.md](references/full.md) 的新任务入口段。 +- 查历史 `codex tasks/unread/output/trace`:读历史任务、未读积压和 Trace/Output 段。 +- 需要 judge、取消、中断、Dev Ready 或 PR preflight:读对应段。 diff --git a/.agents/skills/unidesk-code-queue/references/full.md b/.agents/skills/unidesk-code-queue/references/full.md new file mode 100644 index 00000000..2e93d0c8 --- /dev/null +++ b/.agents/skills/unidesk-code-queue/references/full.md @@ -0,0 +1,237 @@ +--- +name: unidesk-code-queue +description: UniDesk AgentRun-backed Code Queue CLI — Skill(cli-spec)。legacy `codex` 子命令只保留历史只读和残留停止;新任务提交、Aipod/Artificer 派单、session follow-up、events/logs/result、ack/cancel、dispatch、run/command/runner 状态 drill-down 和 HWLAB Code Agent/CaseRun follow-up 必须使用 `agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|send` 资源原语。用户提到 codex、Code Queue、submit、send、resume、tasks、unread、code-queue、aipod、Artificer、HWLAB Code Agent 时使用。 +--- + +# UniDesk Code Queue / AgentRun CLI + +旧 Code Queue 已冻结新任务和写入口。`bun scripts/cli.ts codex ...` 现在只作为历史归档、只读排障和残留任务停止入口;新的指挥官派单、Aipod/Artificer 执行、events/logs/result、ack/cancel、dispatch、session follow-up 必须走 AgentRun 资源原语,并按 cli-spec 渐进披露。UniDesk 是 render-only client:默认输出是低噪声 human 表格/摘要,脚本读取显式使用 `-o json|yaml` 的稳定客户端 schema,`--raw` 只用于查看直连 AgentRun REST envelope。 + +**固定入口前缀**: `cd /root/unidesk && bun scripts/cli.ts agentrun ...` + +--- + +## 新任务入口 + +```bash +# 查看 AgentRun 指挥官队列 +bun scripts/cli.ts agentrun get tasks --queue commander --limit 20 +bun scripts/cli.ts agentrun get tasks --queue commander -o wide + +# 查看一个 task/run/command/runnerjob/session 生命周期 +bun scripts/cli.ts agentrun describe task/ +bun scripts/cli.ts agentrun events run/ --after-seq --limit 100 +bun scripts/cli.ts agentrun logs session/ --tail 100 +bun scripts/cli.ts agentrun result run/ --command +bun scripts/cli.ts agentrun describe command/ --run +bun scripts/cli.ts agentrun describe runnerjob/ --run + +# 查看 / 渲染 AipodSpec(Artificer 是默认分布式开发 agent) +bun scripts/cli.ts agentrun get aipodspecs +bun scripts/cli.ts agentrun describe aipodspec/Artificer +bun scripts/cli.ts agentrun aipod-specs render Artificer --prompt-stdin + +# AipodSpec apply/delete 仍由客户端资源原语发起,服务端只返回 REST 业务事实 +bun scripts/cli.ts agentrun aipod-specs apply --yaml-stdin --dry-run + +# 提交 AgentRun task manifest +bun scripts/cli.ts agentrun apply -f - --dry-run <<'YAML' +kind: Task +spec: + tenantId: unidesk + projectId: example + queue: commander + title: 任务标题 + payload: + prompt: 任务说明 +YAML + +# 用 AipodSpec 提交,优先用于新任务和 Artificer +bun scripts/cli.ts agentrun create task --aipod Artificer \ + --prompt-stdin --idempotency-key + +# 查看/控制 AgentRun session +bun scripts/cli.ts agentrun logs session/ --tail 100 +bun scripts/cli.ts agentrun ack session/ +bun scripts/cli.ts agentrun dispatch task/ +bun scripts/cli.ts agentrun send session/ --aipod Artificer --prompt-stdin +bun scripts/cli.ts agentrun cancel session/ --reason --dry-run +bun scripts/cli.ts agentrun cancel command/ --run --reason --dry-run +``` + +日常 task manifest 优先使用 YAML heredoc:`agentrun apply -f -`;单 prompt 派单优先 `agentrun create task --aipod Artificer --prompt-stdin`;同 session 续跑只使用 `agentrun send session/`。UniDesk 客户端按 `config/agentrun.yaml` 直连 AgentRun REST API,不经过 HWLAB runtime、SSH official CLI 或旧 bridge wrapper;`send` 是唯一用户级 session follow-up 写入口,服务端按 durable session/run/command 状态自动决定内部 `steer` 或新 `turn`,旧 CLI `turn/steer` 路径不保留兼容。`--json-file`、`--prompt-file` 和 `--runner-json-file` 只是客户端输入来源,用于已审阅且可复用的受控文件。它不是旧 Code Queue adapter,不双写,也不迁移旧历史。 + +`AipodSpec` 是 AgentRun v0.1 的声明式 agent 装配:模型 profile、gitbundle、skills/tools、SecretRef 和 tool credential 都从 YAML 规格渲染。`Artificer` 默认用于 UniDesk 分布式开发任务,使用 `sub2api` provider、`gpt-5.5`、`reasoningEffort=xhigh`,并通过 SecretRef 注入 GitHub PR token、GitHub SSH 和 UniDesk SSH 透传能力。更新规格时使用 `agentrun aipod-specs apply --yaml-stdin --dry-run` 先看计划,确认后再去掉 `--dry-run`;不得把 API key、SSH key 或 token 写入 prompt、payload、YAML 或 issue。 + +AgentRun task payload 需要 runner 内使用 UniDesk SSH 透传时,只通过 `executionPolicy.secretScope.toolCredentials[].tool=unidesk-ssh` 请求 `agentrun-v01-tool-unidesk-ssh/UNIDESK_SSH_CLIENT_TOKEN` SecretRef;不要把 token 写入 prompt、payload 或 `transientEnv`。非敏感 endpoint 使用 `UNIDESK_MAIN_SERVER_IP`、`UNIDESK_MAIN_SERVER_HOST` 或 `UNIDESK_FRONTEND_URL`,可由 runner-job `transientEnv` 显式提供;G14 `agentrun-v01` manager 也会在缺省时按受控默认值自动补齐,并在 trace 中只显示 env name/count/hash 与 `valuesPrinted=false`。 + +## Queue 渐进披露 + +AgentRun queue 生命周期不是一个单独的 `queue lifecycle` 命令,而是一组资源原语的渐进披露组合: + +1. 默认总览用 `get tasks --queue commander --limit 20`,只看 task state、queue/lane、run/cmd/rjob/session ref、age 和 attention。 +2. 单任务用 `describe task/`,读取 `latestAttempt.runId`、`commandId`、`runnerJobId`、`sessionId/sessionPath` 和少量 `Next:`。 +3. Run 级状态用 `events run/` 和 `result run/ --command `,判断 terminalClassification、failureKind、provider interruption、timeoutBudget 和 recoveryActions。 +4. Command 级状态用 `describe command/ --run ` 和 `result command/ --run `,确认 command state、ack、terminal status 和结果摘要;确认为单个 active command 卡住时,先用 `cancel command/ --run --reason --dry-run` 核对 `CancelLifecycle` 的 authority、cascade、runner abort 和 fencing,再去掉 `--dry-run` 清理该 command,保留同一个 session 后再用 `send session/` 续跑。 +5. Runner job 只读状态用 `describe runnerjob/ --run `,确认 env image reuse、jobName、namespace、phase、exitCode、retention 和 `valuesPrinted=false`。不要为了这些字段手动调用 `trans G14:k3s kubectl ...`。 +6. Runtime runner Job/Pod retention 或 operator 明确要求强杀 runner 时,不属于单个 task/session 资源原语;使用 `bun scripts/cli.ts agentrun control-plane cleanup-runners --node --lane [--force-active] --dry-run|--confirm`。普通 cleanup 只删 inactive selected runner;`--force-active` 会中断 active run/command/session,必须先 dry-run 确认 selection,并且仍应优先于裸 `kubectl delete pod/job`。 +7. Session trace/output 只在 `describe task` 或 result 里有实际 `sessionId` 时使用 `logs|ack|send|cancel session/`;`sessionRef=null` 时不要猜 session 命令。用户级 follow-up 一律使用 `send session/`,不要回到旧 `turn/steer` 或 `sessions ...` 兼容路径。 +8. 已创建但尚未运行的 task 使用 `dispatch task/` 派发,不再退回旧 bridge `queue dispatch`。 + +默认视图必须低噪声且不是 JSON envelope,`-o json|yaml` 才输出稳定机器结构,`--raw` 才保留直连 AgentRun REST envelope;命令返回里的下一步应优先是 `bun scripts/cli.ts agentrun ...` 资源原语,不得把人工 k8s 查询作为日常下一步。 + +AgentRun cancel 策略由 `config/agentrun.yaml` 的 lane 级 `deployment.runner.cancelLifecycle` 管理;操作 D601、G14 或其他非默认 lane 时必须带 `--node/--lane --dry-run` 先确认 YAML policy,不要依赖全局默认或手动 k8s 强杀来替代资源原语。 + +## HWLAB Code Agent 入口整合 + +HWLAB Code Agent / CaseRun follow-up 的日常派单也归入 AgentRun 资源原语:新任务用 `create task --aipod Artificer` 或包含 HWLAB gitbundle 的 `apply -f -`;运行中纠偏用 `send session/ --aipod Artificer`。需要验证 HWLAB Web/Cloud API 原入口时,仍按 `$hwlab-code-agent` 使用 G14 `/root/hwlab-v02` 的 `hwlab-cli client agent ...` 拉取同一 trace/result/inspect;不要回到旧 `codex submit/resume/steer`。 + +排查 HWLAB v0.3 / D601 Code Agent trace 时,HWLAB `client agent result|trace|inspect` 仍是跨层事实锚点:它会暴露 AgentRun namespace、runId、commandId、jobName 和 providerProfile。若 `bun scripts/cli.ts agentrun events|result run/` 在默认 direct-http 面返回 `run not found`,不要据此判定 run 已丢失;先核对该 run 是否属于 D601 `agentrun-v02` 或其他非默认 lane,并继续用 HWLAB trace/result 或对应 `agentrun control-plane ... --node --lane ` 查询运行面。Codex provider config 的 context/auto-compact 修复属于 AgentRun lane Secret `sourceRef` 问题,应回到 UniDesk `config/agentrun.yaml` 的 `secrets[].sourceRef`、`.state/secrets/agentrun/*config.toml` 和 `agentrun control-plane secret-sync|restart` 收敛,不要在 prompt、HWLAB adapter 或 Kubernetes Secret 里手补。 + +--- + +## 本地 Codex Trace 采集 + +`bun scripts/cli.ts codex trace ...` 是本机 Codex 运行痕迹的只读诊断入口,默认扫描 `~/.codex`,也可以用 `--root ` 指向其他 Codex home 或已拷贝的 trace 目录。它不提交新任务、不续跑 session,也不恢复旧 Code Queue 写入口。 + +```bash +bun scripts/cli.ts codex trace list [--root ~/.codex] [--limit 30] +bun scripts/cli.ts codex trace active [--root ~/.codex] +bun scripts/cli.ts codex trace grep --session --pattern 'playwright|auth-login-failed' [--since ISO] +bun scripts/cli.ts codex trace grep --session --messages --pattern 'Playwright|web-probe' +bun scripts/cli.ts codex trace grep --session --failed-only [--tool exec_command] +bun scripts/cli.ts codex trace grep --pattern 'playwright|auth-login-failed' [--file sessions/...jsonl] [--since ISO] +bun scripts/cli.ts codex trace collect [--root ~/.codex] [--output .state/codex-trace/] [--limit 30] +bun scripts/cli.ts codex trace show --session [--root ~/.codex] [--tail-bytes 12000] +``` + +默认只收集 `sessions/*.jsonl`、`history.jsonl`、`shell_snapshots`、`*.log` 和 trace 命名文本;`auth.json`、`config.toml`、sqlite、cache、`.tmp`、generated images、plugins 和 skills 默认跳过。`active` 从 `/proc` 找正在被 Codex 进程打开的 session JSONL,不依赖外部 `lsof`,并输出可复制的 `SESSION-ID`。`grep/show` 优先用 `--session ` 定位,避免复制长 JSONL 路径;session id 支持完整 UUID、短前缀和文件名片段,歧义时会提示候选。`grep` 默认只扫 active/recent session,并用 `rg`/raw-line prefilter 先定位候选行再解析 JSONL;需要全量文件域时显式加 `--all-files`,需要跳过 raw 预筛、逐行深度解析 decoded 字段时显式加 `--deep`。排查对话脉络优先 `--messages --pattern ...`,排查工具链优先 `--tools --tool `,只看失败调用用 `--failed-only [--tool ]` 且可以不带 pattern。默认输出优先 message 和 tool-call input;tool output 默认折叠,只给失败状态、错误摘要和对应输入,不打印整段 transcript;需要查看命中输出片段时显式加 `--include-output` 或 `-o wide`。它优先用于排查 `playwright`、`auth-login-failed`、`UNIDESK_*HINT` 等高噪声 trace。默认输出是类似 k8s 的简洁 text/table;脚本消费时显式加 `-o json|yaml|name|wide`。需要本地审阅敏感/数据库文件时必须显式加 `--include-sensitive` 或 `--include-sqlite`;`collect` 只复制有界文件并写 manifest,不压缩、不上传、不删除源文件。 + +--- + +## 冻结的旧写入口 + +以下命令必须返回 `ok=false`、`frozen=true`、`degradedReason=legacy-code-queue-frozen`,并提示 AgentRun 替代命令: + +```bash +bun scripts/cli.ts codex submit ... +bun scripts/cli.ts codex enqueue ... +bun scripts/cli.ts codex steer ... +bun scripts/cli.ts codex resume ... +bun scripts/cli.ts codex queue create +bun scripts/cli.ts codex queue merge --into +bun scripts/cli.ts codex move --queue +``` + +不要用兼容开关、legacy mode、adapter、双写或 fallback 绕开冻结边界。 + +--- + +## 历史任务视图 + +### Commander(低噪声轮询) + +```bash +bun scripts/cli.ts codex tasks --view commander [--limit N] +``` + +返回旧 Code Queue 历史/残留任务的有界 action map:active runners 计数、少量 active item、queued/retry_wait 计数、terminal-unread 总数、关键风险计数、分类计数和 drill-down 命令。新任务队列状态用 `agentrun get tasks --queue commander`。 + +### Supervisor(分区视图) + +```bash +bun scripts/cli.ts codex tasks --view supervisor \ + [--status succeeded|running|queued|failed|canceled|judging|retry_wait] \ + [--unread] [--limit N] [--before-id id] +``` + +返回 `activeRunning`、`running`、`completedUnread`、`recentCompleted`、`queued` 分区。状态 alias:`completed|successful → succeeded`、`cancelled → canceled`、`pending → queued`。 + +### 单任务 + +```bash +bun scripts/cli.ts codex task [--detail|--full] [--trace] +``` + +--- + +## 未读积压 + +```bash +# 摘要 +bun scripts/cli.ts codex unread [--queue id] [--repo owner/name] [--issue N] + +# 详细列表 +bun scripts/cli.ts codex unread list [--view full] [--limit N] + +# 标记已读 +bun scripts/cli.ts codex unread mark-read --confirm [--queue id] +``` + +`mark-read` 必须 `--confirm`,单任务审阅优先用 `codex read `。 + +--- + +## Trace / Output 分页 + +```bash +# 逻辑 trace(分页) +bun scripts/cli.ts codex task --trace \ + [--tail|--from-start|--after-seq N|--before-seq N] [--limit N] [--full] + +# 原始 output(分页补取) +bun scripts/cli.ts codex output \ + [--tail|--from-start|--after-seq N|--before-seq N] [--limit N] [--full-text] +``` + +--- + +## 标记已读 + +```bash +bun scripts/cli.ts codex read +``` + +标记单个终态任务已读,同时返回任务身份、终态 attempt 摘要和最终 response。 + +--- + +## Judge(复现评判) + +```bash +bun scripts/cli.ts codex judge --attempt N [--dry-run] [--include-prompt] +``` + +按指定 attempt 单步复现 judge,诊断入口。 + +--- + +## 中断/取消 + +```bash +bun scripts/cli.ts codex interrupt +bun scripts/cli.ts codex cancel +``` + +仅用于停止旧 Code Queue 残留任务;新 AgentRun session 使用 `bun scripts/cli.ts agentrun cancel session/`,单个卡住 command 使用 `bun scripts/cli.ts agentrun cancel command/ --run `。 + +--- + +## 旧队列归档 + +```bash +bun scripts/cli.ts codex queues [--full|--all] [--limit N] [--page N] +``` + +只读查看旧队列摘要。旧 queue create/merge 和 move 已冻结。 + +--- + +## Dev Ready / PR Preflight + +```bash +bun scripts/cli.ts codex dev-ready +bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run ...] [--pr-create-dry-run ...] [--issue N] [--full|--raw] +``` + +--- diff --git a/.agents/skills/unidesk-gh/SKILL.md b/.agents/skills/unidesk-gh/SKILL.md index a4b459ab..4245af8f 100644 --- a/.agents/skills/unidesk-gh/SKILL.md +++ b/.agents/skills/unidesk-gh/SKILL.md @@ -5,430 +5,33 @@ description: UniDesk GitHub CLI - 通过 `bun scripts/cli.ts gh ...` 管理 GitH # UniDesk GitHub CLI -UniDesk 受控 GitHub 操作入口,底层 issue/PR REST 读写走 `bun scripts/cli.ts gh `,不依赖原生 `gh` binary,不手写 `curl`/GraphQL。Issue/PR 正文局部修补的人工首选入口是 `trans gh:/owner/repo/issue/ apply-patch` 或 `trans gh:/owner/repo/pr/ apply-patch`,底层 `gh issue patch` 只作为受控底座或兼容入口。 +GitHub issue/PR 正式读写必须走 `bun scripts/cli.ts gh ...` 或 `trans gh:/... apply-patch`,不依赖原生 `gh` binary,不手写 GitHub API。 -**固定入口前缀**: `cd /root/unidesk && bun scripts/cli.ts gh ...` +## 高频规则 -## 实现边界 +- Issue/PR 正文、评论、关闭/重开、PR 描述和 merge closeout 默认中文。 +- 新 issue 正文必须包含 `目标合并分支: `;不需要合并时写 `目标合并分支: 不适用`。 +- 大计划、后续阶段和独立改进方向创建新 issue;已有 issue 评论只写短进展、证据、阻塞和链接。 +- 多行正文使用 quoted heredoc:`--body-stdin <<'EOF'`;不要把长 Markdown 塞进 shell 参数。 +- PR merge 只走 guarded `gh pr merge`;`gh pr create` 的 Next 默认是 `--merge --delete-branch`,只有确认 ancestry 可丢弃时才显式 `--squash`。 -GitHub 受控入口的长期架构权威是 `project-management/PJ2026-01/specs/PJ2026-010606-github-controlled-entry.md`。`scripts/src/gh.ts` 只保留兼容 re-export;新增或修改 GitHub 子命令必须落到 `scripts/src/gh/` 下对应职责模块(client/options/refs/body/issue/pr/board/attachments/escape-scan/help/index 等),不得把业务实现重新堆回根入口。 - -拆分后的 gh 模块文件头应保留 `SPEC: PJ2026-010606 GitHub入口 <实现引用版本>` 追溯,单个模块超过 3000 行时继续拆分。PR closeout 除常规 smoke 外,应覆盖至少一条 `trans gh:/... apply-patch --dry-run` 或等价写回路径,证明虚拟正文仍走同一 guard。 - -CLI 自检使用 `bun scripts/cli.ts check --syntax-only`、针对被改模块的 `bun --check ` 和必要 `gh ... --dry-run`/read smoke。不要把 `bun --check scripts/cli.ts` 当作低噪声自检;它可能执行根 help 并触发 dump。需要 scripts 类型检查时走 `bun scripts/cli.ts check --scripts-typecheck ...`,让 CLI 输出 heartbeat 和 bounded timeout 结果。 - -## 写入语言 - -- Issue/PR 的正式写入一律使用中文,包括标题、正文、评论、关闭/重开说明、PR 描述、PR 评论和 merge closeout 说明。 -- 命令、路径、trace/session/job id、API path、代码标识符、英文专有名词、原始日志摘录和外部错误原文可以保留原样;解释性文字、结论、风险、计划和验收说明必须中文。 -- 除非用户明确要求外文,不要用英文撰写 issue/PR closeout、进展评论或阻塞说明。 -- Markdown 正文、issue/PR 正文和评论里的 issue/PR 引用必须写成 `[#](https://github.com///issues/)` 或 `[#](https://github.com///pull/)`,显示短号、链接目标保留完整 URL;不要显示裸长链接、裸井号编号或 `owner/repo` 加井号编号。`owner/repo#number` 只允许作为 CLI 命令参数 shorthand。 - ---- - -## Issue 目标分支 / Lane - -- 创建 issue、补充正式 issue 评论、关闭/重开说明或把 trace 发现的问题登记成 issue 时,正文必须显式写明目标合并分支或运行 lane。 -- 推荐固定字段:`目标合并分支: `;例如 `目标合并分支: HWLAB v0.2`、`目标合并分支: AgentRun v0.1`、`目标合并分支: UniDesk master`。 -- 如果该 issue 只记录外部依赖、运维观察或不需要代码合并,仍必须写 `目标合并分支: 不适用`,并用一句话说明原因。 -- 看到用户或上下文已明确目标 lane 时必须照写;不明确时按目标仓库长期 source truth 选择:HWLAB 按当前 runtime lane(如 `v0.2`/`v0.3`)、AgentRun 默认 `v0.1`、UniDesk 默认 `master`。不能确定时写 `目标合并分支: 待确认` 并说明待确认点,不能省略字段。 - ---- - -## 计划与评论分流 - -- 大计划、改进计划、后续工作拆解、独立验收标准或会形成多阶段执行的内容,必须创建新的 issue 承载;不要在已有 issue 评论区开长计划。 -- 既有 issue 评论只写短进展、关键证据、当前结论、阻塞和新 issue 链接;评论用于串联时间线,不承载新的大范围计划正文。 -- 如果调查中发现了独立改进方向,应先用 `gh issue create --body-stdin` 创建新 issue,标题和正文写清目标合并分支/lane、背景、计划、验收标准;然后在原 issue 评论中用 1-3 句说明已拆出,并链接到新 issue。 -- 只有用户明确要求把计划写回当前 issue 正文,或当前 issue 本身就是唯一的专题计划 issue,才允许更新当前 issue 正文;即便如此,评论仍保持短小,不复制整篇计划。 - ---- - -## 认证探测 +## 常用入口 ```bash -bun scripts/cli.ts gh auth status [--repo owner/name] +bun scripts/cli.ts gh auth status --repo pikasTech/unidesk +bun scripts/cli.ts gh issue list --repo pikasTech/unidesk --state open --limit 30 +bun scripts/cli.ts gh issue view --repo pikasTech/unidesk --json body,title,state +bun scripts/cli.ts gh issue create --repo pikasTech/unidesk --title "标题" --body-stdin +bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --title "标题" --body-stdin --base master --head +bun scripts/cli.ts gh pr preflight --repo pikasTech/unidesk +bun scripts/cli.ts gh pr merge --repo pikasTech/unidesk --merge --delete-branch ``` -探测 token 来源(`GH_TOKEN`/`GITHUB_TOKEN`/`gh auth token`)、GitHub REST egress、repo 可见性、issue 可读性。不打印 token。 +完整 issue/PR CRUD、comment、patch、stale-close、scan-escape、PR files/preflight/merge、看板命令和 `trans gh:` 虚拟文件系统见 [references/full.md](references/full.md)。 -## 聚焦帮助 +## 何时读取 reference -具体子命令用法优先直接查 scoped help,不要先打开顶层长 help 再人工搜索: - -```bash -bun scripts/cli.ts gh issue close --help -bun scripts/cli.ts gh issue comment --help -bun scripts/cli.ts gh pr merge --help -``` - -`gh --help`、`gh -h` 和 `gh help` 只输出匹配命令或命令组的 bounded JSON,包括相关 usage、短 notes 和完整 help 入口;`gh --help` / `gh help` 才输出完整顶层命令索引。 - ---- - -## Issue 命令 - -### 列表 - -```bash -bun scripts/cli.ts gh issue list [owner/repo] \ - [--state open|closed|all] [--limit N] [--search text] [--title-prefix text] \ - [--label label[,label...]] [--repo owner/name] \ - [--json number,title,state,url,updatedAt,createdAt,author,labels] [--raw|--full] -``` - -默认 `state=open`、`limit=30`。`owner/repo` 位置参数等价 `--repo`。`--search` 走 GitHub Search API 做查重。`--title-prefix` 在当前有界结果内按 issue 标题前缀做本地过滤,输出 `titleFilter` 的输入/输出/过滤数量,适合 `[FEEDBACK]` 去重: - -```bash -bun scripts/cli.ts gh issue list --repo pikasTech/unidesk --state all \ - --search "[FEEDBACK]" --title-prefix "[FEEDBACK]" \ - --json number,title,state,url -``` - -### 查看 - -```bash -bun scripts/cli.ts gh issue view \ - [--repo owner/name] [--json body,title,state,closed,closedAt,comments,commentCount] [--raw|--full] -``` - -`read` 是兼容别名。支持 `owner/repo#number` shorthand(如 `pikasTech/HWLAB#1024`)。 -人工读取 issue/PR 正文优先走 `trans gh:/owner/repo/issue/ cat|rg` 或 `trans gh:/owner/repo/pr/ cat|rg`;`gh issue view/read` 主要保留为结构化 JSON 底座、metadata 读取和兼容入口。 -`--json comments` 默认只返回 comment id、URL、作者、时间、正文字符数、body SHA 和短 preview;`--full` 仍保持评论列表有界,只有 `--raw` 会显式展开所有评论正文。读取单条完整 comment body 使用 `gh issue comment view --full`,显式 `--json` 路径的 comments 位于 `.data.json.comments`,不要依赖顶层重复 comments。 - -### 创建 - -```bash -bun scripts/cli.ts gh issue create \ - --repo owner/name \ - --title "标题" \ - --label "bug,v0.2" \ - --body-stdin <<'EOF' -目标合并分支: HWLAB v0.2 - -正文(Markdown,支持换行、反引号、表格) -EOF -``` - -`--body-stdin` 是 heredoc/stdin 第一等入口。`--dry-run` 只输出计划不写 GitHub。 - -### 更新正文 - -```bash -# 替换正文 -bun scripts/cli.ts gh issue update --repo owner/name \ - --mode replace --body-stdin <<'EOF' -新正文 -EOF - -# 追加正文 -bun scripts/cli.ts gh issue update --repo owner/name \ - --mode append --body-stdin <<'EOF' -追加内容 -EOF -``` - -`edit` 是 `update --mode replace` 的兼容别名。正式写入默认先读当前 issue 做 body guard。 - -### 局部修补正文(优先走 `trans gh:`) - -```bash -# Issue 正文:route 未写 /1 时默认一楼正文 -trans gh:/owner/name/issue/ apply-patch <<'PATCH' -*** Begin Patch -*** Update File: body.md -@@ --old text -+new text -*** End Patch -PATCH - -# PR 正文同样使用 body.md -trans gh:/owner/name/pr/ apply-patch <<'PATCH' -*** Begin Patch -*** Update File: body.md -@@ --old text -+new text -*** End Patch -PATCH -``` - -GitHub issue/PR 正文局部修补必须优先走 `trans gh:/owner/repo/issue/ apply-patch` 或 `trans gh:/owner/repo/pr/ apply-patch`;虚拟文件固定是 `body.md`,`apply_patch` 和 `patch-apply` 仅作为兼容别名。底层 `bun scripts/cli.ts gh issue patch ... --body-patch-stdin` 是受控写回底座或兼容入口,不作为人工首选。`trans gh:` 会先读取当前正文,把 Codex `apply_patch` envelope 应用到 `body.md`,再通过现有 `gh issue/pr update` guard 写回;普通小补丁不需要固定先跑 `--dry-run`,高风险正文预览时才加 `--dry-run`。 - -### 评论 - -```bash -# 创建评论 -bun scripts/cli.ts gh issue comment create \ - --repo owner/name --body-stdin <<'EOF' -评论正文 -EOF - -# 读取单条评论 -bun scripts/cli.ts gh issue comment view \ - --repo owner/name [--full|--raw] - -# 原地修正评论 -bun scripts/cli.ts gh issue comment update \ - --repo owner/name --body-stdin <<'EOF' -新的评论正文 -EOF - -# 局部修补评论 -bun scripts/cli.ts gh issue comment patch \ - --repo owner/name --body-patch-stdin <<'PATCH' -*** Begin Patch -*** Update File: comment.md -@@ --old text -+new text -*** End Patch -PATCH - -# 删除评论 -bun scripts/cli.ts gh issue comment delete -``` - -`view` 默认返回 comment id、URL、作者、时间、正文字符数、body SHA 和短 preview;`--full` 只展开这一条 comment body。`edit` 是 `comment update` 的兼容别名。`--body ` 仅适合短单行。日常修正文案优先用 `patch` 或 `update/edit` 保留评论 ID 和时间线;`delete` 只用于确实需要删除的评论。`comment patch` 会先读取 comment id 对应的当前正文,把 envelope 应用到虚拟文件 `comment.md`,再 PATCH 单条评论;上下文不匹配时失败且不写入。 - -评论区不要写新的大计划。若评论草稿已经包含多阶段计划、改进清单或验收标准,改为创建新 issue,并在评论中只留下新 issue 链接和短结论。 - -### 关闭/重开 - -```bash -bun scripts/cli.ts gh issue close \ - --repo owner/name [--comment "关闭原因"] - -bun scripts/cli.ts gh issue reopen \ - --repo owner/name [--comment "重开原因"] -``` - -附长证据时先用 `comment create` 写证据评论,再用 `close --comment <短引用>` 关闭。不支持 `delete`(走 `close`)。 - -### 批量过期关闭 - -```bash -bun scripts/cli.ts gh issue stale-close \ - --repo owner/name [--inactive-hours 48] [--limit N] \ - [--label label] [--dry-run] -``` - -关闭 `updatedAt < observedAt - inactiveHours` 的 open issue。先 `--dry-run` 观察,再正式执行。 - -### 转义扫描 - -```bash -bun scripts/cli.ts gh issue scan-escape \ - --repo owner/name [--limit N] [--dry-run] -``` - -只读扫描 issue 正文/评论中的字面量 `\n`、`\t`、ANSI escape 等污染。 - ---- - -## PR 命令 - -### 列表 - -```bash -bun scripts/cli.ts gh pr list [owner/repo] \ - [--state open|closed|all] [--json ...] [--raw|--full] -``` - -### 查看 - -```bash -bun scripts/cli.ts gh pr view \ - [--repo owner/name] \ - [--json body,title,state,stateDetail,headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup] \ - [--raw|--full] -``` - -`stateDetail` 归一化为 `open|closed|merged`。`mergeable`/`mergeStateStatus`/`statusCheckRollup` 走 GraphQL。 - -### 文件变更 - -```bash -bun scripts/cli.ts gh pr files [--repo owner/name] [--limit N] -``` - -返回 changed files 统计,不输出 raw diff。`gh pr diff --stat` 是兼容别名。 - -### 收口预检 - -```bash -bun scripts/cli.ts gh pr preflight \ - [--repo owner/name] [--full|--raw] -``` - -`preflight`/`closeout` 是别名。只读检查 state/draft/conflict/mergeable/statusCheck,不写 GitHub。默认只给 status check 计数与失败预览。 - -### 创建 - -```bash -bun scripts/cli.ts gh pr create \ - --repo owner/name \ - --title "标题" \ - --body-stdin --base master --head \ - [--draft] [--dry-run] <<'EOF' -PR 正文 -EOF -``` - -`pr create` 成功后的 Next 默认提示使用 `gh pr merge ... --merge --delete-branch`。只有确认不需要保留 ancestry 或 merge parent history 时,才显式改用 `--squash`。 - -### 合并 - -```bash -bun scripts/cli.ts gh pr merge \ - [--repo owner/name] [--merge|--squash|--rebase] \ - [--delete-branch] [--dry-run] -``` - -一键 guarded merge:`gh pr merge` 自己读取 closeout metadata、mergeability、mergeStateStatus 和 status checks,不要求用户先跑 `gh pr preflight`。`preflight`/`closeout` 只作为只读诊断入口保留。 - -当 GitHub GraphQL 返回 `mergeable=UNKNOWN/null`、`mergeStateStatus=UNKNOWN/null` 或 status rollup unknown 且没有明确 blockers 时,`gh pr merge` 会按 `config/unidesk-cli.yaml` 的 `github.prMerge.unknownRetry` 做指数退避重试,并在默认表格 `RETRY` 列显示 `1/5`、`2/5` 等尝试次数。重试耗尽仍 unknown 时,命令自动停止且不合并;冲突、draft、失败检查、pending check 等不可合并状态不会无意义重试。已 merged 返回 `alreadyMerged=true`。 - -### 开发任务收口 - -完成开发任务后必须完整收口:创建 PR、通过受控 `gh pr merge` 合并 PR、删除远程任务分支、删除本地任务 worktree,并把主 worktree 快进到最新 `master`。远程分支优先通过 `gh pr merge ... --delete-branch` 删除;若合并后远端分支仍存在,再显式 `git push origin --delete `。 - -删除本地 worktree 前先确认工作区 clean,且任务变更已经进入 `origin/master`;squash merge 场景下不能只看本地分支是否是 `origin/master` 祖先,还要用 PR merged 状态、`git cherry` 等价补丁或关键文件 diff 确认语义已吸收。确认后执行 `git worktree remove .worktree/`;如本地任务分支仅用于该 worktree 且已语义合入,可同步删除本地分支。 - -最后在主 worktree 执行 `git fetch origin master` 和 `git pull --ff-only origin master`,让主 worktree 回到最新 `master`。如果主 worktree 存在并行脏改、非 `master` 分支或其他原因导致不能安全 fast-forward,禁止 `reset`、`stash`、`checkout --` 或清理他人改动;改用干净 `origin/master` worktree 继续后续工具命令,并在最终回复中明确说明主 worktree 未更新的原因。 - -### 编辑 / 评论 / 关闭 / 重开 - -```bash -bun scripts/cli.ts gh pr update --mode replace|append --body-stdin [--title ...] -bun scripts/cli.ts gh pr comment create --body-stdin -bun scripts/cli.ts gh pr comment update --body-stdin -bun scripts/cli.ts gh pr comment edit --body-stdin -bun scripts/cli.ts gh pr comment delete -bun scripts/cli.ts gh pr close [--comment ...] -bun scripts/cli.ts gh pr reopen [--comment ...] -``` - -`pr comment edit` 是 `pr comment update` 的兼容别名。与 issue 对应命令行为一致;PR 评论也是 GitHub issue comment,更新目标使用 commentId。 - ---- - -## 虚拟文件系统 (`trans gh:`) - -```bash -# 列出 repo -trans gh:/pikasTech/HWLAB ls - -# 列出 PR / Issue -trans gh:/pikasTech/HWLAB/pr ls [--state open|closed|all] [--limit N] [--full] -trans gh:/pikasTech/HWLAB/issue ls [--state open|closed|all] [--limit N] [--search TEXT] [--label L] [--full] - -# 读取 / 检索正文 -trans gh:/pikasTech/HWLAB/issue/1236 cat -trans gh:/pikasTech/HWLAB/issue/1236 rg 'API_KEY|YAML-first' - -# 查看单个条目 -trans gh:/pikasTech/HWLAB/pr/507 ls -trans gh:/pikasTech/HWLAB/505/1 cat - -# apply-patch 写回正文(走 gh issue/pr update) -trans gh:/pikasTech/HWLAB/pr/507 apply-patch <<'PATCH' -*** Begin Patch -*** Update File: body.md -@@ - old line --old line -+new line - more context -*** End Patch -PATCH -``` - -正文一楼映射为 `body.md`;route 未写 `/1` 时默认一楼正文。`issue ls --search/--state` 用于替代常见 `bun scripts/cli.ts gh issue list ... --search/--state` 读取;单条正文优先用 `cat` 或 `rg`,不要为了看 body 再走 `gh issue view --json body --full`。写回走 `gh issue/pr update` 的 guard 规则。`apply-patch` 是首选 operation,`apply_patch` 与 `patch-apply` 只作为兼容别名;普通小补丁在已读取上下文后可以直接 apply,`--dry-run` 只作为高风险正文的可选预览。`rm` 对正文结构化拒绝。 - ---- - -## 看板命令 ([#20](https://github.com/pikasTech/unidesk/issues/20) 总看板) - -```bash -# 结构审计 -bun scripts/cli.ts gh issue board-audit \ - --repo pikasTech/unidesk --board-issue 20 [--dry-run] - -# 行列表 -bun scripts/cli.ts gh issue board-row list \ - --board-issue 20 [--state open|closed|all] - -# 行查看 -bun scripts/cli.ts gh issue board-row get --board-issue 20 - -# 行更新(单字段) -bun scripts/cli.ts gh issue board-row update \ - --board-issue 20 \ - --field progress|status|validation|branch|tasks|focus \ - --value \ - [--expect-body-sha |--expect-updated-at ] - -# 行新增/插入(upsert) -bun scripts/cli.ts gh issue board-row upsert \ - --board-issue 20 --section open|closed \ - --branch --tasks --summary \ - --focus --validation --progress \ - [--expect-body-sha |--expect-updated-at ] - -# 行移动 / 删除 -bun scripts/cli.ts gh issue board-row move \ - --board-issue 20 --to open|closed [--status OPEN|CLOSED] -bun scripts/cli.ts gh issue board-row delete \ - --board-issue 20 -``` - -写操作默认 dry-run,正式 PATCH 必须带 `--expect-body-sha` 或 `--expect-updated-at`。 - ---- - -## 关键约定 - -### heredoc 优先 - -多行正文/评论一律用 `--body-stdin <<'EOF'`(quoted heredoc),不用 `--body` 内联长文本、不用临时文件、不用 `gh api -f body=...`: - -```bash -bun scripts/cli.ts gh issue create --repo pikasTech/HWLAB \ - --title "标题" --body-stdin <<'EOF' -正文,支持 `code`、**bold**、表格等 Markdown -EOF -``` - -### owner/repo#number shorthand - -所有接受 issue/PR number 的命令都支持: - -```bash -bun scripts/cli.ts gh issue view pikasTech/HWLAB#1024 -bun scripts/cli.ts gh pr preflight pikasTech/HWLAB#1020 -bun scripts/cli.ts gh issue comment create pikasTech/HWLAB#1024 --body-stdin <<'EOF' -... -EOF -``` - -shorthand 与显式 `--repo` 冲突时结构化失败。 - -### 高风险写入预览 - -整篇 replace、批量 lifecycle、PR merge 或并发敏感正文写入可先 `--dry-run`;`trans gh:/... apply-patch` 的普通小补丁在已读取上下文后不需要固定 dry-run: - -```bash -bun scripts/cli.ts gh issue update 20 --mode append --body-stdin --dry-run <<'EOF' -... -EOF -``` - -### debug 任务入口 - -```bash -bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run ...] [--pr-create-dry-run ...] [--issue N] [--full|--raw] -``` - -用于 PR 型派单 admission,检查 scheduler auth、runner GH token、git worktree、GitHub egress 等。 +- 需要具体 issue/PR/comment 命令参数、`--json` 字段或 body guard:读 [references/full.md](references/full.md) 的 Issue/PR 命令段。 +- 需要局部修补正文或评论:读 `trans gh:` 和 apply-patch 段。 +- 需要维护总看板 [#20](https://github.com/pikasTech/unidesk/issues/20):读看板命令段。 +- 需要 closeout、preflight、merge 或 ancestry/squash 判断:读 PR 命令和关键约定段。 diff --git a/.agents/skills/unidesk-gh/references/full.md b/.agents/skills/unidesk-gh/references/full.md new file mode 100644 index 00000000..a4b459ab --- /dev/null +++ b/.agents/skills/unidesk-gh/references/full.md @@ -0,0 +1,434 @@ +--- +name: unidesk-gh +description: UniDesk GitHub CLI - 通过 `bun scripts/cli.ts gh ...` 管理 GitHub issue/PR,不依赖原生 `gh` binary。用户提到 gh、GitHub issue、GitHub PR、创建 issue、评论 issue、合并 PR、preflight、看板操作时使用。 +--- + +# UniDesk GitHub CLI + +UniDesk 受控 GitHub 操作入口,底层 issue/PR REST 读写走 `bun scripts/cli.ts gh `,不依赖原生 `gh` binary,不手写 `curl`/GraphQL。Issue/PR 正文局部修补的人工首选入口是 `trans gh:/owner/repo/issue/ apply-patch` 或 `trans gh:/owner/repo/pr/ apply-patch`,底层 `gh issue patch` 只作为受控底座或兼容入口。 + +**固定入口前缀**: `cd /root/unidesk && bun scripts/cli.ts gh ...` + +## 实现边界 + +GitHub 受控入口的长期架构权威是 `project-management/PJ2026-01/specs/PJ2026-010606-github-controlled-entry.md`。`scripts/src/gh.ts` 只保留兼容 re-export;新增或修改 GitHub 子命令必须落到 `scripts/src/gh/` 下对应职责模块(client/options/refs/body/issue/pr/board/attachments/escape-scan/help/index 等),不得把业务实现重新堆回根入口。 + +拆分后的 gh 模块文件头应保留 `SPEC: PJ2026-010606 GitHub入口 <实现引用版本>` 追溯,单个模块超过 3000 行时继续拆分。PR closeout 除常规 smoke 外,应覆盖至少一条 `trans gh:/... apply-patch --dry-run` 或等价写回路径,证明虚拟正文仍走同一 guard。 + +CLI 自检使用 `bun scripts/cli.ts check --syntax-only`、针对被改模块的 `bun --check ` 和必要 `gh ... --dry-run`/read smoke。不要把 `bun --check scripts/cli.ts` 当作低噪声自检;它可能执行根 help 并触发 dump。需要 scripts 类型检查时走 `bun scripts/cli.ts check --scripts-typecheck ...`,让 CLI 输出 heartbeat 和 bounded timeout 结果。 + +## 写入语言 + +- Issue/PR 的正式写入一律使用中文,包括标题、正文、评论、关闭/重开说明、PR 描述、PR 评论和 merge closeout 说明。 +- 命令、路径、trace/session/job id、API path、代码标识符、英文专有名词、原始日志摘录和外部错误原文可以保留原样;解释性文字、结论、风险、计划和验收说明必须中文。 +- 除非用户明确要求外文,不要用英文撰写 issue/PR closeout、进展评论或阻塞说明。 +- Markdown 正文、issue/PR 正文和评论里的 issue/PR 引用必须写成 `[#](https://github.com///issues/)` 或 `[#](https://github.com///pull/)`,显示短号、链接目标保留完整 URL;不要显示裸长链接、裸井号编号或 `owner/repo` 加井号编号。`owner/repo#number` 只允许作为 CLI 命令参数 shorthand。 + +--- + +## Issue 目标分支 / Lane + +- 创建 issue、补充正式 issue 评论、关闭/重开说明或把 trace 发现的问题登记成 issue 时,正文必须显式写明目标合并分支或运行 lane。 +- 推荐固定字段:`目标合并分支: `;例如 `目标合并分支: HWLAB v0.2`、`目标合并分支: AgentRun v0.1`、`目标合并分支: UniDesk master`。 +- 如果该 issue 只记录外部依赖、运维观察或不需要代码合并,仍必须写 `目标合并分支: 不适用`,并用一句话说明原因。 +- 看到用户或上下文已明确目标 lane 时必须照写;不明确时按目标仓库长期 source truth 选择:HWLAB 按当前 runtime lane(如 `v0.2`/`v0.3`)、AgentRun 默认 `v0.1`、UniDesk 默认 `master`。不能确定时写 `目标合并分支: 待确认` 并说明待确认点,不能省略字段。 + +--- + +## 计划与评论分流 + +- 大计划、改进计划、后续工作拆解、独立验收标准或会形成多阶段执行的内容,必须创建新的 issue 承载;不要在已有 issue 评论区开长计划。 +- 既有 issue 评论只写短进展、关键证据、当前结论、阻塞和新 issue 链接;评论用于串联时间线,不承载新的大范围计划正文。 +- 如果调查中发现了独立改进方向,应先用 `gh issue create --body-stdin` 创建新 issue,标题和正文写清目标合并分支/lane、背景、计划、验收标准;然后在原 issue 评论中用 1-3 句说明已拆出,并链接到新 issue。 +- 只有用户明确要求把计划写回当前 issue 正文,或当前 issue 本身就是唯一的专题计划 issue,才允许更新当前 issue 正文;即便如此,评论仍保持短小,不复制整篇计划。 + +--- + +## 认证探测 + +```bash +bun scripts/cli.ts gh auth status [--repo owner/name] +``` + +探测 token 来源(`GH_TOKEN`/`GITHUB_TOKEN`/`gh auth token`)、GitHub REST egress、repo 可见性、issue 可读性。不打印 token。 + +## 聚焦帮助 + +具体子命令用法优先直接查 scoped help,不要先打开顶层长 help 再人工搜索: + +```bash +bun scripts/cli.ts gh issue close --help +bun scripts/cli.ts gh issue comment --help +bun scripts/cli.ts gh pr merge --help +``` + +`gh --help`、`gh -h` 和 `gh help` 只输出匹配命令或命令组的 bounded JSON,包括相关 usage、短 notes 和完整 help 入口;`gh --help` / `gh help` 才输出完整顶层命令索引。 + +--- + +## Issue 命令 + +### 列表 + +```bash +bun scripts/cli.ts gh issue list [owner/repo] \ + [--state open|closed|all] [--limit N] [--search text] [--title-prefix text] \ + [--label label[,label...]] [--repo owner/name] \ + [--json number,title,state,url,updatedAt,createdAt,author,labels] [--raw|--full] +``` + +默认 `state=open`、`limit=30`。`owner/repo` 位置参数等价 `--repo`。`--search` 走 GitHub Search API 做查重。`--title-prefix` 在当前有界结果内按 issue 标题前缀做本地过滤,输出 `titleFilter` 的输入/输出/过滤数量,适合 `[FEEDBACK]` 去重: + +```bash +bun scripts/cli.ts gh issue list --repo pikasTech/unidesk --state all \ + --search "[FEEDBACK]" --title-prefix "[FEEDBACK]" \ + --json number,title,state,url +``` + +### 查看 + +```bash +bun scripts/cli.ts gh issue view \ + [--repo owner/name] [--json body,title,state,closed,closedAt,comments,commentCount] [--raw|--full] +``` + +`read` 是兼容别名。支持 `owner/repo#number` shorthand(如 `pikasTech/HWLAB#1024`)。 +人工读取 issue/PR 正文优先走 `trans gh:/owner/repo/issue/ cat|rg` 或 `trans gh:/owner/repo/pr/ cat|rg`;`gh issue view/read` 主要保留为结构化 JSON 底座、metadata 读取和兼容入口。 +`--json comments` 默认只返回 comment id、URL、作者、时间、正文字符数、body SHA 和短 preview;`--full` 仍保持评论列表有界,只有 `--raw` 会显式展开所有评论正文。读取单条完整 comment body 使用 `gh issue comment view --full`,显式 `--json` 路径的 comments 位于 `.data.json.comments`,不要依赖顶层重复 comments。 + +### 创建 + +```bash +bun scripts/cli.ts gh issue create \ + --repo owner/name \ + --title "标题" \ + --label "bug,v0.2" \ + --body-stdin <<'EOF' +目标合并分支: HWLAB v0.2 + +正文(Markdown,支持换行、反引号、表格) +EOF +``` + +`--body-stdin` 是 heredoc/stdin 第一等入口。`--dry-run` 只输出计划不写 GitHub。 + +### 更新正文 + +```bash +# 替换正文 +bun scripts/cli.ts gh issue update --repo owner/name \ + --mode replace --body-stdin <<'EOF' +新正文 +EOF + +# 追加正文 +bun scripts/cli.ts gh issue update --repo owner/name \ + --mode append --body-stdin <<'EOF' +追加内容 +EOF +``` + +`edit` 是 `update --mode replace` 的兼容别名。正式写入默认先读当前 issue 做 body guard。 + +### 局部修补正文(优先走 `trans gh:`) + +```bash +# Issue 正文:route 未写 /1 时默认一楼正文 +trans gh:/owner/name/issue/ apply-patch <<'PATCH' +*** Begin Patch +*** Update File: body.md +@@ +-old text ++new text +*** End Patch +PATCH + +# PR 正文同样使用 body.md +trans gh:/owner/name/pr/ apply-patch <<'PATCH' +*** Begin Patch +*** Update File: body.md +@@ +-old text ++new text +*** End Patch +PATCH +``` + +GitHub issue/PR 正文局部修补必须优先走 `trans gh:/owner/repo/issue/ apply-patch` 或 `trans gh:/owner/repo/pr/ apply-patch`;虚拟文件固定是 `body.md`,`apply_patch` 和 `patch-apply` 仅作为兼容别名。底层 `bun scripts/cli.ts gh issue patch ... --body-patch-stdin` 是受控写回底座或兼容入口,不作为人工首选。`trans gh:` 会先读取当前正文,把 Codex `apply_patch` envelope 应用到 `body.md`,再通过现有 `gh issue/pr update` guard 写回;普通小补丁不需要固定先跑 `--dry-run`,高风险正文预览时才加 `--dry-run`。 + +### 评论 + +```bash +# 创建评论 +bun scripts/cli.ts gh issue comment create \ + --repo owner/name --body-stdin <<'EOF' +评论正文 +EOF + +# 读取单条评论 +bun scripts/cli.ts gh issue comment view \ + --repo owner/name [--full|--raw] + +# 原地修正评论 +bun scripts/cli.ts gh issue comment update \ + --repo owner/name --body-stdin <<'EOF' +新的评论正文 +EOF + +# 局部修补评论 +bun scripts/cli.ts gh issue comment patch \ + --repo owner/name --body-patch-stdin <<'PATCH' +*** Begin Patch +*** Update File: comment.md +@@ +-old text ++new text +*** End Patch +PATCH + +# 删除评论 +bun scripts/cli.ts gh issue comment delete +``` + +`view` 默认返回 comment id、URL、作者、时间、正文字符数、body SHA 和短 preview;`--full` 只展开这一条 comment body。`edit` 是 `comment update` 的兼容别名。`--body ` 仅适合短单行。日常修正文案优先用 `patch` 或 `update/edit` 保留评论 ID 和时间线;`delete` 只用于确实需要删除的评论。`comment patch` 会先读取 comment id 对应的当前正文,把 envelope 应用到虚拟文件 `comment.md`,再 PATCH 单条评论;上下文不匹配时失败且不写入。 + +评论区不要写新的大计划。若评论草稿已经包含多阶段计划、改进清单或验收标准,改为创建新 issue,并在评论中只留下新 issue 链接和短结论。 + +### 关闭/重开 + +```bash +bun scripts/cli.ts gh issue close \ + --repo owner/name [--comment "关闭原因"] + +bun scripts/cli.ts gh issue reopen \ + --repo owner/name [--comment "重开原因"] +``` + +附长证据时先用 `comment create` 写证据评论,再用 `close --comment <短引用>` 关闭。不支持 `delete`(走 `close`)。 + +### 批量过期关闭 + +```bash +bun scripts/cli.ts gh issue stale-close \ + --repo owner/name [--inactive-hours 48] [--limit N] \ + [--label label] [--dry-run] +``` + +关闭 `updatedAt < observedAt - inactiveHours` 的 open issue。先 `--dry-run` 观察,再正式执行。 + +### 转义扫描 + +```bash +bun scripts/cli.ts gh issue scan-escape \ + --repo owner/name [--limit N] [--dry-run] +``` + +只读扫描 issue 正文/评论中的字面量 `\n`、`\t`、ANSI escape 等污染。 + +--- + +## PR 命令 + +### 列表 + +```bash +bun scripts/cli.ts gh pr list [owner/repo] \ + [--state open|closed|all] [--json ...] [--raw|--full] +``` + +### 查看 + +```bash +bun scripts/cli.ts gh pr view \ + [--repo owner/name] \ + [--json body,title,state,stateDetail,headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup] \ + [--raw|--full] +``` + +`stateDetail` 归一化为 `open|closed|merged`。`mergeable`/`mergeStateStatus`/`statusCheckRollup` 走 GraphQL。 + +### 文件变更 + +```bash +bun scripts/cli.ts gh pr files [--repo owner/name] [--limit N] +``` + +返回 changed files 统计,不输出 raw diff。`gh pr diff --stat` 是兼容别名。 + +### 收口预检 + +```bash +bun scripts/cli.ts gh pr preflight \ + [--repo owner/name] [--full|--raw] +``` + +`preflight`/`closeout` 是别名。只读检查 state/draft/conflict/mergeable/statusCheck,不写 GitHub。默认只给 status check 计数与失败预览。 + +### 创建 + +```bash +bun scripts/cli.ts gh pr create \ + --repo owner/name \ + --title "标题" \ + --body-stdin --base master --head \ + [--draft] [--dry-run] <<'EOF' +PR 正文 +EOF +``` + +`pr create` 成功后的 Next 默认提示使用 `gh pr merge ... --merge --delete-branch`。只有确认不需要保留 ancestry 或 merge parent history 时,才显式改用 `--squash`。 + +### 合并 + +```bash +bun scripts/cli.ts gh pr merge \ + [--repo owner/name] [--merge|--squash|--rebase] \ + [--delete-branch] [--dry-run] +``` + +一键 guarded merge:`gh pr merge` 自己读取 closeout metadata、mergeability、mergeStateStatus 和 status checks,不要求用户先跑 `gh pr preflight`。`preflight`/`closeout` 只作为只读诊断入口保留。 + +当 GitHub GraphQL 返回 `mergeable=UNKNOWN/null`、`mergeStateStatus=UNKNOWN/null` 或 status rollup unknown 且没有明确 blockers 时,`gh pr merge` 会按 `config/unidesk-cli.yaml` 的 `github.prMerge.unknownRetry` 做指数退避重试,并在默认表格 `RETRY` 列显示 `1/5`、`2/5` 等尝试次数。重试耗尽仍 unknown 时,命令自动停止且不合并;冲突、draft、失败检查、pending check 等不可合并状态不会无意义重试。已 merged 返回 `alreadyMerged=true`。 + +### 开发任务收口 + +完成开发任务后必须完整收口:创建 PR、通过受控 `gh pr merge` 合并 PR、删除远程任务分支、删除本地任务 worktree,并把主 worktree 快进到最新 `master`。远程分支优先通过 `gh pr merge ... --delete-branch` 删除;若合并后远端分支仍存在,再显式 `git push origin --delete `。 + +删除本地 worktree 前先确认工作区 clean,且任务变更已经进入 `origin/master`;squash merge 场景下不能只看本地分支是否是 `origin/master` 祖先,还要用 PR merged 状态、`git cherry` 等价补丁或关键文件 diff 确认语义已吸收。确认后执行 `git worktree remove .worktree/`;如本地任务分支仅用于该 worktree 且已语义合入,可同步删除本地分支。 + +最后在主 worktree 执行 `git fetch origin master` 和 `git pull --ff-only origin master`,让主 worktree 回到最新 `master`。如果主 worktree 存在并行脏改、非 `master` 分支或其他原因导致不能安全 fast-forward,禁止 `reset`、`stash`、`checkout --` 或清理他人改动;改用干净 `origin/master` worktree 继续后续工具命令,并在最终回复中明确说明主 worktree 未更新的原因。 + +### 编辑 / 评论 / 关闭 / 重开 + +```bash +bun scripts/cli.ts gh pr update --mode replace|append --body-stdin [--title ...] +bun scripts/cli.ts gh pr comment create --body-stdin +bun scripts/cli.ts gh pr comment update --body-stdin +bun scripts/cli.ts gh pr comment edit --body-stdin +bun scripts/cli.ts gh pr comment delete +bun scripts/cli.ts gh pr close [--comment ...] +bun scripts/cli.ts gh pr reopen [--comment ...] +``` + +`pr comment edit` 是 `pr comment update` 的兼容别名。与 issue 对应命令行为一致;PR 评论也是 GitHub issue comment,更新目标使用 commentId。 + +--- + +## 虚拟文件系统 (`trans gh:`) + +```bash +# 列出 repo +trans gh:/pikasTech/HWLAB ls + +# 列出 PR / Issue +trans gh:/pikasTech/HWLAB/pr ls [--state open|closed|all] [--limit N] [--full] +trans gh:/pikasTech/HWLAB/issue ls [--state open|closed|all] [--limit N] [--search TEXT] [--label L] [--full] + +# 读取 / 检索正文 +trans gh:/pikasTech/HWLAB/issue/1236 cat +trans gh:/pikasTech/HWLAB/issue/1236 rg 'API_KEY|YAML-first' + +# 查看单个条目 +trans gh:/pikasTech/HWLAB/pr/507 ls +trans gh:/pikasTech/HWLAB/505/1 cat + +# apply-patch 写回正文(走 gh issue/pr update) +trans gh:/pikasTech/HWLAB/pr/507 apply-patch <<'PATCH' +*** Begin Patch +*** Update File: body.md +@@ + old line +-old line ++new line + more context +*** End Patch +PATCH +``` + +正文一楼映射为 `body.md`;route 未写 `/1` 时默认一楼正文。`issue ls --search/--state` 用于替代常见 `bun scripts/cli.ts gh issue list ... --search/--state` 读取;单条正文优先用 `cat` 或 `rg`,不要为了看 body 再走 `gh issue view --json body --full`。写回走 `gh issue/pr update` 的 guard 规则。`apply-patch` 是首选 operation,`apply_patch` 与 `patch-apply` 只作为兼容别名;普通小补丁在已读取上下文后可以直接 apply,`--dry-run` 只作为高风险正文的可选预览。`rm` 对正文结构化拒绝。 + +--- + +## 看板命令 ([#20](https://github.com/pikasTech/unidesk/issues/20) 总看板) + +```bash +# 结构审计 +bun scripts/cli.ts gh issue board-audit \ + --repo pikasTech/unidesk --board-issue 20 [--dry-run] + +# 行列表 +bun scripts/cli.ts gh issue board-row list \ + --board-issue 20 [--state open|closed|all] + +# 行查看 +bun scripts/cli.ts gh issue board-row get --board-issue 20 + +# 行更新(单字段) +bun scripts/cli.ts gh issue board-row update \ + --board-issue 20 \ + --field progress|status|validation|branch|tasks|focus \ + --value \ + [--expect-body-sha |--expect-updated-at ] + +# 行新增/插入(upsert) +bun scripts/cli.ts gh issue board-row upsert \ + --board-issue 20 --section open|closed \ + --branch --tasks --summary \ + --focus --validation --progress \ + [--expect-body-sha |--expect-updated-at ] + +# 行移动 / 删除 +bun scripts/cli.ts gh issue board-row move \ + --board-issue 20 --to open|closed [--status OPEN|CLOSED] +bun scripts/cli.ts gh issue board-row delete \ + --board-issue 20 +``` + +写操作默认 dry-run,正式 PATCH 必须带 `--expect-body-sha` 或 `--expect-updated-at`。 + +--- + +## 关键约定 + +### heredoc 优先 + +多行正文/评论一律用 `--body-stdin <<'EOF'`(quoted heredoc),不用 `--body` 内联长文本、不用临时文件、不用 `gh api -f body=...`: + +```bash +bun scripts/cli.ts gh issue create --repo pikasTech/HWLAB \ + --title "标题" --body-stdin <<'EOF' +正文,支持 `code`、**bold**、表格等 Markdown +EOF +``` + +### owner/repo#number shorthand + +所有接受 issue/PR number 的命令都支持: + +```bash +bun scripts/cli.ts gh issue view pikasTech/HWLAB#1024 +bun scripts/cli.ts gh pr preflight pikasTech/HWLAB#1020 +bun scripts/cli.ts gh issue comment create pikasTech/HWLAB#1024 --body-stdin <<'EOF' +... +EOF +``` + +shorthand 与显式 `--repo` 冲突时结构化失败。 + +### 高风险写入预览 + +整篇 replace、批量 lifecycle、PR merge 或并发敏感正文写入可先 `--dry-run`;`trans gh:/... apply-patch` 的普通小补丁在已读取上下文后不需要固定 dry-run: + +```bash +bun scripts/cli.ts gh issue update 20 --mode append --body-stdin --dry-run <<'EOF' +... +EOF +``` + +### debug 任务入口 + +```bash +bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run ...] [--pr-create-dry-run ...] [--issue N] [--full|--raw] +``` + +用于 PR 型派单 admission,检查 scheduler auth、runner GH token、git worktree、GitHub egress 等。 diff --git a/.agents/skills/unidesk-oa/SKILL.md b/.agents/skills/unidesk-oa/SKILL.md index 86fd6107..c8c99270 100644 --- a/.agents/skills/unidesk-oa/SKILL.md +++ b/.agents/skills/unidesk-oa/SKILL.md @@ -5,74 +5,24 @@ description: UniDesk 项目管理运行技能。用户提到 UniDesk 项目管 # UniDesk OA -使用本技能时,把 HWLAB Cloud M1 的项目管理稳定地锚定在 UniDesk 仓库内的项目编号目录和相互链接的 GitHub issue 执行记录上。 +HWLAB Cloud M1 / UniDesk / AgentRun 跨仓项目治理锚定在 UniDesk 仓库的 `project-management/` 项目编号目录和 GitHub issue 执行记录上。 -## 仓库 +## 高频规则 -- UniDesk 项目管理真相源:`/root/unidesk/project-management/PJ2026-01`,其中 `specs/PJ2026-01-HWLAB.md` 是 L0 总规格和项目索引,随 `pikasTech/unidesk` 的 `master` 分支版本化。AgentRun 作为 HWLAB Agent编排的执行基础设施归入 `PJ2026-0102` 及其平台运维支撑规格,不单独创建新的 L0 项目。 -- 迁移前 HWLabOA 仓库:`/root/HWLabOA` 只作为历史材料和对照来源,不再作为 HWLAB Cloud M1 规格真相源。 -- 使用 UniDesk GitHub CLI、修改本技能或修改项目管理文档前,必须在 `/root/unidesk` 执行 `git status --short --branch` 和 `git pull --ff-only origin master`。 +- 项目管理 stable source of truth 在 `project-management/PJ2026-01/`,GitHub issue 承载执行流和证据索引。 +- 重大项目/方向/课题/子课题层级、任务书、实施方案、测试规格、阶段报告和项目偏离按项目编号目录维护。 +- 形成多阶段实施、架构或长期 API/数据模型时,先 SPEC,再实现和 issue 阶段计划。 +- GitHub issue/PR 写入仍走 `$unidesk-gh`;正文必须写目标合并分支/lane。 -## 运行模型 +## Reference 路由 -- 将 `project-management/PJ2026-01` 下的 Markdown 视为长期规格真相源。 -- 将 GitHub issue 视为执行控制面、历史讨论入口和长证据承载处:状态、讨论、跨仓引用、PR 链接和收口证据可以在 issue 中流转,但规格正文不得只写在 issue 中。 -- 长证据、trace、CaseRun registry、运行日志、历史 issue 摘要和证据索引保留在 GitHub issue 或具体执行 issue 中,禁止以 `evidence/` 目录或长证据文件形式污染 `project-management/PJ2026-01`。 -- `project-management/PJ2026-01/specs/*.md` 规格文件不保留单独的迁移来源块;历史来源只写在修改历史 `v0.1` 的变更说明中,格式为 `迁移来源 /#`。规格文件引用其他规格必须使用同目录相对路径 Markdown 链接,禁止引用其他规格的 GitHub issue、证据 issue、PR 或裸 `#`;唯一例外是第 7 章过程控制可保留跨 L1 阶段验证 issue 索引,用于内测、灰度和用户反馈分流,不承载问卷正文、参与者资料、原始反馈、长证据或执行流水。 -- AgentRun 仓库内 `docs/reference/spec-v01-*.md` 和 `docs/reference/architecture.md` 不再承载规格正文;迁移后只保留指向 UniDesk OA 规格的交叉引用 stub,避免 AgentRun repo 与 `project-management/PJ2026-01` 双份规格漂移。AgentRun 专项内容按职责落到 `project-management/PJ2026-01/specs/PJ2026-0102-agent-orchestration.md`、`project-management/PJ2026-01/specs/PJ2026-010601-controlled-release.md`、`project-management/PJ2026-01/specs/PJ2026-010602-source-sync.md`、`project-management/PJ2026-01/specs/PJ2026-010603-yaml-first-ops.md`、`project-management/PJ2026-01/specs/PJ2026-010604-public-entry.md` 或 `project-management/PJ2026-01/specs/PJ2026-010605-observability-monitoring.md` 及其 L3,不创建 `PJ2026-02`。 -- HWLAB v0.2/v0.3 仓库内 `docs/reference/spec-*` 以及 `cloud-workbench.md`、`code-agent-chat-readiness.md`、`g14-gitops-cicd.md` / `node-gitops-cicd.md`、`dev-runtime-boundary.md`、`gateway-outbound-demo.md`、`MVP-e2e-acceptance.md`、`architecture.md` 已收编为 UniDesk OA 交叉引用或历史 stub;这些路径只保留链接兼容和运行参考,不能再作为需求规格正文来源。需要变更 Web/CLI/API、Code Agent、runtime boundary、公开入口、测试大纲或 Gateway 主动出站需求时,先更新 `project-management/PJ2026-01/specs/` 和相应测试/过程 issue。 -- 写入 GitHub issue/PR 正文或评论时,issue/PR 引用必须写成 `[#](https://github.com///issues/)` 或 `[#](https://github.com///pull/)`,显示短号、链接目标保留完整 URL;不要显示裸长链接、裸井号编号或 `owner/repo` 加井号编号。CLI 参数中的 `owner/repo#number` shorthand 只作为命令输入例外。 -- 不要让日报或阶段报告成为总规划。阶段报告只总结相对总规格的移动,不能替代中心规划。 -- 当任务缺少上级方向、验收标准或原始验证入口时,先归类为规划/调查,不要直接变成实现任务。 -- L1 方向必须是直接服务 L0 使命的能力域。文档整理、阶段报告、项目管理机制、看板维护、技能维护、仓库名、工具名和临时执行路径都不能作为 L1 方向。 -- L1 是验收主责,不是宽泛标签。跨方向 issue 可以列关联 L1,但必须且只能有一个主责 L1,选择依据是哪个方向定义完成标准。 -- 重大规划型 issue 必须 P0 SPEC-first:P0 阶段先在 `project-management/PJ2026-01/specs/` 维护对应 SPEC,明确 SPEC 编号、上级/关联规格、架构图、数据流图、关键时序图和代码引用规则,再进入后续实现。issue 正文只能承载执行计划和证据,不能替代 SPEC 正文。 +- 项目规划、层级职责、issue 锚点和工作流的完整旧规则在 [references/full.md](references/full.md)。 +- 项目规划细则读 [references/project-planning.md](references/project-planning.md)。 +- 模板读 [references/templates.md](references/templates.md)。 -## 需求规格写作规则 +## 常用检查 -- 需求规格是对外系统能力、边界和职责说明,不写内部治理、issue 关闭规则、长期面板、阶段报告、执行控制面、单次验证流水、PR 过程或证据堆叠。 -- 文档控制表只保留规格自身必要字段;状态只能写 `已生效`、`已废弃` 或 `未生效`。不要在文档控制表里写长期面板、阶段规格、L1 划分、规格来源或迁移来源。 -- 修改历史只记录已经定稿的版本。用户未明确说“可以定稿”前,不新增 `待提交` 版本号,也不把每次小编辑拆成一个版本。 -- L0、L1、L2 需求规格正文固定使用裁剪后的 1-7 章结构:文档控制、目的和范围、术语表、系统边界和接口、内部分工与规格索引、原子需求、过程控制。第 7 章只索引阶段验证、内测、灰度或用户反馈分流 issue;不要在 SPEC 正文追加假设和风险、变更控制、开放缺口、验收标准或执行过程流水。 -- 涉及新能力、跨模块架构、用户可见工作流、长期 API/数据模型、平台运维能力或多阶段实施的 SPEC,必须在正文中包含目标架构图、数据流图和关键时序图;图形优先使用 Markdown 内嵌 `mermaid`,并和原子需求编号互相对应。 -- L0 系统边界必须把 HWLAB 作为完整系统看待,描述外部使用者、外部输入、受控资源、外部输出、用户接口和系统责任边界,不写内部治理材料。 -- 稳定概念用术语表表达;不要写没有判定价值的“运行概念”流水。 -- L0 的 L1 方向树和项目规格索引合并为内部模块分工与规格索引,用相对路径索引到每个内部模块规格文档。 -- 原子需求每条独立成节,信息表使用横向表格且只放短元数据,推荐列为 `编号 | 短名 | 主责模块 | 关联模块`。主责模块必须写下一层主责单元并带项目编号:L0 原子需求写 L1,L1 原子需求写 L2,L2 原子需求写 L3;禁止在 L1/L2 原子需求里把主责模块写成本规格本级。禁止把需求句、职责划分、验收口径或大段说明塞进表格,正文必须承载需求定义、职责边界、意图和范围。 -- 不要为了凑全局原子需求条数自动添加次要派生能力。证据收集、硬编码诊断、硬编码建议、任务状态收集、结果包/结果收集等需求,除非用户明确授意,不得写入 OA 规格或 L0 原子需求。 -- 平台运维在 L0 中只能作为支撑后勤职责出现;对外需求应写成系统可用性、可恢复、资源可控等用户可感知能力,不写成“HWLAB 对外提供平台运维能力”。 -- 单一主责、关闭验收、规格沉淀、回写和偏离判定是治理规则,不得伪装成 L0 原子需求。 -- L2 是稳定能力域,不是仓库文件名或原始 spec 文档一比一搬运目录。单个 L1 下的 L2 通常不超过 6 个;从仓库参考文档迁移过来的大量细项应合并到少量 L2,并把更细的服务、lane、source truth、验证切片放到 L3 或正文分工中。 -- 运维交付内容必须按职责归位:通用 CI/CD、发布判定、镜像 promotion 放到平台运维的发布流水;通用 Git mirror、source truth、GitOps branch、artifact catalog 放到源码同步;Secret/YAML/sourceRef/targetKey/fingerprint 放到 YAML运维。只有能抽象到多服务共用的规则才放通用 L2;AgentRun 固定 branch、namespace、Pipeline、GitOps path、真实 provider turn 等专项细节放到这些通用规格下的 AgentRun 专项 L3,`PJ2026-0102 Agent编排` 只引用这些支撑规格,不把运维后勤 L2 塞进 Agent编排。 -- 大规划型 issue 范围内新增或修改的源码文件,文件头部必须标注遵循的 SPEC 编号、短名和实现引用版本,例如 `SPEC: PJ2026-01060505 Workbench性能 draft-2026-06-17-p0`;不得只写 issue 编号、`latest` 或 `current`。自动生成文件、第三方 vendored 文件、纯配置、锁文件和无法承载注释头的二进制产物可例外,但对应生成器、渲染器或配置入口必须可追溯到 SPEC。 - -## HWLAB 标准 issue 锚点 - -- L0 总规格:`project-management/PJ2026-01/specs/PJ2026-01-HWLAB.md`,历史 issue `[#1194](https://github.com/pikasTech/HWLAB/issues/1194)`。 -- AgentRun 规格入口:`project-management/PJ2026-01/specs/PJ2026-0102-agent-orchestration.md`;核心 L2 为 `PJ2026-010201` AgentRun核心、`PJ2026-010202` Runtime装配、`PJ2026-010203` 队列会话、`PJ2026-010204` 后端Profile、`PJ2026-010205` HWLAB接入。平台运维支撑入口为 `PJ2026-010601` 发布流水、`PJ2026-010602` 源码同步、`PJ2026-010603` YAML运维、`PJ2026-010604` 公开入口和 `PJ2026-010605` 可观测监控;其中 AgentRun 专项 L3 只承载 AgentRun 固定 lane/source 事实,D601 v0.3 用户入口等跨模块 public entry truth 归 `PJ2026-010604`。 -- 长期导航面板:`[#645](https://github.com/pikasTech/HWLAB/issues/645)`(`HWLAB 长期总面板`)。这是面板,不是规格书。 -- 当前 Cloud M1 阶段规格:`project-management/PJ2026-01/specs/stage-cloud-spec-20260601.md`,历史 issue `[#644](https://github.com/pikasTech/HWLAB/issues/644)`。 -- 创建或更新 HWLAB 项目管理 issue 时,非平凡 L1/L2/L3/L4 工作都要链接回 `project-management/PJ2026-01` 中的对应规格文件,并保持 `[#645](https://github.com/pikasTech/HWLAB/issues/645)` 只作为导航索引。 -- 所有管理 issue 和规格都使用 HWLAB 层级编号:项目编号格式为 `PJ-`;`PJ` 表示项目,`2026` 是立项年份,HWLAB L0 是 `PJ2026-01`;L1 编号是 `PJ2026-0101`、`PJ2026-0102` 等;L2 继续追加两位,例如 `PJ2026-010102` 表示 HWLAB L1 第 1 个方向下的第 2 个 L2 课题。稳定节点短名通常控制在 8 个中文汉字以内,解释写在正文,不写进名称。 - -## 工作流 - -1. 做规划类问题时,读取 `references/project-planning.md`。 -2. 创建或更新总规格、issue 正文时,读取 `references/templates.md`。 -3. 写入 GitHub issue/PR 评论时,同时使用 `unidesk-gh`;所有写入必须从 `/root/unidesk` 通过 `bun scripts/cli.ts gh ...` 完成。 -4. 修改长期项目管理文档时,把稳定规划内容放到 `project-management/PJ2026-01` 的总规格/规格文件,阶段报告只保留带日期的总结。 -5. 修订 L1/L2 层级时,先按完成标准判断主责,再看实现表面。工具、CLI、Web、API 形态可以形成关联 L1,但主责 L1 必须是定义完成标准的能力域。 -6. 更新重大规划型 issue 时,先把 P0 SPEC 文件和图维护到位,再用 `unidesk-gh` 回写 issue;后续代码任务必须在 issue 中引用 SPEC 编号和实现引用版本。 - -## 必需层级和职责 - -除非用户给出更严格的层级,否则使用以下任务树: - -- L0:重大项目 / 总规格。负责使命、范围、系统边界、内部模块分工与规格索引、全局原子需求。 -- L1:方向。负责一个稳定能力域、范围边界、成功标准、L2 课题清单和子工作验收定义权。 -- L2:课题。负责某个 L1 内的具体工作计划,包括交付物、阻塞项和验证计划。 -- L3:子课题 / 验收切片。负责一个有界验收路径,例如一条 CaseRun 线、Web 检查、CLI smoke 或部署/控制面验证。 -- L4:执行任务 / PR / CaseRun / smoke / 文档收口。负责具体执行和证据收集,不能重新定义父级范围。 - -每个非平凡 issue 都应写明上级链接、目标分支/lane、验收标准、验证入口和收口回写目标。 -重大规划型 issue 还必须把“更新并确认 SPEC”列为 P0,且 P0 未完成前不得推进代码实现。 +```bash +find project-management/PJ2026-01 -maxdepth 3 -type f | sort +bun scripts/cli.ts gh issue list --repo pikasTech/unidesk --state open --limit 30 +``` diff --git a/.agents/skills/unidesk-oa/references/full.md b/.agents/skills/unidesk-oa/references/full.md new file mode 100644 index 00000000..86fd6107 --- /dev/null +++ b/.agents/skills/unidesk-oa/references/full.md @@ -0,0 +1,78 @@ +--- +name: unidesk-oa +description: UniDesk 项目管理运行技能。用户提到 UniDesk 项目管理目录、HWLAB OA、总项目规格、总规划、重大项目/方向/课题/子课题层级、任务书、实施方案、测试规格、阶段报告、项目偏离,或通过 project-management 与 GitHub issue 管理 HWLAB Cloud M1 / UniDesk / AgentRun 跨仓项目治理时使用。 +--- + +# UniDesk OA + +使用本技能时,把 HWLAB Cloud M1 的项目管理稳定地锚定在 UniDesk 仓库内的项目编号目录和相互链接的 GitHub issue 执行记录上。 + +## 仓库 + +- UniDesk 项目管理真相源:`/root/unidesk/project-management/PJ2026-01`,其中 `specs/PJ2026-01-HWLAB.md` 是 L0 总规格和项目索引,随 `pikasTech/unidesk` 的 `master` 分支版本化。AgentRun 作为 HWLAB Agent编排的执行基础设施归入 `PJ2026-0102` 及其平台运维支撑规格,不单独创建新的 L0 项目。 +- 迁移前 HWLabOA 仓库:`/root/HWLabOA` 只作为历史材料和对照来源,不再作为 HWLAB Cloud M1 规格真相源。 +- 使用 UniDesk GitHub CLI、修改本技能或修改项目管理文档前,必须在 `/root/unidesk` 执行 `git status --short --branch` 和 `git pull --ff-only origin master`。 + +## 运行模型 + +- 将 `project-management/PJ2026-01` 下的 Markdown 视为长期规格真相源。 +- 将 GitHub issue 视为执行控制面、历史讨论入口和长证据承载处:状态、讨论、跨仓引用、PR 链接和收口证据可以在 issue 中流转,但规格正文不得只写在 issue 中。 +- 长证据、trace、CaseRun registry、运行日志、历史 issue 摘要和证据索引保留在 GitHub issue 或具体执行 issue 中,禁止以 `evidence/` 目录或长证据文件形式污染 `project-management/PJ2026-01`。 +- `project-management/PJ2026-01/specs/*.md` 规格文件不保留单独的迁移来源块;历史来源只写在修改历史 `v0.1` 的变更说明中,格式为 `迁移来源 /#`。规格文件引用其他规格必须使用同目录相对路径 Markdown 链接,禁止引用其他规格的 GitHub issue、证据 issue、PR 或裸 `#`;唯一例外是第 7 章过程控制可保留跨 L1 阶段验证 issue 索引,用于内测、灰度和用户反馈分流,不承载问卷正文、参与者资料、原始反馈、长证据或执行流水。 +- AgentRun 仓库内 `docs/reference/spec-v01-*.md` 和 `docs/reference/architecture.md` 不再承载规格正文;迁移后只保留指向 UniDesk OA 规格的交叉引用 stub,避免 AgentRun repo 与 `project-management/PJ2026-01` 双份规格漂移。AgentRun 专项内容按职责落到 `project-management/PJ2026-01/specs/PJ2026-0102-agent-orchestration.md`、`project-management/PJ2026-01/specs/PJ2026-010601-controlled-release.md`、`project-management/PJ2026-01/specs/PJ2026-010602-source-sync.md`、`project-management/PJ2026-01/specs/PJ2026-010603-yaml-first-ops.md`、`project-management/PJ2026-01/specs/PJ2026-010604-public-entry.md` 或 `project-management/PJ2026-01/specs/PJ2026-010605-observability-monitoring.md` 及其 L3,不创建 `PJ2026-02`。 +- HWLAB v0.2/v0.3 仓库内 `docs/reference/spec-*` 以及 `cloud-workbench.md`、`code-agent-chat-readiness.md`、`g14-gitops-cicd.md` / `node-gitops-cicd.md`、`dev-runtime-boundary.md`、`gateway-outbound-demo.md`、`MVP-e2e-acceptance.md`、`architecture.md` 已收编为 UniDesk OA 交叉引用或历史 stub;这些路径只保留链接兼容和运行参考,不能再作为需求规格正文来源。需要变更 Web/CLI/API、Code Agent、runtime boundary、公开入口、测试大纲或 Gateway 主动出站需求时,先更新 `project-management/PJ2026-01/specs/` 和相应测试/过程 issue。 +- 写入 GitHub issue/PR 正文或评论时,issue/PR 引用必须写成 `[#](https://github.com///issues/)` 或 `[#](https://github.com///pull/)`,显示短号、链接目标保留完整 URL;不要显示裸长链接、裸井号编号或 `owner/repo` 加井号编号。CLI 参数中的 `owner/repo#number` shorthand 只作为命令输入例外。 +- 不要让日报或阶段报告成为总规划。阶段报告只总结相对总规格的移动,不能替代中心规划。 +- 当任务缺少上级方向、验收标准或原始验证入口时,先归类为规划/调查,不要直接变成实现任务。 +- L1 方向必须是直接服务 L0 使命的能力域。文档整理、阶段报告、项目管理机制、看板维护、技能维护、仓库名、工具名和临时执行路径都不能作为 L1 方向。 +- L1 是验收主责,不是宽泛标签。跨方向 issue 可以列关联 L1,但必须且只能有一个主责 L1,选择依据是哪个方向定义完成标准。 +- 重大规划型 issue 必须 P0 SPEC-first:P0 阶段先在 `project-management/PJ2026-01/specs/` 维护对应 SPEC,明确 SPEC 编号、上级/关联规格、架构图、数据流图、关键时序图和代码引用规则,再进入后续实现。issue 正文只能承载执行计划和证据,不能替代 SPEC 正文。 + +## 需求规格写作规则 + +- 需求规格是对外系统能力、边界和职责说明,不写内部治理、issue 关闭规则、长期面板、阶段报告、执行控制面、单次验证流水、PR 过程或证据堆叠。 +- 文档控制表只保留规格自身必要字段;状态只能写 `已生效`、`已废弃` 或 `未生效`。不要在文档控制表里写长期面板、阶段规格、L1 划分、规格来源或迁移来源。 +- 修改历史只记录已经定稿的版本。用户未明确说“可以定稿”前,不新增 `待提交` 版本号,也不把每次小编辑拆成一个版本。 +- L0、L1、L2 需求规格正文固定使用裁剪后的 1-7 章结构:文档控制、目的和范围、术语表、系统边界和接口、内部分工与规格索引、原子需求、过程控制。第 7 章只索引阶段验证、内测、灰度或用户反馈分流 issue;不要在 SPEC 正文追加假设和风险、变更控制、开放缺口、验收标准或执行过程流水。 +- 涉及新能力、跨模块架构、用户可见工作流、长期 API/数据模型、平台运维能力或多阶段实施的 SPEC,必须在正文中包含目标架构图、数据流图和关键时序图;图形优先使用 Markdown 内嵌 `mermaid`,并和原子需求编号互相对应。 +- L0 系统边界必须把 HWLAB 作为完整系统看待,描述外部使用者、外部输入、受控资源、外部输出、用户接口和系统责任边界,不写内部治理材料。 +- 稳定概念用术语表表达;不要写没有判定价值的“运行概念”流水。 +- L0 的 L1 方向树和项目规格索引合并为内部模块分工与规格索引,用相对路径索引到每个内部模块规格文档。 +- 原子需求每条独立成节,信息表使用横向表格且只放短元数据,推荐列为 `编号 | 短名 | 主责模块 | 关联模块`。主责模块必须写下一层主责单元并带项目编号:L0 原子需求写 L1,L1 原子需求写 L2,L2 原子需求写 L3;禁止在 L1/L2 原子需求里把主责模块写成本规格本级。禁止把需求句、职责划分、验收口径或大段说明塞进表格,正文必须承载需求定义、职责边界、意图和范围。 +- 不要为了凑全局原子需求条数自动添加次要派生能力。证据收集、硬编码诊断、硬编码建议、任务状态收集、结果包/结果收集等需求,除非用户明确授意,不得写入 OA 规格或 L0 原子需求。 +- 平台运维在 L0 中只能作为支撑后勤职责出现;对外需求应写成系统可用性、可恢复、资源可控等用户可感知能力,不写成“HWLAB 对外提供平台运维能力”。 +- 单一主责、关闭验收、规格沉淀、回写和偏离判定是治理规则,不得伪装成 L0 原子需求。 +- L2 是稳定能力域,不是仓库文件名或原始 spec 文档一比一搬运目录。单个 L1 下的 L2 通常不超过 6 个;从仓库参考文档迁移过来的大量细项应合并到少量 L2,并把更细的服务、lane、source truth、验证切片放到 L3 或正文分工中。 +- 运维交付内容必须按职责归位:通用 CI/CD、发布判定、镜像 promotion 放到平台运维的发布流水;通用 Git mirror、source truth、GitOps branch、artifact catalog 放到源码同步;Secret/YAML/sourceRef/targetKey/fingerprint 放到 YAML运维。只有能抽象到多服务共用的规则才放通用 L2;AgentRun 固定 branch、namespace、Pipeline、GitOps path、真实 provider turn 等专项细节放到这些通用规格下的 AgentRun 专项 L3,`PJ2026-0102 Agent编排` 只引用这些支撑规格,不把运维后勤 L2 塞进 Agent编排。 +- 大规划型 issue 范围内新增或修改的源码文件,文件头部必须标注遵循的 SPEC 编号、短名和实现引用版本,例如 `SPEC: PJ2026-01060505 Workbench性能 draft-2026-06-17-p0`;不得只写 issue 编号、`latest` 或 `current`。自动生成文件、第三方 vendored 文件、纯配置、锁文件和无法承载注释头的二进制产物可例外,但对应生成器、渲染器或配置入口必须可追溯到 SPEC。 + +## HWLAB 标准 issue 锚点 + +- L0 总规格:`project-management/PJ2026-01/specs/PJ2026-01-HWLAB.md`,历史 issue `[#1194](https://github.com/pikasTech/HWLAB/issues/1194)`。 +- AgentRun 规格入口:`project-management/PJ2026-01/specs/PJ2026-0102-agent-orchestration.md`;核心 L2 为 `PJ2026-010201` AgentRun核心、`PJ2026-010202` Runtime装配、`PJ2026-010203` 队列会话、`PJ2026-010204` 后端Profile、`PJ2026-010205` HWLAB接入。平台运维支撑入口为 `PJ2026-010601` 发布流水、`PJ2026-010602` 源码同步、`PJ2026-010603` YAML运维、`PJ2026-010604` 公开入口和 `PJ2026-010605` 可观测监控;其中 AgentRun 专项 L3 只承载 AgentRun 固定 lane/source 事实,D601 v0.3 用户入口等跨模块 public entry truth 归 `PJ2026-010604`。 +- 长期导航面板:`[#645](https://github.com/pikasTech/HWLAB/issues/645)`(`HWLAB 长期总面板`)。这是面板,不是规格书。 +- 当前 Cloud M1 阶段规格:`project-management/PJ2026-01/specs/stage-cloud-spec-20260601.md`,历史 issue `[#644](https://github.com/pikasTech/HWLAB/issues/644)`。 +- 创建或更新 HWLAB 项目管理 issue 时,非平凡 L1/L2/L3/L4 工作都要链接回 `project-management/PJ2026-01` 中的对应规格文件,并保持 `[#645](https://github.com/pikasTech/HWLAB/issues/645)` 只作为导航索引。 +- 所有管理 issue 和规格都使用 HWLAB 层级编号:项目编号格式为 `PJ-`;`PJ` 表示项目,`2026` 是立项年份,HWLAB L0 是 `PJ2026-01`;L1 编号是 `PJ2026-0101`、`PJ2026-0102` 等;L2 继续追加两位,例如 `PJ2026-010102` 表示 HWLAB L1 第 1 个方向下的第 2 个 L2 课题。稳定节点短名通常控制在 8 个中文汉字以内,解释写在正文,不写进名称。 + +## 工作流 + +1. 做规划类问题时,读取 `references/project-planning.md`。 +2. 创建或更新总规格、issue 正文时,读取 `references/templates.md`。 +3. 写入 GitHub issue/PR 评论时,同时使用 `unidesk-gh`;所有写入必须从 `/root/unidesk` 通过 `bun scripts/cli.ts gh ...` 完成。 +4. 修改长期项目管理文档时,把稳定规划内容放到 `project-management/PJ2026-01` 的总规格/规格文件,阶段报告只保留带日期的总结。 +5. 修订 L1/L2 层级时,先按完成标准判断主责,再看实现表面。工具、CLI、Web、API 形态可以形成关联 L1,但主责 L1 必须是定义完成标准的能力域。 +6. 更新重大规划型 issue 时,先把 P0 SPEC 文件和图维护到位,再用 `unidesk-gh` 回写 issue;后续代码任务必须在 issue 中引用 SPEC 编号和实现引用版本。 + +## 必需层级和职责 + +除非用户给出更严格的层级,否则使用以下任务树: + +- L0:重大项目 / 总规格。负责使命、范围、系统边界、内部模块分工与规格索引、全局原子需求。 +- L1:方向。负责一个稳定能力域、范围边界、成功标准、L2 课题清单和子工作验收定义权。 +- L2:课题。负责某个 L1 内的具体工作计划,包括交付物、阻塞项和验证计划。 +- L3:子课题 / 验收切片。负责一个有界验收路径,例如一条 CaseRun 线、Web 检查、CLI smoke 或部署/控制面验证。 +- L4:执行任务 / PR / CaseRun / smoke / 文档收口。负责具体执行和证据收集,不能重新定义父级范围。 + +每个非平凡 issue 都应写明上级链接、目标分支/lane、验收标准、验证入口和收口回写目标。 +重大规划型 issue 还必须把“更新并确认 SPEC”列为 P0,且 P0 未完成前不得推进代码实现。 diff --git a/.agents/skills/unidesk-ops/SKILL.md b/.agents/skills/unidesk-ops/SKILL.md index 5866f121..4eb38549 100644 --- a/.agents/skills/unidesk-ops/SKILL.md +++ b/.agents/skills/unidesk-ops/SKILL.md @@ -3,274 +3,33 @@ name: unidesk-ops description: UniDesk 手动运维 CLI — `server`、`gc` 和 PK01 `platform-db postgres` 子命令,覆盖主 server 启停、健康检查、swap、日志、Docker 镜像清理、磁盘 GC、服务重建/重启和 PK01 host PostgreSQL 运维。用户提到 server start、server status、server swap、server rebuild、server restart、gc、磁盘清理、platform-db、PK01 PostgreSQL、运维时使用。 --- -# UniDesk 手动运维 CLI +# UniDesk Ops -主 server 运维入口,通过 `bun scripts/cli.ts server ...` 和 `bun scripts/cli.ts gc ...` 操作。 +主 server、GC 和 PK01 PostgreSQL 的手动运维入口。常规操作走 `bun scripts/cli.ts server ...`、`gc ...`、`platform-db postgres ...`。 -**固定入口前缀**: `cd /root/unidesk && bun scripts/cli.ts ...` - ---- - -## 启停 +## 高频入口 ```bash +bun scripts/cli.ts server status bun scripts/cli.ts server start bun scripts/cli.ts server stop -bun scripts/cli.ts server restart -``` - -异步 job 模式,返回 `job.id`、日志路径。`start` 执行 Docker 构建+启动,`stop` 停止 Compose project 全部服务。 -`restart` 是无构建单服务维护重启,使用现有镜像执行 `--no-build --no-deps --force-recreate` 并等待容器 `healthy/running`,适合刷新 provider-gateway 这类运行态异常,不能替代镜像发布或源码构建。 - ---- - -## 健康检查 - -```bash -bun scripts/cli.ts server status -``` - -返回公开端口、受限宿主端口、内部端口、swap 摘要、Compose 容器状态、各服务健康检查和访问 URL。 - -低内存时 `swap.warning` 非空,先执行 `server swap ensure`。 - -## 节点资源指标同步 - -资源面板显示旧 CPU/内存/磁盘值时,先对照 provider 上报、backend-core 落库和内部 API 三段: - -```bash -docker logs --tail 120 unidesk-provider-gateway-main | rg 'system_status_sent|docker_status_sent|error' -docker logs --tail 200 unidesk-backend-core | rg 'provider_system_status|provider_docker_status|provider_message_failed|serializing parameter' -docker exec unidesk-backend-core sh -lc 'backend-core --fetch-json http://127.0.0.1:8080/api/nodes/system-status?limit=5' -``` - -provider 已发送但 backend-core 没有 `provider_system_status` / `provider_docker_status`,优先查 backend-core ingest/DB 写入错误;provider 自身停发时才用 `server restart provider-gateway` 做无构建维护重启。修复 backend-core 后必须用 `server rebuild backend-core` 上线并验证 `stale=false`、`currentCollectedAt` 为当前采样。长期语义见 `docs/reference/observability.md#node-resource-status`。 - ---- - -## Swap 管理 - -```bash -bun scripts/cli.ts server swap status -bun scripts/cli.ts server swap ensure [--path /swapfile] [--size 2GiB] [--dry-run] -``` - -`ensure` 在无 active swap 时创建 swapfile(`chmod 600`、`mkswap`、`swapon`、写 `/etc/fstab`)。已有 swap 时 no-op。fstab 写入失败返回 `degraded`。 - ---- - -## 日志 - -```bash -bun scripts/cli.ts server logs -``` - -返回文件日志和 Docker 容器日志尾部,默认限制输出大小。 - ---- - -## Docker 镜像清理 - -```bash -bun scripts/cli.ts server cleanup plan [--min-age-hours 24] [--limit N] -bun scripts/cli.ts server cleanup run --confirm [--min-age-hours 24] [--limit N] -``` - -`plan` 只生成 dry-run 计划;`run --confirm` 只删除同一 classifier 选出的 stale Docker images。保守白名单:保留 running/stopped 容器镜像、UniDesk YAML/GitOps/image catalog 声明的 commit-pinned artifact、Compose stable image。禁止 `docker system prune`、`docker image prune`、`docker volume rm`、`docker compose down -v` 和数据库清理。高风险候选必须额外显式 `--include-high-risk` 才会执行。 - ---- - -## 磁盘 GC - -```bash +bun scripts/cli.ts server logs --tail-bytes 12000 bun scripts/cli.ts gc plan -bun scripts/cli.ts gc run --confirm -bun scripts/cli.ts gc db-trace -bun scripts/cli.ts gc policy -bun scripts/cli.ts gc remote [--target-use-percent N] [--dry-run|--confirm] +bun scripts/cli.ts platform-db postgres status ``` -主 server 和 provider 磁盘高水位缓解。`plan` 只读输出候选、风险、估算收益和保护对象。`run` 必须 `--confirm`。`remote` 通过 SSH 透传执行远端 GC。 +启停、健康检查、节点资源指标、swap、日志、Docker 镜像清理、磁盘 GC、服务重建/重启、PK01 PostgreSQL、YAML-first 运维、Moon Bridge、profile smoke 和 MiniMax recovery 细节见 [references/full.md](references/full.md)。 -常用显式候选和目标口径: +## P0 边界 -```bash -bun scripts/cli.ts gc plan --target-use-percent 69 \ - --include-tool-caches \ - --include-stale-tmp \ - --include-vscode-stale-servers \ - --include-vscode-stale-extensions \ - --include-vscode-cached-vsix \ - --include-baidu-staging \ - --include-vpn-diagnostic-logs -``` +- backend-core 运行面恢复 healthy 后,除非用户明确要求,不主动 rebuild/restart/替换 backend-core。 +- Master server 不作为通用构建机;Docker/Rust/Go/前端高 CPU 构建必须走批准的 CI/运行面。 +- GC 默认 plan 只读,真实删除必须显式 `run --confirm` 并遵循 allowlist/retention。 +- Secret、database URL 和 API key 不打印完整值。 -`--target-use-percent` 按 `df` 显示口径估算 shortfall。主 server GC 的默认 include、保留窗口、输出 limit、Codex session root、worktree main/root/baseRef、worktree 扫描预算和 `.state` allowlist roots 由 `config/unidesk-cli.yaml#gc` 拥有;CLI 参数只做一次性显式覆盖。工具缓存、`/tmp` 非 allowlist 直接子项、VS Code 历史 server/extension 版本、VS Code CachedExtensionVSIXs 下载缓存、Baidu staging 旧 PGDATA tarball、UniDesk `.state` 历史诊断/部署产物、`.state` stale scratch、Codex inactive sessions、merged worktrees、VPN 诊断 ring pcap 均默认不启用;必须显式 include 后才进入候选,且执行时仍受路径断言保护。stale `/tmp` 扫描按 `--limit` 有界枚举候选,避免为了估算全量临时目录而长时间无输出。`.state` retention 通过 `--include-state-artifacts` 和 `--include-state-stale-scratch` 读取 YAML allowlist;不得把 `.state` 根目录当成通用清理对象。Codex session 清理只删除 YAML root 下超过 keepHours 的普通 session 文件,永远不删除 auth/config。Worktree 清理只扫描 YAML root 下 inactive 且已合入 YAML baseRef 或 cherry-equivalent 的 worktree,run 删除前重新校验 full clean 状态并使用 `git worktree remove`。VS Code cached VSIX 只选择 `/root/.vscode-server/data/CachedExtensionVSIXs` 下超过 `--vscode-cached-vsix-keep-days` 的顶层普通缓存文件,执行前检查 active fd;不删除已安装 extensions、server 或 user data。VPN 诊断日志只选择 `/root/vpn-server/logs/hy2-udp-ring-*.pcap` 和 `hy2-monitor-ring-*.pcap` 中超过 `--vpn-diagnostic-log-keep-hours` 的普通文件,执行前检查 active fd;不删除 evidence JSONL。默认 GC 不触碰 `.state/recovery`、`.state/codex-queue/codex-home`、`.state/deploy/work`、`.state/baidu-netdisk`、PGDATA、Docker volumes/images、Codex auth/config state、active/unmerged/dirty worktree、runtime image/snapshot state、Baidu staging 根目录、VPN 日志根目录或 VS Code user data。 +## 何时读取 reference -`gc policy install` 的每日 timer 从 `config/unidesk-cli.yaml#gc.policyTimer` 渲染 VPN 诊断 pcap retention、UniDesk `.state` artifact retention 和 VS Code CachedExtensionVSIXs retention,用于限制长期诊断/部署产物、tcpdump ring 文件与 VS Code 下载缓存增长;手动 `gc plan/run` 仍必须显式 `--include-vpn-diagnostic-logs` / `--include-state-artifacts` / `--include-vscode-cached-vsix` 才会列出或删除这些对象。 - ---- - -## 服务重建 - -```bash -bun scripts/cli.ts server rebuild -``` - -service 可选:`backend-core` | `frontend` | `dev-frontend-proxy` | `provider-gateway` | `todo-note` | `code-queue-mgr` | `project-manager` | `baidu-netdisk` | `oa-event-flow` - -异步 job:构建镜像 → `.state/locks/server-compose.lock` 串行保护 → `--no-deps --force-recreate` 替换容器 → 等待 `healthy/running`。 - -启动后必须轮询 job,不要把提交 job 当成已经完成: - -```bash -bun scripts/cli.ts server rebuild backend-core -bun scripts/cli.ts job status --tail-bytes 12000 -``` - -backend-core 重建完成后再做运行面验证: - -```bash -bun scripts/cli.ts server status -docker exec unidesk-backend-core sh -lc 'backend-core --fetch-json http://127.0.0.1:8080/health --require-ok' -``` - -**禁止事项**: -- backend-core 常规迭代不得在 master server 编译;只有已提交修复需要上线主 server Compose runtime 时,才用 `server rebuild backend-core` 受控异步 job -- D601 Code Queue 执行面不由 `server rebuild` 管理 -- 不重建/删除 database 命名卷 - ---- - -## PK01 Host PostgreSQL - -PK01 host-native PostgreSQL 是平台外置状态库样板,声明文件是 `config/platform-db/postgres-pk01.yaml`,受控入口是: - -```bash -bun scripts/cli.ts platform-db postgres plan --config config/platform-db/postgres-pk01.yaml -bun scripts/cli.ts platform-db postgres status --config config/platform-db/postgres-pk01.yaml -bun scripts/cli.ts platform-db postgres export-secrets --config config/platform-db/postgres-pk01.yaml --confirm -bun scripts/cli.ts platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm -bun scripts/cli.ts platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm --wait -``` - -- `plan` / `status` 只读;`apply --confirm` 默认创建本地异步 job;`apply --confirm --wait` 会启动 PK01 侧 root-owned job 并短轮询。 -- `export-secrets --confirm` 只按 YAML 重新物化本地 Secret source/export 文件,不触碰 PK01 远端 PostgreSQL;连接串格式、consumer target 或 Secret export 变更优先用它,再走对应消费者的 Secret sync。 -- 输出只显示 Secret key 名、presence、fingerprint、连接 host、SSL 状态和状态摘要;禁止打印密码或完整 `DATABASE_URL`。 -- 同一个 PK01 PostgreSQL 实例可承载多个 YAML 声明的 role/database;新增消费者按 `secrets.entries`、`objects.roles`、`objects.databases`、`postgres.auth.pgHba` 和 `exports.connectionStrings` 成套声明,不新开 PostgreSQL 实例,也不默认用 schema 隔离应用状态。 -- 跨节点消费者必须直连 YAML 的 `postgres.network.connectionHost`,当前是 PK01 公网 endpoint;不要让 D601/G14/Sub2API/HWLAB/AgentRun 通过 master server 中转 PostgreSQL。 -- 当前 TLS 口径是 PostgreSQL native TLS + `sslmode=require`。`publicDns` 只是可选 alias;只要 `connectionHost` 是可达 IP,DNS 未解析不作为切库 blocker。 -- 远端 PostgreSQL 配置或 `pg_hba` 来源 CIDR 变化后,先跑 `apply --confirm --wait`,再跑 `status`;若消费者公网出口 IP 变化,必须先更新 YAML `allowSources` 和对应 `pg_hba`。 -- `status` 验收要看 `roles[]`、`databases[]` 和 `appConnections[]`;不要只看旧的 `roleExists` / `databaseExists` 标量。 - -日常复验建议: - -```bash -bun scripts/cli.ts platform-db postgres status --config config/platform-db/postgres-pk01.yaml -trans PK01 sh <<'SH' -systemctl is-active postgresql -systemctl is-enabled postgresql -systemctl is-active unidesk-pk01-sub2api-pgdump.timer -SH -``` - -长期边界见 `docs/reference/pk01.md`;Sub2API 消费侧边界见 `docs/reference/platform-infra.md`。 - -## YAML-First 分布式运维边界 - -UniDesk 自有分布式运维以 `config/**/*.yaml` 为 desired-state truth。服务仓库里的 `deploy.json` 不能作为 UniDesk deployment truth;node/lane、runtime namespace、GitOps branch/path、image artifact、public exposure、Secret、外置数据库、probe 和 rollout 等运维选择必须进入所属 UniDesk YAML,并通过受控 CLI 渲染或同步。 - -AgentRun v0.2/D601 这类 YAML-only lane 的控制面、Secret 同步、外置 DB wiring 和状态检查使用: - -```bash -bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02 -bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --confirm -bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --confirm -bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02 --full -``` - -部署触发和 GitOps promotion 入口归 `$unidesk-cicd`;本 skill 只记录手动运维边界和长期排障入口。长期架构见 `docs/reference/yaml-first-ops.md`,AgentRun 细则见 `docs/reference/agentrun.md`。 - ---- - -## Moon Bridge 管理 - -Moon Bridge 是 Codex ↔ 上游 provider 的桥接服务,通过 profile 级 wrapper 管理: - -```bash -# DeepSeek profile -dscx bridge-start -dscx bridge-status -dscx bridge-smoke dscx-bridge-ok -dscx bridge-stop - -# MiniMax profile -mxcx bridge-start -mxcx bridge-status -mxcx bridge-smoke mxcx-bridge-ok -mxcx bridge-stop -``` - -- `dscx` → `127.0.0.1:38440`(Codex custom provider `deepseek`,DeepSeek V4 Pro) -- `mxcx` → `127.0.0.1:38441`(Codex custom provider `minimax`,MiniMax-M3) -- 启动用 `setsid` + profile-local PID file,进程不随 CLI 退出 -- 日志在 `/logs/moonbridge/` - ---- - -## Codex Profile Smoke - -```bash -# DeepSeek -dscx doctor -dscx bridge-smoke dscx-bridge-ok -dscx exec --skip-git-repo-check 'Reply exactly: dscx-codex-ok' - -# MiniMax -mxcx doctor -mxcx bridge-smoke mxcx-bridge-ok -mxcx exec --skip-git-repo-check 'Reply exactly: mxcx-codex-ok' - -# ACX GPT direct profiles -acx status -acx gpt-only exec --json 'Reply exactly: acx-only-ok' -acx gpt-sub2api exec --json 'Reply exactly: acx-sub2api-ok' -acx exec --json 'Reply exactly: acx-default-ok' -``` - -`bridge-smoke` 验证 Moon Bridge → provider 链路。`exec` 验证完整 Codex CLI → bridge → provider 全链路。 - -`acx` 的 GPT aliases 是 Codex custom provider 直连 Responses 上游,不经过本地 `127.0.0.1:38448` router。GPT 模式下 `acx status` 应输出 `mode=gpt-direct`、`routerRequired=false`、`portPids=[]`;小真实调用应返回期望文本,重复或 resume 流量应能看到非零 `cached_input_tokens`。OpenCode Zen Go aliases 仍通过 `acx route-start|route-status` 走 router → `gocx`/Moon Bridge 路径。长期边界见 `docs/reference/master-server-ops.md`。 - ---- - -## MiniMax Session Recovery - -MiniMax 会话因无效 tool-call arguments 导致 `resume` 反复失败时的恢复流程: - -```bash -# 1. 清理无效 tool arguments -mxcx session-clean - -# 2. 确认幂等(应返回 changed=false) -mxcx session-clean - -# 3. 注入 guard 防止复发 -mxcx session-guard - -# 4. 非交互 smoke 验证恢复 -mxcx exec resume 'Reply exactly: recovered-ok' - -# 5. apply-patch smoke(如涉及远端编辑) -# 验证使用 trans apply-patch,非 download/upload/sed -``` - -`mxcx resume ` 自动执行 `session-clean` + `session-guard` 后再调用 Codex。修复最小化:只修无效 `function_call.arguments`,不压缩/截断/重排 transcript。 - ---- - -## 参考文档 - -- **主 server 架构与行为规范**: `docs/reference/master-server-ops.md`(Execution Boundary、Codex Provider Profile 架构、Moon Bridge 内部规则、MiniMax session-clean 行为约束、apply-patch 策略) -- **磁盘 GC 长期规则**: `docs/reference/gc.md` -- **部署边界**: `docs/reference/deployment.md` +- 需要服务 rebuild/restart、日志、swap 或 health 判定:读 [references/full.md](references/full.md) 的对应段。 +- 需要磁盘/镜像/trace GC:读 Docker 镜像清理和磁盘 GC 段。 +- 需要 PK01 PostgreSQL 或 YAML-first 运维:读对应段。 +- 需要 profile smoke、Moon Bridge 或 session recovery:读对应段。 diff --git a/.agents/skills/unidesk-ops/references/full.md b/.agents/skills/unidesk-ops/references/full.md new file mode 100644 index 00000000..5866f121 --- /dev/null +++ b/.agents/skills/unidesk-ops/references/full.md @@ -0,0 +1,276 @@ +--- +name: unidesk-ops +description: UniDesk 手动运维 CLI — `server`、`gc` 和 PK01 `platform-db postgres` 子命令,覆盖主 server 启停、健康检查、swap、日志、Docker 镜像清理、磁盘 GC、服务重建/重启和 PK01 host PostgreSQL 运维。用户提到 server start、server status、server swap、server rebuild、server restart、gc、磁盘清理、platform-db、PK01 PostgreSQL、运维时使用。 +--- + +# UniDesk 手动运维 CLI + +主 server 运维入口,通过 `bun scripts/cli.ts server ...` 和 `bun scripts/cli.ts gc ...` 操作。 + +**固定入口前缀**: `cd /root/unidesk && bun scripts/cli.ts ...` + +--- + +## 启停 + +```bash +bun scripts/cli.ts server start +bun scripts/cli.ts server stop +bun scripts/cli.ts server restart +``` + +异步 job 模式,返回 `job.id`、日志路径。`start` 执行 Docker 构建+启动,`stop` 停止 Compose project 全部服务。 +`restart` 是无构建单服务维护重启,使用现有镜像执行 `--no-build --no-deps --force-recreate` 并等待容器 `healthy/running`,适合刷新 provider-gateway 这类运行态异常,不能替代镜像发布或源码构建。 + +--- + +## 健康检查 + +```bash +bun scripts/cli.ts server status +``` + +返回公开端口、受限宿主端口、内部端口、swap 摘要、Compose 容器状态、各服务健康检查和访问 URL。 + +低内存时 `swap.warning` 非空,先执行 `server swap ensure`。 + +## 节点资源指标同步 + +资源面板显示旧 CPU/内存/磁盘值时,先对照 provider 上报、backend-core 落库和内部 API 三段: + +```bash +docker logs --tail 120 unidesk-provider-gateway-main | rg 'system_status_sent|docker_status_sent|error' +docker logs --tail 200 unidesk-backend-core | rg 'provider_system_status|provider_docker_status|provider_message_failed|serializing parameter' +docker exec unidesk-backend-core sh -lc 'backend-core --fetch-json http://127.0.0.1:8080/api/nodes/system-status?limit=5' +``` + +provider 已发送但 backend-core 没有 `provider_system_status` / `provider_docker_status`,优先查 backend-core ingest/DB 写入错误;provider 自身停发时才用 `server restart provider-gateway` 做无构建维护重启。修复 backend-core 后必须用 `server rebuild backend-core` 上线并验证 `stale=false`、`currentCollectedAt` 为当前采样。长期语义见 `docs/reference/observability.md#node-resource-status`。 + +--- + +## Swap 管理 + +```bash +bun scripts/cli.ts server swap status +bun scripts/cli.ts server swap ensure [--path /swapfile] [--size 2GiB] [--dry-run] +``` + +`ensure` 在无 active swap 时创建 swapfile(`chmod 600`、`mkswap`、`swapon`、写 `/etc/fstab`)。已有 swap 时 no-op。fstab 写入失败返回 `degraded`。 + +--- + +## 日志 + +```bash +bun scripts/cli.ts server logs +``` + +返回文件日志和 Docker 容器日志尾部,默认限制输出大小。 + +--- + +## Docker 镜像清理 + +```bash +bun scripts/cli.ts server cleanup plan [--min-age-hours 24] [--limit N] +bun scripts/cli.ts server cleanup run --confirm [--min-age-hours 24] [--limit N] +``` + +`plan` 只生成 dry-run 计划;`run --confirm` 只删除同一 classifier 选出的 stale Docker images。保守白名单:保留 running/stopped 容器镜像、UniDesk YAML/GitOps/image catalog 声明的 commit-pinned artifact、Compose stable image。禁止 `docker system prune`、`docker image prune`、`docker volume rm`、`docker compose down -v` 和数据库清理。高风险候选必须额外显式 `--include-high-risk` 才会执行。 + +--- + +## 磁盘 GC + +```bash +bun scripts/cli.ts gc plan +bun scripts/cli.ts gc run --confirm +bun scripts/cli.ts gc db-trace +bun scripts/cli.ts gc policy +bun scripts/cli.ts gc remote [--target-use-percent N] [--dry-run|--confirm] +``` + +主 server 和 provider 磁盘高水位缓解。`plan` 只读输出候选、风险、估算收益和保护对象。`run` 必须 `--confirm`。`remote` 通过 SSH 透传执行远端 GC。 + +常用显式候选和目标口径: + +```bash +bun scripts/cli.ts gc plan --target-use-percent 69 \ + --include-tool-caches \ + --include-stale-tmp \ + --include-vscode-stale-servers \ + --include-vscode-stale-extensions \ + --include-vscode-cached-vsix \ + --include-baidu-staging \ + --include-vpn-diagnostic-logs +``` + +`--target-use-percent` 按 `df` 显示口径估算 shortfall。主 server GC 的默认 include、保留窗口、输出 limit、Codex session root、worktree main/root/baseRef、worktree 扫描预算和 `.state` allowlist roots 由 `config/unidesk-cli.yaml#gc` 拥有;CLI 参数只做一次性显式覆盖。工具缓存、`/tmp` 非 allowlist 直接子项、VS Code 历史 server/extension 版本、VS Code CachedExtensionVSIXs 下载缓存、Baidu staging 旧 PGDATA tarball、UniDesk `.state` 历史诊断/部署产物、`.state` stale scratch、Codex inactive sessions、merged worktrees、VPN 诊断 ring pcap 均默认不启用;必须显式 include 后才进入候选,且执行时仍受路径断言保护。stale `/tmp` 扫描按 `--limit` 有界枚举候选,避免为了估算全量临时目录而长时间无输出。`.state` retention 通过 `--include-state-artifacts` 和 `--include-state-stale-scratch` 读取 YAML allowlist;不得把 `.state` 根目录当成通用清理对象。Codex session 清理只删除 YAML root 下超过 keepHours 的普通 session 文件,永远不删除 auth/config。Worktree 清理只扫描 YAML root 下 inactive 且已合入 YAML baseRef 或 cherry-equivalent 的 worktree,run 删除前重新校验 full clean 状态并使用 `git worktree remove`。VS Code cached VSIX 只选择 `/root/.vscode-server/data/CachedExtensionVSIXs` 下超过 `--vscode-cached-vsix-keep-days` 的顶层普通缓存文件,执行前检查 active fd;不删除已安装 extensions、server 或 user data。VPN 诊断日志只选择 `/root/vpn-server/logs/hy2-udp-ring-*.pcap` 和 `hy2-monitor-ring-*.pcap` 中超过 `--vpn-diagnostic-log-keep-hours` 的普通文件,执行前检查 active fd;不删除 evidence JSONL。默认 GC 不触碰 `.state/recovery`、`.state/codex-queue/codex-home`、`.state/deploy/work`、`.state/baidu-netdisk`、PGDATA、Docker volumes/images、Codex auth/config state、active/unmerged/dirty worktree、runtime image/snapshot state、Baidu staging 根目录、VPN 日志根目录或 VS Code user data。 + +`gc policy install` 的每日 timer 从 `config/unidesk-cli.yaml#gc.policyTimer` 渲染 VPN 诊断 pcap retention、UniDesk `.state` artifact retention 和 VS Code CachedExtensionVSIXs retention,用于限制长期诊断/部署产物、tcpdump ring 文件与 VS Code 下载缓存增长;手动 `gc plan/run` 仍必须显式 `--include-vpn-diagnostic-logs` / `--include-state-artifacts` / `--include-vscode-cached-vsix` 才会列出或删除这些对象。 + +--- + +## 服务重建 + +```bash +bun scripts/cli.ts server rebuild +``` + +service 可选:`backend-core` | `frontend` | `dev-frontend-proxy` | `provider-gateway` | `todo-note` | `code-queue-mgr` | `project-manager` | `baidu-netdisk` | `oa-event-flow` + +异步 job:构建镜像 → `.state/locks/server-compose.lock` 串行保护 → `--no-deps --force-recreate` 替换容器 → 等待 `healthy/running`。 + +启动后必须轮询 job,不要把提交 job 当成已经完成: + +```bash +bun scripts/cli.ts server rebuild backend-core +bun scripts/cli.ts job status --tail-bytes 12000 +``` + +backend-core 重建完成后再做运行面验证: + +```bash +bun scripts/cli.ts server status +docker exec unidesk-backend-core sh -lc 'backend-core --fetch-json http://127.0.0.1:8080/health --require-ok' +``` + +**禁止事项**: +- backend-core 常规迭代不得在 master server 编译;只有已提交修复需要上线主 server Compose runtime 时,才用 `server rebuild backend-core` 受控异步 job +- D601 Code Queue 执行面不由 `server rebuild` 管理 +- 不重建/删除 database 命名卷 + +--- + +## PK01 Host PostgreSQL + +PK01 host-native PostgreSQL 是平台外置状态库样板,声明文件是 `config/platform-db/postgres-pk01.yaml`,受控入口是: + +```bash +bun scripts/cli.ts platform-db postgres plan --config config/platform-db/postgres-pk01.yaml +bun scripts/cli.ts platform-db postgres status --config config/platform-db/postgres-pk01.yaml +bun scripts/cli.ts platform-db postgres export-secrets --config config/platform-db/postgres-pk01.yaml --confirm +bun scripts/cli.ts platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm +bun scripts/cli.ts platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm --wait +``` + +- `plan` / `status` 只读;`apply --confirm` 默认创建本地异步 job;`apply --confirm --wait` 会启动 PK01 侧 root-owned job 并短轮询。 +- `export-secrets --confirm` 只按 YAML 重新物化本地 Secret source/export 文件,不触碰 PK01 远端 PostgreSQL;连接串格式、consumer target 或 Secret export 变更优先用它,再走对应消费者的 Secret sync。 +- 输出只显示 Secret key 名、presence、fingerprint、连接 host、SSL 状态和状态摘要;禁止打印密码或完整 `DATABASE_URL`。 +- 同一个 PK01 PostgreSQL 实例可承载多个 YAML 声明的 role/database;新增消费者按 `secrets.entries`、`objects.roles`、`objects.databases`、`postgres.auth.pgHba` 和 `exports.connectionStrings` 成套声明,不新开 PostgreSQL 实例,也不默认用 schema 隔离应用状态。 +- 跨节点消费者必须直连 YAML 的 `postgres.network.connectionHost`,当前是 PK01 公网 endpoint;不要让 D601/G14/Sub2API/HWLAB/AgentRun 通过 master server 中转 PostgreSQL。 +- 当前 TLS 口径是 PostgreSQL native TLS + `sslmode=require`。`publicDns` 只是可选 alias;只要 `connectionHost` 是可达 IP,DNS 未解析不作为切库 blocker。 +- 远端 PostgreSQL 配置或 `pg_hba` 来源 CIDR 变化后,先跑 `apply --confirm --wait`,再跑 `status`;若消费者公网出口 IP 变化,必须先更新 YAML `allowSources` 和对应 `pg_hba`。 +- `status` 验收要看 `roles[]`、`databases[]` 和 `appConnections[]`;不要只看旧的 `roleExists` / `databaseExists` 标量。 + +日常复验建议: + +```bash +bun scripts/cli.ts platform-db postgres status --config config/platform-db/postgres-pk01.yaml +trans PK01 sh <<'SH' +systemctl is-active postgresql +systemctl is-enabled postgresql +systemctl is-active unidesk-pk01-sub2api-pgdump.timer +SH +``` + +长期边界见 `docs/reference/pk01.md`;Sub2API 消费侧边界见 `docs/reference/platform-infra.md`。 + +## YAML-First 分布式运维边界 + +UniDesk 自有分布式运维以 `config/**/*.yaml` 为 desired-state truth。服务仓库里的 `deploy.json` 不能作为 UniDesk deployment truth;node/lane、runtime namespace、GitOps branch/path、image artifact、public exposure、Secret、外置数据库、probe 和 rollout 等运维选择必须进入所属 UniDesk YAML,并通过受控 CLI 渲染或同步。 + +AgentRun v0.2/D601 这类 YAML-only lane 的控制面、Secret 同步、外置 DB wiring 和状态检查使用: + +```bash +bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02 +bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --confirm +bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --confirm +bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02 --full +``` + +部署触发和 GitOps promotion 入口归 `$unidesk-cicd`;本 skill 只记录手动运维边界和长期排障入口。长期架构见 `docs/reference/yaml-first-ops.md`,AgentRun 细则见 `docs/reference/agentrun.md`。 + +--- + +## Moon Bridge 管理 + +Moon Bridge 是 Codex ↔ 上游 provider 的桥接服务,通过 profile 级 wrapper 管理: + +```bash +# DeepSeek profile +dscx bridge-start +dscx bridge-status +dscx bridge-smoke dscx-bridge-ok +dscx bridge-stop + +# MiniMax profile +mxcx bridge-start +mxcx bridge-status +mxcx bridge-smoke mxcx-bridge-ok +mxcx bridge-stop +``` + +- `dscx` → `127.0.0.1:38440`(Codex custom provider `deepseek`,DeepSeek V4 Pro) +- `mxcx` → `127.0.0.1:38441`(Codex custom provider `minimax`,MiniMax-M3) +- 启动用 `setsid` + profile-local PID file,进程不随 CLI 退出 +- 日志在 `/logs/moonbridge/` + +--- + +## Codex Profile Smoke + +```bash +# DeepSeek +dscx doctor +dscx bridge-smoke dscx-bridge-ok +dscx exec --skip-git-repo-check 'Reply exactly: dscx-codex-ok' + +# MiniMax +mxcx doctor +mxcx bridge-smoke mxcx-bridge-ok +mxcx exec --skip-git-repo-check 'Reply exactly: mxcx-codex-ok' + +# ACX GPT direct profiles +acx status +acx gpt-only exec --json 'Reply exactly: acx-only-ok' +acx gpt-sub2api exec --json 'Reply exactly: acx-sub2api-ok' +acx exec --json 'Reply exactly: acx-default-ok' +``` + +`bridge-smoke` 验证 Moon Bridge → provider 链路。`exec` 验证完整 Codex CLI → bridge → provider 全链路。 + +`acx` 的 GPT aliases 是 Codex custom provider 直连 Responses 上游,不经过本地 `127.0.0.1:38448` router。GPT 模式下 `acx status` 应输出 `mode=gpt-direct`、`routerRequired=false`、`portPids=[]`;小真实调用应返回期望文本,重复或 resume 流量应能看到非零 `cached_input_tokens`。OpenCode Zen Go aliases 仍通过 `acx route-start|route-status` 走 router → `gocx`/Moon Bridge 路径。长期边界见 `docs/reference/master-server-ops.md`。 + +--- + +## MiniMax Session Recovery + +MiniMax 会话因无效 tool-call arguments 导致 `resume` 反复失败时的恢复流程: + +```bash +# 1. 清理无效 tool arguments +mxcx session-clean + +# 2. 确认幂等(应返回 changed=false) +mxcx session-clean + +# 3. 注入 guard 防止复发 +mxcx session-guard + +# 4. 非交互 smoke 验证恢复 +mxcx exec resume 'Reply exactly: recovered-ok' + +# 5. apply-patch smoke(如涉及远端编辑) +# 验证使用 trans apply-patch,非 download/upload/sed +``` + +`mxcx resume ` 自动执行 `session-clean` + `session-guard` 后再调用 Codex。修复最小化:只修无效 `function_call.arguments`,不压缩/截断/重排 transcript。 + +--- + +## 参考文档 + +- **主 server 架构与行为规范**: `docs/reference/master-server-ops.md`(Execution Boundary、Codex Provider Profile 架构、Moon Bridge 内部规则、MiniMax session-clean 行为约束、apply-patch 策略) +- **磁盘 GC 长期规则**: `docs/reference/gc.md` +- **部署边界**: `docs/reference/deployment.md` diff --git a/.agents/skills/unidesk-otel/SKILL.md b/.agents/skills/unidesk-otel/SKILL.md index d57b332c..112d0bf3 100644 --- a/.agents/skills/unidesk-otel/SKILL.md +++ b/.agents/skills/unidesk-otel/SKILL.md @@ -5,172 +5,26 @@ description: UniDesk OpenTelemetry/Tempo 链路追踪运维技能。用户提到 # UniDesk OTel -Skill(cli-spec) +Skill(cli-spec)。用于 UniDesk OpenTelemetry/Tempo 链路追踪运维、按 traceId 查 span、Code Agent/AgentRun/HWLAB 跨服务追踪,以及 provider-stream-disconnected 等问题定位。 -UniDesk 的 OTel 运行面在 `platform-infra` namespace:OTel Collector 负责接收 OTLP traces,Tempo 负责查询。操作入口统一走 UniDesk YAML-first CLI,不直接 `kubectl port-forward`、手写 Tempo API 或裸 `curl`。 - -**固定入口**: `cd /root/unidesk && bun scripts/cli.ts platform-infra observability ...` - -## 基本状态 +## 高频入口 ```bash -bun scripts/cli.ts platform-infra observability status --target D601 -bun scripts/cli.ts platform-infra observability validate --target D601 +bun scripts/cli.ts platform-infra observability status +bun scripts/cli.ts platform-infra observability trace +bun scripts/cli.ts platform-infra observability search --service --limit 20 ``` -- `status` 检查 `platform-infra` namespace、`otel-collector`、`tempo` Deployment/Service/Pod 和 readiness probe。 -- `validate` 生成一条测试 trace,经 Collector 写入 Tempo,再通过受控 service proxy 查询,证明采集和查询闭环可用。 -- `--full` 只在需要展开远端 stdout/stderr 或完整 status payload 时使用;默认输出必须保持低噪声。 +基本状态、trace 查询、噪声压制、业务 trace 映射、Code Agent/AgentRun 排障、codex-stdio 追穿、读取窗口与乱序调查、何时先改进 OTel 的细节见 [references/full.md](references/full.md)。 -## 查询 Trace +## P0 边界 -```bash -bun scripts/cli.ts platform-infra observability trace \ - --target D601 \ - --trace-id -``` +- 可见性问题优先修复;状态、耗时、失败原因、trace、命令结果或关键证据不可见时,先补 CLI/日志/状态输出。 +- OTel 查询默认低噪声摘要;完整 span/context 显式 `--full`/`--raw`。 +- 不把 trace 缺口误判成业务成功;缺少 span 或窗口不完整时,先说明观测边界。 -`--trace-id` 是 32 位 hex OpenTelemetry trace id,不是业务 traceId。默认输出只返回有界摘要: +## 何时读取 reference -- `spanCount`、`serviceCount`、`services` -- `businessTraceIds` -- `errorSpanCount` -- `spanNameCounts` -- `errorSpans` -- 去重后的关键 spans -- 下一步 drill-down 命令 - -默认输出不得展开完整 Tempo JSON;需要原始响应时才用 `--raw`。 - -当只有错误文案而没有 OTel trace id 时,先用 `search` 从 Tempo 最近 trace 中反查候选,再进入 `trace`: - -```bash -bun scripts/cli.ts platform-infra observability search \ - --target D601 \ - --grep 'no rollout found' \ - --lookback-minutes 360 \ - --candidate-limit 80 \ - --limit 20 -``` - -`search` 会通过受控 service proxy 调 Tempo `/api/search` 取候选 trace,并逐条拉 trace 做本地 grep 摘要;默认只输出匹配 trace、服务、业务 traceId、错误 span 和下一步命令。扩大时间窗或候选数必须显式传 `--lookback-minutes` / `--candidate-limit`,避免大 trace 输出淹没上下文。 - -## 噪声压制 - -按错误文案、span 名、failureKind 或关键属性定位时,优先用 `--grep`: - -```bash -bun scripts/cli.ts platform-infra observability trace \ - --target D601 \ - --trace-id \ - --grep provider-stream-disconnected \ - --limit 20 -``` - -- `--grep ` 在 span 名、status message 和关键 attributes 的摘要 JSON 中过滤。 -- `--limit ` 控制返回 span 数,避免大 trace 淹没上下文。 -- `--full` 展开完整 span 摘要,但仍不输出 Tempo raw body。 -- `--raw` 仅用于排查 Tempo 响应结构、CLI 解析器或后端返回本身。 - -## 业务 Trace 映射 - -HWLAB/Code Agent 的业务 traceId 通常形如 `trc_...`。当已知 OTel trace id 时,直接用 `trace --trace-id` 查询;当只知道业务 traceId 时,优先从 HWLAB trace/result、Code Agent result 或已记录的 issue 证据中取得对应 OTel trace id。不要为了找 OTel trace id 去打印 Secret、Authorization header、完整 DSN 或运行面 raw transcript。 - -OTel trace 内常见业务关联属性: - -- `traceId`: HWLAB 业务 traceId -- `otel.trace_id`: OTel trace id -- `runId` / `commandId`: AgentRun run/command -- `sessionId` / `turnId` / `threadId`: HWLAB/AgentRun 会话与 turn 关联 -- `failureKind` / `willRetry` / `terminalStatus`: 错误与终态判断 - -## Code Agent / AgentRun 排障 - -追 `Code Agent 代理暂时无法连接上游`、`provider-stream-disconnected`、Workbench 加载/转圈、turn idle 报错、AgentRun command terminal 状态时: - -优先用一条诊断命令汇总业务 trace、OTel trace、服务追穿、AgentRun 终态、HWLAB 读模型和 HTTP 403/401/5xx 根因: - -```bash -bun scripts/cli.ts platform-infra observability diagnose-code-agent \ - --target D601 \ - --business-trace-id -``` - -默认输出必须保持有界低噪声,重点看: - -- `mapping.businessTraceId` / `mapping.otelTraceId` -- `servicePath` 是否同时到达 `hwlab-cloud-api`、`agentrun-manager`、`agentrun-runner` -- `identity` 里的 `runId`、`commandId`、`sessionId`、`runnerJobId`、`runnerId`、`backendProfile`、`sourceCommit` -- `agentrun.terminalStatus`、`terminalEventType`、`runnerProviderClassification` -- `hwlabReadModel.sourceEventCount`、`requestedSinceSeq`、`turnStatusCounts` -- `http.problemCounts` 和 `projectionLag.status` -- `summary.rootCause` 与按置信度排序的 `rootCauseCandidates` - -只有需要展开 span 明细时使用 `--full`;只有排查 Tempo raw 响应或 CLI 解析器时使用 `--raw`。默认输出不得包含 Secret、Authorization header、DSN、可复制凭据或完整运行 transcript。 - -若没有业务 traceId 或诊断结果还需要 drill-down,再使用低层 trace/search: - -1. 先确认 OTel backend ready: - `bun scripts/cli.ts platform-infra observability status --target D601` -2. 查业务 trace 对应 OTel trace: - `bun scripts/cli.ts platform-infra observability trace --target D601 --trace-id ` -3. 用错误关键词过滤: - `bun scripts/cli.ts platform-infra observability trace --target D601 --trace-id --grep --limit 20` -4. 对照 `errorSpanCount`、`matchedSpanCount`、`terminalStatus`、`willRetry`、`runId`、`commandId` 判断是 terminal failure、retryable transient 还是旧 trace 缺 instrumentation。 - -旧 trace 不会因为后续 instrumentation 修复自动回填。若旧 trace 查不到错误 span,但新的 canary/真实 trace 能查到同类 `runner_error.*` span,应把旧 trace 结论写成“当时未采集到该事件”,不要倒推出运行面没有发生过错误。 - -### AgentRun codex-stdio 追穿检查 - -追 HWLAB Workbench turn idle、`waitingFor=code-agent`、工具调用后无 terminal、`provider-stream-disconnected`,或用户怀疑 AgentRun/codex-stdio 仍在运行但 OTel 没追到时,必须在同一 OTel trace 里同时看到 `hwlab-cloud-api`、`agentrun-manager` 和 `agentrun-runner`。只看到 manager dispatch 或 HWLAB business trace 不算追穿 runner。 - -优先用业务入口拿到 OTel trace id,再按 codex span 过滤: - -```bash -bun scripts/cli.ts platform-infra observability trace \ - --target D601 \ - --trace-id \ - --grep codex_stdio \ - --limit 120 \ - --full -``` - -通过证据应能看到 `codex_app_server.starting`、`codex_app_server.started`、需要进程退出时的 `codex_app_server.exit`、`codex_stdio.thread_start.*` 或 `thread_resume.*`、`codex_stdio.turn_start.*`、`codex_stdio.tool_call.started|completed|failed`、`codex_stdio.turn_completed`,以及问题相关的 `idle_warning`、`idle_timeout`、`provider_stream_disconnected` 或 `missing_terminal_after_tool`。这些 span 应带 `runId`、`commandId`、`runnerJobId`、`runnerId`、`sessionId`、`backendProfile`、`sourceCommit`、`traceId`、`otel.trace_id` 和 `valuesPrinted=false`。若默认摘要或 grep 看不到 `runnerJobId`,先用当前 UniDesk CLI 执行 `platform-infra observability trace --grep runnerJobId --full` 复查摘要器输出,必要时用 `--raw` 只排查 Tempo/CLI 解析结构;只有 raw 或更新后的摘要仍缺 `runnerJobId`,或同一 trace 没有 `agentrun-runner` service,才回 AgentRun runner-side instrumentation 排查。 - -如果需要确认工具调用 started/completed 的归一化,canary 应要求一次只读 shell 工具调用。Codex notification 的 `item/started` + `status=inProgress` 应落为 `codex_stdio.tool_call.started`,不得和 completed 混在一起。长期规则见 `docs/reference/agentrun.md#agentrun--hwlab-otel-追踪口径`。 - -## Trace 读取窗口与乱序调查 - -排查 HWLAB/Workbench trace 乱序、分页缺口、`--after-seq`/`--tail` 不生效、旧 trace 只返回局部事件或 read model 是否完整时,优先查 `trace_events_read` span: - -```bash -bun scripts/cli.ts platform-infra observability trace \ - --target D601 \ - --trace-id \ - --grep trace_events_read \ - --limit 20 \ - --full -``` - -摘要里重点看 `returnedEvents`、`sinceSeq`、`limit`、`fromSeq`、`toSeq`、`totalEvents`、`hasMore`、`fullTraceLoaded`、`rawEventCount`、`maxSeq`、`traceLastSeq`、`endSeq` 和 `commandFiltered`。这些字段用于判断“查询窗口是否正确传到后端”“后端是否只返回了局部事件”“read model 是否已经加载完整 trace”。若 `errorSpanCount=0` 但用户可见 timeline 仍乱序,先把结论写成展示/投影/renderer 调查 issue,不要把它定性为后端错误 span。 - -旧业务 trace 在 runtime 重启或保留策略后可能只剩局部事件;OTel 只能证明读取窗口、span 和当时观测到的字段,不能自动恢复业务事件流。需要验证新 instrumentation 时,使用新 canary 或仍可完整读取的真实 trace。 - -## 何时先改进 OTel - -遇到以下情况,先修 OTel CLI 或 instrumentation,再继续业务排障: - -- trace 命令只能返回 raw/tail,不能给出可读 span 摘要。 -- 大 trace 输出淹没上下文,缺少 `--grep`、`--limit`、错误 span 汇总或下一步 drill-down。 -- 关键 runner/backend/projection 事件只存在业务事件流,不进入 OTel。 -- error span 缺 `failureKind`、`willRetry`、`terminalStatus`、`runId`、`commandId` 等定位字段。 -- CLI 默认输出 Secret、Authorization header、DSN 或其他敏感值。 - -改进后必须用一条 canary 或真实 trace 证明新 span/摘要可查询,再继续定位原业务问题。 - -## 交付边界 - -- OTel 平台配置真相是 `config/platform-infra/observability.yaml`。 -- OTel CLI 实现在 `scripts/src/platform-infra-observability.ts`,帮助入口由 `scripts/src/platform-infra.ts` 暴露。 -- 修改 OTel CLI 属于 UniDesk 轻量 CLI 变更:默认只做语法检查、命令形态验证和真实 trace 查询,不新增合同测试。 -- 修改 AgentRun/HWLAB instrumentation 属于对应仓库/运行面的代码变更,必须按目标 repo 的 source truth、PR/CD 和原入口验收规则执行。 +- 查具体 traceId、span、服务过滤或噪声压制时,读 [references/full.md](references/full.md) 的查询和噪声段。 +- 排障 Code Agent/AgentRun/HWLAB 链路时,读业务映射和 Code Agent/AgentRun 段。 +- 判断是否需要先改进 OTel 或交付边界时,读对应段。 diff --git a/.agents/skills/unidesk-otel/references/full.md b/.agents/skills/unidesk-otel/references/full.md new file mode 100644 index 00000000..d57b332c --- /dev/null +++ b/.agents/skills/unidesk-otel/references/full.md @@ -0,0 +1,176 @@ +--- +name: unidesk-otel +description: UniDesk OpenTelemetry/Tempo 链路追踪运维技能。用户提到 OTel、OpenTelemetry、Tempo、trace backend、platform-infra observability、链路追踪、按 traceId 查 span、provider-stream-disconnected、Code Agent/AgentRun/HWLAB 跨服务追踪、或要求“用 otel 查/改进 otel”时使用。 +--- + +# UniDesk OTel + +Skill(cli-spec) + +UniDesk 的 OTel 运行面在 `platform-infra` namespace:OTel Collector 负责接收 OTLP traces,Tempo 负责查询。操作入口统一走 UniDesk YAML-first CLI,不直接 `kubectl port-forward`、手写 Tempo API 或裸 `curl`。 + +**固定入口**: `cd /root/unidesk && bun scripts/cli.ts platform-infra observability ...` + +## 基本状态 + +```bash +bun scripts/cli.ts platform-infra observability status --target D601 +bun scripts/cli.ts platform-infra observability validate --target D601 +``` + +- `status` 检查 `platform-infra` namespace、`otel-collector`、`tempo` Deployment/Service/Pod 和 readiness probe。 +- `validate` 生成一条测试 trace,经 Collector 写入 Tempo,再通过受控 service proxy 查询,证明采集和查询闭环可用。 +- `--full` 只在需要展开远端 stdout/stderr 或完整 status payload 时使用;默认输出必须保持低噪声。 + +## 查询 Trace + +```bash +bun scripts/cli.ts platform-infra observability trace \ + --target D601 \ + --trace-id +``` + +`--trace-id` 是 32 位 hex OpenTelemetry trace id,不是业务 traceId。默认输出只返回有界摘要: + +- `spanCount`、`serviceCount`、`services` +- `businessTraceIds` +- `errorSpanCount` +- `spanNameCounts` +- `errorSpans` +- 去重后的关键 spans +- 下一步 drill-down 命令 + +默认输出不得展开完整 Tempo JSON;需要原始响应时才用 `--raw`。 + +当只有错误文案而没有 OTel trace id 时,先用 `search` 从 Tempo 最近 trace 中反查候选,再进入 `trace`: + +```bash +bun scripts/cli.ts platform-infra observability search \ + --target D601 \ + --grep 'no rollout found' \ + --lookback-minutes 360 \ + --candidate-limit 80 \ + --limit 20 +``` + +`search` 会通过受控 service proxy 调 Tempo `/api/search` 取候选 trace,并逐条拉 trace 做本地 grep 摘要;默认只输出匹配 trace、服务、业务 traceId、错误 span 和下一步命令。扩大时间窗或候选数必须显式传 `--lookback-minutes` / `--candidate-limit`,避免大 trace 输出淹没上下文。 + +## 噪声压制 + +按错误文案、span 名、failureKind 或关键属性定位时,优先用 `--grep`: + +```bash +bun scripts/cli.ts platform-infra observability trace \ + --target D601 \ + --trace-id \ + --grep provider-stream-disconnected \ + --limit 20 +``` + +- `--grep ` 在 span 名、status message 和关键 attributes 的摘要 JSON 中过滤。 +- `--limit ` 控制返回 span 数,避免大 trace 淹没上下文。 +- `--full` 展开完整 span 摘要,但仍不输出 Tempo raw body。 +- `--raw` 仅用于排查 Tempo 响应结构、CLI 解析器或后端返回本身。 + +## 业务 Trace 映射 + +HWLAB/Code Agent 的业务 traceId 通常形如 `trc_...`。当已知 OTel trace id 时,直接用 `trace --trace-id` 查询;当只知道业务 traceId 时,优先从 HWLAB trace/result、Code Agent result 或已记录的 issue 证据中取得对应 OTel trace id。不要为了找 OTel trace id 去打印 Secret、Authorization header、完整 DSN 或运行面 raw transcript。 + +OTel trace 内常见业务关联属性: + +- `traceId`: HWLAB 业务 traceId +- `otel.trace_id`: OTel trace id +- `runId` / `commandId`: AgentRun run/command +- `sessionId` / `turnId` / `threadId`: HWLAB/AgentRun 会话与 turn 关联 +- `failureKind` / `willRetry` / `terminalStatus`: 错误与终态判断 + +## Code Agent / AgentRun 排障 + +追 `Code Agent 代理暂时无法连接上游`、`provider-stream-disconnected`、Workbench 加载/转圈、turn idle 报错、AgentRun command terminal 状态时: + +优先用一条诊断命令汇总业务 trace、OTel trace、服务追穿、AgentRun 终态、HWLAB 读模型和 HTTP 403/401/5xx 根因: + +```bash +bun scripts/cli.ts platform-infra observability diagnose-code-agent \ + --target D601 \ + --business-trace-id +``` + +默认输出必须保持有界低噪声,重点看: + +- `mapping.businessTraceId` / `mapping.otelTraceId` +- `servicePath` 是否同时到达 `hwlab-cloud-api`、`agentrun-manager`、`agentrun-runner` +- `identity` 里的 `runId`、`commandId`、`sessionId`、`runnerJobId`、`runnerId`、`backendProfile`、`sourceCommit` +- `agentrun.terminalStatus`、`terminalEventType`、`runnerProviderClassification` +- `hwlabReadModel.sourceEventCount`、`requestedSinceSeq`、`turnStatusCounts` +- `http.problemCounts` 和 `projectionLag.status` +- `summary.rootCause` 与按置信度排序的 `rootCauseCandidates` + +只有需要展开 span 明细时使用 `--full`;只有排查 Tempo raw 响应或 CLI 解析器时使用 `--raw`。默认输出不得包含 Secret、Authorization header、DSN、可复制凭据或完整运行 transcript。 + +若没有业务 traceId 或诊断结果还需要 drill-down,再使用低层 trace/search: + +1. 先确认 OTel backend ready: + `bun scripts/cli.ts platform-infra observability status --target D601` +2. 查业务 trace 对应 OTel trace: + `bun scripts/cli.ts platform-infra observability trace --target D601 --trace-id ` +3. 用错误关键词过滤: + `bun scripts/cli.ts platform-infra observability trace --target D601 --trace-id --grep --limit 20` +4. 对照 `errorSpanCount`、`matchedSpanCount`、`terminalStatus`、`willRetry`、`runId`、`commandId` 判断是 terminal failure、retryable transient 还是旧 trace 缺 instrumentation。 + +旧 trace 不会因为后续 instrumentation 修复自动回填。若旧 trace 查不到错误 span,但新的 canary/真实 trace 能查到同类 `runner_error.*` span,应把旧 trace 结论写成“当时未采集到该事件”,不要倒推出运行面没有发生过错误。 + +### AgentRun codex-stdio 追穿检查 + +追 HWLAB Workbench turn idle、`waitingFor=code-agent`、工具调用后无 terminal、`provider-stream-disconnected`,或用户怀疑 AgentRun/codex-stdio 仍在运行但 OTel 没追到时,必须在同一 OTel trace 里同时看到 `hwlab-cloud-api`、`agentrun-manager` 和 `agentrun-runner`。只看到 manager dispatch 或 HWLAB business trace 不算追穿 runner。 + +优先用业务入口拿到 OTel trace id,再按 codex span 过滤: + +```bash +bun scripts/cli.ts platform-infra observability trace \ + --target D601 \ + --trace-id \ + --grep codex_stdio \ + --limit 120 \ + --full +``` + +通过证据应能看到 `codex_app_server.starting`、`codex_app_server.started`、需要进程退出时的 `codex_app_server.exit`、`codex_stdio.thread_start.*` 或 `thread_resume.*`、`codex_stdio.turn_start.*`、`codex_stdio.tool_call.started|completed|failed`、`codex_stdio.turn_completed`,以及问题相关的 `idle_warning`、`idle_timeout`、`provider_stream_disconnected` 或 `missing_terminal_after_tool`。这些 span 应带 `runId`、`commandId`、`runnerJobId`、`runnerId`、`sessionId`、`backendProfile`、`sourceCommit`、`traceId`、`otel.trace_id` 和 `valuesPrinted=false`。若默认摘要或 grep 看不到 `runnerJobId`,先用当前 UniDesk CLI 执行 `platform-infra observability trace --grep runnerJobId --full` 复查摘要器输出,必要时用 `--raw` 只排查 Tempo/CLI 解析结构;只有 raw 或更新后的摘要仍缺 `runnerJobId`,或同一 trace 没有 `agentrun-runner` service,才回 AgentRun runner-side instrumentation 排查。 + +如果需要确认工具调用 started/completed 的归一化,canary 应要求一次只读 shell 工具调用。Codex notification 的 `item/started` + `status=inProgress` 应落为 `codex_stdio.tool_call.started`,不得和 completed 混在一起。长期规则见 `docs/reference/agentrun.md#agentrun--hwlab-otel-追踪口径`。 + +## Trace 读取窗口与乱序调查 + +排查 HWLAB/Workbench trace 乱序、分页缺口、`--after-seq`/`--tail` 不生效、旧 trace 只返回局部事件或 read model 是否完整时,优先查 `trace_events_read` span: + +```bash +bun scripts/cli.ts platform-infra observability trace \ + --target D601 \ + --trace-id \ + --grep trace_events_read \ + --limit 20 \ + --full +``` + +摘要里重点看 `returnedEvents`、`sinceSeq`、`limit`、`fromSeq`、`toSeq`、`totalEvents`、`hasMore`、`fullTraceLoaded`、`rawEventCount`、`maxSeq`、`traceLastSeq`、`endSeq` 和 `commandFiltered`。这些字段用于判断“查询窗口是否正确传到后端”“后端是否只返回了局部事件”“read model 是否已经加载完整 trace”。若 `errorSpanCount=0` 但用户可见 timeline 仍乱序,先把结论写成展示/投影/renderer 调查 issue,不要把它定性为后端错误 span。 + +旧业务 trace 在 runtime 重启或保留策略后可能只剩局部事件;OTel 只能证明读取窗口、span 和当时观测到的字段,不能自动恢复业务事件流。需要验证新 instrumentation 时,使用新 canary 或仍可完整读取的真实 trace。 + +## 何时先改进 OTel + +遇到以下情况,先修 OTel CLI 或 instrumentation,再继续业务排障: + +- trace 命令只能返回 raw/tail,不能给出可读 span 摘要。 +- 大 trace 输出淹没上下文,缺少 `--grep`、`--limit`、错误 span 汇总或下一步 drill-down。 +- 关键 runner/backend/projection 事件只存在业务事件流,不进入 OTel。 +- error span 缺 `failureKind`、`willRetry`、`terminalStatus`、`runId`、`commandId` 等定位字段。 +- CLI 默认输出 Secret、Authorization header、DSN 或其他敏感值。 + +改进后必须用一条 canary 或真实 trace 证明新 span/摘要可查询,再继续定位原业务问题。 + +## 交付边界 + +- OTel 平台配置真相是 `config/platform-infra/observability.yaml`。 +- OTel CLI 实现在 `scripts/src/platform-infra-observability.ts`,帮助入口由 `scripts/src/platform-infra.ts` 暴露。 +- 修改 OTel CLI 属于 UniDesk 轻量 CLI 变更:默认只做语法检查、命令形态验证和真实 trace 查询,不新增合同测试。 +- 修改 AgentRun/HWLAB instrumentation 属于对应仓库/运行面的代码变更,必须按目标 repo 的 source truth、PR/CD 和原入口验收规则执行。 diff --git a/.agents/skills/unidesk-sub2api/SKILL.md b/.agents/skills/unidesk-sub2api/SKILL.md index fd827d3a..7e7a0730 100644 --- a/.agents/skills/unidesk-sub2api/SKILL.md +++ b/.agents/skills/unidesk-sub2api/SKILL.md @@ -5,283 +5,29 @@ description: UniDesk Sub2API 平台运维技能。用户提到 Sub2API、sub2api # UniDesk Sub2API -UniDesk 在 k3s `platform-infra` namespace 运维 Sub2API。D601 是当前默认 target,使用外置 PostgreSQL 和目标级 public exposure;D518 可作为独立 external-active target 与 D601 同时对外服务;G14 由同一 YAML/CLI 控制为 standby predeploy,默认缩容且不运行 sentinel、FRP 或 HTTPS egress proxy。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。 +UniDesk 在 k3s `platform-infra` namespace 运维 Sub2API。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源、不手工调用 Sub2API 管理 API、不打印 Secret。 -**固定入口**: `cd /root/unidesk && bun scripts/cli.ts platform-infra sub2api ...` - -## 先看报表 - -查 Codex pool 哨兵状态、账号冻结/恢复、marker 命中、下一次 probe、最近 CronJob run、token/cost 账本时,优先使用这个低噪声报表入口,不要先翻 ConfigMap、CronJob 日志或 Sub2API UI: +## 高频入口 ```bash -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report +bun scripts/cli.ts platform-infra sub2api report +bun scripts/cli.ts platform-infra sub2api status --target D601 +bun scripts/cli.ts platform-infra sub2api plan --target D601 +bun scripts/cli.ts platform-infra sub2api apply --target D601 --dry-run ``` -需要机器处理或完整字段时再加 `--raw`;需要更多最近运行记录时加 `--events N`。 +先看报表和状态,再做计划或变更。部署、D601 egress proxy、镜像升级、Codex pool、账号代理、FRP 暴露、master Codex 消费端配置和排障细节见 [references/full.md](references/full.md)。 -追溯某个 Codex/Sub2API request id 的中断、上游账号、切号、临时不可调度、账号选择失败和同窗口账号池信号时,优先使用低噪声 trace 报表,不要先手写 `kubectl logs | grep`: +## 边界 -```bash -bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id -``` +- YAML 是 source of truth;target、public exposure、Secret sourceRef、Codex pool 和 sentinel 配置都从 YAML 进入 CLI。 +- Secret 只输出对象名、key 名、presence、fingerprint 或 redacted prefix;禁止打印完整 token/key。 +- D601 是默认 active target;D518/G14 等 target 以 YAML 和 issue 明确目标为准。 +- Codex pool、统一 API key、master `~/.codex` 配置、FRP/Caddy 暴露、账号增删都必须走本技能的受控 CLI。 -默认输出类似 k8s/ps 的短表;机器处理用 `--raw` 读取 `.data.trace.*`;需要审计原始匹配日志时加 `--show-lines`;需要扩大搜索范围时使用 `--since 24h --tail 50000`。该命令只读:读取 Sub2API 日志、账号快照和 admin API 元数据,不改 `schedulable`、不清 runtime backoff、不中断请求。 +## 何时读取 reference -## 先读边界 - -- 仓库长期开发边界见 `docs/reference/platform-infra.md`,本 skill 承担日常操作手册。 -- 配置真相是 YAML:`config/platform-infra/sub2api.yaml` 和 `config/platform-infra/sub2api-codex-pool.yaml`。 -- 业务策略和具体数值只以 YAML 为准。已有字段的数值调整只改 YAML 并跑 `plan` / `sync --confirm` / `validate`;不要自动补代码硬编码、schema 硬范围、合同测试、单元测试或长期参考文档。配置校验只校验格式、类型、必填和可渲染性,不判断数值策略是否“合理”。 -- 本 skill 目录下若存在 `agents/*.yaml`,只作为 skill/agent 展示与调用元数据,不是 Sub2API 或 Codex pool 运行配置;不要在 skill 目录维护第二份账号、capacity、priority、endpoint 或 Secret 配置。 -- Runtime target 由 `config/platform-infra/sub2api.yaml` 声明;默认 target 是 `D601:k3s`,`D518:k3s` 这类 external-active target 必须通过显式 `--target` 选择,`G14:k3s` 是 standby target。master server 只是控制端和消费者,不部署 Sub2API/PostgreSQL/Redis。 -- Standby target 不部署本地 PostgreSQL,不运行 sentinel、FRP 管理入口或 HTTPS egress proxy;只能预部署 namespace、NetworkPolicy、Service,以及 replicas=0 的 Sub2API/Redis Deployment。Redis 激活后也只允许 ephemeral cache。External-active target 仍不部署本地 PostgreSQL,必须直连 YAML 声明的外置 DB,使用本地 ephemeral Redis,并且只有在 YAML 启用时才运行 frpc、egress proxy 和目标级 sentinel;多个 external-active target 可以并存,不得把一个 target 的 public exposure 当作另一个 target 的替代或回退。 -- Secret、`~/.codex/config.toml*`、`~/.codex/auth.json*` 是运行时输入或本地状态,不提交。 -- 默认 `~/.codex/config.toml` 和 `~/.codex/auth.json` 只作为统一 Sub2API consumer 使用;`config.toml` 必须指向 YAML-selected active target 的 consumer URL,当前由 `codex-pool configure-local --confirm` 写入 D601 target-level public URL;`auth.json` 必须使用统一 pool API key。新增上游账号不得覆盖这两个默认文件,只能新增 `config.toml.` / `auth.json.` 并在 YAML 里声明。 -- 输出只能包含 Secret 路径、key 名、presence、fingerprint 和 `valuesPrinted=false`;禁止打印完整 API key、admin password、JWT secret、TOTP key、base64 payload 或可复制的 preview。 - -## 部署与状态 - -```bash -bun scripts/cli.ts platform-infra sub2api plan -bun scripts/cli.ts platform-infra sub2api plan --target G14 -bun scripts/cli.ts platform-infra sub2api apply --dry-run -bun scripts/cli.ts platform-infra sub2api apply --target G14 --dry-run -bun scripts/cli.ts platform-infra sub2api apply --confirm -bun scripts/cli.ts platform-infra sub2api apply --target G14 --confirm -bun scripts/cli.ts platform-infra sub2api status -bun scripts/cli.ts platform-infra sub2api status --target G14 -bun scripts/cli.ts platform-infra sub2api validate -bun scripts/cli.ts platform-infra sub2api validate --target G14 -``` - -- `plan` 读取 `config/platform-infra/sub2api.yaml`,渲染 `src/components/platform-infra/sub2api/sub2api.k8s.yaml`,检查 no Ingress/NodePort/LoadBalancer/hostPort/hostNetwork/resource limits,并要求 `NetworkPolicy/allow-all` 随 manifest 受控创建。 -- `apply --confirm` 默认创建异步 job;按返回的 `job status` 命令轮询,再跑 `status` 和 `validate`。 -- `status --full|--raw` 只在需要展开远端 stdout/stderr 或原始 JSON 时使用。 -- `validate` 是按需验收,不是连续可用性探针。对 standby target,`validate --target ` 验证预部署形态,不要求外置 DB 当前可连接;对 external-active target,必须验证外置 DB、ephemeral Redis、Sub2API service、YAML egress proxy 和目标级 public exposure。 - -## D601 Egress Proxy - -D601 的目标级 `egressProxy` 完全由 `config/platform-infra/sub2api.yaml` 控制。当前成熟形态是 master Docker `shadowsocks-rust` 作为加密出站源,D601 k3s 内 `sing-box` 暴露 HTTP/mixed ClusterIP proxy 给 Sub2API 和按 YAML 启用的 sentinel 使用。不要把 endpoint、端口、密码、健康探针或镜像 tag 写进 skill;只以 YAML 和 `config/platform-infra/sub2api-master-egress-proxy.compose.yaml` 为准。 - -master 侧 proxy 由 UniDesk checkout 内的 compose 文件管理: - -```bash -docker compose -f config/platform-infra/sub2api-master-egress-proxy.compose.yaml up -d --force-recreate -bun scripts/cli.ts platform-infra sub2api apply --target D601 --confirm -bun scripts/cli.ts platform-infra sub2api validate --target D601 -bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601 -``` - -proxy secret/config 文件只允许放在受控 Secret/state 路径,输出只能披露路径、presence、fingerprint 或摘要,不能打印密码、完整订阅或生成配置。若 D601 到上游的 TLS/SNI 路径被 reset,不要用临时 JS 或简陋 HTTP CONNECT proxy 作为最终方案;通过 YAML/compose 更换或修复成熟加密 proxy source,再跑上面的 apply/validate/sync/validate 闭环。 - -## 镜像升级 - -1. 修改 `config/platform-infra/sub2api.yaml` 的 `image.repository`、`image.tag` 或 `pullPolicy`。 -2. 执行 `sub2api plan`,确认策略检查通过。 -3. 执行 `sub2api apply --confirm`,轮询 job 完成。 -4. 执行 `sub2api status`,确认运行镜像等于 YAML 声明。 -5. 执行 `sub2api validate` 或 `codex-pool validate` 做入口验收。 - -不要把镜像版本写进脚本常量、JSON 或 manifest 模板。 - -## Codex Pool - -```bash -bun scripts/cli.ts platform-infra sub2api codex-pool plan -bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601 -bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool validate -bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601 -bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601 -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --target D601 --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account unidesk-codex-hy --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --target D601 --account unidesk-codex-hy --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report -bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report --target D601 -bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D601 --confirm -``` - -`config/platform-infra/sub2api-codex-pool.yaml` 控制: - -- `pool.groupName`: Sub2API group 名称。 -- `pool.apiKeySecretName` / `pool.apiKeySecretKey`: 统一消费 API key 的 k3s Secret 位置,默认 `platform-infra/sub2api-codex-pool-api-key.API_KEY`。 -- `pool.minOwnerBalanceUsd`: pool key owner 最低余额,sync/validate 会补齐。 -- `pool.minOwnerConcurrency`: 可选统一消费 API key owner 最低并发;省略时 CLI 自动使用所有已解析账号 capacity 的总和,sync/validate 会补齐。显式 YAML 值只作为 override,仍必须不小于账号 capacity 总和;未显式写 `profiles.entries[].capacity` 的账号会使用 `pool.defaultAccountCapacity` 参与求和,不要用提高某个 provider capacity 来掩盖用户并发层 WS 1013。 -- `pool.defaultTempUnschedulable`: Sub2API 内置请求路径临时不可调度开关和 YAML 规则列表。当前要求是按 YAML 开启通用规则;sync 把 `temp_unschedulable_enabled` / `temp_unschedulable_rules` 渲染到 managed accounts,让匹配的 400/5xx/超时/模型路由/加密内容错误短暂冷却当前账号并触发同组 failover。 -- `pool.defaultTempUnschedulable` 与外部 `sentinel.*` 分开配置、互不驱动。内置规则负责 near-real-time request-path cooling/failover;哨兵负责 marker health、账号级隔离/恢复和 probe 退避。 -- 外部 sentinel 的写入面只允许通过 Sub2API admin `schedulable` 接口冻结/恢复账号;不能写入、清理或间接清理 `temp_unschedulable_until` / `temp_unschedulable_reason`、rate-limit、overload、model-rate-limit 等 Sub2API 请求路径 runtime 状态,也不能调用 `recover-state` 作为恢复动作。看到 UI 里的“触发时间/解除时间/规则序号/匹配关键词”临时不可调度状态时,默认先归因到 Sub2API 内置 request-path temp-unschedulable,而不是 sentinel。 -- YAML 只选择和配置 Codex 上游,不声明 `schedulable` 长期字段;`codex-pool sync --confirm` 不负责把既有账号恢复为 `schedulable=true`。既有账号的 `schedulable=false` 必须由哨兵先同步 Sub2API runtime 状态,再在 marker probe 命中后恢复。 -- `profiles.entries`: 从 master `~/.codex/` 选择上游 profile 并映射到 Sub2API account。 -- `profiles.entries[].capacity`: 可选 per-account concurrency override;不写则使用 `pool.defaultAccountCapacity`。具体数值只以 `config/platform-infra/sub2api-codex-pool.yaml` 为准,skill 和长期参考只描述规则,不重复写当前值。 -- `profiles.entries[].loadFactor`: 可选 per-account Sub2API `load_factor` override;不写则使用 `pool.defaultAccountLoadFactor`。具体数值只以 `config/platform-infra/sub2api-codex-pool.yaml` 为准,修改后必须 `codex-pool sync --confirm` 和 `codex-pool validate`。 -- `profiles.entries[].trustUpstream`: 可选账号级哨兵信任标记;默认 `false`。可信账号使用 `sentinel.cadence.trustedSuccessMaxIntervalMinutes` 作为连续成功后的最大探测退避,不可信账号使用 `sentinel.cadence.untrustedSuccessMaxIntervalMinutes`。它只影响哨兵探测频率和状态可见性,不改变 Sub2API account priority/capacity/loadFactor。 -- `pool.defaultSentinelProtect`: 账号级哨兵保护默认策略;是否启用、连续确认次数、初始延迟、最大延迟和退避倍率都只以 YAML 为准。marker probe 或 gateway failure 触发冻结前会先按该策略做连续 marker 确认,只有全部失败才进入冻结状态机。 -- `profiles.entries[].sentinelProtect`: 可选账号级哨兵保护覆盖;只用于明确偏离 pool 默认策略。它只影响哨兵冻结判定和 `sentinel-report` 可见性,不改变 Sub2API account priority/capacity/loadFactor。 -- 除非用户明确要求修改配置,不要仅凭推断改账号 membership、priority、capacity、loadFactor、WebSocket mode 或其他调度策略;先保留 YAML,完成 provenance/runtime evidence 溯源,并把结论写回相关 issue 或 runbook 后再提出变更。 -- Sub2API 是 UniDesk 可读源码和可观测运行面的受控组件;排查 Sub2API 调度、failover、错误传播、临时不可调度或 account selection 时,默认先读当前 Sub2API 源码实现,再用真实 request id、Sub2API 日志和原入口流量验证。不要用 mock upstream、临时 probe account 或测试桩作为默认结论来源;这类探针最多是显式 debug 辅助,不能替代源码链路和真实运行证据。 -- `profiles.entries[].tempUnschedulable`: 可选 per-account Sub2API 内置临时不可调度覆盖;只用于明确偏离 pool 默认规则,不用它给某个账号特殊优先级或临时绕过通用 failover。 -- `profiles.entries[].openaiResponsesWebSocketsV2Mode`: 需要 Responses WebSocket v2 的上游才设置,值为 `off`、`ctx_pool` 或 `passthrough`。 -- `profiles.entries[].upstreamUserAgent`: 少数要求 Codex CLI User-Agent 的上游才设置,不能含换行。 -- `manualAccounts.protected`: 已在 Sub2API 手动创建/维护、且必须排除在 UniDesk-managed Codex pool credentials 和 sentinel 控制之外的账号。默认不得改 credentials/status/schedulable/priority/capacity/loadFactor;只有显式声明 `proxyBinding` 时,`sync --confirm` 才允许把该账号的 `proxy_id` 对齐到 YAML 目标的 egress proxy;只有显式声明 `groupBinding.source: pool-group` 时,才允许把该账号加入统一消费 API key 使用的 pool group。 -- `sentinel.monitor.enabled`: 账号级 marker 哨兵监控开关;开启后 `codex-pool sync --confirm` 会在 `platform-infra` 创建/更新 k8s CronJob、ConfigMap、Secret、ServiceAccount、Role 和 RoleBinding。CronJob 直打 YAML-managed 上游账号的 OpenAI Responses `gpt-5.5`,用确定 marker 作为唯一健康标准,并在独立 state ConfigMap 中记录 token/cost 账本。 -- `sentinel.actions.enabled`: 账号级哨兵冻结/恢复动作开关;当前 marker-only guard 要求开启。动作关闭时只记录 `would-freeze`,不会调用 Sub2API admin API 改 `schedulable`。动作开启后,只要不满足 marker match,不论是 HTTP 200 私货、4xx/5xx、非 JSON、连接错误还是空输出,都进入同一个冻结/恢复状态机。 -- `sentinel.sdk.openaiPythonVersion`: 哨兵容器使用的 OpenAI Python SDK 固定版本;模型请求必须通过标准 SDK `responses.create`,不要手工拼 `/v1/responses` 请求体或手写响应解析。后续升级 SDK 只改 YAML 并 `sync --confirm`。 -- `sentinel.probe.maxOutputTokens`: 哨兵本地流式 delta 收集上限,必须保持小值;它不作为上游 `max_output_tokens` 字段发送,以保持与 Sub2API WebUI 默认账号连接测试的 Responses SSE 请求形态一致。哨兵不限制并发和每轮账号数,所有到期账号会在同一轮并发探测。 -- `sentinel.probe.userAgent`: 哨兵 direct upstream probe 的默认 User-Agent,通过 OpenAI SDK `extra_headers` 传递;默认贴近 Sub2API `net/http` 账号连接测试形态,个别账号仍可用 `profiles.entries[].upstreamUserAgent` 覆盖。 -- `sentinel.cadence`: 成功信任指数退避配置。当前口径是从 1 分钟开始,连续成功后按账号 `trustUpstream` 选择可信/不可信最大退避;任意非 marker match 清零成功信任并进入冻结退避。可信/不可信最大退避数值只写 YAML。 -- `sentinel.freeze`: 失败冻结 TTL 指数退避配置。当前口径是初始 1 分钟,失败后 `1m -> 2m -> 4m -> 8m -> 10m`,最大 10 分钟;失败 probe 基本不消耗有效输出 token,因此冻结窗口保持短周期。冻结到期后只做恢复 probe,通过才自动恢复,不能仅靠 TTL 到期解封。 -- `sentinel.pricing`: 直打上游时哨兵自己的 token/cost 估算价格。因为 direct upstream probe 不经过 Sub2API 普通用量账本,哨兵必须自己记录全局与 per-account token/cost;这些账本只用于观察,不作为跳过探测的预算门禁。 - -`sync --confirm` 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 `unidesk-codex-*` accounts、创建/复用统一 API key Secret,并部署/更新哨兵资源;它不把既有 managed account 直接恢复为 `schedulable=true`。恢复只由哨兵在读取 Sub2API runtime `schedulable=false` 后触发 recovery probe,并在 marker 命中时执行。`sync` 默认不删除 YAML 中缺席的 managed account。只有明确退役上游时才使用 `sync --confirm --prune-removed` 删除缺席且 `extra.unidesk_managed=true` 的 `unidesk-codex-*` account。对 `manualAccounts.protected`,`sync` 只执行 YAML 显式允许的窄同步;当前允许项是从目标 `egressProxy` 创建/更新 Sub2API internal proxy 记录并绑定 `proxy_id`,以及把受保护手动账号加入当前 `pool.groupName`。它仍不接管该账号凭据、status、schedulable、priority/capacity/loadFactor 或哨兵状态。 - -`sentinel-image status|build` 管理哨兵 Python 运行环境镜像。镜像由 YAML 的 `sentinel.image` 基础镜像和 `sentinel.sdk.openaiPythonVersion` 派生,发布到目标 runtime 的本地 registry;`build --confirm` 会先检查 registry tag,存在则快速复用,不存在才在目标 host 构建并 push。CronJob 启动时只校验 SDK 版本,不在运行时 `pip install`。目标是否启用哨兵以 `config/platform-infra/sub2api.yaml` 的 `sentinel.enabledOnTargets` 为准;未启用的 target 在 `sync`/`validate` 中应显示 `skipped-target-disabled`,不得要求镜像构建、CronJob、Secret 或 state ConfigMap 存在。 - -`sync --confirm` 同时会按 YAML 渲染账号级哨兵资源,并在 monitor 开启时先确保可复用哨兵镜像存在。当前目标是 `sentinel.monitor.enabled=true` + `sentinel.actions.enabled=true` 的 marker-only 自动冻结/恢复;不要手工 patch CronJob、Secret 或 Sub2API account。若 YAML 新增账号或修改 profile/base URL/API key fingerprint/upstream User-Agent/Responses WebSocket mode,sync 会从变更前 runtime state 写入 pending probe 记录并立即安排 sentinel probe,但不会把既有账号直接恢复为可调度;只有 sentinel 读取到 Sub2API runtime `schedulable=false` 后执行 recovery probe,且 marker 命中,才恢复 `schedulable=true`。sentinel 冻结/恢复只改 `schedulable=false|true`,不得顺手调用 Sub2API `recover-state` 清除请求路径临时不可调度或其他 runtime backoff。无关账号的既有成功/失败退避不能被重置。若 YAML 下调失败冻结最大窗口,sync 会把仍 active 的旧冻结状态迁移到当前最大窗口内并立即安排 recovery probe,但不会直接解冻。若怀疑某个账号被误判,先用 `codex-pool sentinel-probe --account --confirm` 立即触发该账号测量;该命令从现有 CronJob 模板派生一次性 Job,复用同一份 Secret、ConfigMap、OpenAI SDK probe、token/cost 账本和冻结/恢复状态机。 - -`trace --request-id ` 是只读 request 追溯报表,不触发 probe、不修改账号。默认输出请求开始/最终状态、failover、`account_select_failed`、窗口内 `account_temp_unschedulable`、admin schedulable 写入计数和当前账号快照;`reason=failover-attempted-no-candidate` 表示 Sub2API 已进入自动切号,但排除当前失败账号后没有可用候选。需要机器处理时使用 `--raw`,需要原始匹配行时加 `--show-lines`。 - -`sentinel-report` 是只读低噪声报表,不触发 probe、不修改账号。默认输出类似 `ps` 的文本表,展示每个账号的探测次数、Sub2API runtime `schedulable`、最近 marker/HTTP/动作、冻结 TTL、成功退避、下一次 probe 和最近 run 事件;`SCH` 展示 Sub2API runtime schedulable,`PROT` 展示账号级保护阈值,`P_FAIL` 展示最近一次保护确认中的失败次数/阈值;需要机器处理时使用 `sentinel-report --raw`。 - -`sync --confirm` 和 `validate` 可能超过单次 SSH/runtime 短连接窗口。必须继续使用 `bun scripts/cli.ts platform-infra sub2api codex-pool ...`,由 CLI 在 G14 远端提交作业并短轮询状态;不要改用裸 `trans G14:k3s sh` 等一个长连接等待完整结果。若看到 `UNIDESK_SSH_RUNTIME_TIMEOUT`,先按 `docs/reference/platform-infra.md` 的规则处理为控制面可见性问题,修 CLI/job/poll 或重跑受控命令,不要手工 patch Sub2API credentials 或源码。 - -不要给 UniDesk-managed Codex accounts 开 Sub2API `pool_mode`。UniDesk 期望的 failover 是把失败账号临时标记为 unschedulable,让同组其他账号接手;`pool_mode` 会重试同一个 account path。 - -WebSocket v2 是账号能力集合,不是调度 pin。`openaiResponsesWebSocketsV2Mode` 只声明该账号可承担 Codex Responses WSv2 链路;只有 `localCodex.supportsWebSockets=true` / `localCodex.responsesWebSocketsV2=true` 时,`codex-pool validate` 才必须看到至少一个 `webSocketsV2.schedulableEnabled` 账号。真实可用性仍以 direct Codex WSv2 probe、Sub2API 日志和原入口 Codex smoke 为准。 - -Codex 启动时反复出现 WebSocket reconnect、HTTPS fallback、`websocket closed by server before response.completed`,或 Sub2API 日志出现 `openai.websocket_proxy_failed` / `openai.websocket_account_select_failed` / 上游 WS handshake 4xx/5xx 时,先按运行证据定位具体 account 和 transport。若账号的 WSv2 握手失败,优先只在 YAML 中把该账号的 `openaiResponsesWebSocketsV2Mode` 收敛为 `off`;若没有任何 direct Codex WSv2 probe 通过,则同时把 `localCodex.supportsWebSockets` 与 `localCodex.responsesWebSocketsV2` 收敛为 `false`,再 `codex-pool sync --confirm`。不要顺手改 membership、priority、capacity、Secret 或代码 fallback。 - -## 受保护手动账号代理与分组绑定 - -Sub2API 管理 UI 的账号连接测试使用账号级 `ProxyID` / proxy URL 配置上游 HTTP transport;账号没有绑定 proxy 时会直接出站,即使 Sub2API Pod 已经有 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量。看到 WebUI 账号测试连 `chatgpt.com` 超时、但 Pod 内显式走目标 proxy 可通时,先检查该账号是否属于 `manualAccounts.protected` 并声明了 `proxyBinding`。如果同一账号用 `gpt-5.2-pro` 返回 ChatGPT OAuth 不支持 Codex 的模型能力错误,但默认/受支持模型能完成 `hi` 或 `/v1/responses` smoke,这不是代理失败;按模型映射/账号能力另行处理。 - -WebUI 账号连接测试也不经过统一消费 API key 的 pool group 选择器;账号测试正常不代表 PC Codex 客户端能选中该账号。看到 WebUI 账号测试正常、但 `/responses` 或 `/v1/responses` 以 `account-select-failed` / `no available accounts` 返回 503 时,先检查该手动账号是否声明了 `groupBinding.source: pool-group`,并通过 `sync --confirm` 加入当前 `pool.groupName`。 - -受保护手动账号仍由人工在 Sub2API UI 维护 credentials/status 等字段;UniDesk 只允许通过 YAML 做代理和分组窄绑定: - -```bash -bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601 -bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm -bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601 -``` - -`sync` 输出应显示 `manualAccounts.ok=true`、`proxySync.ok=true`、`groupSync.ok=true`,且该账号的 proxy/group `bindingAligned=true`。`sentinel-probe --account --confirm` 对受保护手动账号必须继续拒绝,通常返回 `account-protected-manual`;不要为了测试而把该账号移入 `profiles.entries` 或取消保护。需要证明 WebUI 同款账号测试恢复时,用 Sub2API admin account test 原入口测最小 `hi` 和默认/受支持模型,并只记录 account id、proxy id、event types、HTTP status 和短 output preview,不记录 OAuth token 或 Secret 明文。若指定模型返回 “model is not supported when using Codex with a ChatGPT account” 一类能力错误,先归因到模型能力/映射,而不是 proxy。 - -## 添加上游 - -1. 在 master `~/.codex/` 准备带后缀的上游 profile 文件,例如 `config.toml.` 和 `auth.json.`;禁止覆盖默认 `config.toml` / `auth.json`。 -2. 在 `config/platform-infra/sub2api-codex-pool.yaml` 添加 `profiles.entries` 项,指定 `profile`、`accountName`、`configFile`、`authFile`。 -3. 如需要,给该项加 `priority`、`capacity`、`loadFactor`、`trustUpstream`、`sentinelProtect`、`openaiResponsesWebSocketsV2Mode` 或 `upstreamUserAgent`;capacity/loadFactor/信任退避/保护阈值的具体数值只写在 YAML。只有显式恢复 Sub2API 内置临时不可调度时才添加 per-account `tempUnschedulable`。 -4. 如果新增账号会提高声明 capacity 总和,默认让省略的 `pool.minOwnerConcurrency` 继续按 capacity 总和自动解析;只有 YAML 已经显式写了该 override 时,才同步提高到不低于总 capacity,或删除 override 回到自动解析。 -5. 跑 `codex-pool plan`,确认 profile 可读、`base_url` 和 API key 来源有效,且 stdout 未泄露完整 key。 -6. 跑 `codex-pool sync --confirm`。 -7. 跑 `codex-pool validate`。 - -普通新增上游是 YAML 操作,不走 CI/CD,不改代码。只有需要渲染或校验上游 Sub2API 已经存在的可复用能力时才修改 `scripts/src/platform-infra-sub2api-codex.ts`;Sub2API 本身不支持的能力不在 UniDesk 侧魔改实现。 - -## 删除上游 - -删除上游只用于明确退役、凭据所有权变更或用户明确要求移除 provider;不能作为上游 5xx、compact 失败、限流、模型路由失败或哨兵隔离/恢复问题的处理手段。 - -1. 从 `config/platform-infra/sub2api-codex-pool.yaml` 删除对应 `profiles.entries` 项。 -2. 跑 `codex-pool plan` 检查 desired 列表。 -3. 跑 `codex-pool sync --confirm --prune-removed`。 -4. 确认输出 `accounts.pruned` 只包含期望删除项。 -5. 跑 `codex-pool validate`。 - -CLI 默认保留缺席账号,避免把可用性问题误处理成删除;只有显式 `--prune-removed` 才会 prune `name` 以 `unidesk-codex-` 开头且 `extra.unidesk_managed=true` 的缺席账号。 - -## FRP 暴露 - -```bash -bun scripts/cli.ts platform-infra sub2api codex-pool expose -bun scripts/cli.ts platform-infra sub2api codex-pool expose --confirm -``` - -- 由 YAML `publicExposure` 控制。Codex pool 默认公共端是 `publicBaseUrl`,master 本地消费端是 `masterBaseUrl`;external-active target 的目标级 public exposure 在 `config/platform-infra/sub2api.yaml` 中声明。 -- `expose --confirm` 只为 YAML 指定的 `remotePort` 补 master `frps` allow port,并在 G14 创建/更新 `sub2api-frpc`。 -- master Caddy site 也由 `publicExposure.masterCaddy` 渲染;`responseHeaderTimeoutSeconds` 必须足够覆盖 Codex `/responses/compact` 长请求,避免 Caddy 先返回 504 而 Sub2API 后台实际稍后成功。具体数值只改 `config/platform-infra/sub2api-codex-pool.yaml`,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `response_header_timeout`。 -- master Caddy 的短窗口边缘重试由 `publicExposure.masterCaddy.edgeRetry` 渲染;用于吸收 FRP remotePort 短暂关闭、`connect: connection refused`、EOF 或 connection reset 这类请求尚未稳定到达 Sub2API 的 502。具体 retry 时长、间隔和 `retryMatch` 范围只写 YAML,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `lb_try_duration`、`lb_try_interval` 和 `lb_retry_match`。不要手工 patch `/etc/caddy/Caddyfile`。 -- PK01 `/etc/caddy/Caddyfile` 是 Sub2API、LangBot、n8n、HWLAB 等多 YAML 来源共享的 edge artifact。Sub2API apply/expose 只能更新自己的 managed block 并保留其他 blocks;同一 Sub2API 服务暴露多个 target 时,D601 保留既有 `# BEGIN unidesk managed sub2api`,非默认 target 必须使用 target-scoped owner(例如 `sub2api-d518`),避免 `api.pikapython.com` 和 `api2.pikapython.com` 互相覆盖。若 apply 输出显示 managed block 数异常,先停止 closeout,检查 PK01 Caddy 合并与 validation 结果,不要手工整文件覆盖。 -- 非幂等 POST 的 round-trip retry 必须收窄到 YAML `retryMatch` 声明的安全路径;普通 `/responses` 上游账号错误仍归 Sub2API failover / temp-unschedulable / sentinel 处理,不用 Caddy 重放整段推理请求来掩盖账号池问题。 -- 同一个 FRP TCP 入口同时暴露 OpenAI-compatible API 和 Sub2API 管理 UI `/login`。不要另开第二个管理端口,除非 YAML 明确声明新的暴露决策。 -- Sub2API Kubernetes Service 继续保持 ClusterIP。 -- External-active target 的公开路径是 `client -> PK01 Caddy -> PK01 frps remotePort -> target frpc -> Sub2API`,不经过 pikanode,也不经过 master server 反代。PK01 Caddy 下载必须使用 YAML `publicExposure.pk01.caddyDownloadProxyUrl` 指定的 proxy;如果 Caddy 下载慢,先确认 apply 输出里是 `downloadProxy.mode=curl-proxy`。目标域名必须先解析到 YAML 声明的 PK01 公网地址,HTTPS 才能作为最终验证;D601 `api.pikapython.com` 与 D518 `api2.pikapython.com` 应分别验收。 - -## 配置 master Codex 消费端 - -```bash -bun scripts/cli.ts platform-infra sub2api codex-pool configure-local -bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm -``` - -`configure-local --confirm` 会: - -- 从 `platform-infra/.` 读取统一 API key。 -- 把当前 `~/.codex/config.toml` 和 `~/.codex/auth.json` 备份为 `.`,默认 `.pre-sub2api`。 -- 重写默认 `~/.codex` 消费端,固定指向 `https://sub2api.74-48-78-17.nip.io/`,provider 名称和 wire API 来自 `localCodex`。 -- 按 `localCodex.modelContextWindow` / `localCodex.modelAutoCompactTokenLimit` 写入 `model_context_window` / `model_auto_compact_token_limit`,用于统一控制 Codex auto compact 触发窗口,避免 GPT-5.5 消费端生成过大的 `/responses/compact` 长请求。 -- 按 `localCodex` 写入 Codex transport 标记:`supports_websockets` 与 `[features] responses_websockets_v2` 必须同开同关。只有至少一个上游通过 direct Codex WSv2 probe 时才启用;否则保持 HTTP Responses,避免每次原入口先经历无效 WS reconnect。 -- 用统一 key 做一次 gateway 验证。 - -防递归规则:默认 `config.toml` / `auth.json` 是 Sub2API consumer,不得作为上游账号导回 pool;上游账号必须使用带后缀 profile 文件,并通过 `config/platform-infra/sub2api-codex-pool.yaml` 的 `profiles.entries` 增删。 - -## 验收口径 - -部署 closeout 至少包含: - -- `sub2api status`:Deployment/StatefulSet/Service/Secret/NetworkPolicy 可见,运行镜像与 YAML 一致,`NetworkPolicy/allow-all` 符合 `podSelector: {}`、Ingress/Egress 全放行。 -- `sub2api validate`:app、PostgreSQL、Redis、service proxy、`NetworkPolicy/allow-all` 和临时跨 Pod PostgreSQL/Redis 连通性检查通过。 -- `codex-pool validate`:统一 key 的 `GET /v1/models` 成功,并用 `localCodex.responsesSmokeModel` 跑一次小的 `POST /v1/responses` smoke;owner balance / owner concurrency 已满足 YAML 最小值,capacity、WebSocket v2、Sub2API 内置 temporary-unschedulable 开关/规则和 sentinel runtime 状态与 YAML 对齐;`validation.gatewayResponsesRecent` 汇总最近 6 小时普通 `/responses` 和 `/v1/responses` 的 failover、forward failure、最终 4xx/5xx、慢 final error 与 `context canceled` 证据,`validation.gatewayCompactRecent` 单独汇总 `/responses/compact` 证据。若当前 Responses smoke `ok=true` 但 recent 字段 `degraded=true`,先区分是历史窗口残留还是新的 request id 正在失败;长期判定见 `docs/reference/platform-infra.md`。 -- 若 `publicExposure.enabled=true`,确认 FRP path 可用;`expose --confirm` 会用未带 key 的 public `/v1/models` 401 作为网关可达性探针。 -- 多 target 同时启用 public exposure 时,必须分别验证每个 target 的 root、`/health`、未带 key `/v1/models` 401,以及各自 `codex-pool validate --target `;一个域名可用不能替代另一个域名的验收。 -- 若目标声明了 `egressProxy.enabled=true`,确认 proxy Deployment/Service ready,Sub2API 和 sentinel env 与 YAML 对齐,并通过 YAML 声明的 health URL 完成代理出站探针。 - -如果要证明真实模型请求可用,使用最小 `/v1/responses` 或等价 Codex smoke。不要把 group-level `/v1/models` 成功解释成每个上游 account 都健康。 - -## 排障 - -- Codex pool 哨兵、账号冻结/恢复、marker-only 判断或 probe 周期看不清:第一步跑 `bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report`。这个报表是主观察面;只有报表缺字段或需要底层证据时,才继续看 `--raw`、CronJob log、state ConfigMap 或 Sub2API 管理 UI。若看到“临时不可调度状态”且包含规则序号/匹配关键词,检查 Sub2API `account_temp_unschedulable` 日志和账号 `temp_unschedulable_*` 字段;sentinel 只解释 `schedulable=false` 的 active quarantine,不解释这类内置临时冷却。 -- 只加强监控、不让哨兵自动冻结账号时,把 YAML `sentinel.actions.enabled=false` 后 `codex-pool sync --confirm`。此时 marker probe 和 gateway failure monitor 仍记录 `would-freeze` / observe-only 证据,但不会通过 Sub2API admin 写 `schedulable=false`;`/responses/compact` 的 `codex.remote_compact.failed` 和 compact 上游 5xx failover 只作为 `gateway-compact-*` 观察事件记录,不作为哨兵自动切换触发器。 -- 单个 request id 报 502/503/中断/没有自动切号:第一步跑 `bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id `。先看 `outcome`、`reason`、`FAILOVER`、`SELECT-FAILED`、`ACCOUNT SIGNALS` 和 `WINDOW STATS`;只有 trace 报表缺字段或需要审计原始日志时,才加 `--show-lines` 或 `--raw`。若 `reason=failover-attempted-no-candidate`,说明切号动作已发生,但 scheduler 在排除失败账号后没有可用候选;继续用 `sentinel-report` 和 `validate --full` 区分 sentinel quarantine、request-path temp-unschedulable、账号 status 或容量耗尽。 -- profile invalid:先修 `~/.codex/config.toml.` 的 `base_url`、`wire_api`、`model` 或 `auth.json.` 的 API key;不要在 YAML 中写密钥。 -- 手动 OAuth/API-key 账号的 WebUI account test 连 `chatgpt.com` 超时,但同一 Pod 显式 HTTP proxy 探针可通:不要只看 Pod `HTTP_PROXY` env,按“受保护手动账号代理与分组绑定”小节确认 `manualAccounts.protected[].proxyBinding`,跑 `codex-pool sync --target D601 --confirm` 后再用原账号测试复测。若复测不再 reset/timeout,而是 `gpt-5.2-pro` 这类指定模型返回 ChatGPT OAuth Codex 不支持的能力错误,用默认/受支持模型或统一 key smoke 验证代理,不要把模型错误当作代理仍坏。 -- 手动 OAuth/API-key 账号 WebUI account test 正常,但 PC Codex 客户端通过统一 key 访问 `/responses` 返回 503 且 trace 是 `account-select-failed` / `no available accounts`:按“受保护手动账号代理与分组绑定”小节确认 `manualAccounts.protected[].groupBinding.source: pool-group`,跑 `codex-pool sync --target D601 --confirm` 后用 `codex-pool validate --target D601 --full` 复测统一 key。 -- Sub2API 卡在 `wait-postgres` / `wait-redis` 或服务内大量 `context deadline exceeded`:先跑 `sub2api status` 看 `networkPolicy.ok`,再跑 `sub2api validate` 看 `postgresCrossPodPgIsReady` / `redisCrossPodPing`;缺失或异常时用 `sub2api apply --confirm` 恢复受控 `NetworkPolicy/allow-all`,不要保留手工 iptables bypass 作为长期修复。 -- pool key 401:跑 `codex-pool sync --confirm` 重建 Sub2API key 与 k3s Secret 绑定,再跑 `codex-pool validate`。 -- pool key、admin password 或 k8s Secret `.data` 被 stdout、日志、issue 或本地 transcript 打印时,按泄露处理:撤销对应 Sub2API key 或 token,删除/重建受影响的 target Secret,通过 `codex-pool sync --target --confirm` 或相应 YAML sourceRef 重新下发,再用 fingerprint、presence 和 `valuesPrinted=false` 作为 closeout 证据;不要复述旧值或新值。 -- 运行中过去的验证探针残留:只用 `codex-pool cleanup-probes --confirm` 清理 `unidesk-probe-*` 临时资源;不要把真实 managed account 删除当作探针清理或可用性恢复。 -- FRP 不通:先看 `codex-pool expose --confirm` 输出的 `masterFrps`、`masterCaddy`、`sub2api-frpc` 和 public 401 probe;需要低层证据时只用 `trans G14:k3s` 做 bounded 查询。 -- D601 external-active 的 `api.pikapython.com` 不通:先区分 DNS/TLS/Caddy/FRP/Sub2API。DNS 未解析到 YAML 声明的 PK01 地址时,Caddy ACME 会失败,`https://api.pikapython.com` 不能算完成;可用 PK01 loopback FRP 端口和 PK01 公网 remotePort 证明 D601 FRP 数据路径,但最终仍要等 DNS 生效后重跑 HTTPS health、`/v1/models` 和 `/v1/responses`。 -- D601 external-active apply 后其他 PK01 HTTPS 服务消失:优先怀疑共享 Caddy managed block 合并失败或旧整文件写入路径复现。用受控 Sub2API apply 输出和 PK01 Caddy managed block markers 取证,再通过各服务自己的 YAML apply/public-exposure 入口恢复;不要手工复制某一份 Caddyfile 作为长期修复。 -- Caddy 下载慢或失败:先确认 `config/platform-infra/sub2api.yaml` 已设置 `publicExposure.pk01.caddyDownloadProxyUrl`,并重跑 `sub2api apply --target D601 --confirm` 看 PK01 apply summary 中的 `downloadProxy.mode=curl-proxy`。不要反复裸连 GitHub release。 -- `/responses/compact` 在接近 master Caddy `response_header_timeout` 的固定时长后返回 504,或 Sub2API 日志稍后记录 `codex.remote_compact.succeeded` 时,优先检查 master Caddy `response_header_timeout` 是否由 YAML `publicExposure.masterCaddy.responseHeaderTimeoutSeconds` 渲染,修正后跑 `codex-pool expose --confirm`;这类边缘代理超时不会触发 Sub2API 账号级临时下线。reload 前已经在途的 compact 请求仍可能按旧 timeout 结束,判断修复是否生效时只看 reload 之后新发起的请求。 -- `/responses/compact` 或普通 public URL 在几秒窗口内出现 502,Caddy 日志显示 `dial tcp 127.0.0.1:: connect: connection refused`、`EOF` 或 `connection reset by peer`,同时 frps 日志出现 `platform-infra-sub2api proxy closing` / `listener is closed` / `new proxy ... success`,说明失败在 master Caddy 与 FRP remotePort 边缘层,Sub2API 和 sentinel 可能完全看不到。先确认 `publicExposure.masterCaddy.edgeRetry` 已按 YAML 渲染并 `codex-pool expose --confirm` 生效;若仍频繁发生,再继续查 G14 `sub2api-frpc` 到 master `frps` 的控制连接稳定性。不要把这类边缘 502 误判成账号池上游错误,也不要通过禁用账号恢复。 -- default profile 递归:检查 YAML default entry 是否使用 `*.pre-sub2api` 备份文件;必要时恢复备份后重新 `configure-local --confirm`。 -- 上游需要 WebSocket v2:先做 direct Codex WSv2 probe;通过后才给该 profile 配 `openaiResponsesWebSocketsV2Mode: ctx_pool|passthrough` 并跑 `sync --confirm`;把它当 capability candidate,容量仍以 YAML 中的 `capacity` 或默认值为准。 -- Codex 启动 WebSocket 回退:用原入口 Codex smoke 复现,再用 bounded Sub2API 日志确认 account;对 WS handshake 4xx/5xx、`openai.websocket_account_select_failed` 或 close-before-`response.completed` 的账号关闭 YAML WSv2 能力后同步。若没有剩余 WSv2-capable account,把 `localCodex.supportsWebSockets` 和 `localCodex.responsesWebSocketsV2` 一起关掉,不把临时可用性推断写成调度配置。 -- 上游要求 Codex User-Agent:只给该 profile 配 `upstreamUserAgent`,跑 `sync --confirm`。 -- 上游报 capacity/rate-limit/overload/Bad Gateway/Gateway Timeout 后没有隔离或频繁先失败再恢复:先看 `codex-pool sentinel-report` 的 marker、动作、冻结 TTL 和下一次 probe,也看 `codex-pool validate --full` 的 recent gateway failover/forward failure 证据;同时对照当前 Sub2API 源码里 `/v1/responses` handler、`Forward`、`shouldFailoverOpenAIUpstreamResponse` 和 `handleOpenAIAccountUpstreamError` 的真实传播路径。不要手动禁用账号、删除账号、改 membership/priority/capacity/loadFactor 或从 YAML 移除问题账号来替代通用 failover 与哨兵隔离/恢复。 -- `codex-pool sync --confirm` 或 `codex-pool validate` 超时:先区分 CLI 传输超时和 Sub2API 运行失败。受控 CLI 应返回远端作业进度和 stdout/stderr tail;如果只是低层 `trans` 60s 超时,不能据此判定 Sub2API failover 不工作。改用或修复 CLI 的远端 job/poll 路径后重跑,并以最终结构化结果作为证据。 -- Codex 报 weekly-limit、`less than 10% of your weekly limit left`、`Run /status for a breakdown` 等账号状态/软配额提示并要求切号:不要把新关键词写成 Sub2API 内置临时不可调度策略来恢复可用性;由 marker-only 哨兵按非 marker 响应统一冻结,并用 `sentinel-report` / `sentinel-probe` 验证。 -- 上游 400/503 响应体出现 `invalid_encrypted_content`、`bad_response_status_code`、`invalid_request_error` + 稳定 unsupported-model 文案、unsupported-model、`暂不支持` / `可用模型`、`model_not_found`、`No available channel for model ...` 或同类稳定模型路由 / Responses encrypted-content 兼容性失败:按通用 temp-unschedulable/failover 加哨兵 marker 证据处理,不用 account membership、priority、capacity、loadFactor、WebSocket mode、User-Agent 或 provider pinning 掩盖该错误族。 -- 上游错误反复触发:`invalid_encrypted_content`、unsupported-model、`Recovered upstream error ...`、`Bad Gateway`、`Gateway Timeout`、Cloudflare `524`、Codex-facing `Upstream request failed`、`Unknown error`、`context deadline exceeded`、`context canceled`、`model_not_found`、`No available channel for model`、大上下文 `413` 和 `openai_error` 这类稳定包装文案,先确认 YAML temp-unschedulable 已同步、Sub2API 源码会把该错误族传播成 `UpstreamFailoverError`、运行日志出现 `openai.upstream_failover_switching`。若匹配规则后仍只看到 `openai.forward_failed`,根因是 Sub2API HTTP `/responses` 没把该错误传播成 `UpstreamFailoverError`,应修 Sub2API failover classifier/error propagation,不硬编码账号或给 `only` 特权。 -- Codex auto compact 后丢上下文:先确认 YAML `localCodex` 是否声明启用 WSv2;若启用,再确认本机 `~/.codex/config.toml` 是否有 `supports_websockets = true` 和 `responses_websockets_v2 = true`,并看 `codex-pool validate` 的 WSv2 candidate 和 Sub2API 日志里的 `transport=responses_websockets_v2`。若 YAML 当前禁用 WSv2,则按 HTTP Responses 稳定性排查,不把旧 WS 口径当成验收要求。 -- Codex smoke 有 reconnect/1013:这是上游并发/可用性问题,和 HTTP-only compact context-loss 分开处理;记录 session/log 证据并关联专项 issue,不要用运行时手补覆盖 YAML 容量。 - -## 禁止事项 - -- 不用原生 `kubectl apply/delete/patch` 作为正式操作入口。 -- 不在 master server 部署或运行 Sub2API/PostgreSQL/Redis。 -- 不新增 Ingress、NodePort、LoadBalancer、hostPort、hostNetwork 或宽 FRP 端口段。 -- 不用 Sub2API 的 YAML 渲染结果整文件覆盖共享 PK01 Caddyfile;只能通过 managed block merge 更新 Sub2API 自己的块。 -- 不给 Sub2API manifest 添加 CPU/memory limits,除非有新的 YAML 化明确决策。 -- 不打印完整 API key、admin password 或 Secret 明文。 -- 不把普通上游增删做成代码变更、CI/CD、feature flag 或兼容双路径。 -- 不把手动禁用账号、删除账号、移除 YAML entry、降低 membership、临时改 priority/capacity/loadFactor、provider pinning 或给某个账号特权当作通用 failover / 哨兵隔离恢复问题的修复。 -- 不魔改 Sub2API:Sub2API 本身不支持的能力就不做,不通过 UniDesk 脚本、k8s 原地热补、本地 fork、YAML 伪声明或隐藏 fallback 代替上游实现。 +- 添加/删除上游、受保护账号代理、分组绑定:读 [references/full.md](references/full.md) 的账号管理段。 +- 部署/状态/镜像升级/FRP 暴露:读部署、镜像、FRP 段。 +- master Codex 消费端、`/v1/models`、Codex pool 验收:读 Codex Pool 和验收口径段。 +- 排障或禁止事项不确定时,读排障和禁止事项段。 diff --git a/.agents/skills/unidesk-sub2api/references/full.md b/.agents/skills/unidesk-sub2api/references/full.md new file mode 100644 index 00000000..fd827d3a --- /dev/null +++ b/.agents/skills/unidesk-sub2api/references/full.md @@ -0,0 +1,287 @@ +--- +name: unidesk-sub2api +description: UniDesk Sub2API 平台运维技能。用户提到 Sub2API、sub2api、platform-infra sub2api、Codex pool、统一 API key、Sub2API FRP 暴露、Sub2API 管理 UI、配置 master ~/.codex 走 Sub2API、添加/删除 Codex 上游账号、校验 Sub2API /v1/models 时使用。 +--- + +# UniDesk Sub2API + +UniDesk 在 k3s `platform-infra` namespace 运维 Sub2API。D601 是当前默认 target,使用外置 PostgreSQL 和目标级 public exposure;D518 可作为独立 external-active target 与 D601 同时对外服务;G14 由同一 YAML/CLI 控制为 standby predeploy,默认缩容且不运行 sentinel、FRP 或 HTTPS egress proxy。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。 + +**固定入口**: `cd /root/unidesk && bun scripts/cli.ts platform-infra sub2api ...` + +## 先看报表 + +查 Codex pool 哨兵状态、账号冻结/恢复、marker 命中、下一次 probe、最近 CronJob run、token/cost 账本时,优先使用这个低噪声报表入口,不要先翻 ConfigMap、CronJob 日志或 Sub2API UI: + +```bash +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report +``` + +需要机器处理或完整字段时再加 `--raw`;需要更多最近运行记录时加 `--events N`。 + +追溯某个 Codex/Sub2API request id 的中断、上游账号、切号、临时不可调度、账号选择失败和同窗口账号池信号时,优先使用低噪声 trace 报表,不要先手写 `kubectl logs | grep`: + +```bash +bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id +``` + +默认输出类似 k8s/ps 的短表;机器处理用 `--raw` 读取 `.data.trace.*`;需要审计原始匹配日志时加 `--show-lines`;需要扩大搜索范围时使用 `--since 24h --tail 50000`。该命令只读:读取 Sub2API 日志、账号快照和 admin API 元数据,不改 `schedulable`、不清 runtime backoff、不中断请求。 + +## 先读边界 + +- 仓库长期开发边界见 `docs/reference/platform-infra.md`,本 skill 承担日常操作手册。 +- 配置真相是 YAML:`config/platform-infra/sub2api.yaml` 和 `config/platform-infra/sub2api-codex-pool.yaml`。 +- 业务策略和具体数值只以 YAML 为准。已有字段的数值调整只改 YAML 并跑 `plan` / `sync --confirm` / `validate`;不要自动补代码硬编码、schema 硬范围、合同测试、单元测试或长期参考文档。配置校验只校验格式、类型、必填和可渲染性,不判断数值策略是否“合理”。 +- 本 skill 目录下若存在 `agents/*.yaml`,只作为 skill/agent 展示与调用元数据,不是 Sub2API 或 Codex pool 运行配置;不要在 skill 目录维护第二份账号、capacity、priority、endpoint 或 Secret 配置。 +- Runtime target 由 `config/platform-infra/sub2api.yaml` 声明;默认 target 是 `D601:k3s`,`D518:k3s` 这类 external-active target 必须通过显式 `--target` 选择,`G14:k3s` 是 standby target。master server 只是控制端和消费者,不部署 Sub2API/PostgreSQL/Redis。 +- Standby target 不部署本地 PostgreSQL,不运行 sentinel、FRP 管理入口或 HTTPS egress proxy;只能预部署 namespace、NetworkPolicy、Service,以及 replicas=0 的 Sub2API/Redis Deployment。Redis 激活后也只允许 ephemeral cache。External-active target 仍不部署本地 PostgreSQL,必须直连 YAML 声明的外置 DB,使用本地 ephemeral Redis,并且只有在 YAML 启用时才运行 frpc、egress proxy 和目标级 sentinel;多个 external-active target 可以并存,不得把一个 target 的 public exposure 当作另一个 target 的替代或回退。 +- Secret、`~/.codex/config.toml*`、`~/.codex/auth.json*` 是运行时输入或本地状态,不提交。 +- 默认 `~/.codex/config.toml` 和 `~/.codex/auth.json` 只作为统一 Sub2API consumer 使用;`config.toml` 必须指向 YAML-selected active target 的 consumer URL,当前由 `codex-pool configure-local --confirm` 写入 D601 target-level public URL;`auth.json` 必须使用统一 pool API key。新增上游账号不得覆盖这两个默认文件,只能新增 `config.toml.` / `auth.json.` 并在 YAML 里声明。 +- 输出只能包含 Secret 路径、key 名、presence、fingerprint 和 `valuesPrinted=false`;禁止打印完整 API key、admin password、JWT secret、TOTP key、base64 payload 或可复制的 preview。 + +## 部署与状态 + +```bash +bun scripts/cli.ts platform-infra sub2api plan +bun scripts/cli.ts platform-infra sub2api plan --target G14 +bun scripts/cli.ts platform-infra sub2api apply --dry-run +bun scripts/cli.ts platform-infra sub2api apply --target G14 --dry-run +bun scripts/cli.ts platform-infra sub2api apply --confirm +bun scripts/cli.ts platform-infra sub2api apply --target G14 --confirm +bun scripts/cli.ts platform-infra sub2api status +bun scripts/cli.ts platform-infra sub2api status --target G14 +bun scripts/cli.ts platform-infra sub2api validate +bun scripts/cli.ts platform-infra sub2api validate --target G14 +``` + +- `plan` 读取 `config/platform-infra/sub2api.yaml`,渲染 `src/components/platform-infra/sub2api/sub2api.k8s.yaml`,检查 no Ingress/NodePort/LoadBalancer/hostPort/hostNetwork/resource limits,并要求 `NetworkPolicy/allow-all` 随 manifest 受控创建。 +- `apply --confirm` 默认创建异步 job;按返回的 `job status` 命令轮询,再跑 `status` 和 `validate`。 +- `status --full|--raw` 只在需要展开远端 stdout/stderr 或原始 JSON 时使用。 +- `validate` 是按需验收,不是连续可用性探针。对 standby target,`validate --target ` 验证预部署形态,不要求外置 DB 当前可连接;对 external-active target,必须验证外置 DB、ephemeral Redis、Sub2API service、YAML egress proxy 和目标级 public exposure。 + +## D601 Egress Proxy + +D601 的目标级 `egressProxy` 完全由 `config/platform-infra/sub2api.yaml` 控制。当前成熟形态是 master Docker `shadowsocks-rust` 作为加密出站源,D601 k3s 内 `sing-box` 暴露 HTTP/mixed ClusterIP proxy 给 Sub2API 和按 YAML 启用的 sentinel 使用。不要把 endpoint、端口、密码、健康探针或镜像 tag 写进 skill;只以 YAML 和 `config/platform-infra/sub2api-master-egress-proxy.compose.yaml` 为准。 + +master 侧 proxy 由 UniDesk checkout 内的 compose 文件管理: + +```bash +docker compose -f config/platform-infra/sub2api-master-egress-proxy.compose.yaml up -d --force-recreate +bun scripts/cli.ts platform-infra sub2api apply --target D601 --confirm +bun scripts/cli.ts platform-infra sub2api validate --target D601 +bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601 +``` + +proxy secret/config 文件只允许放在受控 Secret/state 路径,输出只能披露路径、presence、fingerprint 或摘要,不能打印密码、完整订阅或生成配置。若 D601 到上游的 TLS/SNI 路径被 reset,不要用临时 JS 或简陋 HTTP CONNECT proxy 作为最终方案;通过 YAML/compose 更换或修复成熟加密 proxy source,再跑上面的 apply/validate/sync/validate 闭环。 + +## 镜像升级 + +1. 修改 `config/platform-infra/sub2api.yaml` 的 `image.repository`、`image.tag` 或 `pullPolicy`。 +2. 执行 `sub2api plan`,确认策略检查通过。 +3. 执行 `sub2api apply --confirm`,轮询 job 完成。 +4. 执行 `sub2api status`,确认运行镜像等于 YAML 声明。 +5. 执行 `sub2api validate` 或 `codex-pool validate` 做入口验收。 + +不要把镜像版本写进脚本常量、JSON 或 manifest 模板。 + +## Codex Pool + +```bash +bun scripts/cli.ts platform-infra sub2api codex-pool plan +bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601 +bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool validate +bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601 +bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601 +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --target D601 --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account unidesk-codex-hy --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --target D601 --account unidesk-codex-hy --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report +bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report --target D601 +bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D601 --confirm +``` + +`config/platform-infra/sub2api-codex-pool.yaml` 控制: + +- `pool.groupName`: Sub2API group 名称。 +- `pool.apiKeySecretName` / `pool.apiKeySecretKey`: 统一消费 API key 的 k3s Secret 位置,默认 `platform-infra/sub2api-codex-pool-api-key.API_KEY`。 +- `pool.minOwnerBalanceUsd`: pool key owner 最低余额,sync/validate 会补齐。 +- `pool.minOwnerConcurrency`: 可选统一消费 API key owner 最低并发;省略时 CLI 自动使用所有已解析账号 capacity 的总和,sync/validate 会补齐。显式 YAML 值只作为 override,仍必须不小于账号 capacity 总和;未显式写 `profiles.entries[].capacity` 的账号会使用 `pool.defaultAccountCapacity` 参与求和,不要用提高某个 provider capacity 来掩盖用户并发层 WS 1013。 +- `pool.defaultTempUnschedulable`: Sub2API 内置请求路径临时不可调度开关和 YAML 规则列表。当前要求是按 YAML 开启通用规则;sync 把 `temp_unschedulable_enabled` / `temp_unschedulable_rules` 渲染到 managed accounts,让匹配的 400/5xx/超时/模型路由/加密内容错误短暂冷却当前账号并触发同组 failover。 +- `pool.defaultTempUnschedulable` 与外部 `sentinel.*` 分开配置、互不驱动。内置规则负责 near-real-time request-path cooling/failover;哨兵负责 marker health、账号级隔离/恢复和 probe 退避。 +- 外部 sentinel 的写入面只允许通过 Sub2API admin `schedulable` 接口冻结/恢复账号;不能写入、清理或间接清理 `temp_unschedulable_until` / `temp_unschedulable_reason`、rate-limit、overload、model-rate-limit 等 Sub2API 请求路径 runtime 状态,也不能调用 `recover-state` 作为恢复动作。看到 UI 里的“触发时间/解除时间/规则序号/匹配关键词”临时不可调度状态时,默认先归因到 Sub2API 内置 request-path temp-unschedulable,而不是 sentinel。 +- YAML 只选择和配置 Codex 上游,不声明 `schedulable` 长期字段;`codex-pool sync --confirm` 不负责把既有账号恢复为 `schedulable=true`。既有账号的 `schedulable=false` 必须由哨兵先同步 Sub2API runtime 状态,再在 marker probe 命中后恢复。 +- `profiles.entries`: 从 master `~/.codex/` 选择上游 profile 并映射到 Sub2API account。 +- `profiles.entries[].capacity`: 可选 per-account concurrency override;不写则使用 `pool.defaultAccountCapacity`。具体数值只以 `config/platform-infra/sub2api-codex-pool.yaml` 为准,skill 和长期参考只描述规则,不重复写当前值。 +- `profiles.entries[].loadFactor`: 可选 per-account Sub2API `load_factor` override;不写则使用 `pool.defaultAccountLoadFactor`。具体数值只以 `config/platform-infra/sub2api-codex-pool.yaml` 为准,修改后必须 `codex-pool sync --confirm` 和 `codex-pool validate`。 +- `profiles.entries[].trustUpstream`: 可选账号级哨兵信任标记;默认 `false`。可信账号使用 `sentinel.cadence.trustedSuccessMaxIntervalMinutes` 作为连续成功后的最大探测退避,不可信账号使用 `sentinel.cadence.untrustedSuccessMaxIntervalMinutes`。它只影响哨兵探测频率和状态可见性,不改变 Sub2API account priority/capacity/loadFactor。 +- `pool.defaultSentinelProtect`: 账号级哨兵保护默认策略;是否启用、连续确认次数、初始延迟、最大延迟和退避倍率都只以 YAML 为准。marker probe 或 gateway failure 触发冻结前会先按该策略做连续 marker 确认,只有全部失败才进入冻结状态机。 +- `profiles.entries[].sentinelProtect`: 可选账号级哨兵保护覆盖;只用于明确偏离 pool 默认策略。它只影响哨兵冻结判定和 `sentinel-report` 可见性,不改变 Sub2API account priority/capacity/loadFactor。 +- 除非用户明确要求修改配置,不要仅凭推断改账号 membership、priority、capacity、loadFactor、WebSocket mode 或其他调度策略;先保留 YAML,完成 provenance/runtime evidence 溯源,并把结论写回相关 issue 或 runbook 后再提出变更。 +- Sub2API 是 UniDesk 可读源码和可观测运行面的受控组件;排查 Sub2API 调度、failover、错误传播、临时不可调度或 account selection 时,默认先读当前 Sub2API 源码实现,再用真实 request id、Sub2API 日志和原入口流量验证。不要用 mock upstream、临时 probe account 或测试桩作为默认结论来源;这类探针最多是显式 debug 辅助,不能替代源码链路和真实运行证据。 +- `profiles.entries[].tempUnschedulable`: 可选 per-account Sub2API 内置临时不可调度覆盖;只用于明确偏离 pool 默认规则,不用它给某个账号特殊优先级或临时绕过通用 failover。 +- `profiles.entries[].openaiResponsesWebSocketsV2Mode`: 需要 Responses WebSocket v2 的上游才设置,值为 `off`、`ctx_pool` 或 `passthrough`。 +- `profiles.entries[].upstreamUserAgent`: 少数要求 Codex CLI User-Agent 的上游才设置,不能含换行。 +- `manualAccounts.protected`: 已在 Sub2API 手动创建/维护、且必须排除在 UniDesk-managed Codex pool credentials 和 sentinel 控制之外的账号。默认不得改 credentials/status/schedulable/priority/capacity/loadFactor;只有显式声明 `proxyBinding` 时,`sync --confirm` 才允许把该账号的 `proxy_id` 对齐到 YAML 目标的 egress proxy;只有显式声明 `groupBinding.source: pool-group` 时,才允许把该账号加入统一消费 API key 使用的 pool group。 +- `sentinel.monitor.enabled`: 账号级 marker 哨兵监控开关;开启后 `codex-pool sync --confirm` 会在 `platform-infra` 创建/更新 k8s CronJob、ConfigMap、Secret、ServiceAccount、Role 和 RoleBinding。CronJob 直打 YAML-managed 上游账号的 OpenAI Responses `gpt-5.5`,用确定 marker 作为唯一健康标准,并在独立 state ConfigMap 中记录 token/cost 账本。 +- `sentinel.actions.enabled`: 账号级哨兵冻结/恢复动作开关;当前 marker-only guard 要求开启。动作关闭时只记录 `would-freeze`,不会调用 Sub2API admin API 改 `schedulable`。动作开启后,只要不满足 marker match,不论是 HTTP 200 私货、4xx/5xx、非 JSON、连接错误还是空输出,都进入同一个冻结/恢复状态机。 +- `sentinel.sdk.openaiPythonVersion`: 哨兵容器使用的 OpenAI Python SDK 固定版本;模型请求必须通过标准 SDK `responses.create`,不要手工拼 `/v1/responses` 请求体或手写响应解析。后续升级 SDK 只改 YAML 并 `sync --confirm`。 +- `sentinel.probe.maxOutputTokens`: 哨兵本地流式 delta 收集上限,必须保持小值;它不作为上游 `max_output_tokens` 字段发送,以保持与 Sub2API WebUI 默认账号连接测试的 Responses SSE 请求形态一致。哨兵不限制并发和每轮账号数,所有到期账号会在同一轮并发探测。 +- `sentinel.probe.userAgent`: 哨兵 direct upstream probe 的默认 User-Agent,通过 OpenAI SDK `extra_headers` 传递;默认贴近 Sub2API `net/http` 账号连接测试形态,个别账号仍可用 `profiles.entries[].upstreamUserAgent` 覆盖。 +- `sentinel.cadence`: 成功信任指数退避配置。当前口径是从 1 分钟开始,连续成功后按账号 `trustUpstream` 选择可信/不可信最大退避;任意非 marker match 清零成功信任并进入冻结退避。可信/不可信最大退避数值只写 YAML。 +- `sentinel.freeze`: 失败冻结 TTL 指数退避配置。当前口径是初始 1 分钟,失败后 `1m -> 2m -> 4m -> 8m -> 10m`,最大 10 分钟;失败 probe 基本不消耗有效输出 token,因此冻结窗口保持短周期。冻结到期后只做恢复 probe,通过才自动恢复,不能仅靠 TTL 到期解封。 +- `sentinel.pricing`: 直打上游时哨兵自己的 token/cost 估算价格。因为 direct upstream probe 不经过 Sub2API 普通用量账本,哨兵必须自己记录全局与 per-account token/cost;这些账本只用于观察,不作为跳过探测的预算门禁。 + +`sync --confirm` 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 `unidesk-codex-*` accounts、创建/复用统一 API key Secret,并部署/更新哨兵资源;它不把既有 managed account 直接恢复为 `schedulable=true`。恢复只由哨兵在读取 Sub2API runtime `schedulable=false` 后触发 recovery probe,并在 marker 命中时执行。`sync` 默认不删除 YAML 中缺席的 managed account。只有明确退役上游时才使用 `sync --confirm --prune-removed` 删除缺席且 `extra.unidesk_managed=true` 的 `unidesk-codex-*` account。对 `manualAccounts.protected`,`sync` 只执行 YAML 显式允许的窄同步;当前允许项是从目标 `egressProxy` 创建/更新 Sub2API internal proxy 记录并绑定 `proxy_id`,以及把受保护手动账号加入当前 `pool.groupName`。它仍不接管该账号凭据、status、schedulable、priority/capacity/loadFactor 或哨兵状态。 + +`sentinel-image status|build` 管理哨兵 Python 运行环境镜像。镜像由 YAML 的 `sentinel.image` 基础镜像和 `sentinel.sdk.openaiPythonVersion` 派生,发布到目标 runtime 的本地 registry;`build --confirm` 会先检查 registry tag,存在则快速复用,不存在才在目标 host 构建并 push。CronJob 启动时只校验 SDK 版本,不在运行时 `pip install`。目标是否启用哨兵以 `config/platform-infra/sub2api.yaml` 的 `sentinel.enabledOnTargets` 为准;未启用的 target 在 `sync`/`validate` 中应显示 `skipped-target-disabled`,不得要求镜像构建、CronJob、Secret 或 state ConfigMap 存在。 + +`sync --confirm` 同时会按 YAML 渲染账号级哨兵资源,并在 monitor 开启时先确保可复用哨兵镜像存在。当前目标是 `sentinel.monitor.enabled=true` + `sentinel.actions.enabled=true` 的 marker-only 自动冻结/恢复;不要手工 patch CronJob、Secret 或 Sub2API account。若 YAML 新增账号或修改 profile/base URL/API key fingerprint/upstream User-Agent/Responses WebSocket mode,sync 会从变更前 runtime state 写入 pending probe 记录并立即安排 sentinel probe,但不会把既有账号直接恢复为可调度;只有 sentinel 读取到 Sub2API runtime `schedulable=false` 后执行 recovery probe,且 marker 命中,才恢复 `schedulable=true`。sentinel 冻结/恢复只改 `schedulable=false|true`,不得顺手调用 Sub2API `recover-state` 清除请求路径临时不可调度或其他 runtime backoff。无关账号的既有成功/失败退避不能被重置。若 YAML 下调失败冻结最大窗口,sync 会把仍 active 的旧冻结状态迁移到当前最大窗口内并立即安排 recovery probe,但不会直接解冻。若怀疑某个账号被误判,先用 `codex-pool sentinel-probe --account --confirm` 立即触发该账号测量;该命令从现有 CronJob 模板派生一次性 Job,复用同一份 Secret、ConfigMap、OpenAI SDK probe、token/cost 账本和冻结/恢复状态机。 + +`trace --request-id ` 是只读 request 追溯报表,不触发 probe、不修改账号。默认输出请求开始/最终状态、failover、`account_select_failed`、窗口内 `account_temp_unschedulable`、admin schedulable 写入计数和当前账号快照;`reason=failover-attempted-no-candidate` 表示 Sub2API 已进入自动切号,但排除当前失败账号后没有可用候选。需要机器处理时使用 `--raw`,需要原始匹配行时加 `--show-lines`。 + +`sentinel-report` 是只读低噪声报表,不触发 probe、不修改账号。默认输出类似 `ps` 的文本表,展示每个账号的探测次数、Sub2API runtime `schedulable`、最近 marker/HTTP/动作、冻结 TTL、成功退避、下一次 probe 和最近 run 事件;`SCH` 展示 Sub2API runtime schedulable,`PROT` 展示账号级保护阈值,`P_FAIL` 展示最近一次保护确认中的失败次数/阈值;需要机器处理时使用 `sentinel-report --raw`。 + +`sync --confirm` 和 `validate` 可能超过单次 SSH/runtime 短连接窗口。必须继续使用 `bun scripts/cli.ts platform-infra sub2api codex-pool ...`,由 CLI 在 G14 远端提交作业并短轮询状态;不要改用裸 `trans G14:k3s sh` 等一个长连接等待完整结果。若看到 `UNIDESK_SSH_RUNTIME_TIMEOUT`,先按 `docs/reference/platform-infra.md` 的规则处理为控制面可见性问题,修 CLI/job/poll 或重跑受控命令,不要手工 patch Sub2API credentials 或源码。 + +不要给 UniDesk-managed Codex accounts 开 Sub2API `pool_mode`。UniDesk 期望的 failover 是把失败账号临时标记为 unschedulable,让同组其他账号接手;`pool_mode` 会重试同一个 account path。 + +WebSocket v2 是账号能力集合,不是调度 pin。`openaiResponsesWebSocketsV2Mode` 只声明该账号可承担 Codex Responses WSv2 链路;只有 `localCodex.supportsWebSockets=true` / `localCodex.responsesWebSocketsV2=true` 时,`codex-pool validate` 才必须看到至少一个 `webSocketsV2.schedulableEnabled` 账号。真实可用性仍以 direct Codex WSv2 probe、Sub2API 日志和原入口 Codex smoke 为准。 + +Codex 启动时反复出现 WebSocket reconnect、HTTPS fallback、`websocket closed by server before response.completed`,或 Sub2API 日志出现 `openai.websocket_proxy_failed` / `openai.websocket_account_select_failed` / 上游 WS handshake 4xx/5xx 时,先按运行证据定位具体 account 和 transport。若账号的 WSv2 握手失败,优先只在 YAML 中把该账号的 `openaiResponsesWebSocketsV2Mode` 收敛为 `off`;若没有任何 direct Codex WSv2 probe 通过,则同时把 `localCodex.supportsWebSockets` 与 `localCodex.responsesWebSocketsV2` 收敛为 `false`,再 `codex-pool sync --confirm`。不要顺手改 membership、priority、capacity、Secret 或代码 fallback。 + +## 受保护手动账号代理与分组绑定 + +Sub2API 管理 UI 的账号连接测试使用账号级 `ProxyID` / proxy URL 配置上游 HTTP transport;账号没有绑定 proxy 时会直接出站,即使 Sub2API Pod 已经有 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量。看到 WebUI 账号测试连 `chatgpt.com` 超时、但 Pod 内显式走目标 proxy 可通时,先检查该账号是否属于 `manualAccounts.protected` 并声明了 `proxyBinding`。如果同一账号用 `gpt-5.2-pro` 返回 ChatGPT OAuth 不支持 Codex 的模型能力错误,但默认/受支持模型能完成 `hi` 或 `/v1/responses` smoke,这不是代理失败;按模型映射/账号能力另行处理。 + +WebUI 账号连接测试也不经过统一消费 API key 的 pool group 选择器;账号测试正常不代表 PC Codex 客户端能选中该账号。看到 WebUI 账号测试正常、但 `/responses` 或 `/v1/responses` 以 `account-select-failed` / `no available accounts` 返回 503 时,先检查该手动账号是否声明了 `groupBinding.source: pool-group`,并通过 `sync --confirm` 加入当前 `pool.groupName`。 + +受保护手动账号仍由人工在 Sub2API UI 维护 credentials/status 等字段;UniDesk 只允许通过 YAML 做代理和分组窄绑定: + +```bash +bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601 +bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm +bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601 +``` + +`sync` 输出应显示 `manualAccounts.ok=true`、`proxySync.ok=true`、`groupSync.ok=true`,且该账号的 proxy/group `bindingAligned=true`。`sentinel-probe --account --confirm` 对受保护手动账号必须继续拒绝,通常返回 `account-protected-manual`;不要为了测试而把该账号移入 `profiles.entries` 或取消保护。需要证明 WebUI 同款账号测试恢复时,用 Sub2API admin account test 原入口测最小 `hi` 和默认/受支持模型,并只记录 account id、proxy id、event types、HTTP status 和短 output preview,不记录 OAuth token 或 Secret 明文。若指定模型返回 “model is not supported when using Codex with a ChatGPT account” 一类能力错误,先归因到模型能力/映射,而不是 proxy。 + +## 添加上游 + +1. 在 master `~/.codex/` 准备带后缀的上游 profile 文件,例如 `config.toml.` 和 `auth.json.`;禁止覆盖默认 `config.toml` / `auth.json`。 +2. 在 `config/platform-infra/sub2api-codex-pool.yaml` 添加 `profiles.entries` 项,指定 `profile`、`accountName`、`configFile`、`authFile`。 +3. 如需要,给该项加 `priority`、`capacity`、`loadFactor`、`trustUpstream`、`sentinelProtect`、`openaiResponsesWebSocketsV2Mode` 或 `upstreamUserAgent`;capacity/loadFactor/信任退避/保护阈值的具体数值只写在 YAML。只有显式恢复 Sub2API 内置临时不可调度时才添加 per-account `tempUnschedulable`。 +4. 如果新增账号会提高声明 capacity 总和,默认让省略的 `pool.minOwnerConcurrency` 继续按 capacity 总和自动解析;只有 YAML 已经显式写了该 override 时,才同步提高到不低于总 capacity,或删除 override 回到自动解析。 +5. 跑 `codex-pool plan`,确认 profile 可读、`base_url` 和 API key 来源有效,且 stdout 未泄露完整 key。 +6. 跑 `codex-pool sync --confirm`。 +7. 跑 `codex-pool validate`。 + +普通新增上游是 YAML 操作,不走 CI/CD,不改代码。只有需要渲染或校验上游 Sub2API 已经存在的可复用能力时才修改 `scripts/src/platform-infra-sub2api-codex.ts`;Sub2API 本身不支持的能力不在 UniDesk 侧魔改实现。 + +## 删除上游 + +删除上游只用于明确退役、凭据所有权变更或用户明确要求移除 provider;不能作为上游 5xx、compact 失败、限流、模型路由失败或哨兵隔离/恢复问题的处理手段。 + +1. 从 `config/platform-infra/sub2api-codex-pool.yaml` 删除对应 `profiles.entries` 项。 +2. 跑 `codex-pool plan` 检查 desired 列表。 +3. 跑 `codex-pool sync --confirm --prune-removed`。 +4. 确认输出 `accounts.pruned` 只包含期望删除项。 +5. 跑 `codex-pool validate`。 + +CLI 默认保留缺席账号,避免把可用性问题误处理成删除;只有显式 `--prune-removed` 才会 prune `name` 以 `unidesk-codex-` 开头且 `extra.unidesk_managed=true` 的缺席账号。 + +## FRP 暴露 + +```bash +bun scripts/cli.ts platform-infra sub2api codex-pool expose +bun scripts/cli.ts platform-infra sub2api codex-pool expose --confirm +``` + +- 由 YAML `publicExposure` 控制。Codex pool 默认公共端是 `publicBaseUrl`,master 本地消费端是 `masterBaseUrl`;external-active target 的目标级 public exposure 在 `config/platform-infra/sub2api.yaml` 中声明。 +- `expose --confirm` 只为 YAML 指定的 `remotePort` 补 master `frps` allow port,并在 G14 创建/更新 `sub2api-frpc`。 +- master Caddy site 也由 `publicExposure.masterCaddy` 渲染;`responseHeaderTimeoutSeconds` 必须足够覆盖 Codex `/responses/compact` 长请求,避免 Caddy 先返回 504 而 Sub2API 后台实际稍后成功。具体数值只改 `config/platform-infra/sub2api-codex-pool.yaml`,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `response_header_timeout`。 +- master Caddy 的短窗口边缘重试由 `publicExposure.masterCaddy.edgeRetry` 渲染;用于吸收 FRP remotePort 短暂关闭、`connect: connection refused`、EOF 或 connection reset 这类请求尚未稳定到达 Sub2API 的 502。具体 retry 时长、间隔和 `retryMatch` 范围只写 YAML,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `lb_try_duration`、`lb_try_interval` 和 `lb_retry_match`。不要手工 patch `/etc/caddy/Caddyfile`。 +- PK01 `/etc/caddy/Caddyfile` 是 Sub2API、LangBot、n8n、HWLAB 等多 YAML 来源共享的 edge artifact。Sub2API apply/expose 只能更新自己的 managed block 并保留其他 blocks;同一 Sub2API 服务暴露多个 target 时,D601 保留既有 `# BEGIN unidesk managed sub2api`,非默认 target 必须使用 target-scoped owner(例如 `sub2api-d518`),避免 `api.pikapython.com` 和 `api2.pikapython.com` 互相覆盖。若 apply 输出显示 managed block 数异常,先停止 closeout,检查 PK01 Caddy 合并与 validation 结果,不要手工整文件覆盖。 +- 非幂等 POST 的 round-trip retry 必须收窄到 YAML `retryMatch` 声明的安全路径;普通 `/responses` 上游账号错误仍归 Sub2API failover / temp-unschedulable / sentinel 处理,不用 Caddy 重放整段推理请求来掩盖账号池问题。 +- 同一个 FRP TCP 入口同时暴露 OpenAI-compatible API 和 Sub2API 管理 UI `/login`。不要另开第二个管理端口,除非 YAML 明确声明新的暴露决策。 +- Sub2API Kubernetes Service 继续保持 ClusterIP。 +- External-active target 的公开路径是 `client -> PK01 Caddy -> PK01 frps remotePort -> target frpc -> Sub2API`,不经过 pikanode,也不经过 master server 反代。PK01 Caddy 下载必须使用 YAML `publicExposure.pk01.caddyDownloadProxyUrl` 指定的 proxy;如果 Caddy 下载慢,先确认 apply 输出里是 `downloadProxy.mode=curl-proxy`。目标域名必须先解析到 YAML 声明的 PK01 公网地址,HTTPS 才能作为最终验证;D601 `api.pikapython.com` 与 D518 `api2.pikapython.com` 应分别验收。 + +## 配置 master Codex 消费端 + +```bash +bun scripts/cli.ts platform-infra sub2api codex-pool configure-local +bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm +``` + +`configure-local --confirm` 会: + +- 从 `platform-infra/.` 读取统一 API key。 +- 把当前 `~/.codex/config.toml` 和 `~/.codex/auth.json` 备份为 `.`,默认 `.pre-sub2api`。 +- 重写默认 `~/.codex` 消费端,固定指向 `https://sub2api.74-48-78-17.nip.io/`,provider 名称和 wire API 来自 `localCodex`。 +- 按 `localCodex.modelContextWindow` / `localCodex.modelAutoCompactTokenLimit` 写入 `model_context_window` / `model_auto_compact_token_limit`,用于统一控制 Codex auto compact 触发窗口,避免 GPT-5.5 消费端生成过大的 `/responses/compact` 长请求。 +- 按 `localCodex` 写入 Codex transport 标记:`supports_websockets` 与 `[features] responses_websockets_v2` 必须同开同关。只有至少一个上游通过 direct Codex WSv2 probe 时才启用;否则保持 HTTP Responses,避免每次原入口先经历无效 WS reconnect。 +- 用统一 key 做一次 gateway 验证。 + +防递归规则:默认 `config.toml` / `auth.json` 是 Sub2API consumer,不得作为上游账号导回 pool;上游账号必须使用带后缀 profile 文件,并通过 `config/platform-infra/sub2api-codex-pool.yaml` 的 `profiles.entries` 增删。 + +## 验收口径 + +部署 closeout 至少包含: + +- `sub2api status`:Deployment/StatefulSet/Service/Secret/NetworkPolicy 可见,运行镜像与 YAML 一致,`NetworkPolicy/allow-all` 符合 `podSelector: {}`、Ingress/Egress 全放行。 +- `sub2api validate`:app、PostgreSQL、Redis、service proxy、`NetworkPolicy/allow-all` 和临时跨 Pod PostgreSQL/Redis 连通性检查通过。 +- `codex-pool validate`:统一 key 的 `GET /v1/models` 成功,并用 `localCodex.responsesSmokeModel` 跑一次小的 `POST /v1/responses` smoke;owner balance / owner concurrency 已满足 YAML 最小值,capacity、WebSocket v2、Sub2API 内置 temporary-unschedulable 开关/规则和 sentinel runtime 状态与 YAML 对齐;`validation.gatewayResponsesRecent` 汇总最近 6 小时普通 `/responses` 和 `/v1/responses` 的 failover、forward failure、最终 4xx/5xx、慢 final error 与 `context canceled` 证据,`validation.gatewayCompactRecent` 单独汇总 `/responses/compact` 证据。若当前 Responses smoke `ok=true` 但 recent 字段 `degraded=true`,先区分是历史窗口残留还是新的 request id 正在失败;长期判定见 `docs/reference/platform-infra.md`。 +- 若 `publicExposure.enabled=true`,确认 FRP path 可用;`expose --confirm` 会用未带 key 的 public `/v1/models` 401 作为网关可达性探针。 +- 多 target 同时启用 public exposure 时,必须分别验证每个 target 的 root、`/health`、未带 key `/v1/models` 401,以及各自 `codex-pool validate --target `;一个域名可用不能替代另一个域名的验收。 +- 若目标声明了 `egressProxy.enabled=true`,确认 proxy Deployment/Service ready,Sub2API 和 sentinel env 与 YAML 对齐,并通过 YAML 声明的 health URL 完成代理出站探针。 + +如果要证明真实模型请求可用,使用最小 `/v1/responses` 或等价 Codex smoke。不要把 group-level `/v1/models` 成功解释成每个上游 account 都健康。 + +## 排障 + +- Codex pool 哨兵、账号冻结/恢复、marker-only 判断或 probe 周期看不清:第一步跑 `bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report`。这个报表是主观察面;只有报表缺字段或需要底层证据时,才继续看 `--raw`、CronJob log、state ConfigMap 或 Sub2API 管理 UI。若看到“临时不可调度状态”且包含规则序号/匹配关键词,检查 Sub2API `account_temp_unschedulable` 日志和账号 `temp_unschedulable_*` 字段;sentinel 只解释 `schedulable=false` 的 active quarantine,不解释这类内置临时冷却。 +- 只加强监控、不让哨兵自动冻结账号时,把 YAML `sentinel.actions.enabled=false` 后 `codex-pool sync --confirm`。此时 marker probe 和 gateway failure monitor 仍记录 `would-freeze` / observe-only 证据,但不会通过 Sub2API admin 写 `schedulable=false`;`/responses/compact` 的 `codex.remote_compact.failed` 和 compact 上游 5xx failover 只作为 `gateway-compact-*` 观察事件记录,不作为哨兵自动切换触发器。 +- 单个 request id 报 502/503/中断/没有自动切号:第一步跑 `bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id `。先看 `outcome`、`reason`、`FAILOVER`、`SELECT-FAILED`、`ACCOUNT SIGNALS` 和 `WINDOW STATS`;只有 trace 报表缺字段或需要审计原始日志时,才加 `--show-lines` 或 `--raw`。若 `reason=failover-attempted-no-candidate`,说明切号动作已发生,但 scheduler 在排除失败账号后没有可用候选;继续用 `sentinel-report` 和 `validate --full` 区分 sentinel quarantine、request-path temp-unschedulable、账号 status 或容量耗尽。 +- profile invalid:先修 `~/.codex/config.toml.` 的 `base_url`、`wire_api`、`model` 或 `auth.json.` 的 API key;不要在 YAML 中写密钥。 +- 手动 OAuth/API-key 账号的 WebUI account test 连 `chatgpt.com` 超时,但同一 Pod 显式 HTTP proxy 探针可通:不要只看 Pod `HTTP_PROXY` env,按“受保护手动账号代理与分组绑定”小节确认 `manualAccounts.protected[].proxyBinding`,跑 `codex-pool sync --target D601 --confirm` 后再用原账号测试复测。若复测不再 reset/timeout,而是 `gpt-5.2-pro` 这类指定模型返回 ChatGPT OAuth Codex 不支持的能力错误,用默认/受支持模型或统一 key smoke 验证代理,不要把模型错误当作代理仍坏。 +- 手动 OAuth/API-key 账号 WebUI account test 正常,但 PC Codex 客户端通过统一 key 访问 `/responses` 返回 503 且 trace 是 `account-select-failed` / `no available accounts`:按“受保护手动账号代理与分组绑定”小节确认 `manualAccounts.protected[].groupBinding.source: pool-group`,跑 `codex-pool sync --target D601 --confirm` 后用 `codex-pool validate --target D601 --full` 复测统一 key。 +- Sub2API 卡在 `wait-postgres` / `wait-redis` 或服务内大量 `context deadline exceeded`:先跑 `sub2api status` 看 `networkPolicy.ok`,再跑 `sub2api validate` 看 `postgresCrossPodPgIsReady` / `redisCrossPodPing`;缺失或异常时用 `sub2api apply --confirm` 恢复受控 `NetworkPolicy/allow-all`,不要保留手工 iptables bypass 作为长期修复。 +- pool key 401:跑 `codex-pool sync --confirm` 重建 Sub2API key 与 k3s Secret 绑定,再跑 `codex-pool validate`。 +- pool key、admin password 或 k8s Secret `.data` 被 stdout、日志、issue 或本地 transcript 打印时,按泄露处理:撤销对应 Sub2API key 或 token,删除/重建受影响的 target Secret,通过 `codex-pool sync --target --confirm` 或相应 YAML sourceRef 重新下发,再用 fingerprint、presence 和 `valuesPrinted=false` 作为 closeout 证据;不要复述旧值或新值。 +- 运行中过去的验证探针残留:只用 `codex-pool cleanup-probes --confirm` 清理 `unidesk-probe-*` 临时资源;不要把真实 managed account 删除当作探针清理或可用性恢复。 +- FRP 不通:先看 `codex-pool expose --confirm` 输出的 `masterFrps`、`masterCaddy`、`sub2api-frpc` 和 public 401 probe;需要低层证据时只用 `trans G14:k3s` 做 bounded 查询。 +- D601 external-active 的 `api.pikapython.com` 不通:先区分 DNS/TLS/Caddy/FRP/Sub2API。DNS 未解析到 YAML 声明的 PK01 地址时,Caddy ACME 会失败,`https://api.pikapython.com` 不能算完成;可用 PK01 loopback FRP 端口和 PK01 公网 remotePort 证明 D601 FRP 数据路径,但最终仍要等 DNS 生效后重跑 HTTPS health、`/v1/models` 和 `/v1/responses`。 +- D601 external-active apply 后其他 PK01 HTTPS 服务消失:优先怀疑共享 Caddy managed block 合并失败或旧整文件写入路径复现。用受控 Sub2API apply 输出和 PK01 Caddy managed block markers 取证,再通过各服务自己的 YAML apply/public-exposure 入口恢复;不要手工复制某一份 Caddyfile 作为长期修复。 +- Caddy 下载慢或失败:先确认 `config/platform-infra/sub2api.yaml` 已设置 `publicExposure.pk01.caddyDownloadProxyUrl`,并重跑 `sub2api apply --target D601 --confirm` 看 PK01 apply summary 中的 `downloadProxy.mode=curl-proxy`。不要反复裸连 GitHub release。 +- `/responses/compact` 在接近 master Caddy `response_header_timeout` 的固定时长后返回 504,或 Sub2API 日志稍后记录 `codex.remote_compact.succeeded` 时,优先检查 master Caddy `response_header_timeout` 是否由 YAML `publicExposure.masterCaddy.responseHeaderTimeoutSeconds` 渲染,修正后跑 `codex-pool expose --confirm`;这类边缘代理超时不会触发 Sub2API 账号级临时下线。reload 前已经在途的 compact 请求仍可能按旧 timeout 结束,判断修复是否生效时只看 reload 之后新发起的请求。 +- `/responses/compact` 或普通 public URL 在几秒窗口内出现 502,Caddy 日志显示 `dial tcp 127.0.0.1:: connect: connection refused`、`EOF` 或 `connection reset by peer`,同时 frps 日志出现 `platform-infra-sub2api proxy closing` / `listener is closed` / `new proxy ... success`,说明失败在 master Caddy 与 FRP remotePort 边缘层,Sub2API 和 sentinel 可能完全看不到。先确认 `publicExposure.masterCaddy.edgeRetry` 已按 YAML 渲染并 `codex-pool expose --confirm` 生效;若仍频繁发生,再继续查 G14 `sub2api-frpc` 到 master `frps` 的控制连接稳定性。不要把这类边缘 502 误判成账号池上游错误,也不要通过禁用账号恢复。 +- default profile 递归:检查 YAML default entry 是否使用 `*.pre-sub2api` 备份文件;必要时恢复备份后重新 `configure-local --confirm`。 +- 上游需要 WebSocket v2:先做 direct Codex WSv2 probe;通过后才给该 profile 配 `openaiResponsesWebSocketsV2Mode: ctx_pool|passthrough` 并跑 `sync --confirm`;把它当 capability candidate,容量仍以 YAML 中的 `capacity` 或默认值为准。 +- Codex 启动 WebSocket 回退:用原入口 Codex smoke 复现,再用 bounded Sub2API 日志确认 account;对 WS handshake 4xx/5xx、`openai.websocket_account_select_failed` 或 close-before-`response.completed` 的账号关闭 YAML WSv2 能力后同步。若没有剩余 WSv2-capable account,把 `localCodex.supportsWebSockets` 和 `localCodex.responsesWebSocketsV2` 一起关掉,不把临时可用性推断写成调度配置。 +- 上游要求 Codex User-Agent:只给该 profile 配 `upstreamUserAgent`,跑 `sync --confirm`。 +- 上游报 capacity/rate-limit/overload/Bad Gateway/Gateway Timeout 后没有隔离或频繁先失败再恢复:先看 `codex-pool sentinel-report` 的 marker、动作、冻结 TTL 和下一次 probe,也看 `codex-pool validate --full` 的 recent gateway failover/forward failure 证据;同时对照当前 Sub2API 源码里 `/v1/responses` handler、`Forward`、`shouldFailoverOpenAIUpstreamResponse` 和 `handleOpenAIAccountUpstreamError` 的真实传播路径。不要手动禁用账号、删除账号、改 membership/priority/capacity/loadFactor 或从 YAML 移除问题账号来替代通用 failover 与哨兵隔离/恢复。 +- `codex-pool sync --confirm` 或 `codex-pool validate` 超时:先区分 CLI 传输超时和 Sub2API 运行失败。受控 CLI 应返回远端作业进度和 stdout/stderr tail;如果只是低层 `trans` 60s 超时,不能据此判定 Sub2API failover 不工作。改用或修复 CLI 的远端 job/poll 路径后重跑,并以最终结构化结果作为证据。 +- Codex 报 weekly-limit、`less than 10% of your weekly limit left`、`Run /status for a breakdown` 等账号状态/软配额提示并要求切号:不要把新关键词写成 Sub2API 内置临时不可调度策略来恢复可用性;由 marker-only 哨兵按非 marker 响应统一冻结,并用 `sentinel-report` / `sentinel-probe` 验证。 +- 上游 400/503 响应体出现 `invalid_encrypted_content`、`bad_response_status_code`、`invalid_request_error` + 稳定 unsupported-model 文案、unsupported-model、`暂不支持` / `可用模型`、`model_not_found`、`No available channel for model ...` 或同类稳定模型路由 / Responses encrypted-content 兼容性失败:按通用 temp-unschedulable/failover 加哨兵 marker 证据处理,不用 account membership、priority、capacity、loadFactor、WebSocket mode、User-Agent 或 provider pinning 掩盖该错误族。 +- 上游错误反复触发:`invalid_encrypted_content`、unsupported-model、`Recovered upstream error ...`、`Bad Gateway`、`Gateway Timeout`、Cloudflare `524`、Codex-facing `Upstream request failed`、`Unknown error`、`context deadline exceeded`、`context canceled`、`model_not_found`、`No available channel for model`、大上下文 `413` 和 `openai_error` 这类稳定包装文案,先确认 YAML temp-unschedulable 已同步、Sub2API 源码会把该错误族传播成 `UpstreamFailoverError`、运行日志出现 `openai.upstream_failover_switching`。若匹配规则后仍只看到 `openai.forward_failed`,根因是 Sub2API HTTP `/responses` 没把该错误传播成 `UpstreamFailoverError`,应修 Sub2API failover classifier/error propagation,不硬编码账号或给 `only` 特权。 +- Codex auto compact 后丢上下文:先确认 YAML `localCodex` 是否声明启用 WSv2;若启用,再确认本机 `~/.codex/config.toml` 是否有 `supports_websockets = true` 和 `responses_websockets_v2 = true`,并看 `codex-pool validate` 的 WSv2 candidate 和 Sub2API 日志里的 `transport=responses_websockets_v2`。若 YAML 当前禁用 WSv2,则按 HTTP Responses 稳定性排查,不把旧 WS 口径当成验收要求。 +- Codex smoke 有 reconnect/1013:这是上游并发/可用性问题,和 HTTP-only compact context-loss 分开处理;记录 session/log 证据并关联专项 issue,不要用运行时手补覆盖 YAML 容量。 + +## 禁止事项 + +- 不用原生 `kubectl apply/delete/patch` 作为正式操作入口。 +- 不在 master server 部署或运行 Sub2API/PostgreSQL/Redis。 +- 不新增 Ingress、NodePort、LoadBalancer、hostPort、hostNetwork 或宽 FRP 端口段。 +- 不用 Sub2API 的 YAML 渲染结果整文件覆盖共享 PK01 Caddyfile;只能通过 managed block merge 更新 Sub2API 自己的块。 +- 不给 Sub2API manifest 添加 CPU/memory limits,除非有新的 YAML 化明确决策。 +- 不打印完整 API key、admin password 或 Secret 明文。 +- 不把普通上游增删做成代码变更、CI/CD、feature flag 或兼容双路径。 +- 不把手动禁用账号、删除账号、移除 YAML entry、降低 membership、临时改 priority/capacity/loadFactor、provider pinning 或给某个账号特权当作通用 failover / 哨兵隔离恢复问题的修复。 +- 不魔改 Sub2API:Sub2API 本身不支持的能力就不做,不通过 UniDesk 脚本、k8s 原地热补、本地 fork、YAML 伪声明或隐藏 fallback 代替上游实现。 diff --git a/.agents/skills/unidesk-trans/SKILL.md b/.agents/skills/unidesk-trans/SKILL.md index 0c13e790..4006f163 100644 --- a/.agents/skills/unidesk-trans/SKILL.md +++ b/.agents/skills/unidesk-trans/SKILL.md @@ -3,351 +3,31 @@ name: unidesk-trans description: UniDesk SSH 透传与 apply-patch 语法 — `trans ` 分布式执行入口,包含 route 语法、workspace/k3s/Windows 路由、apply-patch envelope 格式、sh/bash/py/upload/download operation 和 60s 短连接约束。用户提到 trans、tran、ssh 透传、远端执行、apply-patch、apply_patch、远端 patch、k3s route、workspace route 时使用。 --- -# UniDesk Trans(SSH 透传 + apply-patch) +# UniDesk Trans -UniDesk 分布式 SSH 透传入口,统一通过 `trans ` 在远端 host/k3s/Windows 上执行命令或文本 patch。 +分布式 SSH 透传入口。`trans ` 中 route 只定位目标,后续 token 都属于 operation parser。 -**固定入口**: `trans ...`(wrapper 位于 `/root/.local/bin/trans`,委托 repo 内 ssh-only 启动入口 `bun scripts/ssh-cli.ts ssh "$@"`,避免被无关 CLI 子命令模块解析失败拖垮) - ---- - -## Route 语法 - -``` -trans [/absolute/workspace] [args...] -trans :[::[:]] [args...] -``` - -### Host workspace route +## 高频 route ```bash -trans G14:/root/hwlab git status --short --branch -trans G14:/root/hwlab-v02 sh -- 'git fetch origin v0.2 && git pull --ff-only origin v0.2' -trans D601:/home/ubuntu/workspace/unidesk-dev sh <<'SH' -... -SH -``` - -**规则**: workspace 路径写在 route 第一个 token,不写进 `cd` 串。反面示例:~~`trans G14 sh -- 'cd /root/hwlab && git status'`~~ - -### k3s route - -```bash -# 控制面 -trans G14:k3s kubectl get pods -n hwlab-dev +trans D601:/home/ubuntu/workspace/unidesk-dev git status --short --branch trans D601:k3s kubectl get pods -A - -# 指定 namespace + workload -trans G14:k3s:hwlab-dev:hwlab-cloud-web:app exec --cwd /app -- cat /app/version.txt -trans G14:k3s:hwlab-dev:pod:hwlab-cloud-web-abc:app apply-patch --cwd /workspace <<'PATCH' -... -PATCH -``` - -CLI 自动注入 `KUBECONFIG=/etc/rancher/k3s/k3s.yaml`。k3s route 中 `:` 是分布式路由分隔符,`/` 只表示容器内文件系统 cwd;容器选择必须写 `:` 或 operation 参数 `--container `,不要写成 `pod//`。 - -### Windows route - -```bash +trans D601:k3s:namespace:workload[:container] logs --tail 120 trans D601:win ps <<'PS' -Get-ChildItem C:\test -PS - -trans D601:win/c/test cmd cd +trans gh:/owner/repo/issue/ cat ``` -Windows operation 必须显式区分:`ps` 走 PowerShell,`cmd` 走 cmd.exe。 +Host workspace、k3s、Windows、GitHub issue/PR route,sh/bash/argv/apply-patch/py/upload/download/playwright/kubectl/logs/skills/tcp-pool 操作,以及 apply-patch envelope 语法和 quoting 陷阱见 [references/full.md](references/full.md)。 -### GitHub issue/PR route +## P0 边界 -```bash -trans gh:/pikasTech/HWLAB/issue ls --state all --limit 20 --search 'YAML-first 测试账号 API_KEY 准备 PJ2026-0105' --full -trans gh:/pikasTech/HWLAB/issue/1236 cat -trans gh:/pikasTech/HWLAB/issue/1236 rg 'API_KEY|YAML-first' -trans gh:/pikasTech/HWLAB/issue/1236 apply-patch <<'PATCH' -*** Begin Patch -*** Update File: body.md -@@ --old text -+new text -*** End Patch -PATCH -``` +- 远端文本修改优先 `trans apply-patch`;不要 download/upload/sed 拼临时 diff 代替 patch。 +- `sh`/`bash` 必须显式声明 shell;单进程命令优先 direct argv 或已知 operation。 +- 普通 trans/ssh 短连接硬预算 60s;长 CI/CD、trace、logs、build、硬件流程必须 submit-and-poll。 +- Windows route 使用 `win ps` 或 `win cmd`;不要把 POSIX shell 当 Windows shell。 -GitHub issue/PR 正文读写也走 `trans gh:/owner/repo/...`。常见读取优先用 `issue ls --search/--state`、`cat`、`rg`,不要为了看正文先回到 `bun scripts/cli.ts gh issue view --json body --full`;普通小补丁可直接 `apply-patch`,`--dry-run` 只作为高风险正文预览。 +## 何时读取 reference ---- - -## Operation - -### sh / bash(显式 shell heredoc) - -```bash -# POSIX /bin/sh heredoc -trans G14:/root/hwlab sh <<'SH' -echo "step 1" -git status --short --branch -echo "step 2" -SH - -# Bash heredoc:只有用到 pipefail、数组、[[ ... ]]、${var:0:8} 等 Bash 语法时使用 -trans G14:/root/hwlab bash <<'BASH' -set -euo pipefail -short="${GITHUB_SHA:0:8}" -printf '%s\n' "$short" -BASH - -# one-liner -trans G14:/root/hwlab sh -- 'git fetch origin G14 && git pull --ff-only origin G14' - -# 单进程 direct argv 不放在 sh/bash 下,直接用已知子命令或 argv -trans D601:/path sed -n '1,20p' AGENTS.md -trans D601:/path argv sed -n '1,20p' AGENTS.md -``` - -`script` 和 `shell` operation 已移除并会失败;必须在 operation 位置明确写 `sh` 或 `bash`,让调用者和 Agent 都知道脚本语法边界。Windows PowerShell 必须用 `ps`。 - -### argv(单命令) - -```bash -trans G14:/root/hwlab git status --short --branch -trans D601 hostname -``` - -### apply-patch(远端文本 patch) - - -- **P0: 所有远端文本/源码修改必须优先使用 `apply-patch`,禁止用 `py`(python3 写文件)或 `sed` heredoc 拼接大段文本替代。** 只有当 apply-patch 本身不可用或需处理非文本/批量机械生成文件时,才使用其他受控方式;使用前必须在注释中说明原因,修改后立即用 `git diff` 或文件尾部检查确认没有截断或污染。 - -```bash -trans G14:/root/hwlab apply-patch <<'PATCH' -*** Begin Patch -*** Update File: AGENTS.md -@@ - ## Heading - context line --old line -+new line - more context -*** End Patch -PATCH - -# 从文件读 -trans G14:/root/hwlab apply-patch < patch.diff -``` - -v2 引擎(默认):本地 TypeScript 解析 hunk,远端只读写文件。v1 legacy 入口:`apply-patch-v1`。 - -### py(远端 Python 脚本) - -```bash -trans D601 py -- arg1 arg2 < script.py -``` - -自动写到远端临时 `.py` 文件,`python3 -u` 执行后清理。 - -### upload / download(整文件传输) - -```bash -trans G14:/root/hwlab upload ./local-file.txt /root/remote-file.txt -trans G14:/root/hwlab download /root/remote-file.txt ./local-file.txt -``` - -自动校验 SHA-256,结果中 `verified=true` 即完整性证明。 - -### playwright(远端浏览器验收) - -`trans playwright` 只保留为跨 host 短生命周期浏览器执行和截图/PDF 回传底座。UniDesk/HWLAB Web 开发、HWLAB `web-probe run|script`、fake-server Playwright、fixture 脱敏、截图 artifact 与 Workbench/Performance 判定口径统一见 `$unidesk-webdev`;本 SSH 透传 skill 不维护第二套 Web 测试流程。 - -### kubectl / logs(k3s 诊断) - -```bash -trans G14:k3s kubectl get pods -n hwlab-dev -trans G14:k3s logs deploy/hwlab-cloud-api -n hwlab-dev --tail 50 -trans G14:k3s:hwlab-dev:hwlab-cloud-web-abc logs --tail 100 -``` - -### skills(远端 skill 发现) - -```bash -trans G14 skills [--scope all|wsl|windows] [--limit N] -``` - -### tcp-pool 状态与并发 smoke - -查看 Provider 是否使用 SSH TCP data pool: - -```bash -bun scripts/cli.ts debug health -``` - -在输出或 frontend 原始 JSON 里看这些 labels:`providerGatewaySshDataTransport=tcp-pool`、`providerGatewaySshDataPoolReady`、`providerGatewaySshDataPoolClaimed`、`providerGatewaySshDataPoolDesired`、`providerGatewaySshDataPoolLastError`。 - -单个 provider 的低噪声池状态: - -```bash -bun scripts/cli.ts debug ssh-pool D601 -``` - -如果 `trans`/`tran` stderr 出现 `UNIDESK_SSH_TCP_POOL_HINT`,优先按其中的 `failureKind` 处理:`provider-data-channel-closed` 表示会话中途 data channel 断开,`provider-data-channel-missing` 表示 core/provider 对 channel 状态不一致,`provider-data-pool-exhausted` 表示没有空闲 channel。对幂等受控操作先查 `debug ssh-pool `,再重试原受控 CLI;不要把这类 hint 单独定性为远端 runtime 配置失败。 - -快速验证 D601 维护桥: - -```bash -trans D601 argv true -trans D601 argv hostname -``` - -并发 smoke 用短命令并给每路加本地 timeout: - -```bash -tmp=$(mktemp -d /tmp/trans-d601-pool-10.XXXXXX) -for i in $(seq 1 10); do - timeout 30s trans D601 sh -- 'printf start:'"$i"':%s\\n "$(date +%s%3N)"; sleep 2; printf end:'"$i"':%s\\n "$(date +%s%3N)"' >"$tmp/$i.out" 2>"$tmp/$i.err" & -done -wait -for i in $(seq 1 10); do echo "[$i]"; cat "$tmp/$i.out"; cat "$tmp/$i.err" >&2; done -``` - -期望是每一路 rc=0、stderr 为空、stdout 同时包含 start/end;结束后 pool labels 回到 `claimed=0`。 - ---- - -## apply-patch 语法(Envelope 格式) - -**不是 unified diff**,是 Codex 专用 envelope。 - -``` -*** Begin Patch -*** Update File: -@@ - context line --old line -+new line - more context -*** End Patch -``` - -### 关键规则 - -| 规则 | 正确 | 错误 | -|------|------|------| -| 路径必须相对 | `AGENTS.md` | `/root/unidesk/AGENTS.md` | -| hunk 分隔符 | `@@` | `@@ -N,M +K,L @@` | -| 空行 | ` \n`(空格+换行) | 裸 `\n` | -| 首行/尾行 | `*** Begin Patch` / `*** End Patch` | 外层不能有空行 | - -### 操作类型 - -```bash -*** Update File: path/to/file.ext # 修改现有文件 -*** Add File: path/to/new.ext # 新增文件(可覆盖已有) -*** Delete File: path/to/old.ext # 删除文件 -*** Move File: old/path -> new/path # 移动/重命名 -``` - -### 上下文定位 - -默认 3 行上文 + 3 行下文。同一 hunk 在文件中重复时用 `@@` 跳到 class/function: - -``` -*** Update File: src/app.ts -@@ class MyComponent - old context --old code -+new code - more context -``` - -### 常见失败 - -| 症状 | 原因 | 处理 | -|------|------|------| -| `failed to find expected lines` | 上下文不匹配(文件已变) | 重读目标块,缩小 hunk | -| 空 stdout + stderr 报错 | 首行/路径格式错误 | 检查相对路径、envelope | -| partialChanges | 前序 hunk 成功,当前失败 | 基于当前文件状态补小 patch | - -成功 stdout:`Success. Updated the following files:` + 文件列表。失败 stdout 为空,stderr 写原因。 - -### 远端 patch 正确姿势 - -第一个 route token 直接定位到目标 pod/container,容器内 cwd 用 operation 参数 `--cwd /path`,不要从 host 生成 diff 再改路径上传: - -```bash -# ✅ 正确 -trans G14:k3s:hwlab-dev:pod:hwlab-cloud-web-abc:app apply-patch --cwd /web <<'PATCH' -*** Begin Patch -*** Update File: app.mjs -@@ --old -+new -*** End Patch -PATCH - -# ❌ 错误:host 生成 diff → 本地 sed 改路径 → 管道到 pod -``` - ---- - -## 60s 硬超时与短连接 - -`trans` 有 60 秒硬超时(不可调大)。长任务必须拆成 submit-and-poll: - -```bash -# ✅ 异步启动 + 短轮询 -trans G14:/root/hwlab sh -- 'hwlab-cli case run start d601-f103-v2-compile' -trans G14:/root/hwlab sh -- 'hwlab-cli case run status ' - -# ❌ 长时间挂着等 -trans G14:/root/hwlab sh -- 'long_running_command && wait' -``` - -### HINT 信号 - -| stderr 输出 | 含义 | -|-------------|------| -| `UNIDESK_SSH_HINT` | SSH 握手/超时摩擦,提示改用 `sh`/`bash` stdin heredoc | -| `UNIDESK_TRAN_TIMEOUT_HINT` | 60s 硬超时触发,提示改用短查询+轮询 | -| `UNIDESK_SSH_TIMING` | 慢命令 warning(>10s),用于性能回归监控 | -| `UNIDESK_APPLY_PATCH_TIMING` | apply-patch 耗时摘要 | - ---- - -## 关键约定 - -### heredoc 优先 - -多行远端脚本优先用 heredoc,不拼接 shell 字符串: - -```bash -# ✅ 推荐 -trans G14:/root/hwlab sh <<'SH' -cmd1 -cmd2 -SH - -# ❌ 避免 -trans G14 sh -- 'cmd1 && cmd2 && cmd3' -``` - -### 本地 shell 运算符陷阱 - -`&&` / `;` / `|` 在 `trans` 后面会被本地 shell 截获。需要远端执行多条命令时用 `sh` 或 `bash` 包裹: - -```bash -# ❌ 第二个 sed 在 master server 本地执行 -trans G14:/root/hwlab sed -n '1,20p' a && sed -n '1,20p' b - -# ✅ 两个 sed 都在远端 -trans G14:/root/hwlab sh -- 'sed -n "1,20p" a && sed -n "1,20p" b' -``` - -### Windows quoting - -Windows PowerShell heredoc 用单引号 `<<'PS'`: - -```bash -trans D601:win ps <<'PS' -Write-Output "hello" -PS -``` +- 不确定 route 语法、k3s/workspace/Windows 定位时,读 [references/full.md](references/full.md) 的 Route 语法段。 +- 编写远端 patch 前,读 apply-patch 语法、上下文定位和常见失败段。 +- 需要 shell heredoc、Python、upload/download、Playwright 或超时处理时,读 Operation 和 60s 段。 diff --git a/.agents/skills/unidesk-trans/references/full.md b/.agents/skills/unidesk-trans/references/full.md new file mode 100644 index 00000000..0c13e790 --- /dev/null +++ b/.agents/skills/unidesk-trans/references/full.md @@ -0,0 +1,353 @@ +--- +name: unidesk-trans +description: UniDesk SSH 透传与 apply-patch 语法 — `trans ` 分布式执行入口,包含 route 语法、workspace/k3s/Windows 路由、apply-patch envelope 格式、sh/bash/py/upload/download operation 和 60s 短连接约束。用户提到 trans、tran、ssh 透传、远端执行、apply-patch、apply_patch、远端 patch、k3s route、workspace route 时使用。 +--- + +# UniDesk Trans(SSH 透传 + apply-patch) + +UniDesk 分布式 SSH 透传入口,统一通过 `trans ` 在远端 host/k3s/Windows 上执行命令或文本 patch。 + +**固定入口**: `trans ...`(wrapper 位于 `/root/.local/bin/trans`,委托 repo 内 ssh-only 启动入口 `bun scripts/ssh-cli.ts ssh "$@"`,避免被无关 CLI 子命令模块解析失败拖垮) + +--- + +## Route 语法 + +``` +trans [/absolute/workspace] [args...] +trans :[::[:]] [args...] +``` + +### Host workspace route + +```bash +trans G14:/root/hwlab git status --short --branch +trans G14:/root/hwlab-v02 sh -- 'git fetch origin v0.2 && git pull --ff-only origin v0.2' +trans D601:/home/ubuntu/workspace/unidesk-dev sh <<'SH' +... +SH +``` + +**规则**: workspace 路径写在 route 第一个 token,不写进 `cd` 串。反面示例:~~`trans G14 sh -- 'cd /root/hwlab && git status'`~~ + +### k3s route + +```bash +# 控制面 +trans G14:k3s kubectl get pods -n hwlab-dev +trans D601:k3s kubectl get pods -A + +# 指定 namespace + workload +trans G14:k3s:hwlab-dev:hwlab-cloud-web:app exec --cwd /app -- cat /app/version.txt +trans G14:k3s:hwlab-dev:pod:hwlab-cloud-web-abc:app apply-patch --cwd /workspace <<'PATCH' +... +PATCH +``` + +CLI 自动注入 `KUBECONFIG=/etc/rancher/k3s/k3s.yaml`。k3s route 中 `:` 是分布式路由分隔符,`/` 只表示容器内文件系统 cwd;容器选择必须写 `:` 或 operation 参数 `--container `,不要写成 `pod//`。 + +### Windows route + +```bash +trans D601:win ps <<'PS' +Get-ChildItem C:\test +PS + +trans D601:win/c/test cmd cd +``` + +Windows operation 必须显式区分:`ps` 走 PowerShell,`cmd` 走 cmd.exe。 + +### GitHub issue/PR route + +```bash +trans gh:/pikasTech/HWLAB/issue ls --state all --limit 20 --search 'YAML-first 测试账号 API_KEY 准备 PJ2026-0105' --full +trans gh:/pikasTech/HWLAB/issue/1236 cat +trans gh:/pikasTech/HWLAB/issue/1236 rg 'API_KEY|YAML-first' +trans gh:/pikasTech/HWLAB/issue/1236 apply-patch <<'PATCH' +*** Begin Patch +*** Update File: body.md +@@ +-old text ++new text +*** End Patch +PATCH +``` + +GitHub issue/PR 正文读写也走 `trans gh:/owner/repo/...`。常见读取优先用 `issue ls --search/--state`、`cat`、`rg`,不要为了看正文先回到 `bun scripts/cli.ts gh issue view --json body --full`;普通小补丁可直接 `apply-patch`,`--dry-run` 只作为高风险正文预览。 + +--- + +## Operation + +### sh / bash(显式 shell heredoc) + +```bash +# POSIX /bin/sh heredoc +trans G14:/root/hwlab sh <<'SH' +echo "step 1" +git status --short --branch +echo "step 2" +SH + +# Bash heredoc:只有用到 pipefail、数组、[[ ... ]]、${var:0:8} 等 Bash 语法时使用 +trans G14:/root/hwlab bash <<'BASH' +set -euo pipefail +short="${GITHUB_SHA:0:8}" +printf '%s\n' "$short" +BASH + +# one-liner +trans G14:/root/hwlab sh -- 'git fetch origin G14 && git pull --ff-only origin G14' + +# 单进程 direct argv 不放在 sh/bash 下,直接用已知子命令或 argv +trans D601:/path sed -n '1,20p' AGENTS.md +trans D601:/path argv sed -n '1,20p' AGENTS.md +``` + +`script` 和 `shell` operation 已移除并会失败;必须在 operation 位置明确写 `sh` 或 `bash`,让调用者和 Agent 都知道脚本语法边界。Windows PowerShell 必须用 `ps`。 + +### argv(单命令) + +```bash +trans G14:/root/hwlab git status --short --branch +trans D601 hostname +``` + +### apply-patch(远端文本 patch) + + +- **P0: 所有远端文本/源码修改必须优先使用 `apply-patch`,禁止用 `py`(python3 写文件)或 `sed` heredoc 拼接大段文本替代。** 只有当 apply-patch 本身不可用或需处理非文本/批量机械生成文件时,才使用其他受控方式;使用前必须在注释中说明原因,修改后立即用 `git diff` 或文件尾部检查确认没有截断或污染。 + +```bash +trans G14:/root/hwlab apply-patch <<'PATCH' +*** Begin Patch +*** Update File: AGENTS.md +@@ + ## Heading + context line +-old line ++new line + more context +*** End Patch +PATCH + +# 从文件读 +trans G14:/root/hwlab apply-patch < patch.diff +``` + +v2 引擎(默认):本地 TypeScript 解析 hunk,远端只读写文件。v1 legacy 入口:`apply-patch-v1`。 + +### py(远端 Python 脚本) + +```bash +trans D601 py -- arg1 arg2 < script.py +``` + +自动写到远端临时 `.py` 文件,`python3 -u` 执行后清理。 + +### upload / download(整文件传输) + +```bash +trans G14:/root/hwlab upload ./local-file.txt /root/remote-file.txt +trans G14:/root/hwlab download /root/remote-file.txt ./local-file.txt +``` + +自动校验 SHA-256,结果中 `verified=true` 即完整性证明。 + +### playwright(远端浏览器验收) + +`trans playwright` 只保留为跨 host 短生命周期浏览器执行和截图/PDF 回传底座。UniDesk/HWLAB Web 开发、HWLAB `web-probe run|script`、fake-server Playwright、fixture 脱敏、截图 artifact 与 Workbench/Performance 判定口径统一见 `$unidesk-webdev`;本 SSH 透传 skill 不维护第二套 Web 测试流程。 + +### kubectl / logs(k3s 诊断) + +```bash +trans G14:k3s kubectl get pods -n hwlab-dev +trans G14:k3s logs deploy/hwlab-cloud-api -n hwlab-dev --tail 50 +trans G14:k3s:hwlab-dev:hwlab-cloud-web-abc logs --tail 100 +``` + +### skills(远端 skill 发现) + +```bash +trans G14 skills [--scope all|wsl|windows] [--limit N] +``` + +### tcp-pool 状态与并发 smoke + +查看 Provider 是否使用 SSH TCP data pool: + +```bash +bun scripts/cli.ts debug health +``` + +在输出或 frontend 原始 JSON 里看这些 labels:`providerGatewaySshDataTransport=tcp-pool`、`providerGatewaySshDataPoolReady`、`providerGatewaySshDataPoolClaimed`、`providerGatewaySshDataPoolDesired`、`providerGatewaySshDataPoolLastError`。 + +单个 provider 的低噪声池状态: + +```bash +bun scripts/cli.ts debug ssh-pool D601 +``` + +如果 `trans`/`tran` stderr 出现 `UNIDESK_SSH_TCP_POOL_HINT`,优先按其中的 `failureKind` 处理:`provider-data-channel-closed` 表示会话中途 data channel 断开,`provider-data-channel-missing` 表示 core/provider 对 channel 状态不一致,`provider-data-pool-exhausted` 表示没有空闲 channel。对幂等受控操作先查 `debug ssh-pool `,再重试原受控 CLI;不要把这类 hint 单独定性为远端 runtime 配置失败。 + +快速验证 D601 维护桥: + +```bash +trans D601 argv true +trans D601 argv hostname +``` + +并发 smoke 用短命令并给每路加本地 timeout: + +```bash +tmp=$(mktemp -d /tmp/trans-d601-pool-10.XXXXXX) +for i in $(seq 1 10); do + timeout 30s trans D601 sh -- 'printf start:'"$i"':%s\\n "$(date +%s%3N)"; sleep 2; printf end:'"$i"':%s\\n "$(date +%s%3N)"' >"$tmp/$i.out" 2>"$tmp/$i.err" & +done +wait +for i in $(seq 1 10); do echo "[$i]"; cat "$tmp/$i.out"; cat "$tmp/$i.err" >&2; done +``` + +期望是每一路 rc=0、stderr 为空、stdout 同时包含 start/end;结束后 pool labels 回到 `claimed=0`。 + +--- + +## apply-patch 语法(Envelope 格式) + +**不是 unified diff**,是 Codex 专用 envelope。 + +``` +*** Begin Patch +*** Update File: +@@ + context line +-old line ++new line + more context +*** End Patch +``` + +### 关键规则 + +| 规则 | 正确 | 错误 | +|------|------|------| +| 路径必须相对 | `AGENTS.md` | `/root/unidesk/AGENTS.md` | +| hunk 分隔符 | `@@` | `@@ -N,M +K,L @@` | +| 空行 | ` \n`(空格+换行) | 裸 `\n` | +| 首行/尾行 | `*** Begin Patch` / `*** End Patch` | 外层不能有空行 | + +### 操作类型 + +```bash +*** Update File: path/to/file.ext # 修改现有文件 +*** Add File: path/to/new.ext # 新增文件(可覆盖已有) +*** Delete File: path/to/old.ext # 删除文件 +*** Move File: old/path -> new/path # 移动/重命名 +``` + +### 上下文定位 + +默认 3 行上文 + 3 行下文。同一 hunk 在文件中重复时用 `@@` 跳到 class/function: + +``` +*** Update File: src/app.ts +@@ class MyComponent + old context +-old code ++new code + more context +``` + +### 常见失败 + +| 症状 | 原因 | 处理 | +|------|------|------| +| `failed to find expected lines` | 上下文不匹配(文件已变) | 重读目标块,缩小 hunk | +| 空 stdout + stderr 报错 | 首行/路径格式错误 | 检查相对路径、envelope | +| partialChanges | 前序 hunk 成功,当前失败 | 基于当前文件状态补小 patch | + +成功 stdout:`Success. Updated the following files:` + 文件列表。失败 stdout 为空,stderr 写原因。 + +### 远端 patch 正确姿势 + +第一个 route token 直接定位到目标 pod/container,容器内 cwd 用 operation 参数 `--cwd /path`,不要从 host 生成 diff 再改路径上传: + +```bash +# ✅ 正确 +trans G14:k3s:hwlab-dev:pod:hwlab-cloud-web-abc:app apply-patch --cwd /web <<'PATCH' +*** Begin Patch +*** Update File: app.mjs +@@ +-old ++new +*** End Patch +PATCH + +# ❌ 错误:host 生成 diff → 本地 sed 改路径 → 管道到 pod +``` + +--- + +## 60s 硬超时与短连接 + +`trans` 有 60 秒硬超时(不可调大)。长任务必须拆成 submit-and-poll: + +```bash +# ✅ 异步启动 + 短轮询 +trans G14:/root/hwlab sh -- 'hwlab-cli case run start d601-f103-v2-compile' +trans G14:/root/hwlab sh -- 'hwlab-cli case run status ' + +# ❌ 长时间挂着等 +trans G14:/root/hwlab sh -- 'long_running_command && wait' +``` + +### HINT 信号 + +| stderr 输出 | 含义 | +|-------------|------| +| `UNIDESK_SSH_HINT` | SSH 握手/超时摩擦,提示改用 `sh`/`bash` stdin heredoc | +| `UNIDESK_TRAN_TIMEOUT_HINT` | 60s 硬超时触发,提示改用短查询+轮询 | +| `UNIDESK_SSH_TIMING` | 慢命令 warning(>10s),用于性能回归监控 | +| `UNIDESK_APPLY_PATCH_TIMING` | apply-patch 耗时摘要 | + +--- + +## 关键约定 + +### heredoc 优先 + +多行远端脚本优先用 heredoc,不拼接 shell 字符串: + +```bash +# ✅ 推荐 +trans G14:/root/hwlab sh <<'SH' +cmd1 +cmd2 +SH + +# ❌ 避免 +trans G14 sh -- 'cmd1 && cmd2 && cmd3' +``` + +### 本地 shell 运算符陷阱 + +`&&` / `;` / `|` 在 `trans` 后面会被本地 shell 截获。需要远端执行多条命令时用 `sh` 或 `bash` 包裹: + +```bash +# ❌ 第二个 sed 在 master server 本地执行 +trans G14:/root/hwlab sed -n '1,20p' a && sed -n '1,20p' b + +# ✅ 两个 sed 都在远端 +trans G14:/root/hwlab sh -- 'sed -n "1,20p" a && sed -n "1,20p" b' +``` + +### Windows quoting + +Windows PowerShell heredoc 用单引号 `<<'PS'`: + +```bash +trans D601:win ps <<'PS' +Write-Output "hello" +PS +``` diff --git a/.agents/skills/unidesk-webdev/SKILL.md b/.agents/skills/unidesk-webdev/SKILL.md index cff55632..687a1499 100644 --- a/.agents/skills/unidesk-webdev/SKILL.md +++ b/.agents/skills/unidesk-webdev/SKILL.md @@ -5,264 +5,35 @@ description: UniDesk Web 开发与浏览器验证技能。用户处理 UniDesk/H # UniDesk WebDev -本技能是 UniDesk/HWLAB Web 开发、浏览器复测和 Playwright 回归的唯一操作面。需求真相源仍是 UniDesk OA 规格,例如 `project-management/PJ2026-01/specs/PJ2026-010401-web-workbench.md` 和 `PJ2026-01060505-workbench-performance.md`;本技能只规定如何开发、采集、复现和验收。 +本技能是 UniDesk/HWLAB Web 开发、浏览器复测和 Playwright 回归的入口。需求真相源仍是 `project-management/PJ2026-01/specs/`;本技能只规定如何开发、采集、复现和验收。 -## P0 防分叉原则 +## 快速规则 -- 单一权威入口:先按 issue/CLI 明确的 node + lane 解析 workspace、web-probe origin、public origin、namespace、sourceRef 和目标分支。没有明确目标时才读取受控 YAML;不得把 D601、G14、v0.2、v0.3、旧端口、ClusterIP 或本地 dev server 写成隐藏默认。 -- 防分叉原则:Web/Workbench 修复必须先确定唯一 authority、唯一 API 契约和唯一状态投影入口;不得写成“当字段缺失/请求失败/状态不符/超时/刷新后,再改走另一个 endpoint、旧 workspace/conversation 模型、localStorage、trace polling、result polling、GET read-through repair、内部 manager、legacy route 或测试专用后门”。字段缺失、状态滞后或投影不完整时,修 schema、projector、read model 或正式 mutation 源头;代码里只能保留单一路径内的输入规范化、校验和错误暴露,不能用 `A ?? B`、`if missing then fallback`、兼容分支或双写双读把缺口遮住。 -- 禁止状态竞争行为:不得在代码、API、reducer、read model、fake-server 或测试中保留两个事实源,再用“覆盖、压过、override、优先级、优先采用、以 A 为准否则 B、A 赢过 B”等仲裁规则决定展示状态;这就是条件分叉和竞争投影。必须先定义唯一投影对象、唯一归属字段和唯一生成时机,让 UI/API 只消费该投影对象的单一状态;其他原始字段只能作为审计输入、诊断字段或待收敛数据,不能参与展示状态仲裁。 -- 严禁读侧推理:REST GET、SSE consumer、compat wrapper、CLI renderer、Web reducer/selectors、Trace renderer、fake-server 和测试断言都不能从 trace tail、message text、tool event、result cache、session summary、list row、workspace snapshot 或 localStorage 推断 turn/session/message 的 lifecycle、terminal、final response 或 running 状态。读侧只能读取唯一投影对象已经写好的字段;字段缺失、投影滞后或多字段矛盾时必须暴露 diagnostic/blocker 并修 projection writer/finalizer/schema,不得用 `result?.status ?? trace?.status`、最后一条 event `completed`、message fallback、session fallback、elapsed timeout 或 UI heuristic 补造事实。 -- Sealed final response 不可被读侧诊断覆盖:assistant final response 一旦由唯一 durable projection 写入 terminal/sealed 字段,主消息正文、finalResponse、message status 和 turn terminal 就是 sealed 用户结果;turn polling、trace hydration、SSE gap、realtime timeout、transport close、compat wrapper error 或 read model lag 只能进入 trace detail、transport diagnostics、projection diagnostics、session health 或消息详情入口,不能替换主 timeline 正文。 -- 无多路径、无 fallback:这是本技能的核心原则。Web、CLI、fake-server 和线上 web-probe 必须复用同一套 API 契约、同一 route/session/conversation/trace authority 和同一状态投影;不得为了让测试通过增加第二套 dispatcher、localStorage 真相、workspace snapshot 真相、内部 manager 调用、旧 lane 端口、raw request、临时兼容路径或“测试专用后门”。 -- 严禁事后 repair / 0repair:Web、Workbench、fake-server 和 web-probe 不得通过 reload、切换 session、realign、`sessionRepair`、workspace selection repair、active tab repair、GET read-through、SSE gap repair、localStorage truth 或测试 helper 自动点击,把已经分裂的 route/session/message/trace 状态补成看起来正确。合法页面必须在首次进入、刷新、deep link、session 切换和 SSE 重连时按同一权威自然收敛;不一致必须暴露为 blocker/diagnostic,并修 schema、projector、read model、reducer 或 route authority 源头。允许 session-bound hydrate/gap-fill 只写目标 session 自己的 cache/read model,不得改变 active selection、URL 或当前消息区。 -- Workbench GET 纯读投影:`GET /v1/workbench/*` 只能读取已持久化的 Workbench 投影事实并组装摘要;不得在 GET 中调用 AgentRun、Code Agent 或其他上游执行面做同步、补事件、修 trace、finalize terminal、改 running/completed 或写 message/session。投影滞后必须显式暴露 `projectionStatus`、`lastProjectedSeq`、`sourceRunId`、`blocker` 或等价诊断字段,并由后台 projector/finalizer 或显式受控 mutation 处理;刷新页面、切换 session 或打开详情不能成为事实推进入口。 -- Workbench session lifecycle:空消息/无活动 session 的回收只能由后端 lifecycle GC、后台 finalizer 或显式受控 mutation 完成,TTL 和开关从选中 node/lane 的受控配置进入运行面;Web、fake-server 和 web-probe 不得靠隐藏 tab、前端 DELETE timer、GET read-through 或 deep link repair 清理。归档/删除后的 session deep link 必须观察同一 authority 的 404/archived diagnostic,不能补建或复活 session。 -- Workbench 前端无破坏性投影权:删除 session、清空 active session、清空当前消息页、清空 tabs、把 composer 降成 `session_required`,或把 route session 标记为 not-found/archived/deleted,都是 lifecycle mutation;Web reducer/selectors、route hydrate、GET/list/detail/messages/SSE consumer、fake-server 和测试 helper 都不能由读侧失败、404、空列表、网络瞬断或 late response 触发这些 mutation。只有用户显式 mutation 成功,或后端 canonical lifecycle projection,能改变 session lifecycle。需求真相见 UniDesk OA `PJ2026-010401 Web工作台` 的“破坏性投影权”。 -- fake-server 不是第二后端:它只按正式 API 契约重放脱敏 fixture 和必要边界变形。未 mock 的 `/auth/*`、`/v1/*`、`/health*` 请求应失败并暴露 path;不得访问 live Cloud API、AgentRun、HWPOD、数据库或 Kubernetes 作为通过条件。 -- 真实数据优先:fixture seed 优先从目标 node/lane 的受控真实样本采集。合成 fixture 只补真实样本难以稳定覆盖的边界,并标明 `derivedFrom` 与 `syntheticReason`。 -- 原入口闭环:fake-server Playwright 用例负责可重复红灯和源码回归;线上 `web-probe` 负责同一 node/lane public origin 的 P4 原入口验收。二者不能互相替代。 -- Master server 禁重型验证:不要在 master server 跑仓库级 check、Web build、Playwright/browser smoke 或镜像构建。HWLAB Web 验证走目标 node/lane workspace、k3s/Tekton、D601 runner 或受控 web-probe。 -- 禁止裸写 Playwright:UniDesk/HWLAB Web 复现、截图、DOM/API 采样、长程 Workbench 观测和线上 closeout 默认必须走 `web-probe run|script|observe`;不得直接写 `trans playwright` heredoc、`playwright-cli`、临时 Node Playwright 脚本或本地 browser daemon 作为正式证据入口。确有非 HWLAB 外站截图/PDF 等 web-probe 不覆盖的短生命周期需求时,必须说明例外原因,且可复用动作要回收进 web-probe。 +- 线上 Web bug、Workbench、Performance、前端状态投影、截图、fake-server 和 web-probe 任务必须先用本技能。 +- 真实用户入口验证优先;源码检查、构建通过或截图局部正常不能替代原入口验收。 +- Web probe、Playwright、fake-server 的详细命令和历史判定口径见 [references/full.md](references/full.md)。 +- 前端改动遵循仓库既有设计系统和 `$frontend-design` 全局 UI 规则;不要做营销式落地页替代真实工具页面。 -## 工作流 +## 高频工作流 -1. 定位目标:确认 repo、node、lane、workspace、public origin 和目标 SPEC。HWLAB D601 v0.3 例子是 `/home/ubuntu/workspace/hwlab-v03` + `https://hwlab.pikapython.com`,但只能在 issue/CLI 指向该目标时使用。 -2. 读规则:进入目标 workspace 前读取目标 `AGENTS.md`;涉及规格或测试设计时读取 UniDesk OA 对应 SPEC。 -3. 线上复现:短动作用 `web-probe run|script`,长程 Workbench/session 观测用 `web-probe observe start|command|status|stop|analyze`;保存 `scriptSha256`、`runDir`、observer id、stateDir、截图名、截图 SHA、URL、DOM/API 摘要、prompt hash、trace/session/run id。 -4. 建红灯:修 Workbench/Performance 用户可见 bug 前,在目标 HWLAB workspace 的 `web/hwlab-cloud-web` fake-server Playwright 套件中补确定性用例;fixture 从真实采集样本脱敏产生。 -5. 修源码:保持状态读写单一路径。状态投影类修复优先收敛 server-state/reducer/projection,不在 UI 组件、trace polling、result polling 或 localStorage 中新增竞争事实。 -6. 验证:先跑 fake-server Playwright 目标用例,再回到同一 node/lane public origin 用 web-probe 复测;多轮任务必须用同一个 observer/session 采样到终态。截图、report hash 和 analyze finding 作为证据回传,不进入源码仓库。 -7. 写 issue/closeout:问题登记和收口必须写明目标合并分支、node/lane、fixture 来源、Playwright fake-server 用例、线上 web-probe 命令/脚本 SHA、截图 SHA 和剩余边界。 +1. 先确认目标:repo、分支/lane、node、用户入口 URL、预期页面或 workflow。 +2. 读取对应 SPEC 或 issue,明确验收口径;Workbench 和 Performance 页不要只凭局部 DOM 判断。 +3. 用受控 CLI 或 Playwright wrapper 复现:优先 `bun scripts/cli.ts web-probe ...`,必要时用 `bun scripts/playwright-cli.ts ...`。 +4. 修改前端代码后执行最小相关验证,再用同一原入口复测。 +5. 需要关闭用户反馈/issue 时,记录真实入口证据、截图/trace/session 和失败或通过结论。 -## HWLAB Web Probe - -线上 Cloud Web DOM 验收优先使用受控入口: +## 常用入口 ```bash -bun scripts/cli.ts web-probe run --node D601 --lane v03 +bun scripts/cli.ts web-probe --help +bun scripts/playwright-cli.ts --help +bun scripts/cli.ts check --syntax-only ``` -web-probe 入口分三类: +HWLAB Web probe、fake-server Playwright、fixture 采集脱敏、Workbench/Performance 判定、Web E2E 和线上复测的完整命令矩阵在 [references/full.md](references/full.md)。 -- `run`:repo-owned 标准 DOM probe,适合固定 P4 验收和已有脚本。 -- `script`:受控 Playwright 托管脚本,适合一轮 55 秒内完成的 DOM/API 断言、截图、route/intercept 和边界采样。 -- `observe`:纯客户端长程观测,适合同一 Workbench session 多轮任务、realtime/projection 问题、长时间 trace/DOM/network 采样和无副作用报告生成。长程 Workbench 观测默认同时打开两个浏览器页面:control 页面只执行显式 `observe command` 用户动作,observer 页面只打开同一个 session 做被动观察,并默认每 3 分钟整页刷新一次同一 session,模拟用户离开后返回,用来抓多用户/多页面下同一 session 的投影差异、历史 trace 丢失、耗时跳变和 loading 差异。 +## 何时读取 reference -工测优先级:Workbench trace 乱序、完成行位置、耗时不一致、时间跳变、final response flicker、session 不刷新、性能慢路径和多轮可靠性问题,默认先用 `observe` 采样并跑 `observe analyze`;不要把临时 Playwright spec、裸 DOM 调试脚本或单次 `script` 结果当作主要工测证据。`script/run` 只作为短路径断言、截图/API 摘要或 observe/analyze 后的补充复核。 - -需要 Playwright route/intercept、延迟 API、读取 in-flight DOM 或截图时仍使用受控 `web-probe script`,不要裸写 Playwright: - -```bash -bun scripts/cli.ts web-probe script --node D601 --lane v03 <<'JS' -export default async ({ gotoStable, screenshot, fetchJson, fetchApiMatrix, recordStep }) => { - await gotoStable('/workbench'); - const sessionsResponse = await fetchJson('/v1/workbench/sessions?limit=5'); - const firstSession = sessionsResponse.body?.sessions?.[0] ?? sessionsResponse.body?.items?.[0] ?? null; - const sessionId = firstSession?.sessionId ?? firstSession?.id ?? null; - const apiMatrix = await fetchApiMatrix([ - '/v1/workbench/sessions?limit=5', - ...(sessionId ? [ - `/v1/workbench/sessions/${encodeURIComponent(sessionId)}`, - `/v1/workbench/sessions/${encodeURIComponent(sessionId)}/messages` - ] : []), - '/auth/session' - ]); - recordStep('workbench-ready', { sessionsOk: sessionsResponse.ok, sessionId, apiMatrixOk: apiMatrix.ok }); - return { - sessionsOk: sessionsResponse.ok, - sessionCount: sessionsResponse.body?.sessions?.length ?? sessionsResponse.body?.items?.length ?? null, - sessionId, - screenshot: await screenshot('workbench.png') - }; -}; -JS -``` - -长程 Workbench 观测使用 `observe` 命令组: - -```bash -bun scripts/cli.ts web-probe observe start --node D601 --lane v03 --target-path /workbench --sample-interval-ms 5000 --screenshot-interval-ms 60000 --command-timeout-seconds 55 -bun scripts/cli.ts web-probe observe command webobs-xxxx --type newSession -bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectProvider --provider codex-api -bun scripts/cli.ts web-probe observe command webobs-xxxx --type sendPrompt --text 'ping' -bun scripts/cli.ts web-probe observe command webobs-xxxx --type steer --text '继续观察当前 trace' -bun scripts/cli.ts web-probe observe command webobs-xxxx --type cancel -bun scripts/cli.ts web-probe observe command webobs-xxxx --type sendPrompt --text-stdin <<'EOF' -long prompt -EOF -bun scripts/cli.ts web-probe observe status webobs-xxxx --tail-lines 6 -bun scripts/cli.ts web-probe observe collect webobs-xxxx --view turn-summary -bun scripts/cli.ts web-probe observe collect webobs-xxxx --view timeline --command-id cmd-xxxx -bun scripts/cli.ts web-probe observe collect webobs-xxxx --view trace-frame --trace-id trc_xxx --sample-seq 42 -bun scripts/cli.ts web-probe observe stop webobs-xxxx -bun scripts/cli.ts web-probe observe analyze webobs-xxxx -bun scripts/cli.ts web-probe sentinel plan --node D601 --lane v03 --dry-run -bun scripts/cli.ts web-probe sentinel status --node D601 --lane v03 -bun scripts/cli.ts web-probe sentinel image status --node D601 --lane v03 -bun scripts/cli.ts web-probe sentinel image build --node D601 --lane v03 --dry-run -bun scripts/cli.ts web-probe sentinel control-plane plan --node D601 --lane v03 --dry-run -bun scripts/cli.ts web-probe sentinel control-plane status --node D601 --lane v03 -bun scripts/cli.ts web-probe sentinel control-plane trigger-current --node D601 --lane v03 --dry-run -bun scripts/web-probe-sentinel-service.ts --node D601 --lane v03 --state-root .state/web-probe-sentinel-smoke --scheduler-disabled --once -``` - -`observe analyze` 的 duplicate final response 判定必须以 trace-frame 可见行事实为准。`observe collect --view trace-frame` 固定渲染的 `Final Response` 区块是 summary,不是第二条业务 assistant message;只有同一 trace-frame 中出现两个可见 assistant final rows 且内容重复时,才应报告 duplicate finding,并在证据中写明 `finalResponseSummaryBlockCounted=false`。 - -`observe collect --view timeline` 用于 issue closeout 前的 artifact drill-down:它只读取现有 `control.jsonl`、`samples.jsonl` 和 `commands/{pending,processing,done,failed,abandoned}/*.json`,默认输出 bounded timeline、关键 metadata、脱敏 disclosure 和下一步命令。按 `--command-id`、`--turn`、`--trace-id`、`--sample-seq`、`--timestamp` 或 `--window-ms` 缩小窗口;需要完整原始内容时必须显式使用 `--raw` 或 `--view files --file ...`。 - -项目管理 / MDTODO 页面同样优先使用 `observe`,不要退回一次性 Playwright 脚本。MDTODO 主动编辑验收必须把常见动作沉淀成 `observe command`,同一 observer 串联 source 配置、HWPOD probe/reindex、文件/任务选择、Rxx 树操作、编辑写回和 Workbench launch: - -```bash -bun scripts/cli.ts web-probe observe start --node D601 --lane v03 --target-path /projects/mdtodo --sample-interval-ms 5000 --screenshot-interval-ms 60000 --command-timeout-seconds 55 -bun scripts/cli.ts web-probe observe command webobs-xxxx --type gotoProjectMdtodo -bun scripts/cli.ts web-probe observe command webobs-xxxx --type configureMdtodoHwpodSource --hwpod-id d601-f103-v2 --node-id node-d601-f103-v2 --root docs/MDTODO/ -bun scripts/cli.ts web-probe observe command webobs-xxxx --type probeMdtodoSource -bun scripts/cli.ts web-probe observe command webobs-xxxx --type reindexMdtodoSource -bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectMdtodoSource --source-id -bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectMdtodoFile --file-ref -bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectMdtodoTask --task-ref -bun scripts/cli.ts web-probe observe command webobs-xxxx --type editMdtodoTaskTitle --task --text 'web-probe interactive edit acceptance' -bun scripts/cli.ts web-probe observe command webobs-xxxx --type editMdtodoTaskBody --task --text 'body updated through web-probe command' -bun scripts/cli.ts web-probe observe command webobs-xxxx --type toggleMdtodoTaskStatus --task --status completed -bun scripts/cli.ts web-probe observe command webobs-xxxx --type addMdtodoSubTask --parent --title 'web-probe command subtask' -bun scripts/cli.ts web-probe observe command webobs-xxxx --type continueMdtodoTask --task --title 'web-probe sibling task' -bun scripts/cli.ts web-probe observe command webobs-xxxx --type deleteMdtodoTask --task -bun scripts/cli.ts web-probe observe command webobs-xxxx --type launchWorkbenchFromMdtodo --task -bun scripts/cli.ts web-probe observe collect webobs-xxxx --view project-mdtodo-summary -bun scripts/cli.ts web-probe observe analyze webobs-xxxx -``` - -`observe command --type newSession` 是显式用户/control action:它通过当前 Workbench UI 点击 `#session-create` 创建新 session,等待 active session id 改变和 composer ready,并把前后 snapshot 写入 control log。它只能用于采样开始时建立目标 session,或作为用户明确的新建会话动作;不得在 route/session mismatch 后当作 repair 手段。 - -约束: - -- `web-probe script` 不运行默认探针,必须通过 stdin heredoc 或 `--script-file ` 提供脚本;只需要 repo-owned 标准 DOM probe 时使用 `web-probe run`。 -- `web-probe run|script|observe start` 的默认 URL、browser proxy mode、observe/analyze 报警阈值和 project-management 采样/命令 allowlist 必须来自 `config/hwlab-node-lanes.yaml` 的 `webProbe`;需要排除公网/FRP/跨国 proxy 抖动时,在 YAML 里把目标 node/lane 的 `webProbe.defaultOrigin` 配成内部 Service ClusterIP origin,不要在命令行长期手写 `--url` 或裸 Playwright。 -- `web-probe observe start` 默认是被动观测:记录 DOM 摘要、自然页面 request/response/requestfailed、截图和 performance 样本,不主动 fetch Workbench API、不切换 control session、不拦截路由、不调用 repair helper。长程 Workbench 观测必须保留 control/observer 双页面模型:control 页面执行显式 command,observer 页面只同步到同一 session URL 后被动采样,并按默认 180000ms 周期整页刷新同一 session 来模拟用户往返;周期刷新只作用于 observer,不得改变 control active session 或作为通过条件。两页的 `pageRole`、`pageId`、`sampleGroupSeq` 必须进入样本和 analyzer 报表。任何 `newSession`、`selectProvider`、`sendPrompt`、`steer`、`cancel`、`goto`、`screenshot`、`mark`、`stop` 都必须通过 `observe command` 显式下发,并进入 `control.jsonl`;长 prompt 必须优先用 `sendPrompt --text-stdin` 或 `steer --text-stdin`,不要为了绕开 shell quoting 退回裸 Playwright 或临时脚本。 -- `observe command --type steer` 和 `--type cancel` 是显式用户/control action:steer 复用当前 Workbench composer 的运行中 turn 引导路径,cancel 复用同一 composer 主按钮的取消路径。二者必须进入 `control.jsonl`,不能用后端私有 API、AgentRun direct cancel 或测试后门替代。`configureMdtodoHwpodSource`、`probeMdtodoSource`、`reindexMdtodoSource`、`selectMdtodoSource`、`selectMdtodoFile`、`selectMdtodoTask`、`editMdtodoTaskTitle`、`editMdtodoTaskBody`、`toggleMdtodoTaskStatus`、`addMdtodoSubTask`、`continueMdtodoTask`、`deleteMdtodoTask` 和 `launchWorkbenchFromMdtodo` 也是显式用户/control action,只能使用页面公开 `data-*` id、正式按钮和 YAML 允许的自然 API;它们通过 public source/file/task id 与 Workbench 关联,不能读取内部 store、私有后端或把 MDTODO 页面包含进 Workbench。 -- `observe collect --view turn-summary` 是第一层 CLI 阅读视图:只从 `samples.jsonl`、`control.jsonl` 和已有 `analysis/report.json` 按需渲染同一 session 的多 turn 摘要,包含用户消息 preview/hash、traceId、状态、耗时/最近更新时间、steer/cancel 标记和 Final Response 摘要。`observe collect --view trace-frame --trace-id --sample-seq ` 是第二层 CLI 阅读视图:从同一采样帧渲染单帧 trace 文字截图,并固定输出 `Final Response` 区块。`observe collect --view project-summary|project-mdtodo-summary` 从同一 artifact 渲染项目管理 / MDTODO DOM 采样、Source/File/Task 计数、command/mutation 结果、Workbench launch、捕获到的 `x-hwlab-otel-trace-id` 和 OTel/Tempo drill-down 线索;project collect 的远端 payload 必须保持 bounded compact rows,由本地 renderer 生成表格,避免 `trans` stdout 截断后 JSON parse 失败。collect 视图不是采样器新增保存物,不构成第二事实源。 -- `observe start/status/command/collect/analyze` 默认输出包含 `Wrapper contract` 区块;该区块证明 Web 哨兵只能 wrap 现有 observe CLI verb、现有 runner/analyzer 和既有 artifact contract,不新增第二套 Playwright runner、analyzer、状态机或私有 web-probe API。 -- `web-probe sentinel plan|status` 只读取 `observability.webProbe.sentinel.enabled/configRefs` 和 owning YAML,渲染 redacted 配置引用图、文件 hash、缺失字段和跨 ref 冲突;`web-probe sentinel image|control-plane` 继续从 owning YAML 渲染 image、GitOps、Argo 和 manifest 计划,并在远端 publish job 接通前拒绝报告部署 mutation。它不启动浏览器、不读取 Secret 值、不保存采样结果,也不是第二套 runner/analyzer。真正的采样和判定仍以 `observe start|command|collect|analyze` artifacts 为准。 -- Web 哨兵 public dashboard/origin 必须以 issue/SPEC/YAML 既定计划为准;当前 P6 计划沿用 `monitor.pikapython.com`,不要未经明确变更改成 `hwlab-monitor.pikapython.com` 或其他新域名。验证 report 时记录 `publicOrigin`,但不要把域名硬编码到 runner/analyzer 逻辑里。 -- `scripts/web-probe-sentinel-service.ts` 是 Web 哨兵 Pod entrypoint;`--once` 只做 config/PVC/SQLite/scheduler/analyzer-command health 快照,`--scheduler-disabled` 仅用于本地服务健康冒烟,不能作为生产运行参数。HTTP 服务只提供 `/api/health`、`/api/status`、`/api/runs`、`/api/maintenance`、`/metrics` 和 redacted dashboard 外壳,底层采样仍只能经 observe CLI adapter。 -- `trace-frame` 出现 `(无 trace rows;这是 blocker...)` 时,必须先看同一输出中的 `TRACE DIAGNOSTIC`:记录 pageRole/pageId、traceRows/turns/messages 数量、sampleTraceIds、尾部 traceRow/turn/message 归属。若目标 trace 的 turn/message/final 存在但 traceRows 全部属于旧 trace,应按 Workbench read model authority 分裂登记到架构/业务 issue(例:HWLAB #2124),不得把旧 traceRows 当作新 turn 通过证据,也不得让 analyzer 的聚合计数压过 CLI trace 视图。 -- analyzer finding 不得压过 CLI `trace-frame` 人工视图。尤其 `trace-assistant-message-duplicates-final-response` 只有在 `trace-frame` 中同一 completed turn 可见多条相同 assistant final rows 时才按业务 bug 处理;如果 `trace-frame` 只有一条 assistant final row、后面固定 `Final Response` 区块正确且 API messages/turns 对齐,该 amber 归类为 analyzer 精度问题,应登记/修工具,不得阻止业务 closeout。 -- 若 `observe status` 显示 PID still alive 但 heartbeat/sample 不推进、`commands/pending/*.json` 不被消费,或 `observe stop --force` 只是继续排队 stop command,应先按 web-probe runner 工具缺陷处理(例:UniDesk #874),用 route 只读确认 PID/heartbeat 后清理进程;不要把 pending command、未触发的 cancel 或 runner stale 混入 Workbench 业务结论。 -- `web-probe observe` 的 issue evidence 优先记录 observer id、stateDir、report JSON/Markdown SHA、samples/control/network/artifact 计数、routeSessionId、activeSessionId、prompt hash/textBytes、traceId、AgentRun runId/commandId、最终 status 和必要摘要;不要把 prompt 原文、assistant 大段正文、完整 stdout/stderr 或 provider payload 粘贴到 issue。 -- 多轮 Workbench 采样必须证明同一个 `sessionId` 连续承载所有轮次;每轮至少记录 prompt hash、traceId、终态、最终回答摘要和性能/产物表。若 Web UI 投影卡住但 Code Agent/AgentRun result 已 terminal,应同时登记“执行终态”和“Workbench 投影未收敛”,不得用 `goto`、reload、切 session 或 result polling 把 UI 失败伪装成通过。 -- `observe command --type sendPrompt` 是普通新 turn 路径,composer 主按钮应处于 `data-action=turn`;如果仍是 `steer` 或 `cancel`,必须作为工具/页面状态不一致失败暴露,不能点击后再等待错误接口。空输入状态下 submit disabled 可以为 true,不能把 disabled 当作新 turn 不可用的唯一依据;应先看 input present/enabled、warning absent 和 action=turn,再由正式填入 prompt 后提交。 -- `observe analyze` 是离线分析,只读取 artifact JSONL 并写 `analysis/report.md` 与 `analysis/report.json`,不访问 Workbench API、不驱动浏览器。`observe start` 每次启动必须先把同一 stateDir 中已有的根目录 JSONL 轮转到带时间戳的 `archive/` 文件;`observe analyze` 默认只分析当前根目录 JSONL,不扫描历史 archive,只有显式指定 archive prefix 时才分析历史轮转窗口。报告必须输出采样点 vs 每个 turn 的总耗时/最近更新时间表、trace row 视觉顺序异常、terminal/轮次完成 row 是否最后、Code Agent 卡片耗时与 trace/轮次完成总耗时一致性、可见“加载中”的数量/归属/并发 owner/连续出现区间、DOM diagnostic/HTTP/console/requestfailed/runtime execution error 分组、page asset provenance segment、同源 API Resource Timing 分位表和超过 YAML `webProbe.alertThresholds` budget 的慢路径 finding;项目管理页还必须输出 DOM readiness、source/file/task 计数、缺失 public task ref、Workbench launch success/failure、captured OTel trace header、自然 project-management API 分组和超过 YAML `webProbe.projectManagement.slowApiBudgetMs` 的慢路径 finding。页面/API 加载、可见“加载中”、长连接打开耗时、turn timing 跳变、trace row 顺序、卡片耗时/轮次完成耗时一致性、session fallback 标题比例和项目管理 API 慢路径阈值只能改 YAML,不能在 analyzer/renderer 中写死。修复必须降低真实请求、投影、渲染或后端路径耗时,禁止为了减少“加载中”出现时间而提前展示未加载完的内容,也不能靠下游 retry/reload/fallback 掩盖。报告里的 `trace-row-order-nonmonotonic`、`trace-completion-row-not-last`、`round-completion-elapsed-mismatch`、`code-agent-card-duration-underreported`、`final-response-flicker`、`uncommanded-visible-state-change`、`mdtodo-workbench-launch-otel-trace-missing`、`project-management-api-slow`、session changed、network 503 等 finding 是排障线索;用于 closeout 时必须结合原始 session/trace/DOM 证据解释,避免把采样噪声直接当作业务结论。 -- `sentinel validate --quick-verify` 超过 120s 是严重超时,必须保持 warning/red 并优先从 envreuse、git mirror、warm runner 复用、first tool execution、Workbench 投影和 observe/analyze 开销排查;不要通过提高 budget、减少轮数、放宽 analyzer 或绕过 CLI trace 视图来让场景变绿。quick-verify 等待每轮终态时应读取既有 sampler artifacts 和 bounded collect 视图,不能反复启动完整 collect 或新增第二份“trace 截图”保存来源。 -- 自定义 `web-probe script` 仍运行在 UniDesk `trans` 60s 最外层短连接约束内;能在一轮内完成的 P4 验收优先把 `--command-timeout-seconds` 控制在 55 秒以内,并减少无界 selector/network 等待。确需等待更久时,改用 `web-probe run` 的异步 job/status 语义,或把动作拆成“提交/采样/截图/状态读取”多次短 probe。若输出出现 `UNIDESK_SSH_RUNTIME_TIMEOUT` 但同时恢复了 `reportPath`、`reportSha256`、screenshots 或 DOM steps,先按远端报告判断脚本/页面实际状态;最终关闭证据仍优先用一次未触发短连接超时的 bounded rerun。 -- issue closeout 优先引用 `web-probe script` 输出的顶层 `issueEvidence` 或 `summary.issueEvidence`;只有需要展开调查时才粘贴 `probe.script.result`、`probe.steps` 或完整 `reportPath`,避免 stdout、summary 和 report 多层重复同一证据。 -- stdin heredoc 与 `--script-file` 都按 ES module 加载,脚本必须导出 `export default async ({ page, gotoStable, recordStep, ... }) => { ... }`;不要在模块顶层直接写 `return`。失败为 `Illegal return statement`、`does not provide an export named default` 或 finalUrl 仍是 `about:blank` 且 stepCount=0 时,先按 probe 脚本入口误用处理,不要归因成 Cloud Web 行为失败。 -- 自定义脚本需要主动失败时,优先返回 `{ ok: false, failedCondition: "..." }` 或抛出错误;兼容返回 `{ pass: false }`、`{ success: false }`,且 `{ ok: true, pass: false }` 仍按失败处理。`failedCondition`、`errorMessage`、`message`、`error`、`reason` 或 `summary` 会进入失败摘要;不要只把失败埋在普通业务字段里。 -- web-probe 由 UniDesk CLI 从 YAML 声明的 bootstrap admin sourceRef 读取凭据并建立同源 `hwlab_session`;脚本不得自行读取、打印或复制 Web 登录凭据、cookie、token 或完整 API key。 -- 需要禁用 `EventSource`、mock `Date`/clock、注入 preload hook 或修改浏览器启动前全局对象时,`page.addInitScript()` 必须在目标页面整页加载前注册,并用 `page.goto(new URL(path, origin).toString(), { waitUntil: "domcontentloaded" })` 进入目标 deep link;不要依赖 `gotoStable()` 复用当前 SPA 页面后再期待 init script 生效。若只在已加载页面上 route/block 请求,既有 SSE 或 store 状态可能继续更新,不能作为“无后端刷新依赖”的验收。 -- 脚本构造 URL 时使用 `new URL(path, baseUrl).toString()`;不要拼出 `//v1/...`。 -- 先通过 `gotoStable`、`waitWorkbenchReady` 或等价导航进入目标 origin,再用 `fetchJson` 读同源 API;不要在 `about:blank` 上请求 `/v1/...`。`fetchJson` 返回包装对象 `{ ok, status, body, failureKind, ... }`,业务字段必须从 `.body` 读取,并同时记录 `ok/status/failureKind`;如果返回 `status=0` 且页面还不是目标 origin,先按 probe 脚本误用处理,不要归因成 Cloud API 或 Workbench 投影失败。 -- Workbench 有 SSE/长轮询时不要用 `networkidle` 判断通过;用明确 DOM/API 条件,如 final URL、route sessionId、active tab、message card、trace row 或 durable session detail/messages。 -- 当前 HWLAB v0.3 Workbench durable read model 的验收端点是 `/v1/workbench/sessions`、`/v1/workbench/sessions/{sessionId}`、`/v1/workbench/sessions/{sessionId}/messages`、`/v1/workbench/turns/{traceId}` 和 `/v1/workbench/traces/{traceId}/events`;提交 mutation 可用正式 `/v1/agent/chat` 产生真实 turn。`/v1/workbench/workspace` 只在当前 API 契约声明为选择/config 快照时采集,不作为消息或 trace authority;`/v1/agent/conversations` 属于旧模型,不得作为 v0.3 Workbench durable projection 的通过条件。 -- 需要稳定 running message、timing、SSE 或 projection 样本时,优先通过正式同源 mutation 创建样本:`POST /v1/agent/sessions` 建 session,再按前端同一路径提交 `/v1/agent/chat`,轮询 `/v1/workbench/sessions/{sessionId}/messages` 等待 durable projection。不要把历史 session 是否仍 running 当作验收前提,也不要用旧 conversation/workspace 端点造样本。验收结束后能取消的 turn 必须调用 `/v1/agent/chat/cancel` 清理,并在 issue evidence 只记录 trace/session、status、script/report SHA 和必要摘要,避免粘贴完整 provider payload。 -- HWLAB v0.3 `fa2ad4845a111e0f9e86d473eb4204ed1b2b0a3c` 后,repo-owned `scripts/web-live-dom-probe.mjs` 的默认 `web-probe run --fresh-session` 已使用 durable Workbench session 端点,不应再依赖 `/v1/workbench/workspace` 或 `/v1/agent/conversations` 作为消息/trace authority。若 `session-not-selected` 复现,先确认目标 workspace 已包含该提交,再对 helper 做旧端点扫描;只有扫描仍命中旧端点时才按工具链回归处理。 -- `web-probe run --fresh-session` 空消息路径的基线证据是 D601 v0.3 job `web-live-dom-probe-1781842424721-44066`:`freshSessionAligned=true`、`messageCount=0`、report SHA `d34c6765943e14bbd639b91bfc17139d582f670ed5cf409a957acd40fae7871f`、screenshot SHA `8d341b7c30293baa9be8f1e63e147ea0fdafbdbcfc43f0b05e4fb9f316ef996c`。 -- 若带 `--message` 的 fresh-session 路径已经达到 `freshSessionAligned=true`、`promptSubmitted=true`、`messageCount>0` 和 session API 200,但随后报 `trace-id-missing`、`Workbench turn is not visible to the current actor` 或 trace 面板同类不可见文案,应登记为 Workbench turn/trace actor visibility 问题,不再归因到 fresh-session 选择 helper 或旧端点。 -- Playwright `page.evaluate` 只能传一个可序列化参数;多个值包成对象,或用 `safeEvaluate(fn, { a, b })`。 -- `safeEvaluate` 返回结构化包装对象;脚本断言前必须解包,例如 `const evaluated = await safeEvaluate(...); const dom = evaluated?.value ?? evaluated;`。`recordStep` 可以记录包装对象用于诊断,但行为断言不要直接读包装对象的业务字段,避免把已通过的 DOM 误判成 `undefined`。 -- 脚本用 `page.evaluate` 模拟滚动、resize、click 或派发 DOM event 后,若断言 Vue/React 绑定出的 `data-*`、ARIA、按钮状态或 scroll-follow 状态,必须先等待一次浏览器/框架更新,例如 `await wait(300)`、`page.waitForFunction(...)` 或下一次明确 DOM 条件;不要在同一个 evaluate 内写完事件就立刻读取状态,否则会把尚未 flush 的响应式状态误判为业务失败。验证用户滚动暂停/恢复时优先记录操作前后 scroll metrics 和 `data-following` 的延迟稳定值。 -- `web-probe script` 顶层/default export 运行在 Node 上下文,浏览器全局 `window`、`document`、`location`、`CSS` 只在 `safeEvaluate`/`page.evaluate` 回调内可用。Node 侧 deep link 用 `const origin = new URL(page.url()).origin; await page.goto(new URL('/workbench/sessions/' + encodeURIComponent(id), origin).toString())`,或直接用 `gotoStable('/workbench/sessions/')`;不要在 Node 侧写 `location.origin`、`CSS.escape(...)`,也不要用没有 baseURL 的 `page.goto('/relative/path')`。若失败为 `location is not defined`、`CSS is not defined` 或相对 URL invalid,先按脚本误用处理;工具提示改进跟踪见 [pikasTech/unidesk#479](https://github.com/pikasTech/unidesk/issues/479)。 -- 脚本中用 `recordStep(name, data)` 保存关键 DOM/API partial evidence;API 批量探测优先用 `fetchApiMatrix(paths)`,单个 API 失败不应让后续证据丢失。 -- deleted/archived/not-found session deep link 验收用 `web-probe script` 直接 `gotoStable('/workbench/sessions/')`,再用同源 `fetchJson('/v1/workbench/sessions/')`、messages 和 list API 断言 404/不在列表;不要调用要求 active session selection、composer readiness 或 workspace repair 的 helper 作为通过条件。 -- 验证 Cloud Web runtime config 或 HTML 注入时,优先加 cache-busting query 和 `cache-control: no-cache`,并按层拆开判断:Deployment env、Pod 内 `127.0.0.1` HTML、公网原始 HTML、浏览器 DOM。不要只凭一次 web-probe DOM 缺字段就判定 rollout 失败;先确认是运行面未渲染、Pod 未滚动、边缘缓存,还是脚本读取层级错误。 -- web-probe 只能观察、截图和断言,不得用 `sessionRepair`、`realignFreshSession`、自动点击 session、reload 循环或 workspace selection repair 作为通过条件。发现 `routeSessionId`、`activeSessionId`、`activeConversationId`、message session 或 trace session 不一致时,必须记录 mismatch 并失败;截图和 API 摘要是证据,不是修复动作。 -- 失败证据至少保留 `failureKind`、`errorMessage`、`scriptSha256`、`runDir`、`lastUrl`、`lastScreenshot`、`probe.summary` 和 `reportPath`/`reportSha256`;默认失败截图是 `failure.png`。 - -## Fake-Server Playwright - -Workbench 回归入口在目标 HWLAB repo 的 `web/hwlab-cloud-web`: - -```bash -bun run e2e:workbench -- --project=chromium -``` - -运行面要求: - -- 在 issue/CLI 选中的 node/lane workspace 或其独立 worktree 上运行,不在 master server 本地运行。 -- 同源 fake-server 只提供正式 API 契约,覆盖 `/auth/*`、session list/detail/messages、turn snapshot、trace event page、Workbench event stream、Performance summary 等当前用例所需端点。 -- 测试断言优先使用用户可见文本、ARIA role 和稳定 DOM 标识;不要通过内部 store、localStorage、数据库或 live API 直接判定通过。 -- 非终态瞬时不变量必须用立即 DOM 快照断言:例如 running/pending 阶段要求 final response 为空时,先定位当前 agent card,再用 `locator.evaluate` / `page.evaluate` 直接读取 `.message-markdown.message-text` 数组并立刻判断;不要用会自动等待的 `expect(locator).toHaveCount(0)`、`not.toContainText` 或类似负向 locator 断言,否则测试可能等到 terminal DOM 替换后误通过,掩盖 running 阶段污染。 -- 失败必须保留截图、Playwright trace 或等价 artifact;关键路径可生成命名截图。 - -Workbench 至少覆盖: - -- session 切换后主工作区 loading 和目标 conversation 恢复; -- 刷新页面后以同一 message/turn/trace 标识还原 timeline; -- SSE 断线、丢事件或重连后通过 REST snapshot 与 trace page 补洞; -- session tab、workspace card、message card、composer 主按钮、Trace detail 的 `running`/`completed`/`failed`/`canceled` 一致性; -- completed assistant 已 sealed 后,turn snapshot、trace event page、SSE 或 realtime diagnostic 失败不覆盖主消息正文,诊断只进入详情/感叹号/transport health; -- Trace 阅读按事件顺序渲染可读 row,正确处理分页、终态、失败、自动展开和终态折叠; -- deep link 与普通点击 session 走同一 authority path; -- Performance 页的摘要、错误态、刷新、移动端表格可达性和低基数脱敏展示。 - -## Fixture 采集与脱敏 - -- 采集范围至少包括 session list/detail/messages、session/turn snapshot、trace event page、Workbench event stream 和 Web performance summary;workspace selection/config 只有在当前 API 契约仍声明该读模型时才采集。 -- 产物必须记录 `capturedFrom`、`capturedAt`、schema/redaction 版本和 `valuesPrinted=false`。 -- 使用稳定伪 ID 映射保留 conversation、session、thread、turn、trace、message、sourceSeq 的关系。 -- 删除或替换 `HWLAB_API_KEY`、cookie、Authorization header、DB DSN、provider token、真实用户身份、非公开 prompt、stdout/stderr 大段原文和完整 provider payload。 -- 真实样本难以覆盖的延迟响应、SSE 断线、分页缺口、列表缺少当前选中项、可选字段异常、空集合和特定 HTTP 错误可以合成,但必须说明来源和原因。 - -## Workbench 判定口径 - -- Workbench API 的 GET 路径不是修复入口。若 session/messages/turn/trace 出现状态不一致,应登记为投影唯一性或投影延迟问题,修 backend projector/read model;不得用前端轮询、session 切换、reload、read-through sync 或 GET 内部修复把问题掩盖。 -- Workbench 读侧不得推理生命周期。`turn.status`、`message.status`、`session.running`、`trace terminal`、`finalResponse`、`projectionStatus` 只能来自唯一 durable projection;trace event row 的 `completed` 只表示该 event/tool row 完成,不能终结 turn;message 文本为空只表示无可展示正文,不能生成占位 final response;list summary、session detail、turn snapshot、trace page 之间的差异只能作为 diagnostic,不得用优先级函数在读侧“选一个看起来合理的状态”。 -- Workbench sealed final response 必须与读侧诊断分仓。`messageProjection` 承载 role/status/text/finalResponse/sealedAt,`traceDetail` 承载 events/page hydration/trace diagnostics,`sessionStatus` 承载 rail/card 运行摘要,`transportDiagnostics` 承载 SSE/poll/hydration timeout 等可见性诊断;等价结构可以使用不同命名,但主消息投影和 diagnostic 不能竞争同一字段。`showMessageText()`、message diagnostic selector 和组件模板不得让 diagnostic 压过 sealed final response。 -- Workbench trace 视觉顺序的唯一权威是 durable projection 分配的 `projectedSeq`。`projectedSeq` 必须由持久化 runtime store 在 trace 内全局递增分配,并按 `traceId + sourceEventId` 幂等复用;本地 `event.seq`、`sourceSeq`、事件到达顺序、时间戳、DOM 顺序和 renderer 输入顺序只能作为审计输入或源游标,不能直接派生视觉位置或参与仲裁。重复源事件不能分配新 `projectedSeq`;不同源事件即使局部 `event.seq` 重置为 1 也必须分配新的全局 `projectedSeq`,这个幂等性需要有最小单元测试覆盖。 -- Workbench trace 时间字段的 source truth 是后端和 trace event page 暴露的 ISO/UTC 时间戳;后端 projector、durable read model 和共享 renderer 默认行为不得改成本地时区字符串。Web 展示北京时间或其他用户时区时,只能在 Vue/Web 渲染层从 runtime `displayTime` 注入 formatter;CLI、CaseRun 和原始 trace API 仍显示/返回 UTC,不通过多字段仲裁、默认 fallback 或后端补写时区字符串修显示。 -- Trace event page 检测到同一 trace 内历史 `projectedSeq` collision 时必须暴露 `projectionStatus=blocked`、`projectionHealth=degraded` 和稳定 blocker code,不能把损坏序列交给 CLI/Web renderer 继续展示。CLI/Web trace renderer 只能按 projection 已给定的位置一次渲染;不得通过 assistant 文本相等/包含、final response 匹配、尾部排序、terminal row 合并或 completion row 追加来事后修正视觉顺序。 -- Workbench durable projection 验收必须覆盖同一 session/trace 的 list、detail、messages、turn、trace events 和 DOM session tab/message card;尤其要用 fresh browser 或 runtime 重启后的历史 terminal 样本证明 read model 能从 durable trace events 恢复终态。若内存 trace 仍显示 running 而 durable trace 已 terminal,应修唯一 read model 的投影对象,不得在 GET handler、前端 reducer 或 probe 脚本里做临时状态仲裁。 -- HWLAB #1585/#1596/#1690 是 Workbench 投影禁令的固定判例:AgentRun 已 terminal 但 Workbench 停在旧 seq 时,不得用 GET/read-side 补 result;turn/card completed 但 TraceEventPage seq/range 不合法时,不得用 completed 状态压过 trace 不可读事实;Trace timeline 乱序或 terminal row 重排时,不得让 `sourceSeq`、局部 `event.seq`、输入顺序和 renderer 文本匹配多来源仲裁。合法修复是后台 projector/resumer 补 durable projection,在同一 trace event 快照内修正分页/顺序契约,或暴露 historical projection blocker;不得推断 lifecycle、补写 GET、切 session/reload 修页面、添加多来源优先级或在 renderer 做事后 repair。 -- Session rail 会话集合只消费 `/v1/workbench/sessions`;当前 session detail 只消费 `/v1/workbench/sessions/{sessionId}`;消息本体只消费 `/v1/workbench/sessions/{sessionId}/messages`;turn 状态只消费 `/v1/workbench/turns/{traceId}`;trace 阅读只消费 `/v1/workbench/traces/{traceId}/events` 或正式 trace event page。不要让 workspace snapshot、列表摘要、localStorage、trace polling 或 result polling 互相覆盖。 -- 空消息 session 回收验收必须同时证明:未超过 TTL 的空 session 保留,有消息、running/admitting、terminal result 或 active trace 的 session 不被回收;超过 TTL 的空 idle session 由后端 GC 归档/删除后,rail/list/detail/messages/deep link 都按同一 canonical lifecycle 状态表现。 -- UI transient state 只保存 route、显式选择、composer draft、scroll、展开状态和临时交互状态;服务端事实进入 server-state/reducer/projection。 -- running turn 必须同时反映在主任务区、composer 主按钮、session tab 和可取消入口;清空、切换、刷新不得把 running trace 隐藏成空态。 -- 显式新建或选择 session 是 active selection 的 mutation authority;`POST /v1/agent/sessions` 返回 201 或用户点击目标 session 后,URL、active tab、当前消息区和 composer 必须立即归属目标 session。目标 session detail/messages 慢只能表现为该目标 session 的 loading/empty 态;旧 session 的 list refresh、detail、turn、trace、SSE 迟到事件只能更新旧 session 自己的 cache/status,不得继续占据或夺回 active route、message area、composer/cancel 状态。 -- 读侧没有破坏性投影权。`GET /v1/workbench/sessions`、session detail、messages、SSE、route hydrate 或 refreshSessions 的失败、404、空列表、网络错误和迟到响应,只能显示 loading/degraded/unknown/blocker 或保留上一份成功投影;不得 `forgetSession()`、`replaceActiveSessionSelection(null)`、清空 messages/tabs,或把 composer 改成 `session_required`。deleted/archived/not-found 必须来自后端 canonical lifecycle projection,或来自用户显式 DELETE mutation 成功后的展示更新。 -- terminal failure、blocked、canceled 都是最终结果;若 API 返回可读 error/blocker,final response 展示区必须直接显示,不能只放在 trace rows 或详情面板中。 -- completed、failed、canceled、blocked 的 sealed final result 一旦显示,后续 polling/hydration/SSE/realtime 的 timeout、500、gap、close 和 lag 只能改变诊断区,不得把已显示 final response 闪回 `Code Agent 代理暂时无法连接上游`、`turn 超时无新活动`、`projection-resume:sync-failed` 或同类读侧错误态。 -- session 切换验收必须覆盖点击态和持久化恢复态:选择目标 session、确认 route/session list/detail/messages 归属同一 `sessionId`、刷新后同一 session 仍 active。 -- completed turn / Trace 重放 / deep link 验收必须用 fresh browser context 或等价 fresh login 直接打开 `/workbench/sessions/`,不能只用同页 reload。 -- 0repair 验收必须用 fresh context 直达 session/deep link,或从当前 session 显式切换到目标 session;不得依赖测试 helper 选择、reload、切走再切回、`sessionRepair` 或 `realignFreshSession` 修正页面后再判通过。旧 session 的延迟 detail、turn、trace、SSE 或 refreshSessions 晚到,只能更新该 session cache/list,不能改变 active URL、active tab、current messages 或 composer。 -- Workbench reducer/selectors/0repair 收敛类 closeout 必须做负向源码扫描并在 issue/PR 证据里写明结果;至少覆盖直接写服务端事实和旧 repair 名称,例如 `messages.value =`、`messages.value.push`、`sessions.value =`、`previousMessages`、`previousSessionId`、`restorePrevious`、`restoreMessagesTraceAuthority`、`repairWorkbenchMessagePage`、`findMessageTextFallback`、`projectionCatchup`、`PROJECTION_CATCHUP` 和组件侧 `workbench.messages`。这些扫描是辅助证据,不能替代 fake-server Playwright 与 public web-probe。 -- Trace 实时性要用间隔采样,区分“加载中”和“思考中”;终态卡片不得继续保留“思考中”空态。 -- 用户反馈“当前 session 不刷新、切走再切回才刷新”时,证据必须成对采集:直达目标 URL、原地等待、切到其他 session、切回目标 session 的 DOM 截图;同时记录 session list、session detail、messages、turn snapshot 和 trace event page 的状态。issue 正文要明确这是事件投影/重放问题,不能只登记成普通 deep link 或单接口 404。 - -## Performance 页判定口径 - -- `/performance` 只展示低基数、脱敏的 Workbench/RUM/API/long task 汇总;不得泄露 `conversationId`、`sessionId`、`traceId`、`runId`、prompt、assistant 正文、tool 参数、stdout/stderr、Secret 或用户身份。 -- 单位以 API `unit` 为准;后端若以 seconds 存储,前端显示 ms 时必须只做一次换算。 -- 移动端和窄屏断言要检查目标表格/按钮是否可见、可点击、layout rect 非零且可触达;不要只用 desktop selector readiness 代表所有 viewport。 -- 刷新按钮应有明确 loading/error/empty 状态,重复点击不能制造 stuck loading、旧数据冒充新数据或未捕获 console/page error。 - -## UniDesk Frontend E2E - -UniDesk 主前端交付门禁仍由 `bun scripts/cli.ts e2e run` 承担;本技能规定浏览器侧如何选取和解释验证。 - -- 先跑最小相关选择集,例如 `bun scripts/cli.ts e2e run --only frontend:pipeline`;目标检查通过后再跑完整 `bun scripts/cli.ts e2e run`。选择集是真执行,不是输出过滤。 -- 浏览器必须打开 `config.json` / publicHost 派生的公开 frontend 或 dev frontend proxy;不要用 localhost、Docker internal URL 或后端 API 直调替代用户入口。 -- 前端断言优先使用用户可见文本、ARIA role、稳定 DOM 标识和 React 控件状态;禁止用 raw JSON、内部 store、localStorage 或后端数据库绕过 UI。 -- 高信息密度区域如 Pipeline 右侧栏、Trace timeline、详情抽屉、甘特图坐标、资源监控表和 Performance 面板,必须显式检查总高度、横向滚动条、关键控件可见可点、文本不重叠。 -- 用户服务页必须等待真实后端数据,而不是只看页面骨架;Todo Note、OA Event Flow、Code Queue、FindJob、Pipeline、MET Nonlinear、ClaudeQQ 等页面的通过条件以当前 E2E check 名称和 issue scope 为准,不在文档里复制所有字段清单。 -- raw JSON 只能在用户显式点击 `查看原始JSON` 后出现;默认页面必须把 JSON 渲染为表格、卡片、徽章、树、图或其他可读控件。 -- 任何 frontend E2E 失败都应保留截图路径、resultPath、failedChecks 和关键 console/page error 摘要;不要把 missing browser/deps 当作 skip,按锁文件和目标执行面补依赖后继续。 - -## 受控 web-probe 代替裸 Playwright - -UniDesk/HWLAB Web 工作不再把裸 Playwright 当作默认操作面。需要截图、DOM 断言、API matrix、route/intercept、SSE 降级、长程 session 采样、性能页面复测或 Workbench 多轮任务时,统一选择 `web-probe run|script|observe`。 - -`web-probe observe analyze` 必须把 Workbench session 列表标题纳入默认采样与报告:可见列表中 `Session ses_...` fallback 标题超过一半时输出红灯 finding。修复方向必须让上游 session list projection 直接携带名称;点击详情后的下游补名、reload repair 或多来源仲裁不能作为修复。 - -`trans playwright`、`playwright-cli` 和临时 Node Playwright 脚本只允许作为非 HWLAB 外站短生命周期截图/PDF、或 web-probe 尚未覆盖且一次性不可复用的诊断例外。例外使用前必须写明为什么 `web-probe script` 或 `web-probe observe` 不适用;同类动作出现第二次就应补进 web-probe CLI 或 repo-owned probe。 - -需要把截图回传到本机时,优先用 `web-probe script` 的 `screenshot(name)`,或长程观测中使用 `observe command --type screenshot --label