fix(feedback): address review — accurate release wording, current-state notes, default-mode snapshot lane

This commit is contained in:
creatixchu
2026-08-26 10:05:31 +08:00
parent 106e5ce0bc
commit ac4a2f9792
24 changed files with 239 additions and 41 deletions
@@ -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 .agents/notes/implemented/feature/2026-07-31-web-telemetry-default-mount.md
2026-07-31-web-telemetry-default-mount.md: a492356eccba9f272ee216777eb518750c7b6b62
2026-07-31-web-telemetry-default-mount.zh.md: 3d852229c069ae32f328c8dae38cfdf1744c7293
2026-07-31-web-telemetry-default-mount.md: aea90ef928afaa7669df8046fa98f16c575618f0
2026-07-31-web-telemetry-default-mount.zh.md: 122b7ad593c18cd547ade835827e8903d34da441
@@ -10,12 +10,12 @@ The telemetry seam and OTel backend ([revival Note](2026-07-23-session-telemetry
## Decision
The shared dsh base bundle (`packages/bundle/base/cordis.patch.yml`) mounts the `session-telemetry-otel` row with a baked-in production endpoint, so every base-backed profile has one consistent telemetry capability. The standalone [`sdk-minimal` profile](../architecture/2026-08-24-standalone-sdk-minimal-profile.md) deliberately omits that row. The [default-off decision](2026-08-10-telemetry-default-off.md) keeps the mounted row in `DISABLED` mode unless a deployment explicitly selects `FULL` or `FEEDBACK_ONLY`; the endpoint alone does not authorize reporting. Web and headless use the [bounded, escalating process-shutdown controller](../bug-fix/2026-08-03-cli-signal-shutdown-escalation.md) on SIGINT/SIGTERM, giving an enabled backend's three-second shutdown deadline time to drain before the five-second launcher bound.
The shared dsh base bundle (`packages/bundle/base/cordis.patch.yml`) mounts the `session-telemetry-otel` row with a baked-in production endpoint, so every base-backed profile has one consistent telemetry capability. The standalone [`sdk-minimal` profile](../architecture/2026-08-24-standalone-sdk-minimal-profile.md) deliberately omits that row. The [default-off decision](2026-08-10-telemetry-default-off.md) originally kept the mounted row in `DISABLED` mode; the [feedback-gated default](2026-08-25-feedback-gated-telemetry-default.md) now resolves an unset mode to `FEEDBACK_ONLY`, uploading only when the user records `/feedback`. The endpoint alone still does not authorize reporting. Web and headless use the [bounded, escalating process-shutdown controller](../bug-fix/2026-08-03-cli-signal-shutdown-escalation.md) on SIGINT/SIGTERM, giving an enabled backend's three-second shutdown deadline time to drain before the five-second launcher bound.
| Ruling | Value | Rationale |
|---|---|---|
| Mount surface | `packages/bundle/base/cordis.patch.yml` | One capability row for every profile that loads the shared base |
| Sharing mode | `DSH_TELEMETRY_MODE`, default `DISABLED`; explicit `FULL` or `FEEDBACK_ONLY` opts in | A fresh profile makes no telemetry network request, while internal deployments retain both upload policies |
| Sharing mode | `DSH_TELEMETRY_MODE`, default `FEEDBACK_ONLY` ([feedback-gated default](2026-08-25-feedback-gated-telemetry-default.md)); explicit `FULL` or `DISABLED` overrides | A fresh profile uploads only when the user records `/feedback`, while internal deployments retain both explicit policies |
| Endpoint | `DSH_TELEMETRY_OTLP_URL`, default `https://harness-telemetry.deepseeksvc.com/v1/logs` | Internal collector; the env override serves local/dev runs |
| Hard opt-out | any non-empty `DSH_TELEMETRY_DISABLED` (including `0`/`false`) disables the row | The launcher patch takes effect before load-time transport validation and overrides every configured mode |
| Cadence | `processor.scheduledDelayMillis: 10000` (10s/batch) in uploading modes | Streaming while the session runs, never exit-time-only; a crash loses at most the last unexported interval |
@@ -23,7 +23,7 @@ The shared dsh base bundle (`packages/bundle/base/cordis.patch.yml`) mounts the
| Compression | `compression: gzip` | Event bodies carry full content; cross-datacenter bandwidth |
| CI isolation | top-level `env: DSH_TELEMETRY_DISABLED: '1'` in GitHub workflows | Defense in depth keeps test sessions local even when a job explicitly selects an uploading mode |
The base bundle test pins the shipped `DISABLED` mode expression, the backend suite pins that omitted mode constructs no transport, and the real Loader composition suite explicitly selects each uploading mode when it verifies OTLP delivery.
The base bundle test pins the shipped `FEEDBACK_ONLY` mode expression, the backend suite pins that omitted mode constructs no transport, and the real Loader composition suite explicitly selects each uploading mode when it verifies OTLP delivery.
## Alternatives considered
@@ -35,6 +35,6 @@ The base bundle test pins the shipped `DISABLED` mode expression, the backend su
## Consequences
- A developer running `dsh web` without telemetry configuration makes no telemetry network request. An internal deployment sets `DSH_TELEMETRY_MODE` and may point `DSH_TELEMETRY_OTLP_URL` at another collector.
- A developer running `dsh web` without telemetry configuration makes no telemetry network request until they record `/feedback`. An internal deployment sets `DSH_TELEMETRY_MODE` and may point `DSH_TELEMETRY_OTLP_URL` at another collector.
- **No redaction rule is mounted**: explicitly enabled exports are the raw captured copy (full user/assistant message text, tool arguments and results, the system prompt, the local `session.cwd` path). Crossing a trust boundary requires `session-telemetry/record` rules first — the redaction rule, remaining identity Resource attributes, and usage metrics remain separate deployment work. The anonymous user id ships through the [anonymous-user-id Note](2026-07-31-telemetry-anonymous-user-id.md).
- Test rigs remain local by default; explicit uploading-mode tests provide their own collector and mode.
@@ -10,12 +10,12 @@ Status: implemented
## 决策
共享 dsh 基础组合包(`packages/bundle/base/cordis.patch.yml`)挂载带有内置生产 endpoint 的 `session-telemetry-otel` 配置行,使每个基于 base 的 profile 都具有一致的遥测能力。独立的 [`sdk-minimal` profile](../architecture/2026-08-24-standalone-sdk-minimal-profile.zh.md)刻意省略该配置项。[默认关闭决策](2026-08-10-telemetry-default-off.zh.md)让已挂载配置项保持 `DISABLED` 模式,除非部署方显式选择 `FULL``FEEDBACK_ONLY`仅配置 endpoint 不构成上报授权。Web 与 headless 在 SIGINT/SIGTERM 时使用[有界、可升级的进程关闭控制器](../bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md),在启动器 5 秒上限到期前,先给已启用的后端 3 秒关闭截止时间完成排空。
共享 dsh 基础组合包(`packages/bundle/base/cordis.patch.yml`)挂载带有内置生产 endpoint 的 `session-telemetry-otel` 配置行,使每个基于 base 的 profile 都具有一致的遥测能力。独立的 [`sdk-minimal` profile](../architecture/2026-08-24-standalone-sdk-minimal-profile.zh.md)刻意省略该配置项。[默认关闭决策](2026-08-10-telemetry-default-off.zh.md)最初让已挂载配置项保持 `DISABLED` 模式[反馈门控默认值决定](2026-08-25-feedback-gated-telemetry-default.zh.md)现在把未设置的模式解析为 `FEEDBACK_ONLY`,只在用户记录 `/feedback` 时上传。仅配置 endpoint 不构成上报授权。Web 与 headless 在 SIGINT/SIGTERM 时使用[有界、可升级的进程关闭控制器](../bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md),在启动器 5 秒上限到期前,先给已启用的后端 3 秒关闭截止时间完成排空。
| 决策项 | 取值 | 理由 |
|---|---|---|
| 挂载面 | `packages/bundle/base/cordis.patch.yml` | 每个加载共享基础组合包的 profile 都使用同一个能力配置行 |
| 共享模式 | `DSH_TELEMETRY_MODE`,默认 `DISABLED`;显式设置 `FULL``FEEDBACK_ONLY`启用 | 新 profile 不发出遥测网络请求,内部部署仍可使用两种上传策略 |
| 共享模式 | `DSH_TELEMETRY_MODE`,默认 `FEEDBACK_ONLY`[反馈门控默认值决定](2026-08-25-feedback-gated-telemetry-default.zh.md);显式设置 `FULL``DISABLED`覆盖 | 新 profile 只在用户记录 `/feedback` 时上传,内部部署仍可使用两种显式策略 |
| endpoint | `DSH_TELEMETRY_OTLP_URL`,缺省 `https://harness-telemetry.deepseeksvc.com/v1/logs` | 内部 collectorenv 覆盖供本地/联调 |
| 硬性退出 | `DSH_TELEMETRY_DISABLED` 非空(含 `0`/`false`)即禁用该配置行 | 启动器 patch 在加载期传输校验之前生效,并覆盖所有已配置模式 |
| 上报节奏 | 上传模式中为 `processor.scheduledDelayMillis: 10000`(10s/批) | 在会话运行期间流式上报,而非仅在退出时上报;崩溃至多丢失最后一个尚未导出间隔内的数据 |
@@ -24,7 +24,7 @@ Status: implemented
| CI 隔离 | GitHub 工作流顶层 `env: DSH_TELEMETRY_DISABLED: '1'` | 即使 CI 任务显式选择上传模式,纵深防御也会让测试会话留在本地 |
基础组合包测试固定交付的 `DISABLED` 模式表达式,后端测试套件固定省略模式时不构造传输,真实 Loader 组合测试则在验证 OTLP 投递时显式选择每种上传模式。
基础组合包测试固定交付的 `FEEDBACK_ONLY` 模式表达式,后端测试套件固定省略模式时不构造传输,真实 Loader 组合测试则在验证 OTLP 投递时显式选择每种上传模式。
## 考虑过的替代方案
@@ -36,6 +36,6 @@ Status: implemented
## 后果
- 开发者运行没有遥测配置的 `dsh web` 时,不会发出遥测网络请求。内部部署需设置 `DSH_TELEMETRY_MODE`,并可让 `DSH_TELEMETRY_OTLP_URL` 指向其他 collector。
- 开发者运行没有遥测配置的 `dsh web` 时,在记录 `/feedback` 之前不会发出遥测网络请求。内部部署需设置 `DSH_TELEMETRY_MODE`,并可让 `DSH_TELEMETRY_OTLP_URL` 指向其他 collector。
- **没有挂载任何脱敏规则**:显式启用的导出即原始捕获副本(用户/助手消息全文、工具参数与工具结果、系统提示词、`session.cwd` 本地路径)。跨信任边界前必须先挂载 `session-telemetry/record` 规则;脱敏规则、其余身份 Resource 属性和使用情况指标仍是独立的部署工作。匿名 user id 由[匿名 user id Note](2026-07-31-telemetry-anonymous-user-id.zh.md)交付。
- 测试载具默认将数据留在本地;显式启用上传模式的测试提供自己的 collector 和模式。
@@ -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 .agents/notes/implemented/feature/2026-08-10-telemetry-default-off.md
2026-08-10-telemetry-default-off.md: db55eda83628dd2908b312000457c75e0bc07c8f
2026-08-10-telemetry-default-off.zh.md: e444f4aad2782eb46c4b787f5fd14dd84b67003e
2026-08-10-telemetry-default-off.md: 1bc9719f7036509f1fff8c28f0607669505280ad
2026-08-10-telemetry-default-off.zh.md: 8f8bbb57810125059c177dc1c94f7631040a1d58
@@ -14,7 +14,7 @@ Both feeds use `DSH_TELEMETRY_MODE` as their positive consent setting. Unset and
The dsh-sdk launcher reads the same variable without parsing `cordis.yml` or booting Cordis. `FULL` permits reporting; `FEEDBACK_ONLY`, `DISABLED`, unset, and empty values deny it. Consent is frozen from the launching environment before the command runs, because `dsh-sdk start` loads a project `.env` and project code can mutate `process.env`: resolving afterwards would let a project grant reporting of its own configuration, which the [configuration source ownership decision](../architecture/2026-08-04-configuration-source-ownership.md) denies for the whole `DSH_*` namespace. An unsupported mode denies rather than throwing at that boundary, since telemetry may never change a command's result. This rule superseded the default-on launcher consent before the launcher and its proposal were deleted by the [SDK project toolchain removal](../simplification/2026-08-11-remove-sdk-project-toolchain.md).
The [CLI reference README](../../../../apps/cli/reference/README.md) documents the deployment stance: Session Log upload is off by default, `DSH_TELEMETRY_MODE=FEEDBACK_ONLY` and `DSH_TELEMETRY_MODE=FULL` are the two opt-in choices, and explicitly enabled exports can contain complete session content. The restored [testing-stage onboarding notice](2026-08-13-shared-modal-product-onboarding.md) contains no telemetry copy, so the product still presents no prompt about enabling upload.
The [CLI reference README](../../../../apps/cli/reference/README.md) documents the current deployment stance: the shared base defaults to feedback-gated sharing ([feedback-gated default](2026-08-25-feedback-gated-telemetry-default.md)), `DSH_TELEMETRY_MODE=FULL` and `DSH_TELEMETRY_MODE=DISABLED` are the explicit overrides, and enabled exports can contain complete session content. The restored [testing-stage onboarding notice](2026-08-13-shared-modal-product-onboarding.md) contains no telemetry copy, so the product still presents no prompt about enabling upload.
## Alternatives considered
@@ -28,4 +28,4 @@ The [CLI reference README](../../../../apps/cli/reference/README.md) documents t
## Consequences
Fresh profiles and projects make no telemetry network request. Internal deployments select one mode for both feeds: `FEEDBACK_ONLY` permits only feedback-triggered Session Log sharing, while `FULL` also enables launcher reporting. The existing hard opt-out remains effective, and uploading modes retain their endpoint validation, redaction responsibility, batching, and shutdown behavior.
Fresh profiles and projects make no telemetry network request until the user records `/feedback` ([feedback-gated default](2026-08-25-feedback-gated-telemetry-default.md)). `FULL` still requires an explicit setting; the launcher feed it once also enabled was deleted with the SDK project toolchain. The existing hard opt-out remains effective, and uploading modes retain their endpoint validation, redaction responsibility, batching, and shutdown behavior.
@@ -14,7 +14,7 @@ DeepSeek Harness 有两路出站遥测数据流。在内测阶段,共享基础
dsh-sdk 启动器读取同一变量,不解析 `cordis.yml`,也不启动 Cordis。`FULL` 允许上报;`FEEDBACK_ONLY``DISABLED`、未设置和空值都会拒绝。授权在命令执行前从启动环境冻结:`dsh-sdk start` 会加载项目 `.env`,项目代码也能修改 `process.env`,若在执行后解析,项目便能自行授权上报其自身配置,而[配置来源所有权决策](../architecture/2026-08-04-configuration-source-ownership.zh.md)对整个 `DSH_*` 命名空间禁止这种行为。在该边界上,不受支持的模式按拒绝处理而非抛出,因为遥测不得改变命令结果。此规则在启动器及其提案被[SDK 项目工具链移除决策](../simplification/2026-08-11-remove-sdk-project-toolchain.zh.md)删除之前,仅取代了启动器默认允许上报的规则。
[CLI reference README](../../../../apps/cli/reference/README.zh.md) 记录了这一部署口径:会话日志上传默认关闭`DSH_TELEMETRY_MODE=FEEDBACK_ONLY``DSH_TELEMETRY_MODE=FULL` 是两种显式启用选项,显式开启后的导出可能包含完整会话内容。恢复后的[测试阶段引导声明](2026-08-13-shared-modal-product-onboarding.zh.md)不包含遥测文案,因此产品仍不提供任何关于开启上传的提示。
[CLI reference README](../../../../apps/cli/reference/README.zh.md) 记录了当前的部署口径:共享基础配置默认按反馈门控共享([反馈门控默认值决定](2026-08-25-feedback-gated-telemetry-default.zh.md)`DSH_TELEMETRY_MODE=FULL``DSH_TELEMETRY_MODE=DISABLED` 是显式覆盖值,开启后的导出可能包含完整会话内容。恢复后的[测试阶段引导声明](2026-08-13-shared-modal-product-onboarding.zh.md)不包含遥测文案,因此产品仍不提供任何关于开启上传的提示。
## 考虑过的替代方案
@@ -28,4 +28,4 @@ dsh-sdk 启动器读取同一变量,不解析 `cordis.yml`,也不启动 Cord
## 后果
全新 profile 和项目不发出任何遥测网络请求。内部部署为两路数据流选择一个模式:`FEEDBACK_ONLY` 只允许由反馈触发的 Session Log 共享,`FULL` 还会启用启动器上报。现有硬性退出继续生效,上传模式也保留 endpoint 校验、脱敏责任、批处理和关闭行为。
全新 profile 和项目在用户记录 `/feedback` 之前不发出任何遥测网络请求([反馈门控默认值决定](2026-08-25-feedback-gated-telemetry-default.zh.md))。`FULL` 仍需显式设置;它曾一并启用启动器数据流已随 SDK 项目工具链删除。现有硬性退出继续生效,上传模式也保留 endpoint 校验、脱敏责任、批处理和关闭行为。
@@ -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 .agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.md
2026-08-25-feedback-gated-telemetry-default.md: 1a3766ee44907cee330a5b381aad3a91c7efc58f
2026-08-25-feedback-gated-telemetry-default.zh.md: 677574fdb51a38f36db7cc45ba479a36b5bdd629
2026-08-25-feedback-gated-telemetry-default.md: 772d134da53386292083790148dce738a98f2c0f
2026-08-25-feedback-gated-telemetry-default.zh.md: ea05d4d687bc2270f907983a71f592b880d7449c
@@ -10,7 +10,7 @@ Diagnosing a `/feedback` report needs the session data the report describes. Wit
## Decision
The shared dsh base resolves an unset or empty `DSH_TELEMETRY_MODE` to `FEEDBACK_ONLY` instead of `DISABLED`. Nothing is uploaded before the user records `/feedback`; recording feedback releases the canonical session-log prefix through that exact event to the configured OTLP endpoint, and the acknowledgement's sharing disclosure states that recording feedback releases the session prefix. `FULL` and `DISABLED` remain explicit `DSH_TELEMETRY_MODE` overrides, any non-empty `DSH_TELEMETRY_DISABLED` remains the authoritative pre-load hard opt-out, and the plugin's own omitted-`mode` default stays `DISABLED`: the default changes only in the shared base's config expression, where deployments already override it.
The shared dsh base resolves an unset or empty `DSH_TELEMETRY_MODE` to `FEEDBACK_ONLY` instead of `DISABLED`. Nothing is uploaded before the user records `/feedback`; each recorded feedback uploads the not-yet-shared session-log records — from the last handoff through that exact event to the configured OTLP endpoint, a resumed session shares only its current lifecycle, and the acknowledgement's sharing disclosure states that recording feedback uploads the records not yet shared. `FULL` and `DISABLED` remain explicit `DSH_TELEMETRY_MODE` overrides, any non-empty `DSH_TELEMETRY_DISABLED` remains the authoritative pre-load hard opt-out, and the plugin's own omitted-`mode` default stays `DISABLED`: the default changes only in the shared base's config expression, where deployments already override it.
This supersedes the session-backend default of the [default-off decision](2026-08-10-telemetry-default-off.md), accepting the user's explicit feedback action as the release authorization that note required a deployment setting for. That note's hard opt-out and its launcher-feed history remain current, and the [default-mount decision](2026-07-31-web-telemetry-default-mount.md) continues to own the endpoint, batching cadence, and exit-drain settings.
@@ -24,6 +24,6 @@ This supersedes the session-backend default of the [default-off decision](2026-0
## Consequences
- A fresh installation uploads the session-log prefix to the production collector when — and only when — the user records `/feedback`; no other trigger uploads.
- A fresh installation uploads the not-yet-shared session-log records to the production collector when — and only when — the user records `/feedback`; no other trigger uploads.
- Released exports remain the raw captured copy: the shipped base mounts no `session-telemetry/record` redaction rule, so they can contain message text, tool arguments and results, and workspace paths.
- The sharing disclosure is part of the `/feedback` acknowledgement, so the user reads it after the release has been triggered. A deployment that requires prior informed consent must override the default to `DISABLED` or add a pre-upload confirmation before this default is defensible there.
@@ -10,7 +10,7 @@ Status: implemented
## 决定
共享 dsh 基础配置把未设置或为空的 `DSH_TELEMETRY_MODE` 解析为 `FEEDBACK_ONLY` 而不是 `DISABLED`。用户记录 `/feedback` 之前不上传任何数据;记录反馈时通过该事件把权威会话日志前缀释放到已配置的 OTLP 端点,确认信息中的共享声明会说明记录反馈将释放会话前缀`FULL``DISABLED` 仍是显式的 `DSH_TELEMETRY_MODE` 覆盖值,任何非空的 `DSH_TELEMETRY_DISABLED` 仍是加载前的强制关闭开关,插件自身省略 `mode` 的默认值仍是 `DISABLED`:默认值只在共享基础配置的配置表达式中改变,部署本来就在那里覆盖它。
共享 dsh 基础配置把未设置或为空的 `DSH_TELEMETRY_MODE` 解析为 `FEEDBACK_ONLY` 而不是 `DISABLED`。用户记录 `/feedback` 之前不上传任何数据;每条已记录反馈把尚未共享的会话日志记录——自上次交接至该事件为止——上传到已配置的 OTLP 端点,恢复的会话只共享当前生命周期,确认信息中的共享声明会说明记录反馈将上传尚未共享的记录`FULL``DISABLED` 仍是显式的 `DSH_TELEMETRY_MODE` 覆盖值,任何非空的 `DSH_TELEMETRY_DISABLED` 仍是加载前的强制关闭开关,插件自身省略 `mode` 的默认值仍是 `DISABLED`:默认值只在共享基础配置的配置表达式中改变,部署本来就在那里覆盖它。
本决定取代[默认关闭决定](2026-08-10-telemetry-default-off.zh.md)中会话后端的默认值,把用户显式的反馈动作接受为该决定原本要求由部署设置提供的释放授权。该决定的强制关闭开关和 launcher 上报历史仍然有效,端点、批处理节奏和退出排空设置仍由[默认挂载决定](2026-07-31-web-telemetry-default-mount.zh.md)持有。
@@ -24,6 +24,6 @@ Status: implemented
## 后果
- 全新安装只在用户记录 `/feedback` 时把会话日志前缀上传到生产 collector;没有其他触发上传的途径。
- 全新安装只在用户记录 `/feedback` 时把尚未共享的会话日志记录上传到生产 collector;没有其他触发上传的途径。
- 释放的导出仍是未加工的原始副本:随附基础配置没有挂载 `session-telemetry/record` 脱敏规则,导出可能包含消息文本、工具参数和结果,以及 workspace 路径。
- 共享声明是 `/feedback` 确认信息的一部分,用户读到它时释放已被触发。要求事先知情同意的部署必须把默认值覆盖为 `DISABLED`,或在上传前增加确认步骤,此默认值在那类部署中才站得住。
+2 -2
View File
@@ -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 apps/cli/reference/README.md
README.md: e2814bbda5249fbf0ca0fb9c7f2e98175c5d4e0c
README.zh.md: e714e08147f5430871c17023e67563eefa85118f
README.md: ae6af4bafde7e08bc49f8cb208a206615e6a4c2b
README.zh.md: 96198b6294f2812cc81c7f627a5ba8c986340c33
+2 -2
View File
@@ -89,9 +89,9 @@ New sessions in base-backed profiles default to the `workspace-write` permission
## Shared deployment behavior
The base bundle mounts the native DeepSeek adapter, settings and credential providers, stable `web_search`, and disabled session telemetry. Provider credentials resolve from the inherited environment, `$DSH_HOME/.credentials.yaml`, the invoking directory's `.env`, then `$DSH_HOME/.env`; the managed document is never materialized into `process.env`, while both `.env` files are ordinary launch environment layers. Search uses `DEEPSEEK_API_KEY` and accepts `DEEPSEEK_SEARCH_BASE_URL`; `web_fetch` is disabled unless a patch layer inserts a provider and enables it.
The base bundle mounts the native DeepSeek adapter, settings and credential providers, stable `web_search`, and feedback-gated session telemetry. Provider credentials resolve from the inherited environment, `$DSH_HOME/.credentials.yaml`, the invoking directory's `.env`, then `$DSH_HOME/.env`; the managed document is never materialized into `process.env`, while both `.env` files are ordinary launch environment layers. Search uses `DEEPSEEK_API_KEY` and accepts `DEEPSEEK_SEARCH_BASE_URL`; `web_fetch` is disabled unless a patch layer inserts a provider and enables it.
Session telemetry defaults to feedback-gated sharing: nothing is uploaded until the user records `/feedback`, and recording feedback releases the session-log prefix through that event. `DSH_TELEMETRY_MODE=FULL` instead streams every projected session event as OTLP/HTTP logs, `DSH_TELEMETRY_MODE=DISABLED` keeps everything local, and any non-empty `DSH_TELEMETRY_DISABLED` remains an authoritative hard opt-out. `DSH_TELEMETRY_OTLP_URL` selects another collector. The shipped base has no telemetry redaction rule, so released exports can contain message text, tool arguments and results, and workspace paths; the [feedback-gated-default Agent Note](../../../.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.md) owns that deployment decision.
Session telemetry defaults to feedback-gated sharing: nothing is uploaded until the user records `/feedback`, and each recorded feedback uploads the session records not yet shared, through that event; a resumed session shares only its current lifecycle. `DSH_TELEMETRY_MODE=FULL` instead streams every projected session event as OTLP/HTTP logs, `DSH_TELEMETRY_MODE=DISABLED` keeps everything local, and any non-empty `DSH_TELEMETRY_DISABLED` remains an authoritative hard opt-out. `DSH_TELEMETRY_OTLP_URL` selects another collector. The shipped base has no telemetry redaction rule, so released exports can contain message text, tool arguments and results, and workspace paths; the [feedback-gated-default Agent Note](../../../.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.md) owns that deployment decision.
Install external plugin bundles through `dsh plugin --profile <name> add <package-or-git-spec>`. The installed package owns its dependencies and contributes its declared `cordis.patch.yml` layer. The CLI also ships `@deepseek-ai/dsh-mcp-client` as a dependency for patch layers, but no MCP server is enabled by default because each server command is trusted executable code outside the agent sandbox.
+2 -2
View File
@@ -89,9 +89,9 @@ dsh web --help
## 共享部署行为
基础组合包挂载原生 DeepSeek 适配器、settings 与凭据提供方、稳定的 `web_search`已禁用的会话遥测。提供方凭据依次从继承环境、`$DSH_HOME/.credentials.yaml`、调用目录的 `.env``$DSH_HOME/.env` 解析;受管文档从不物化进 `process.env`,而两个 `.env` 文件都是普通启动环境层。搜索使用 `DEEPSEEK_API_KEY` 并接受 `DEEPSEEK_SEARCH_BASE_URL`;只有 patch 层插入提供方并启用 `web_fetch` 后,该工具才可用。
基础组合包挂载原生 DeepSeek 适配器、settings 与凭据提供方、稳定的 `web_search`按反馈门控的会话遥测。提供方凭据依次从继承环境、`$DSH_HOME/.credentials.yaml`、调用目录的 `.env``$DSH_HOME/.env` 解析;受管文档从不物化进 `process.env`,而两个 `.env` 文件都是普通启动环境层。搜索使用 `DEEPSEEK_API_KEY` 并接受 `DEEPSEEK_SEARCH_BASE_URL`;只有 patch 层插入提供方并启用 `web_fetch` 后,该工具才可用。
会话遥测默认按反馈门控共享:在用户记录 `/feedback` 之前不上传任何数据,记录反馈通过该事件释放会话日志前缀`DSH_TELEMETRY_MODE=FULL` 改为将每条已投影会话事件作为 OTLP/HTTP 日志流式发送,`DSH_TELEMETRY_MODE=DISABLED` 让全部数据留在本地,任何非空的 `DSH_TELEMETRY_DISABLED` 仍是具有最终效力的遥测强制关闭开关。`DSH_TELEMETRY_OTLP_URL` 选择其他 collector。随附基础配置没有遥测脱敏规则,因此释放的导出可能包含消息文本、工具参数和结果,以及 workspace 路径;相关部署决策见[反馈门控默认值 Agent Note](../../../.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.zh.md)。
会话遥测默认按反馈门控共享:在用户记录 `/feedback` 之前不上传任何数据,每条已记录反馈通过该事件上传尚未共享的会话记录;恢复的会话只共享当前生命周期`DSH_TELEMETRY_MODE=FULL` 改为将每条已投影会话事件作为 OTLP/HTTP 日志流式发送,`DSH_TELEMETRY_MODE=DISABLED` 让全部数据留在本地,任何非空的 `DSH_TELEMETRY_DISABLED` 仍是具有最终效力的遥测强制关闭开关。`DSH_TELEMETRY_OTLP_URL` 选择其他 collector。随附基础配置没有遥测脱敏规则,因此释放的导出可能包含消息文本、工具参数和结果,以及 workspace 路径;相关部署决策见[反馈门控默认值 Agent Note](../../../.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.zh.md)。
通过 `dsh plugin --profile <name> add <package-or-git-spec>` 安装外部插件组合包。安装的包拥有其依赖,并贡献其声明的 `cordis.patch.yml` 层。CLI 还随附 `@deepseek-ai/dsh-mcp-client` 作为供 patch 层使用的依赖,但默认不启用 MCP 服务器,因为每条服务器命令都是 agent(智能体)沙箱之外的受信任可执行代码。
+137
View File
@@ -0,0 +1,137 @@
// Keyless assembled-browser coverage for the shipped FEEDBACK_ONLY default
// over the Web bundles and the real host wire. The scaffold mounts the
// shipped telemetry row in FEEDBACK_ONLY mode against this suite's own
// loopback mock collector, so the default release path is real: /feedback
// releases the session records through that event (exactly one OTLP request,
// carrying the drive prompt and the feedback text), the acknowledgement pins
// the feedback-gated disclosure sentence, and a second feedback releases only
// the records since the first handoff — the earlier prompt does not repeat.
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import { createServer, type Server } from 'node:http'
import { once } from 'node:events'
import { gunzipSync } from 'node:zlib'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/feedback-release', import.meta.url))
// The release path needs only a settled ordinary turn, so this lane replays
// the feedback-command scenario's recorded session (declared as this
// manifest's `session.source`) instead of recording a duplicate.
const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/feedback-command/session.jsonl', import.meta.url))
const ACK_EXPECTED = join(SNAPSHOT_DIR, 'ack.expected.md')
const MODE = webSnapshotMode()
const PROMPT = 'Reply with the single word LIGHTHOUSE and stop.'
describe('web e2e: feedback-gated release under the shipped default mode', () => {
let scaffold: WebScaffold
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let collector: Server
const uploads: string[] = []
beforeAll(async () => {
collector = createServer((request, response) => {
const chunks: Buffer[] = []
request.on('data', chunk => chunks.push(chunk as Buffer))
request.on('end', () => {
const raw = Buffer.concat(chunks)
uploads.push((request.headers['content-encoding'] === 'gzip' ? gunzipSync(raw) : raw).toString())
response.writeHead(200, { 'content-type': 'application/json' }).end('{}')
})
})
collector.listen(0, '127.0.0.1')
await once(collector, 'listening')
const address = collector.address()
if (address === null || typeof address === 'string') throw new Error('collector has no port')
scaffold = await launchWebScaffold({
telemetryUrl: `http://127.0.0.1:${address.port}/v1/logs`,
telemetryMode: 'FEEDBACK_ONLY',
// The replayed session.jsonl belongs to the feedback-command scenario;
// comparing (or refreshing) the persisted session here would rewrite
// that shared source with this lane's feedback events. The release
// evidence lives in this lane's golden and collector assertions.
compareReplaySession: false,
...(MODE === 'record' ? {} : { replayFixture: FIXTURE }),
})
browser = await chromium.launch()
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
await connectFreshWorkspace(page, scaffold.workspaceCwd)
}, 120_000)
afterAll(async () => {
await browser?.close()
await scaffold?.close()
collector?.close()
collector?.closeAllConnections()
})
it('drives the recorded prompt to a settled turn (all modes)', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-feedback-release-drive'))
if (MODE !== 'record') {
// Drift guard: the shared fixture must carry exactly the drive prompt.
expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT])
}
const input = page.locator('textarea').first()
await input.waitFor({ timeout: 10_000 })
const settled = scaffold.whenTurnSettled()
await input.fill(PROMPT)
await input.press('Enter')
const sessionId = await settled
if (MODE === 'record') {
// Re-records the SHARED feedback-command session this lane replays.
await recordFixture(scaffold, sessionId, FIXTURE)
}
}, 60_000)
it.skipIf(MODE === 'record')('releases the session records through the feedback and pins the disclosure', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-feedback-release'))
await page.getByText('LIGHTHOUSE', { exact: true }).waitFor({ timeout: 15_000 })
expect(uploads).toEqual([])
const input = page.locator('textarea').first()
await input.fill('/feedback the diff view is unreadable')
await input.press('Enter')
await page.getByText(/Feedback recorded for session/).waitFor({ timeout: 10_000 })
expect(await page.getByText(/recording feedback uploads the session records not yet shared/).count()).toBe(1)
// FEEDBACK_ONLY releases through the committed feedback event: exactly
// one request reaches the collector, carrying the whole unshared range.
await expect.poll(() => uploads.length, { timeout: 15_000 }).toBe(1)
expect(uploads[0]).toContain('the diff view is unreadable')
expect(uploads[0]).toContain(PROMPT)
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(ACK_EXPECTED, snapshot, MODE)
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])
}, 60_000)
it.skipIf(MODE === 'record')('releases only the records since the last handoff on a second feedback', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-feedback-release-suffix'))
const input = page.locator('textarea').first()
await input.fill('/feedback the second remark')
await input.press('Enter')
await expect.poll(() => uploads.length, { timeout: 15_000 }).toBe(2)
// Suffix semantics: the second release starts after the first feedback's
// handoff, so the drive prompt already shared must not repeat.
expect(uploads[1]).toContain('the second remark')
expect(uploads[1]).not.toContain(PROMPT)
}, 60_000)
it.skipIf(MODE === 'record')('keeps the fixture inventory closed', async () => {
await assertFixtureInventory(SNAPSHOT_DIR, ['ack.expected.md'])
})
})
+7 -5
View File
@@ -319,12 +319,14 @@ export interface LaunchOptions {
default: string
}
/**
* Mount the shipped telemetry row in FULL mode against this exporter URL
* instead of disabling it. Used to pin a real backend disclosure in
* assembled coverage; point the URL at a local dead endpoint so no record
* leaves the process.
* Mount the shipped telemetry row against this exporter URL instead of
* disabling it. Used to pin a real backend disclosure in assembled
* coverage; point the URL at a local endpoint (a dead port, or a scenario's
* own mock collector) so no record leaves the machine.
*/
telemetryUrl?: string
/** Uploading mode for the mounted telemetry row. Defaults to `FULL`. */
telemetryMode?: 'FULL' | 'FEEDBACK_ONLY'
/**
* Browse through a trusted non-loopback hostname that the browser resolves
* to loopback (for example `*.localhost`). The test server stays bound to
@@ -487,7 +489,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
: {
id: 'session-telemetry-otel',
config: {
mode: 'FULL',
mode: options.telemetryMode ?? 'FULL',
exporter: { url: options.telemetryUrl },
shutdownTimeoutMillis: 1_000,
},
+1
View File
@@ -80,6 +80,7 @@
"tests/shipped-composition.e2e.ts",
"tests/schedule-after.e2e.ts",
"tests/feedback-command.e2e.ts",
"tests/feedback-release.e2e.ts",
"tests/startup-auto-selection.e2e.ts",
"tests/produced-files.e2e.ts",
"tests/produced-file-mentions.e2e.ts",
+4 -2
View File
@@ -139,8 +139,10 @@
name: '@deepseek-ai/dsh-session-projection'
# Session telemetry defaults to feedback-gated sharing: FEEDBACK_ONLY
# uploads the canonical session-log prefix only after the user records
# /feedback. DSH_TELEMETRY_MODE overrides to FULL or DISABLED; uploading
# uploads only when the user records /feedback, releasing the session
# records since the last handoff through that event (a resumed session
# shares only its current lifecycle). DSH_TELEMETRY_MODE overrides to
# FULL or DISABLED; uploading
# mirrors session-log records onto OTLP/HTTP logs with no session-telemetry/record
# redaction rule, so exports are the raw captured copy. The deployment
# stance, env seams, and follow-ups are pinned in the feedback-gated-default
@@ -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 packages/feedback/command-feedback/README.md
README.md: e3b67e8ecf582681968a4d009b07ee90abbb66a8
README.zh.md: 2544126f5b918eb2f9af1fcab4d4e8b2cc71918a
README.md: d58ae57fced07f3c26c8b5bfeab22a1651cc6eac
README.zh.md: 395b678089077c342375c42b031328bd85958883
+1 -1
View File
@@ -20,7 +20,7 @@ The acknowledgement names the receiving session id and reports how that session
| Disclosed status | Acknowledgement sentence |
|---|---|
| `full` | `Session sharing is enabled.` |
| `feedback-only` | `Session sharing is feedback-gated; recording feedback releases the session prefix for sharing.` |
| `feedback-only` | `Session sharing is feedback-gated; recording feedback uploads the session records not yet shared.` |
| `disabled` | `Session sharing is disabled.` |
| no service | `Session sharing is not configured.` |
@@ -20,7 +20,7 @@
| 披露的状态 | 确认文本中的句子 |
|---|---|
| `full` | `Session sharing is enabled.` |
| `feedback-only` | `Session sharing is feedback-gated; recording feedback releases the session prefix for sharing.` |
| `feedback-only` | `Session sharing is feedback-gated; recording feedback uploads the session records not yet shared.` |
| `disabled` | `Session sharing is disabled.` |
| 无服务 | `Session sharing is not configured.` |
@@ -29,7 +29,7 @@ function sharingSentence(sharing: SessionTelemetrySharingStatus): string {
case 'full':
return 'Session sharing is enabled.'
case 'feedback-only':
return 'Session sharing is feedback-gated; recording feedback releases the session prefix for sharing.'
return 'Session sharing is feedback-gated; recording feedback uploads the session records not yet shared.'
case 'disabled':
return 'Session sharing is disabled.'
/* v8 ignore next 2 -- the seam's closed union cannot reach the default; a future status must be given a sentence here. */
@@ -192,7 +192,7 @@ describe('/feedback human command', () => {
const test = await harness('feedback-only')
await expect(run(test, ' gated sharing')).resolves.toEqual({
kind: 'success',
text: `Feedback recorded for session ${test.session.id}\nAnonymous user: ${USER_ID}. Session sharing is feedback-gated; recording feedback releases the session prefix for sharing.`,
text: `Feedback recorded for session ${test.session.id}\nAnonymous user: ${USER_ID}. Session sharing is feedback-gated; recording feedback uploads the session records not yet shared.`,
})
expect(feedbackTexts(test.session)).toEqual(['gated sharing'])
})
@@ -0,0 +1,46 @@
- banner:
- navigation "Session hierarchy":
- button "Reply with the single word" [disabled]
- img
- text: Standard mode
- button "Session log":
- text: Session log
- img
- tablist:
- tab "Chat" [selected]
- tab "Trajectory"
- text: Reply with the single word LIGHTHOUSE and stop. {{clock}}
- button "Copy":
- img
- button "Context injection @deepseek-ai/dsh-system-prompt":
- img
- img
- text: Context injection @deepseek-ai/dsh-system-prompt
- button "Think The user wants me to reply with a single word. Let me comply.":
- img
- img
- text: Think The user wants me to reply with a single word. Let me comply.
- paragraph: LIGHTHOUSE
- button "Copy":
- img
- button "Good response":
- img
- button "Bad response":
- img
- button "Branch into a new conversation":
- img
- text: {{clock}} Ran for {{duration}} TTFT {{duration}} {{throughput}} tok/s
- 'button "feedback Feedback recorded for session session-{{uuid}} Anonymous user: {{uuid}}. Session sharing is feedback-gated; recording feedback uploads the session records not yet shared."':
- img
- img
- text: "feedback Feedback recorded for session session-{{uuid}} Anonymous user: {{uuid}}. Session sharing is feedback-gated; recording feedback uploads the session records not yet shared."
- textbox "Message the agent"
- button "Commands":
- img
- 'button "Access mode, current: Workspace Write"': Workspace Write
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "6% of context used"
- button "Send message" [disabled]
- text: 1 turns · 1 steps LLM {{duration}} TTFT avg {{duration}} · {{throughput}} tok/s Cache hit 99% Input 7.8K tok · Output 21 tok
@@ -0,0 +1,9 @@
version: 1
scenario: feedback-release
profile: web
composition: web-default
recording: live
session:
source: ../feedback-command/session.jsonl
header:
class: web-default
+1
View File
@@ -68,6 +68,7 @@
"apps/web/tests/goal-bar.e2e.ts",
"apps/web/tests/schedule-after.e2e.ts",
"apps/web/tests/feedback-command.e2e.ts",
"apps/web/tests/feedback-release.e2e.ts",
"apps/web/tests/goal-command-presentation.e2e.ts",
"apps/web/tests/startup-auto-selection.e2e.ts",
"apps/web/tests/produced-files.e2e.ts",