Merge remote-tracking branch 'origin/master' into feat/py-types-code-mode
This commit is contained in:
@@ -867,7 +867,7 @@ export interface StreamableHttpConfig {
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/mcp/mcp-client/src/index.ts:93`](../packages/mcp/mcp-client/src/index.ts)
|
||||
Source: [`packages/mcp/mcp-client/src/index.ts:96`](../packages/mcp/mcp-client/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-permission`
|
||||
|
||||
@@ -995,6 +995,22 @@ export interface Config {
|
||||
|
||||
Source: [`packages/guard/repeat-tool-guard/src/index.ts:28`](../packages/guard/repeat-tool-guard/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-repository-plugin`
|
||||
|
||||
Requires: `loader`
|
||||
|
||||
```ts config-catalog
|
||||
/** Repository Plugin runtime and source-list configuration. */
|
||||
export interface Config {
|
||||
/** GitHub repository sources with explicit refs and optional `.dsh-plugin` subpaths. */
|
||||
repositories?: string[]
|
||||
/** Persistent generation cache; defaults to `$DSH_HOME/cache/repository-plugins`. */
|
||||
cacheDir?: string
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/cordis/repository-plugin/src/index.ts:42`](../packages/cordis/repository-plugin/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-sandbox-local`
|
||||
|
||||
```ts config-catalog
|
||||
@@ -1312,6 +1328,10 @@ Requires: `skills`
|
||||
```ts config-catalog
|
||||
/** Local filesystem skill provider configuration. */
|
||||
export interface Config {
|
||||
/** Unique provider name. Defaults to `local`. */
|
||||
providerName?: string
|
||||
/** Whether project and user roots are included around custom roots. */
|
||||
includeDefaultRoots?: boolean
|
||||
/** DeepSeek Harness config root. Defaults to `$DSH_HOME` or `~/.dsh`. */
|
||||
dshHome?: string
|
||||
/** Shared agent config root. Defaults to `$DSH_AGENTS_HOME` or `~/.agents`. */
|
||||
@@ -1330,7 +1350,7 @@ export interface Config {
|
||||
watchMaxProjects?: number
|
||||
/** Whether watched symbolic links follow their target files. */
|
||||
watchFollowSymlinks?: boolean
|
||||
/** Bundled skill root; defaults to `$DSH_BUNDLED_SKILL_DIR`, otherwise mounts none. */
|
||||
/** Bundled skill root; defaults to `$DSH_BUNDLED_SKILL_DIR` when default roots are included, otherwise mounts none. */
|
||||
bundledSkillDir?: string
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
responding-to-pr-review-on-a-stack.md: 3fb7eb943eeb8d703303be3f6a844870cc26fd47
|
||||
responding-to-pr-review-on-a-stack.zh.md: d96323b853c093265931904c20996df335f82926
|
||||
# pnpm run verify-translation-pairing --write docs/cookbook/responding-to-pr-review-on-a-stack.md
|
||||
responding-to-pr-review-on-a-stack.md: 6bb7be3daf8613666f02f21e96bf7b1ac8cb3606
|
||||
responding-to-pr-review-on-a-stack.zh.md: 94c2bd2cdd03b9eeca1e180b09bb71471db91c0a
|
||||
|
||||
@@ -2,25 +2,31 @@
|
||||
|
||||
English | [中文](responding-to-pr-review-on-a-stack.zh.md)
|
||||
|
||||
Review comments may target several PRs in a dependent stack (`A ← B ← C …`). This guide explains how to resolve them without corrupting the stack. The two invariants it rests on are standing orders in the root [AGENTS.md](../../AGENTS.md) § Conventions: merge commits only, and never rewrite a pushed branch.
|
||||
Review comments may target several PRs in a dependent stack (`A ← B ← C …`). Keep that chain linked through GitHub's official stacked-PR feature. This guide owns review-fix placement and propagation; the [dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill owns linkage checks and landing.
|
||||
|
||||
## Ground rules
|
||||
|
||||
1. **One worktree per PR branch.** Each PR's fixes happen in that PR's own worktree; parallel fixes never share a checkout.
|
||||
2. **Bring a child up to date by merging the parent down** (`git merge <parent-branch>` into the child, a new merge commit). Never rebase/amend/force-push a pushed branch: rewriting diverges it from what the parent PR and GitHub recorded, breaks the stacked-merge graph, and erases the review-fix history.
|
||||
3. **A fix lands on the PR that INTRODUCED the issue, then flows down.** When a comment on PR `B` points at code `B` introduced, fix it on `B` and merge `B` into `C` — even if `C` also carries the file. Originating the fix downstream leaves `B` shipping the unfixed code and hides the fix from `B`'s reviewer.
|
||||
4. **Each review fix is a separate commit, never an amend.** The "fix review findings" commit documents what the review caught. Amending is fine only for your own not-yet-pushed, not-yet-reviewed work.
|
||||
2. **GitHub's stack object is authoritative.** Base branches establish the expected dependency order, while `PullRequest.stack` and `stackEntry.position` prove that GitHub recognizes it. Do not treat a matching branch chain as an official stack without checking those fields.
|
||||
3. **A fix lands on the PR that INTRODUCED the issue, then flows up-stack.** When a comment on PR `B` points at code `B` introduced, fix it on `B` and propagate `B` into `C` — even if `C` also carries the file. Originating the fix downstream leaves `B` shipping the unfixed code and hides the fix from `B`'s reviewer.
|
||||
4. **Each review fix remains a distinct commit.** A later rebase may change its OID, but do not amend a reviewed fix out of the branch history. Amend only your own not-yet-pushed, not-yet-reviewed work.
|
||||
5. **Choose merge-forward or rebase deliberately.** Both histories are allowed after review. A rewritten push must be lease-protected and must abort rather than overwrite a concurrently advanced remote head; raw `--force` is forbidden.
|
||||
|
||||
## Resolve comments through the stack
|
||||
|
||||
1. Triage every comment on the merits before acting: verify the claim against the code — a reviewer flagging the right symptom can still mis-diagnose the cause.
|
||||
2. Map each accepted finding to its originating PR, fix it there, then merge down the chain in order.
|
||||
3. Delegated fixes are trust-but-verify: a sub-agent's report describes intent, not necessarily what landed. Re-run the gates yourself on the actual tree, and for a regression guard, prove it FAILS on the unfixed code (introduce the regression, watch red, revert) — a guard that passes both ways guards nothing. A sub-agent that reframes a problem as already-handled is a signal to dig in personally.
|
||||
4. Reply in the review thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level comment, stating the fix and the commit that carries it.
|
||||
5. Before merging the stack, check dependents: deleting a PR's base branch auto-closes the dependent PR — check each branch with `gh pr list --state open --base <branch> --json number --jq length` (non-zero = open dependents), and merge without `--delete-branch` where a child still bases on the branch. The full landing procedure is the [dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill.
|
||||
1. Triage every comment on the merits before acting: verify the claim against the code — a reviewer flagging the right symptom can still misdiagnose the cause.
|
||||
2. Map each accepted finding to its originating PR and fix it there.
|
||||
3. Propagate the fixed layer through every affected child in order:
|
||||
- **Merge-forward:** merge the fixed parent branch into its child, validate the child, and continue upward. Preserve each in-progress checkpoint under the [incremental-retargeting decision](../../.agents/notes/implemented/process/2026-07-26-incremental-pr-base-retargeting.md).
|
||||
- **Native cascading rebase:** use `gh stack rebase`, validate the rewritten layers, then publish with `gh stack push`; or use `gh stack sync`, which may publish first and therefore requires immediate post-sync validation under [dsh-pre-push-checks](../../.agents/skills/dsh-pre-push-checks/SKILL.md).
|
||||
4. Treat delegated fixes as trust-but-verify: a sub-agent's report describes intent, not necessarily what landed. Re-run the gates yourself on the actual tree, and for a regression guard, prove it FAILS on the unfixed code (introduce the regression, watch red, revert) — a guard that passes both ways guards nothing. A sub-agent that reframes a problem as already handled is a signal to dig in personally.
|
||||
5. Reply in the review thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level comment, stating the fix and the current commit or head that carries it.
|
||||
6. After any rewritten push, re-read unresolved threads, approvals, mergeability, and checks. A force-pushed commit OID or outdated inline anchor is not current evidence that the finding remains resolved.
|
||||
7. Land only through the official stack procedure. If the PRs are not yet linked, the landing skill automatically links a same-author chain, asks before linking mixed authors, and hard-stops when native stack support is unavailable.
|
||||
|
||||
## Verify
|
||||
|
||||
- Every fixed PR shows a new commit (no force-push icon in the PR timeline).
|
||||
- Each child PR's diff against its parent still shows only its own changes.
|
||||
- The gates pass on every PR in the stack, not just the top.
|
||||
- Every fixed PR's current diff contains the intended correction at the layer that introduced the issue.
|
||||
- GraphQL reports one official stack in the expected order, and each child diff against its parent shows only that child's changes.
|
||||
- Unresolved threads, approvals, mergeability, and checks were re-audited after every rewritten push.
|
||||
- The relevant gates pass on every affected PR in the stack, not just the top.
|
||||
|
||||
@@ -2,25 +2,31 @@
|
||||
|
||||
[English](responding-to-pr-review-on-a-stack.md) | 中文
|
||||
|
||||
评审意见可能同时针对一条依赖堆叠(`A ← B ← C …`)中的多个 PR(Pull Request)。本指南说明如何在不破坏堆叠的前提下解决这些意见。它依赖的两个不变式是根 [AGENTS.md](../../AGENTS.md) § Conventions 中的常设指令:只用 merge commit,以及永远不改写已推送的分支。
|
||||
评审意见可能同时针对一条依赖堆叠(`A ← B ← C …`)中的多个 PR(Pull Request)。请通过 GitHub 官方的堆叠 PR 功能保持这条链的关联。本指南负责评审修复的归属与传播;[dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill(技能)负责检查关联关系和落地。
|
||||
|
||||
## 基本规则
|
||||
|
||||
1. **每个 PR 分支一个 worktree。** 每个 PR 的修复在该 PR 自己的 worktree 中进行;并行修复绝不共享同一个 checkout。
|
||||
2. **通过将父分支向下合并来更新子分支**(在子分支中执行 `git merge <parent-branch>`,产生一个新的 merge commit)。绝不对已推送的分支做 rebase/amend/force-push:改写会使分支与父 PR 及 GitHub 记录的内容产生分歧,破坏堆叠合并图,并抹去评审修复历史。
|
||||
3. **修复落在引入问题的那个 PR 上,然后向下流动。** 当 PR `B` 上的评论指向 `B` 引入的代码时,在 `B` 上修复,再将 `B` 合并到 `C`——即使 `C` 也包含该文件。把修复发起在下游会导致 `B` 带着未修复的代码交付,并对 `B` 的评审者隐藏修复。
|
||||
4. **每个评审修复是一个独立 commit,绝不 amend。** "修复评审发现"的 commit 记录了评审捕获的内容。只有你自己尚未推送、尚未评审的工作才可以 amend。
|
||||
2. **GitHub 的 stack 对象是权威依据。** base 分支确定预期的依赖顺序,`PullRequest.stack` 和 `stackEntry.position` 则证明 GitHub 已识别该堆叠。未经检查这些字段,不得仅凭分支链吻合就将其视为官方堆叠。
|
||||
3. **修复落在引入问题的那个 PR 上,然后沿堆叠向上流动。** 当 PR `B` 上的评论指向 `B` 引入的代码时,在 `B` 上修复,再将 `B` 的变更传播到 `C`,即使 `C` 也包含该文件。把修复发起在下游会导致 `B` 带着未修复的代码交付,并对 `B` 的评审者隐藏修复。
|
||||
4. **每项评审修复都保留为独立 commit。** 后续 rebase 可能改变其 OID,但不得通过 amend 把已经评审的修复从分支历史中抹去。只有你自己尚未推送且尚未评审的工作才可以 amend。
|
||||
5. **明确选择 merge-forward 或 rebase。** 评审后允许采用这两种历史更新方式。改写历史的推送必须受 lease 保护;如果远端 head 在此期间前移,操作必须中止,不得将其覆盖。禁止直接使用 `--force`。
|
||||
|
||||
## 沿堆叠解决评审意见
|
||||
|
||||
1. 在行动之前先就事论事地审视每条评论:对照代码验证其论断——评审者指出了正确的症状,但仍可能误诊原因。
|
||||
2. 将每个被接受的发现映射到其发起 PR,在那里修复,然后按顺序沿链向下合并。
|
||||
3. 委派的修复需要信任但验证:子 agent(智能体)的报告描述的是意图,不一定是实际落地的内容。请亲自在实际代码树上重新运行门禁;对于回归守卫,要证明它在未修复的代码上**失败**(引入回归、观察变红、再还原)——两种情况都通过的守卫什么也守不住。子 agent 将问题重新定性为「已处理」时,这是一个需要亲自深入的信号。
|
||||
4. 在评审线程中回复(`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`),而非发顶层评论;说明修复内容及承载修复的 commit。
|
||||
5. 合并堆叠之前,检查依赖方:删除一个 PR 的 base 分支会自动关闭依赖它的 PR。用 `gh pr list --state open --base <branch> --json number --jq length` 检查每个分支(非零 = 有打开的依赖方),当子 PR 仍以该分支为 base 时,合并时不带 `--delete-branch`。完整的落地流程见 [dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill(技能)。
|
||||
2. 将每个被接受的发现映射到其发起 PR,并在那里修复。
|
||||
3. 将修复后的层按顺序传播到每个受影响的子 PR:
|
||||
- **Merge-forward:** 将修复后的父分支合并到其子分支,验证子分支,然后继续沿堆叠向上传播。依照[增量更新 base 的决策](../../.agents/notes/implemented/process/2026-07-26-incremental-pr-base-retargeting.md),保留每个正在处理的检查点。
|
||||
- **原生级联 rebase:** 使用 `gh stack rebase`,验证所有已改写的层,然后通过 `gh stack push` 发布;也可以使用 `gh stack sync`,该命令可能先发布,因此必须按照 [dsh-pre-push-checks](../../.agents/skills/dsh-pre-push-checks/SKILL.md) 在同步后立即验证。
|
||||
4. 委派的修复需要信任但验证:子 agent(智能体)的报告描述的是意图,不一定是实际落地的内容。请亲自在实际代码树上重新运行门禁;对于回归守卫,要证明它在未修复的代码上**失败**(引入回归、观察变红、再还原)——两种情况都通过的守卫什么也守不住。子 agent 将问题重新定性为「已处理」时,这是一个需要亲自深入的信号。
|
||||
5. 在评审线程中回复(`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`),而非发顶层评论;说明修复内容及当前承载修复的 commit 或 head。
|
||||
6. 每次改写推送后,都要重新读取未解决线程、批准状态、可合并性和检查结果。经 force-push 改写的 commit OID 或已过时的内联锚点,都不足以证明该发现当前仍处于已解决状态。
|
||||
7. 仅可通过官方堆叠流程落地。如果这些 PR 尚未关联,落地 skill 会自动关联作者相同的链;如果作者不同,则先询问用户;如果原生堆叠支持不可用,则硬性停止流程。
|
||||
|
||||
## 验证
|
||||
|
||||
- 每个已修复的 PR 显示一个新 commit(PR 时间线中没有 force-push 图标)。
|
||||
- 每个子 PR 相对其父 PR 的 diff 仍然只包含自身的变更。
|
||||
- 门禁在堆叠中的每个 PR 上都通过,而不仅仅是顶部。
|
||||
- 每个已修复 PR 的当前 diff 都在引入问题的那一层包含预期修正。
|
||||
- GraphQL 报告的官方堆叠只有一个且顺序符合预期;每个子 PR 相对于父 PR 的 diff 只显示该子 PR 自身的变更。
|
||||
- 每次改写推送后,均重新审计了未解决线程、批准状态、可合并性和检查结果。
|
||||
- 相关门禁在堆叠中的每个受影响 PR 上都通过,而不仅仅是顶部。
|
||||
|
||||
@@ -256,8 +256,8 @@ Dispose and immediately reload this plugin with its current config.
|
||||
*
|
||||
* @param config — the new raw config; validated before anything restarts.
|
||||
* @param noSave — hint for persistence hooks not to write the change back.
|
||||
* @returns nothing; the restart runs behind the `internal/update` waterfall.
|
||||
* @throws {ValidationError} when the new config fails validation.
|
||||
* @returns the update waterfall result; the default restart returns a promise.
|
||||
* @throws when validation, an update listener, or the restarted plugin fails.
|
||||
*/
|
||||
update(config: any, noSave = false)
|
||||
```
|
||||
@@ -269,7 +269,7 @@ Runs the `internal/update` waterfall first, so update hooks (and HMR) can veto o
|
||||
- `config` — the new raw config; validated before anything restarts.
|
||||
- `noSave` — hint for persistence hooks not to write the change back.
|
||||
|
||||
**Returns** nothing; the restart runs behind the `internal/update` waterfall.
|
||||
**Returns** the update waterfall result; the default restart returns a promise.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L734)
|
||||
|
||||
|
||||
@@ -1183,7 +1183,8 @@ The framework events every plugin also sees, beyond the harness vocabulary above
|
||||
- `internal/listener` — A listener was registered. ([`vendor/cordis/src/events.ts:340`](../../vendor/cordis/src/events.ts))
|
||||
- `internal/dispatch` — An event is being dispatched to listeners. ([`vendor/cordis/src/events.ts:342`](../../vendor/cordis/src/events.ts))
|
||||
- `hmr/change` — A watched source file changed on disk. ([`vendor/hmr/src/index.ts:20`](../../vendor/hmr/src/index.ts))
|
||||
- `hmr/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:21`](../../vendor/hmr/src/index.ts))
|
||||
- `hmr/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:22`](../../vendor/hmr/src/index.ts))
|
||||
- `hmr/config-update-failed` — A watched config-file refresh failed. ([`vendor/hmr/src/index.ts:29`](../../vendor/hmr/src/index.ts))
|
||||
- `exit` — The process is exiting on a signal. ([`vendor/loader/src/index.ts:23`](../../vendor/loader/src/index.ts))
|
||||
- `loader/config-update` — The loader config tree changed. ([`vendor/loader/src/index.ts:24`](../../vendor/loader/src/index.ts))
|
||||
- `loader/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts))
|
||||
|
||||
@@ -94,6 +94,7 @@ flowchart TD
|
||||
pkg_plan_mode["plan-mode"]
|
||||
end
|
||||
subgraph group_cordis["packages/cordis"]
|
||||
pkg_repository_plugin["repository-plugin"]
|
||||
pkg_tool_cordis["tool-cordis"]
|
||||
end
|
||||
subgraph group_hooks["packages/hooks"]
|
||||
@@ -904,6 +905,10 @@ flowchart TD
|
||||
pkg_tool_subagent --> pkg_subagent
|
||||
pkg_tool_subagent --> pkg_tasks
|
||||
pkg_tool_subagent --> pkg_tools
|
||||
pkg_repository_plugin --> pkg_invariants
|
||||
pkg_repository_plugin --> pkg_mcp_client
|
||||
pkg_repository_plugin --> pkg_paths
|
||||
pkg_repository_plugin --> pkg_skill_local
|
||||
pkg_hooks_claude --> pkg_agent
|
||||
pkg_hooks_claude --> pkg_hook_protocol
|
||||
pkg_hooks_claude --> pkg_invariants
|
||||
@@ -1208,6 +1213,7 @@ flowchart TD
|
||||
| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) |
|
||||
| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`repository-plugin`](../packages/cordis/repository-plugin) | `cordis` | [`invariants`](../packages/support/invariants), [`mcp-client`](../packages/mcp/mcp-client), [`paths`](../packages/util/paths), [`skill-local`](../packages/skill/skill-local) |
|
||||
| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
| [`tui`](../packages/ui/tui) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`commands`](../packages/ui/commands), [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-reference`](../packages/context/session-reference), [`session-title`](../packages/session-title/session-title), [`skill`](../packages/skill/skill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`client-ui-model`](../packages/client/ui-model) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-command`](../packages/client/ui-command), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/postmortem/0003-web-agent-gui-feedback-loop.md
|
||||
0003-web-agent-gui-feedback-loop.md: 13d13a607babfe7f5ddfdb6773c94f973bbef0db
|
||||
0003-web-agent-gui-feedback-loop.zh.md: f80530605ef9274638c6848bf5a256f76057915c
|
||||
53
docs/postmortem/0003-web-agent-gui-feedback-loop.md
Normal file
53
docs/postmortem/0003-web-agent-gui-feedback-loop.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Post-mortem 0003: Web agent validated a replacement server instead of its current GUI
|
||||
|
||||
English | [中文](0003-web-agent-gui-feedback-loop.zh.md)
|
||||
|
||||
Status: resolved
|
||||
|
||||
## Executive summary
|
||||
|
||||
A Web agent changed the GUI source but did not know which URL and process hosted its session. It delegated acceptance to the user, then treated a bare Vite HTTP 200 as success despite a missing `window.__DSH_BOOT__` white screen, and finally validated a replacement `dsh web` server on another port while the original page had already picked up rebuilt artifacts. The fix makes the current URL and runtime mode model-visible and shell-queryable, rejects standalone Vite before listen, and verifies production refresh and development HMR against external state.
|
||||
|
||||
## Summary
|
||||
|
||||
The session ran inside the DeepSeek Harness Web GUI at port 3081 while its selected Workspace was an empty `test/` directory. The model request named neither the GUI nor its source checkout, URL, process, or update mode. Repository affordances exposed `apps/web` with a Vite development script, while the full browser composition lived behind `dsh web`.
|
||||
|
||||
The resulting actions were individually plausible but did not share one acceptance target. A source edit, a successful build, an HTTP 200, an injected boot manifest, and the user's existing page were treated as interchangeable facts.
|
||||
|
||||
The evidence source is the persisted event log for `session-3eb796c2-5159-4686-affe-df8719f6f987`, whose header records cwd `/Users/tn.shen/Documents/deepseek-harness-gui-master/test`. Its initial request header is sequence 6; the user-facing handoff, bare-Vite launch, replacement-host launch, boot-manifest probe, and first 3081 process probe are sequences 30939, 31865, 34309, 34441, and 34681 respectively. The timeline below follows those events rather than reconstructing intent from the later report.
|
||||
|
||||
## Impact
|
||||
|
||||
The user had to identify three consecutive mistakes: acceptance was delegated back to them; the proposed preview was a blank page; and the reported successful URL was not the page they were using. An unmanaged replacement server also outlived the turn until the user challenged it.
|
||||
|
||||
No change in this investigation restarted or modified the read-only 3081 and 3082 trial services.
|
||||
|
||||
## Timeline
|
||||
|
||||
- In turn 2, after editing the theme, the agent's sequence-30939 message told the user to run `pnpm run demo:tui` or open an unspecified Web application. It ran no assembled Web acceptance.
|
||||
- In turn 3, the agent read `apps/web/package.json`, launched bare Vite on port 5173 at sequence 31865, observed HTTP 200, and declared success. The browser instead threw `client-modules: window.__DSH_BOOT__ is missing or not an object` and rendered a white page.
|
||||
- In turn 4, the agent found the full `dsh web` path, rebuilt the shell, launched an unmanaged process on port 3334 at sequence 34309, and checked only that this replacement returned 200 with a boot manifest at sequence 34441. It never probed port 3081.
|
||||
- In turn 5, the user reported at sequence 34556 that 3081 already showed the new theme. Only then, at sequence 34681, did the agent inspect the existing process and remove the redundant server.
|
||||
|
||||
## Root cause
|
||||
|
||||
The Web assembly had no model-visible identity for the current GUI, canonical URL, or runtime mode. The session cwd correctly represented the user's selected Workspace, but the model mistook that project boundary for the application boundary. No durable contract related the GUI source checkout, built artifacts, serving process, target origin, and browser acceptance.
|
||||
|
||||
The wrong startup path looked legitimate because bare Vite returned HTTP 200. `window.__DSH_BOOT__` is injected only by the full host, so transport readiness did not imply application readiness. The first regression test repeated this mistake in another form: a timeout killed Vite and satisfied a nonzero-exit assertion. Live reproduction exposed that false positive.
|
||||
|
||||
Background process semantics were also bypassed with shell `&`, so task identity, completion notices, collection, and cleanup did not apply. Verifying port 3334 therefore proved only that a second service worked.
|
||||
|
||||
## Guardrails added
|
||||
|
||||
- The Web launcher publishes the canonical loopback URL and actual production/development mode in the logged `app:web-surface` prompt section and managed `$DSH_WEB_URL`/`$DSH_WEB_MODE` environment.
|
||||
- Production guidance requires rebuilding artifacts and verifying the existing URL after refresh. Development guidance explains that `dsh web --dev` mounts only the HMR receiver; `pnpm run dev:web` in the same checkout must also rebuild client-plugin bundles, while shell and plain-package changes still require refresh.
|
||||
- `apps/web` standalone Vite serve mode rejects during configuration. Its subprocess test proves natural exit and instruments `Server.listen()` so a transient bind cannot pass unnoticed.
|
||||
- Layered real-path tests cover the CLI request, exact production/development prompts, shell runtime facts, same-port static replacement, source watcher rebuild, host stat polling, and browser HMR under an unchanged page identity.
|
||||
- PR evidence preserves screenshots from the original 3081 session and a real-model before/after GUI run; external browser, HTTP, process, and session-log observations carry acceptance.
|
||||
|
||||
## Lessons
|
||||
|
||||
- The agent must know hidden runtime prerequisites before it can guide the user; startup mode is application context, not tribal knowledge.
|
||||
- HTTP readiness, build success, and a boot manifest are different facts. Acceptance names the exact origin and externally observes the requested change there.
|
||||
- A replacement service cannot prove that an existing page changed. Long-running processes use managed task lifecycles when they are actually requested.
|
||||
- A regression test must be able to fail for the reported mechanism. Process timeout is not equivalent to fail-fast, and post-exit port availability does not prove the port was never bound.
|
||||
53
docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md
Normal file
53
docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 事故复盘(postmortem) 0003:Web agent(智能体)验收了替代服务器,而非其当前 GUI
|
||||
|
||||
[English](0003-web-agent-gui-feedback-loop.md) | 中文
|
||||
|
||||
Status: resolved
|
||||
|
||||
## 摘要
|
||||
|
||||
Web agent 修改了 GUI 源码,却不知道由哪个 URL 和进程承载当前会话。它把验收交还给用户,随后在 `window.__DSH_BOOT__` 缺失导致白屏的情况下,仍把裸 Vite 返回的 HTTP 200 当作成功;最后,原页面其实已经加载了重建产物,它却去验收另一个端口上的替代 `dsh web` 服务器。修复让当前 URL 和运行模式对模型可见且可由 shell 查询,在独立 Vite 开始监听前拒绝启动,并依据外部状态验收生产模式刷新与开发模式 HMR(热模块替换)。
|
||||
|
||||
## 概述
|
||||
|
||||
该会话运行在端口 3081 的 DeepSeek Harness Web GUI 中,而用户选择的 Workspace 是空的 `test/` 目录。模型请求既未指明该 GUI,也未提供它的源码检出目录、URL、进程或更新模式。仓库在 `apps/web` 中提供了 Vite 开发脚本,完整的浏览器组合则由 `dsh web` 提供。
|
||||
|
||||
由此产生的各个动作单看都合理,却没有指向同一个验收目标。源码修改、成功构建、HTTP 200、注入的启动 manifest(元数据清单)和用户原本打开的页面,被当成了可以相互替代的事实。
|
||||
|
||||
证据源是 `session-3eb796c2-5159-4686-affe-df8719f6f987` 的持久化事件日志,其头部记录的 cwd 为 `/Users/tn.shen/Documents/deepseek-harness-gui-master/test`。初始请求头位于序列 6;面向用户的交接、裸 Vite 启动、替代宿主启动、启动 manifest 探测,以及首次探测 3081 进程,分别位于序列 30939、31865、34309、34441 和 34681。下方时间线以这些事件为依据,而不是根据后续报告反推意图。
|
||||
|
||||
## 影响
|
||||
|
||||
用户不得不连续指出三个错误:agent 把验收交还给用户;建议预览的页面一片空白;报告成功的 URL 并不是用户正在使用的页面。一个不受管理的替代服务器还持续运行到下一轮,直到用户提出质疑。
|
||||
|
||||
本次调查没有重启或修改只读的 3081 和 3082 试验服务。
|
||||
|
||||
## 时间线
|
||||
|
||||
- 在第 2 轮中,agent 修改主题后,在序列 30939 的消息中让用户运行 `pnpm run demo:tui` 或打开一个未明确指定的 Web 应用。它没有对组装后的 Web 应用执行任何验收。
|
||||
- 在第 3 轮中,agent 读取 `apps/web/package.json`,在序列 31865 于端口 5173 上启动裸 Vite,观察到 HTTP 200 后便宣布成功。浏览器却抛出 `client-modules: window.__DSH_BOOT__ is missing or not an object`,并显示白屏。
|
||||
- 在第 4 轮中,agent 找到了完整的 `dsh web` 启动路径,重新构建 shell,在序列 34309 于端口 3334 上启动一个不受管理的进程,并且只在序列 34441 检查了这个替代服务是否返回 200 和启动 manifest。它从未探测端口 3081。
|
||||
- 在第 5 轮中,用户在序列 34556 报告 3081 已经显示新主题。直到序列 34681,agent 才检查既有进程并移除冗余服务器。
|
||||
|
||||
## 根因
|
||||
|
||||
Web 组合没有向模型提供当前 GUI、规范 URL 或运行模式的身份信息。会话 cwd 正确表示了用户选择的 Workspace,但模型误把这个项目边界当成了应用边界。系统也没有持久契约将 GUI 源码检出目录、构建产物、服务进程、目标 origin 和浏览器验收关联起来。
|
||||
|
||||
裸 Vite 返回 HTTP 200,使错误的启动路径看似合理。`window.__DSH_BOOT__` 只由完整宿主注入,因此传输层就绪不代表应用已就绪。首个回归测试以另一种方式重复了同样的错误:超时机制终止 Vite 后,非零退出断言仍会通过。真实复现暴露了这一误报。
|
||||
|
||||
agent 还通过 shell `&` 绕过了后台进程语义,因此任务身份、完成通知、结果收集和清理机制均未生效。验证端口 3334 只能证明第二个服务可以工作。
|
||||
|
||||
## 已添加的防护措施
|
||||
|
||||
- Web 启动器在记录到日志的 `app:web-surface` 提示词区段,以及受管的 `$DSH_WEB_URL`/`$DSH_WEB_MODE` 环境变量中,发布规范环回 URL 和实际的生产/开发模式。
|
||||
- 生产模式指南要求重新构建产物,并在刷新后验证既有 URL。开发模式指南说明,`dsh web --dev` 只挂载 HMR 接收端;同一源码检出目录中的 `pnpm run dev:web` 还必须重新构建客户端插件 bundle,而 Web shell 和普通包的改动仍然需要刷新页面。
|
||||
- `apps/web` 的独立 Vite 服务模式会在配置阶段拒绝启动。其子进程测试验证进程自然退出,并插桩 `Server.listen()`,确保短暂绑定端口也不会漏检。
|
||||
- 分层的真实路径测试覆盖 CLI(命令行界面)请求、精确的生产/开发模式提示词、shell 运行时事实、同端口静态产物替换、源码 watcher 重建、宿主 stat 轮询,以及页面 identity 不变的浏览器 HMR。
|
||||
- PR(Pull Request)证据保留了原始 3081 会话的截图,以及真实模型驱动的 GUI 修改前后对比;验收以外部浏览器、HTTP、进程和会话日志的观测结果为准。
|
||||
|
||||
## 教训
|
||||
|
||||
- agent 必须先知道隐藏的运行时前置条件,才能指导用户;启动模式属于应用上下文,不应依赖团队口口相传。
|
||||
- HTTP 就绪、构建成功和启动 manifest 是不同的事实。验收必须明确指定确切的 origin,并从外部观察所请求的改动是否在该 origin 生效。
|
||||
- 替代服务无法证明既有页面已经改变。确实收到启动长时间运行进程的请求时,应使用受管任务生命周期。
|
||||
- 回归测试必须能够针对所报告的机制失败。进程超时不等同于快速失败,进程退出后端口可用也不能证明该端口从未被绑定。
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/postmortem/README.md
|
||||
README.md: df0e2fcb8540aeed005153dbecc451d781ca5ff1
|
||||
README.zh.md: e364ef30e342f8484a40f35e3970dea0f2f86ef3
|
||||
README.md: 4858f8841e92a895f2d1a840b59b42758e83d952
|
||||
README.zh.md: 127eb19422f1eb6738c1d246791064096f4382f9
|
||||
|
||||
@@ -14,3 +14,4 @@ Every post-mortem opens with an **Executive summary**: one short paragraph a bus
|
||||
|---|---|
|
||||
| [0001](0001-acp-default-export-drops-inject.md) | ACP server crashed on connect: `export default` dropped the plugin's `inject` |
|
||||
| [0002](0002-js-expression-disabled-filesystem-tools.md) | Filesystem snapshot tools were permanently disabled by a literal `!!js` object |
|
||||
| [0003](0003-web-agent-gui-feedback-loop.md) | Web agent validated a replacement server instead of the GUI hosting its session |
|
||||
|
||||
@@ -14,3 +14,4 @@
|
||||
|---|---|
|
||||
| [0001](0001-acp-default-export-drops-inject.md) | ACP(Agent Client Protocol)服务器在连接时崩溃:`export default` 丢失了插件的 `inject` |
|
||||
| [0002](0002-js-expression-disabled-filesystem-tools.md) | 文件系统快照工具被一个字面量 `!!js` 对象永久禁用 |
|
||||
| [0003](0003-web-agent-gui-feedback-loop.md) | Web agent 验证了替代服务器,而非承载其会话的 GUI |
|
||||
|
||||
Reference in New Issue
Block a user