docs: preserve parallel UniDesk updates
This commit is contained in:
@@ -46,31 +46,11 @@ If a manual repair is needed to unblock the platform, the durable fix must be co
|
||||
|
||||
## 分布式敏捷流程
|
||||
|
||||
“分布式敏捷”是 UniDesk 对 distributed agile field repair 的固定流程名。后续 issue、PR、指挥记录或用户反馈提到“分布式敏捷”时,默认指下面这套流程:先在真实分布式运行面快速探测和实验补丁,形成可复现的证据与复盘 issue,再把有效修复收敛为 Git/PR/CI/CD 的持久化交付,最后从原始用户入口复测。它允许快速现场学习,但不允许运行面改动变成隐藏部署真相。
|
||||
“分布式敏捷”是 UniDesk 对 distributed agile field repair 的固定流程名;通用 P1/P2/P3/P4 阶段、禁止行为和证据边界由 `$dad-dev` skill 维护,本参考不再重复展开。UniDesk 项目内只保留下面的特有约束:必须使用结构化 `trans`/UniDesk CLI 进入真实 provider、pod、host bridge 或 service port;运行面热补只能证明方向或临时恢复,不能成为隐藏部署真相;持久化完成必须回到 Git/PR/CI/CD 后原入口复测。
|
||||
|
||||
Before classifying a failure as an external blocker, the operator must complete the field anti-misclassification check. This is P0 for model providers, API providers, hardware links, cross-platform bridges, CLI/trans/tran paths and frequently used tooling:
|
||||
在模型 provider、API provider、硬件链路、跨平台 bridge、CLI/trans/tran 或高频工具链问题上,判定外部 blocker 前仍需完成 UniDesk 的防误判核查:确认当前 runtime config / Secret key presence / env / proxy / NO_PROXY / endpoint / args,使用实际目标运行面复现,并尽量与 UniDesk/HWLAB 成熟实现对照。用户反馈或新证据推翻 blocker 判断时,立即切回 `$dad-dev` 的现场修复闭环。
|
||||
|
||||
1. Confirm the exact runtime configuration used by the failing path: committed source ref, deployed image or script revision, redacted Secret names and key presence, env/proxy/NO_PROXY shape, endpoint identity and command args. Do not infer these values from memory or from a different workspace.
|
||||
2. Reproduce the symptom from the actual target provider, pod, host bridge or service port through UniDesk passthrough or the service entry that failed. A commander-machine-only check is supporting evidence, not classification evidence.
|
||||
3. Compare with the mature local implementation when one exists. For Codex/model-provider work, inspect the current UniDesk/HWLAB stdio, forwarder, proxy, env-stripping and config-loading paths before concluding the provider itself is broken.
|
||||
4. Run narrow one-variable experiments in the live target environment. Typical variables are explicit versus config-derived model, endpoint, proxy or NO_PROXY, env inheritance, secret mount shape, CLI version, protocol start parameters and request payload. Record the success case and the failure case with trace ids, run ids, job names, rollout objects or bounded logs.
|
||||
5. Only call the condition an external blocker after the current runtime config has been verified, the minimal real-path probe still fails, a mature reference path or equivalent cross-check also fails, and the evidence rules out local adapter/config mistakes.
|
||||
|
||||
If user feedback or fresh evidence contradicts an initial blocker claim, the operator must stop repeating the blocker narrative and switch to field repair mode immediately. The expected sequence is passthrough probing, single-variable live experiments, a bounded hotfix experiment when needed, a source PR, CI/CD rollout and re-test from the original entry point. The hotfix proves direction or restores a live path; it does not complete the task.
|
||||
|
||||
The standard flow is:
|
||||
|
||||
1. Probe the real runtime surface first. Use structured UniDesk passthrough, service health endpoints, trace/result polling, bounded logs, object metadata and user-entry requests to reproduce the symptom on the actual target environment. Prefer short single-step commands that return promptly and can be repeated.
|
||||
2. Apply an experimental runtime patch only when it is needed to prove a fix direction. The patch must be narrow, named, reversible and scoped to the affected deployment, pod, ConfigMap, env key, script mount or file. It must not include secrets, broad filesystem rewrites, unmanaged image builds, destructive resets or unrelated cleanup.
|
||||
3. Validate the runtime patch from the user or service entry that exposed the problem. Supporting internal checks are useful, but the decisive evidence should include the external URL, API route, trace id, operation id, rollout object, health metadata or other runtime identity that proves the real path changed.
|
||||
4. Write a recap issue before treating the fix as complete. The issue must include reproduction steps, runtime evidence, root cause, exact experimental patch shape, rollback or cleanup notes, durable source changes needed, and post-CD validation criteria. Sensitive values stay redacted; copy-pastable secret or credential mutation commands do not belong in the recap.
|
||||
5. Convert the working runtime patch into the owning repository. The formal fix must be committed, pushed, reviewed through PR when applicable, and validated by the smallest appropriate CI gate on an approved execution surface. Master server local checks remain prohibited for heavy gates.
|
||||
6. Roll the fix through the standard CD path. CD must consume commit-pinned artifacts or desired-state manifests rather than the live hotfix. If publish and desired-state commits differ, the rollout target is the already published source commit, not a later documentation-only or desired-state commit.
|
||||
7. Re-test after CD rollout from the original user or service entry. The final evidence must show the deployed commit/image/runtime metadata, the relevant health or trace result, and that the temporary runtime patch is gone or no longer active.
|
||||
|
||||
This flow deliberately separates agility from persistence: runtime probing and experimental patches are allowed to shorten diagnosis, while Git, PR, CI/CD and post-rollout validation remain the only durable completion path. If a recurring field step is painful because of quoting, target routing, kubeconfig selection, output volume or missing helpers, improve the UniDesk passthrough tool and document the new helper instead of preserving another one-off command recipe.
|
||||
|
||||
Full CI/CD, GitOps rollout, image build, hardware run, long trace replay and model-provider compatibility work must not be used as the inner loop. First prove the smallest real loop inside the target provider, Pod, host bridge or service port, then promote the proven change into source and the normal release path. For model-provider work, prefer official docs and mature protocol bridges over hand-written protocol translation; a local compatibility shim should stay thin and preserve provider cache/tool semantics.
|
||||
如果某个现场步骤因为 quoting、route 定位、kubeconfig、输出体积或缺少 helper 而反复痛苦,优先改进 UniDesk passthrough / CLI 并在本文件的 `Distributed Command Passthrough` 或 `docs/reference/cli.md` 中记录稳定入口,不要沉淀一批一次性 shell 菜谱。
|
||||
|
||||
## Distributed Command Passthrough
|
||||
|
||||
|
||||
+50
-12
@@ -100,24 +100,14 @@ Do not turn `v0.2` expansion governance into a stack of broad compatibility gate
|
||||
|
||||
## v0.2 Worktree + PR Workflow
|
||||
|
||||
`v0.2` source-of-truth changes must enter through a task-scoped worktree on a feature branch and then merge through a PR, not by direct commits to `v0.2`. The canonical sequence is:
|
||||
`v0.2` source-of-truth changes must enter through a task-scoped worktree on a feature branch and then merge through a PR, not by direct commits to `v0.2`. The generic P2/P3/P4 flow is owned by `$dad-dev`; this section only fixes the G14/v0.2-specific source route, branch and lane:
|
||||
|
||||
```bash
|
||||
trans G14:/root/hwlab-v02 script -- 'git fetch origin v0.2 && git pull --ff-only origin v0.2 && git status --short --branch'
|
||||
# 1. Create a task-scoped worktree from latest origin/v0.2
|
||||
trans G14:/root/hwlab-v02 script -- 'git worktree add .worktree/<task> -b fix/issue<N>-<short-name> origin/v0.2'
|
||||
# 2. Develop, commit, and push the feature branch from inside the worktree
|
||||
trans G14:/root/hwlab-v02/.worktree/<task> script -- 'git push -u origin fix/issue<N>-<short-name>'
|
||||
# 3. Open a PR through UniDesk CLI (no native gh, no curl)
|
||||
bun scripts/cli.ts gh pr create --repo pikasTech/HWLAB \
|
||||
--head fix/issue<N>-<short-name> --base v0.2 --title "..." --body-file -
|
||||
# 4. Merge through UniDesk CLI; do not bypass to native gh pr merge
|
||||
bun scripts/cli.ts gh pr merge <number> --repo pikasTech/HWLAB --squash
|
||||
# 5. Pull the merged v0.2 head back into the fixed workspace
|
||||
trans G14:/root/hwlab-v02 script -- 'git pull --ff-only origin v0.2'
|
||||
```
|
||||
|
||||
The fixed repo at `/root/hwlab-v02` is not a scratch area and must not carry parallel worktree state on the `v0.2` branch itself. All worktree branches should follow the `fix/issue<N>-<short-name>` naming so PR titles and merge commits stay scannable.
|
||||
The fixed repo at `/root/hwlab-v02` is not a scratch area and must not carry parallel worktree state on the `v0.2` branch itself. All worktree branches should follow the `fix/issue<N>-<short-name>` naming so PR titles and merge commits stay scannable. GitHub PR writes, merge, rollout trigger and final original-entry validation follow `$dad-dev` plus the UniDesk CLI control rules in `AGENTS.md`.
|
||||
|
||||
### Recovery From a Direct Commit To v0.2
|
||||
|
||||
@@ -218,3 +208,51 @@ docker build --network host \
|
||||
The backup proxy uses `HTTP_PROXY=http://127.0.0.1:11809`, `HTTPS_PROXY=http://127.0.0.1:11809` and `ALL_PROXY=socks5h://127.0.0.1:11808`.
|
||||
|
||||
This proxy is not a replacement for UniDesk runtime egress. k3s workloads such as Code Queue must still use the cataloged `g14-provider-egress-proxy` Kubernetes Service and `g14-tcp-egress-gateway` for normal runtime access to PostgreSQL, OA Event Flow and external APIs. The node-local VPN proxy is allowed only for G14 host-side bootstrap, image build, cache prewarm or recovery steps, and those steps should record the proxy choice in issue or deployment evidence.
|
||||
|
||||
## v0.2 device-pod cloud-api architecture
|
||||
|
||||
`v0.2` device-pod integration is the cloud-api → executor → D601 Windows `device-host-cli.mjs` chain under `internal/cloud/access-control.ts`, `cmd/hwlab-device-pod/main.ts` and the host-side `F:\Work\ConStart\tools\device-host-cli.mjs`. PR #765 (selector cheat sheet + fail-fast) and PR #778 (output.text propagation + evidence selector + read-only sub-action `--reason` exemption) are the two anchor PRs; PR #779 tracks the still-open host-side ops work. Earlier work used raw `MUTATING_INTENTS.has(intent) && !reason` and a single-pass `textOr(output.text, …)` extractor; both are obsolete and must not be re-introduced.
|
||||
|
||||
### Intent / sub-action / reason matrix
|
||||
|
||||
`DEVICE_JOB_INTENTS` (cloud-api) enumerates the full supported surface; `MUTATING_INTENTS` is the strict subset whose default sub-action is mutating. Only `workspace.build` and `debug.download` carry a structured sub-action (`start` / `status` / `output` / `wait` / `cancel` / `evidence`) and are listed in `DEVICE_JOB_ACTIONABLE_INTENTS`; for those two, `_deviceJobRequiresReason(intent, args, reason)` returns `false` when `reason` is provided OR when `args.action` is in `DEVICE_JOB_READ_ONLY_SUB_ACTIONS`. Any other mutating intent (`workspace.apply-patch`, `workspace.put`, `debug.reset`, `io.uart.write`, `io.uart.jsonrpc`, `io.uart.read-after-launch-flash`, etc.) still always requires a non-empty `reason`. Adding a new actionable mutating intent requires extending both `MUTATING_INTENTS` and `DEVICE_JOB_ACTIONABLE_INTENTS` together; adding a new read-only sub-action requires only the `DEVICE_JOB_READ_ONLY_SUB_ACTIONS` set.
|
||||
|
||||
The `evidence` sub-action on `workspace.evidence` / `debug.evidence` is a first-class intent, not a `workspace.build` sub-action. Code Agent sees `<pod>:workspace:/ build evidence [jobId]` and `<pod>:debug-probe download evidence [jobId]`; cloud-api maps to a new device-pod executor job, the executor maps to `deviceHostArgs = ["workspace", "evidence", kind, ...]`, and the host-side `device-host-cli.mjs` dispatches via `if (command === "evidence")` at the top level of `main()` (not nested under `if (command === "build")`). `workspace.evidence kind=build` → `keil-build` job; `debug.evidence kind=download` → `keil-download` job; the kind sub-arg must be `build` / `download` and the optional jobId selects a specific past job.
|
||||
|
||||
### Output text propagation chain
|
||||
|
||||
`body.output.text` flows through three layers in order; each layer tries more fields and only falls back when earlier sources are empty:
|
||||
|
||||
1. **host `device-host-cli.mjs`** returns a JSON envelope that already contains `stdout` / `stderr` / `summary` / `logTail` / `buildSummary` for build/download ops; `workspace.ls` / `workspace.cat` / `workspace.rg` are inline and include a JSON body.
|
||||
2. **executor `cmd/hwlab-device-pod/main.ts` `gatewayDispatchText(result, dispatch)`** walks `result.stdout` → `result.stderr` → `dispatch.stdout` → `dispatch.stderr` → `result.evidence.{text,logTail,summary}` → `dispatch.message` (only when `dispatchStatus === "completed"`) → `dispatch.summary` → `result.summary` → `dispatch.buildSummary` → `result.text` → `JSON.stringify(result)`. The executor stores this as `job.output` and exposes it via `boundedOutput()` which clips at `DEVICE_JOB_OUTPUT_MAX_BYTES` (12000) and drops `executor` / nested `output` when truncated.
|
||||
3. **cloud-api `executorOutputPayload(body, httpStatus)`** wraps what the executor sent and exposes `body.text` / `body.output` / `body.bytes` / `body.truncation` to the `/v1/device-pods/{id}/jobs/{jobId}/output` endpoint. `text` is `firstString(body?.text, output.text, nestedOutput.text, output.summary, nestedOutput.summary, evidence.text, evidence.logTail, evidence.summary)`; the matched key is recorded by caller convention. `executor` payload stays on the response so callers can read `dispatch.exitCode` / `dispatch.message` / `dispatch.stdout` even when text is empty.
|
||||
|
||||
The `evidence.*` and `*summary` lookups exist so a dispatcher that already includes host `logTail` / `buildSummary` becomes visible without a separate `bootsharp` re-run on the Code Agent side. The `summary` lookups also keep error messages (`dispatch.message`) in the response even when `dispatchStatus` is not `completed`; this is the reason `body.error.message` always has something to show for failed host dispatches.
|
||||
|
||||
### Cloud-api vs host-side boundary
|
||||
|
||||
`/root/hwlab-v02/skills/device-pod-cli/assets/device-host-cli.mjs` is the v0.2-shipped copy of the host-side CLI. The actual hardware host runs a separate `F:\Work\ConStart\tools\device-host-cli.mjs` that is **not** a deployment of the v0.2 repo; it is a D601 ops-side copy that must be synced manually when the v0.2 repo changes host-side behavior. The two-step contract is:
|
||||
|
||||
- v0.2 cloud-api / executor changes are valid once `PipelineRun Succeeded` + `git mirror flush` complete; runtime revision is `commit.id` from `/health/live` and source commit can be forced to match via the next `trigger-current`.
|
||||
- v0.2 host-side `device-host-cli.mjs` changes are NOT visible until someone replaces `F:\Work\ConStart\tools\device-host-cli.mjs` on the D601 Windows host; cloud-api `body.text` will faithfully surface the "unsupported command" JSON error from the stale host binary, which proves the cloud-api propagation chain works but the host side is stale.
|
||||
|
||||
A live `workspace.evidence` / `debug.evidence` / `download evidence` selector that returns the host `logTail` end-to-end therefore requires both (a) the v0.2 PR merged and rolled, and (b) the D601 host binary replaced; missing either half is a known gap tracked in #779.
|
||||
|
||||
### v0.2 device-pod closeout checks
|
||||
|
||||
Device-pod fixes still follow `$dad-dev` and the `## v0.2 Worktree + PR Workflow` route above. The device-pod-specific closeout is the three-layer runtime matrix below; keep these checks because they prove the cloud-api -> executor -> D601 host chain, while generic PR/CI/CD and worktree mechanics stay in `$dad-dev`.
|
||||
|
||||
```bash
|
||||
trans G14:/root/hwlab-v02/.worktree/<task> script -- 'cd tools && bun test device-pod-cli.test.ts'
|
||||
trans G14:/root/hwlab-v02/.worktree/<task> script -- 'cd cmd/hwlab-device-pod && bun test main.test.ts'
|
||||
trans G14:/root/hwlab-v02/.worktree/<task> script -- 'cd internal/cloud && bun test access-control.test.ts'
|
||||
trans G14:/root/hwlab-v02/.worktree/<task> script -- 'node --check skills/device-pod-cli/assets/device-host-cli.mjs'
|
||||
```
|
||||
|
||||
Treat `access-control.test.ts` workbench failures as pre-existing on the v0.2 base unless the new test list explicitly covers them. After PR merge and `trigger-current --lane v02 --confirm`, the live `http://74.48.78.17:19667/` CLI 验收 must hit all three layers:
|
||||
|
||||
1. `body.output.text` non-empty for at least one happy-path intent (`workspace.ls` / `workspace.cat` are the cheapest ones to verify propagation without needing a real D601 build).
|
||||
2. `workspace.evidence kind=build` / `kind=download` accepted by cloud-api, dispatched to executor, executor `blocker === null` and `job.reason === ""`.
|
||||
3. `<mutating intent> action=status` accepted without `--reason` while the same intent with `action=start` is still rejected with `device_job_reason_required`.
|
||||
|
||||
There is no separate `device-pod` doc; this section is the single authoritative reference for the architecture, and the AGENTS.md index points to it.
|
||||
|
||||
@@ -60,6 +60,10 @@ Code Queue runner 也是分布式开发执行面。runner 镜像必须内置 `tr
|
||||
- 代理路径:只允许 `/api/` 前缀;允许方法为 `GET`、`HEAD`、`POST`、`DELETE`,用于保持 Todo Note 原有清单创建/删除、任务增删改、提醒、展开/收起、移动、撤销/重做等功能。
|
||||
- **写操作端点形态(2026-06-01 复盘 [#188](https://github.com/pikasTech/unidesk/issues/188) 固化)**:Todo Note 不走 REST 集合(如 `/api/instances/:id/todos`),所有 task 写都走 **action 队列**模式 `POST /api/instances/:id/actions` + body `{action: {type, ...}}`。已注册 action type:`addTodo` / `updateTodoTitle` / `toggleTodoCompleted` / `toggleTodoExpanded` / `setAllTodosExpanded` / `moveTodo` / `deleteTodo` / `renameInstance` / `setTodoReminder`。其他写端点:`POST /api/instances`(body `{name}` 新建清单)、`DELETE /api/instances/:id`、`POST /api/instances/:id/undo`、`POST /api/instances/:id/redo`。
|
||||
- **`TODO_NOTE_BACKEND_ONLY=1` 真实语义**:仅关闭 Todo Note 自带 Vite 前端 SPA,不阻挡任何已注册 API 路由(包括所有 POST/DELETE 写)。`/api/health` 暴露的 `backendOnly` 字段是观察用,不是读路径开关。看到 `404 {"error":"Todo Note is running in backend-only mode"}` 时的第一反应是路径写错(用了不存在的 REST 端点被 catch-all 兜底),不是写被 mode 锁。CLI 写操作范式见 `docs/reference/cli.md` 的 `microservice proxy` 段。
|
||||
|
||||
- **Catch-all 误导文案 + CLI 改写 / 预检(issue #198 interim)**:上游 `gitee.com/Lyon1998/todo_note` 的 catch-all 把所有未注册路径都回 `404 {"ok":false,"error":"Todo Note is running in backend-only mode"}`,秘书/agent 看到这条第一反应容易误判成 "写被 mode 锁了"。UniDesk CLI 侧在 `microservice proxy todo-note` 检测到该特征 body 时会做就地改写,输出结构化诊断(`writableApiEndpoints`、`actionTypes`、`backendOnly: true`、`method`、`path`、`issueReference`、`upstreamBody` 完整保留原始误导文案用于审计),并把 `bodyRewritten: true` 显式返回;上游 catch-all 修复 PR 合并后改写自动退化为 no-op,CLI 不再覆盖。
|
||||
- 同样在 CLI 侧提供 `--check-path` 预检:未命中时返回同源结构化诊断并**不调用 upstream**;命中时返回 `ok: true, matched: true, endpoint: {...}`。当前 `--check-path` 只支持 `service=todo-note`,对其他服务返回结构化 `unsupported` 错误。命令样例见 `docs/reference/cli.md` 的 `microservice proxy` 段。
|
||||
- 验证门禁走 `scripts/src/e2e.ts` 的 `microservice:todo-note-route-diagnostic`(覆盖错路径 rewrite、命中 / 未命中 check-path、非 todo-note 服务的 unsupported)。该门禁是 CLI 侧 interim 自检,不替代上游 catch-all 修复 PR;上游落地后由 `bun scripts/cli.ts server rebuild todo-note` 拉新 commit 镜像,并继续用 `microservice health todo-note` + `microservice proxy todo-note /api/instances/<id>/actions` 验证。
|
||||
- UniDesk 前端:`用户服务 / Todo Note` React 页面负责展示清单列表、树形任务、筛选、提醒、拖放/上移下移、撤销/重做、字号控制和显式原始 JSON 按钮。
|
||||
|
||||
Todo Note 在 UniDesk 语境中按纯后端服务管理:不得继续公开 Todo Note 自身 Vite/Web 前端,也不得把 `4211` 映射为公网端口。浏览器只能通过 UniDesk frontend 的 `/api/microservices/todo-note/...` 同源代理访问 Todo Note 后端。标准 artifact consumer 路径为 `bun scripts/cli.ts deploy apply --env dev|prod --service todo-note`;由于 Todo Note 源码仍在外部 Gitee 仓库,D601 registry 中必须先已有 `127.0.0.1:5000/unidesk/todo-note:<commit>`。Compose 在 recreate 时注入 `UNIDESK_TODO_NOTE_DEPLOY_*`,artifact consumer 的健康探针读取 `/api/health` 并合成 `deploy.commit` 和 `deploy.requestedCommit` 供强校验。
|
||||
|
||||
Reference in New Issue
Block a user