diff --git a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.i18n.yaml index ae54aff2a9..ca6497cb81 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.i18n.yaml @@ -3,17 +3,17 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md /agent-note-schedule-as-an-opt-in-optional-bundle: - en: d234bf064c5990f1 - zh: c54b941429b4658c + en: 8c2381fd778a9888 + zh: 8877255cb980eea1 /agent-note-schedule-as-an-opt-in-optional-bundle/problem: - en: 2409ea3a006ea5d9 - zh: dbcda58a4c6b5b9f + en: bb23c2f7f93a462b + zh: 9fb600fc8e203323 /agent-note-schedule-as-an-opt-in-optional-bundle/decision: - en: 5aa944749a231645 - zh: c14651be6367ee13 + en: 483d1dd51b9cc073 + zh: b069e2e2385c8132 /agent-note-schedule-as-an-opt-in-optional-bundle/alternatives-considered: - en: 1782c8ecdbe6bf87 - zh: dd6997d067257562 + en: 8e5f4d8fa8c2e78d + zh: 3353c7042c6a1d6b /agent-note-schedule-as-an-opt-in-optional-bundle/consequences: - en: 8ea1d9aaf25f1702 - zh: 32e721347ea55475 + en: 36cd0eb71abeedbe + zh: 7c134095cc19fcfd diff --git a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md index bd4b47d25a..865f97848d 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md +++ b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md @@ -4,19 +4,17 @@ Status: implemented English | [中文](2026-09-24-schedule-opt-in-optional-bundle.zh.md) -> Supersedes [Schedule in the shipped Web composition](../../archived/architecture/2026-09-24-web-default-schedule-composition.md): the three rows now ship disabled, and switching them on is this bundle's job. - ## Problem -`packages/bundle/web-app/cordis.patch.yml` inserts `time-context`, `schedule`, and `ui-schedule` with `disabled: true`, so the shipped Web composition exposes no Schedule surface: no `schedule_*` tools, no Session reminder catalog, no Automation tasks page, and no per-step clock reading. The rows have no switch on the product surface; a deployment that wants them edits a profile patch layer or passes a `--patch` overlay, and both are configuration files a person using the shipped Web profile cannot reach. +Schedule adds four tool schemas to every live root Agent request and one durable clock message per eligible step, so the shipped Web composition must not mount it by default. A person using the shipped Web profile still needs a product-surface switch to turn it on; a profile patch layer or a `--patch` overlay is a configuration file that person cannot reach. ## Decision -The rows stay in the composition because an id-targeted patch overrides an existing row: a profile patch or a bundle sets a row's `disabled` to `false` without inserting it again. +`packages/bundle/web-app/cordis.patch.yml` carries none of `time-context`, `schedule`, and `ui-schedule`, so the shipped Web composition exposes no Schedule surface: no `schedule_*` tools, no Session reminder catalog, no Automation tasks page, and no per-step clock reading. -`@deepseek-ai/dsh-experimental-schedule-bundle` (`packages/experimental/schedule-bundle/`) carries three id-targeted patches in its `cordis.patch.yml`, one per row, each setting `disabled: false`; it inserts no row. `OPTIONAL_BUNDLES` in `packages/boot/app-boot/src/profile.ts` names the package, and `apps/cli` declares it as a runtime dependency, so every installation ships it switched off. The [experimental-as-optional-bundles decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns the `OPTIONAL_BUNDLES` conventions and the localized `icon` and `meta.title` / `meta.description` metadata the Web Plugins page renders in its Official group. +`@deepseek-ai/dsh-experimental-schedule-bundle` (`packages/experimental/schedule-bundle/`) inserts the three rows in its `cordis.patch.yml` and depends on their packages, as every other optional bundle inserts the rows it ships. `OPTIONAL_BUNDLES` in `packages/boot/app-boot/src/profile.ts` names the package, and `apps/cli` declares it as a runtime dependency, so every installation ships it switched off. The [experimental-as-optional-bundles decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns the `OPTIONAL_BUNDLES` conventions and the localized `icon` and `meta.title` / `meta.description` metadata the Web Plugins page renders in its Official group. -Enabling the bundle turns on `time-context` (a per-step clock reading with the sampled instant, the browser zone attached to the open request, and the elapsed time since the preceding model-visible message), `schedule` (durable reminders plus `schedule_create`, `schedule_list`, `schedule_update`, and `schedule_delete` on live root Agents), and `ui-schedule` (the Session reminder catalog and the Automation tasks page). The bundle switches `time-context` on with `schedule` because a reminder request states a wall-clock target: the sampled instant is what lets the model turn a request such as "tomorrow at nine" into an offset-bearing `at` value. The [Schedule subsystem](../../../../docs/subsystems/schedule.md) owns the durable records, delivery, and management operations; the [Web bundle](../../../../packages/bundle/web-app/README.md) owns the composition that carries the disabled rows. Disabling the bundle restores the shipped composition, and the Host stops scheduling while the `schedule` row stays disabled; stored task records remain in the Schedule domain. +Enabling the bundle inserts `time-context` (a per-step clock reading with the sampled instant, the browser zone attached to the open request, and the elapsed time since the preceding model-visible message), `schedule` (durable reminders plus `schedule_create`, `schedule_list`, `schedule_update`, and `schedule_delete` on live root Agents), and `ui-schedule` (the Session reminder catalog and the Automation tasks page). The bundle carries `time-context` with `schedule` because a reminder request states a wall-clock target: the sampled instant is what lets the model turn a request such as "tomorrow at nine" into an offset-bearing `at` value. The [Schedule subsystem](../../../../docs/subsystems/schedule.md) owns the durable records, delivery, and management operations; the [Web bundle](../../../../packages/bundle/web-app/README.md) owns the composition the bundle adds the rows to. Disabling the bundle restores the shipped composition, and the Host stops scheduling while the `schedule` row is absent; stored task records remain in the Schedule domain. ## Alternatives considered @@ -24,11 +22,14 @@ Enabling the bundle turns on `time-context` (a per-step clock reading with the s **Keep a `--patch` overlay file.** An overlay is a launch-time argument rather than a product-surface switch, so a person using the shipped Web profile cannot reach it. Re-declaring the rows through its `insert` list does not override them: `applyEntryPatches` appends that list without de-duplicating ids, and the Loader collapses the composed list to one entry per id with the last declaration winning, so the overlay replaces the Web bundle's rows instead of setting fields on them. -**Extract the three rows out of the Web composition into the bundle.** The bundle would insert the rows instead of overriding them, so a profile that also declares one of those ids would have two declarations for it, of which the Loader keeps only the last. The disabled rows are what an override-style switch targets. +**Keep the rows in the Web composition with `disabled: true` and switch them on with id-targeted patches.** The bundle would then set fields on existing rows, but the plugin manager lists only the rows a bundle inserts, so the bundle's page reported no components and needed a second listing path for overridden rows, while every other optional bundle inserts its rows. A profile that also declares one of the ids resolves the same way as for any other bundle: the Loader keeps the last declaration. + +**Lock the three rows to the bundle switch.** A bundle manifest field that makes the plugin manager refuse to switch the rows one by one adds a plugin-manager concept only this bundle would use. The bundle's page offers a switch per row, as for every other bundle, and the bundle README records that the three rows work only together. ## Consequences - A default `dsh web` session carries no Schedule tool schemas, no Session reminder catalog, no Automation tasks page, and no per-step clock reading. An installation that wants them enables the optional bundle from the Plugins page or lists the package in a profile's `dsh.profile.bundles`. - An enabled installation adds four tool schemas to every live root Agent and one durable clock message per eligible step; the reading is model-visible and durable, so it replays, compacts, and appears in exported Session logs like any other user message. -- A composition that does not carry the three Web rows reports one `patch: entry not found` warning per patch and mounts nothing from this bundle, and its package declares no plugin dependency, so enabling it cannot mount a Host service twice. +- The Plugins page lists the three rows under the bundle with the titles their packages' `locale/*.json` declare, their state, and, while the bundle is on, a switch per row; switching one of them off leaves Schedule without that part. +- A profile patch or `--patch` overlay that targets one of the three ids matches no row while the bundle is not selected, and the loader warns `patch: entry not found` for it; selecting the bundle replaces switching the rows on by id. - The switch is configuration-only: `src/index.ts` is an empty module, the patch carries the runtime content, and the package owns no mutable runtime state, so it publishes no invariant companion. diff --git a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.zh.md b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.zh.md index eaa59aa17d..a90516606a 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.zh.md @@ -4,19 +4,17 @@ Status: implemented [English](2026-09-24-schedule-opt-in-optional-bundle.md) | 中文 -> 取代[默认 Web 组合中的 Schedule](../../archived/architecture/2026-09-24-web-default-schedule-composition.md):这三行现在默认禁用,打开它们是此 bundle 的职责。 - ## Problem -`packages/bundle/web-app/cordis.patch.yml` 以 `disabled: true` 插入 `time-context`、`schedule` 与 `ui-schedule`,因此随发行版交付的 Web 组合不提供任何定时能力:没有 `schedule_*` 工具、没有 Session 提醒目录、没有自动化任务页面,也没有逐步时钟读数。这些条目在产品界面上没有开关;想要它们的部署要改 profile patch 层或传 `--patch` overlay,而两者都是使用随发行版 Web profile 的人无法触及的配置文件。 +Schedule 会给每个活跃根 Agent 的请求增加四个工具 schema,并在每个符合条件的步骤追加一条持久时钟消息,因此随发行版交付的 Web 组合不能默认挂载它。使用随发行版 Web profile 的人仍需要产品界面上的开关来打开它;profile patch 层或 `--patch` overlay 都是这个人无法触及的配置文件。 ## Decision -这三行仍留在组合中,因为按 id 定位的 patch 覆盖的是已存在的行:profile patch 或 bundle 把某行的 `disabled` 设为 `false`,无需再次插入它。 +`packages/bundle/web-app/cordis.patch.yml` 不含 `time-context`、`schedule` 与 `ui-schedule` 中的任何一行,因此随发行版交付的 Web 组合不提供任何定时能力:没有 `schedule_*` 工具、没有 Session 提醒目录、没有自动化任务页面,也没有逐步时钟读数。 -`@deepseek-ai/dsh-experimental-schedule-bundle`(`packages/experimental/schedule-bundle/`)在其 `cordis.patch.yml` 中携带三个按 id 定位的 patch,每行一个,各自把 `disabled` 设为 `false`;它不插入任何行。`packages/boot/app-boot/src/profile.ts` 的 `OPTIONAL_BUNDLES` 列出该包,`apps/cli` 将其声明为运行时依赖,因此每次安装都随包携带且默认关闭。[实验性能力作为可选 bundle 的决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)负责 `OPTIONAL_BUNDLES` 的约定,以及 Web 插件管理页在 Official 分组中渲染的本地化 `icon` 与 `meta.title` / `meta.description` 元数据。 +`@deepseek-ai/dsh-experimental-schedule-bundle`(`packages/experimental/schedule-bundle/`)在其 `cordis.patch.yml` 中插入这三行并依赖它们的包,与其他可选 bundle 插入各自随附的行相同。`packages/boot/app-boot/src/profile.ts` 的 `OPTIONAL_BUNDLES` 列出该包,`apps/cli` 将其声明为运行时依赖,因此每次安装都随包携带且默认关闭。[实验性能力作为可选 bundle 的决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)负责 `OPTIONAL_BUNDLES` 的约定,以及 Web 插件管理页在“官方”分组中渲染的本地化 `icon` 与 `meta.title` / `meta.description` 元数据。 -启用该 bundle 会打开 `time-context`(每个步骤的时钟读数,包含采样瞬时、附加到当前开放请求的浏览器时区,以及自前一条模型可见消息以来的经过时长)、`schedule`(持久提醒,以及每个活跃根 Agent 上的 `schedule_create`、`schedule_list`、`schedule_update` 与 `schedule_delete`),以及 `ui-schedule`(Session 提醒目录与自动化任务页面)。该 bundle 把 `time-context` 与 `schedule` 一起打开,因为提醒请求给出的是挂钟时间目标:采样瞬时正是让模型把“明天九点”这类请求转换成带偏移量的 `at` 值的依据。持久记录、投递与管理操作由 [Schedule 子系统](../../../../docs/subsystems/schedule.zh.md)负责;承载这些禁用行的组合由 [Web bundle](../../../../packages/bundle/web-app/README.zh.md)负责。禁用该 bundle 会恢复随发行版交付的组合,`schedule` 行保持禁用期间 Host 不再调度;已存储的任务记录保留在 Schedule domain 中。 +启用该 bundle 会插入 `time-context`(每个步骤的时钟读数,包含采样瞬时、附加到当前开放请求的浏览器时区,以及自前一条模型可见消息以来的经过时长)、`schedule`(持久提醒,以及每个活跃根 Agent 上的 `schedule_create`、`schedule_list`、`schedule_update` 与 `schedule_delete`)与 `ui-schedule`(Session 提醒目录与自动化任务页面)。该 bundle 把 `time-context` 与 `schedule` 一起携带,因为提醒请求给出的是挂钟时间目标:采样瞬时正是让模型把“明天九点”这类请求转换成带偏移量的 `at` 值的依据。持久记录、投递与管理操作由 [Schedule 子系统](../../../../docs/subsystems/schedule.zh.md)负责;该 bundle 向其加入这些行的组合由 [Web bundle](../../../../packages/bundle/web-app/README.zh.md) 负责。禁用该 bundle 会恢复随发行版交付的组合,`schedule` 行不存在期间 Host 不再调度;已存储的任务记录保留在 Schedule domain 中。 ## Alternatives considered @@ -24,11 +22,14 @@ Status: implemented **保留一个 `--patch` overlay 文件。** overlay 是启动时的参数,而不是产品界面上的开关,因此使用随发行版 `web` profile 的人无法触及它。通过它的 `insert` 列表重新声明这些条目并不能覆盖它们:`applyEntryPatches` 追加该列表时不对 id 去重,而 Loader 会把组合出的列表按 id 收敛为一个条目、最后一个声明生效,因此该 overlay 会替换 Web Bundle 的条目,而不是给它们设值。 -**把这三行从 Web 组合中抽出、移入 bundle。** 这样 bundle 会插入这些行而不是覆盖它们;某个 profile 若也声明了这些 id,同一个 id 就会有两份声明,而 Loader 只保留最后一份。按需开关针对的正是这些禁用行。 +**把这三行以 `disabled: true` 留在 Web 组合中,用按 id 定位的 patch 打开。** 这样 bundle 只是给已有的行设值,但插件管理器只列出 bundle 插入的行,因此该 bundle 的页面显示不含任何组件,需要为被覆盖的行另建一条列出路径;而其他可选 bundle 都插入各自的行。某个 profile 若也声明了其中某个 id,处理方式与任何其他 bundle 相同:Loader 保留最后一份声明。 + +**把这三行锁定在 bundle 开关上。** 让插件管理器拒绝逐行开关的 bundle 清单字段,会给插件管理器增加一个只有这个 bundle 使用的概念。该 bundle 的页面像其他所有 bundle 一样为每行提供开关,由 bundle README 说明这三行只能一起工作。 ## Consequences - 默认的 `dsh web` 会话不带 Schedule 工具 schema、Session 提醒目录、自动化任务页面与逐步时钟读数。需要它们的安装可从插件管理页启用该可选 bundle,或把该包列进 profile 的 `dsh.profile.bundles`。 - 启用后的安装会给每个活跃根 Agent 增加四个工具 schema,并在每个符合条件的步骤追加一条持久时钟消息;该读数对模型可见且持久,因此它与其他 user 消息一样参与回放、压缩,并出现在导出的 Session 日志中。 -- 在不携带这三行 Web 行的组合中,该 bundle 的每个补丁都会报告一条 `patch: entry not found` 警告且不挂载任何东西;其包不声明插件依赖,因此启用它不会重复挂载同一个 Host 服务。 +- 插件管理页在该 bundle 下列出这三行,显示各自包的 `locale/*.json` 声明的标题、它们的状态,并在 bundle 打开时为每行提供开关;关掉其中一行会让 Schedule 缺少这一部分。 +- 未选中该 bundle 时,按 id 定位这三行之一的 profile 补丁或 `--patch` overlay 匹配不到任何条目,加载器会为它报告 `patch: entry not found` 警告;选中该 bundle 即可取代按 id 打开这些行。 - 该开关只涉及配置:`src/index.ts` 是空模块,运行时内容由 patch 承载,包本身不拥有可变的运行时状态,因此不发布不变量伴随模块。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml new file mode 100644 index 0000000000..727bb883f4 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-09-19-failed-step-tool-results.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.md +/agent-note-record-unanswered-tool-results-before-closing-a-failed-step: + en: 8fd2a50a041b1b18 + zh: 01aed07a6425a322 +/agent-note-record-unanswered-tool-results-before-closing-a-failed-step/problem: + en: 1fa7525f83af9035 + zh: 401ce968e3dd5434 +/agent-note-record-unanswered-tool-results-before-closing-a-failed-step/decision: + en: 3d58367a05183b4a + zh: f9ccc3a7011a6240 +/agent-note-record-unanswered-tool-results-before-closing-a-failed-step/alternatives-considered: + en: 37da4d9c8533b736 + zh: e52a59763ae2b62e +/agent-note-record-unanswered-tool-results-before-closing-a-failed-step/consequences: + en: 1ea72421a98551d8 + zh: c01e02ddac8a3454 diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.md b/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.md new file mode 100644 index 0000000000..80c813598e --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.md @@ -0,0 +1,33 @@ +# Agent Note: Record unanswered tool results before closing a failed step + +Status: implemented + +English | [中文](2026-09-19-failed-step-tool-results.zh.md) + +## Problem + +A committed assistant message can request tools whose scheduler fails before all results are recorded. Closing the step and turn with those calls unanswered makes later model requests invalid for providers that require paired tool calls and results. An error turn is already closed, so crash-tail repair cannot recover it on resume. + +## Decision + +The AgentLoop driver records recovery results for unanswered calls before it closes a failed step, then propagates the original failure. Live step recovery and `interruptedTurnClosers` share incremental pending-call tracking and result construction; the live driver still owns its step and turn events. A step-local observer retains pending identities from committed events without copying session history. The scheduler stops replenishment and drains started dispatches before rejecting, so result recovery cannot race a live dispatch. + +Recovery preserves committed results and follows the assistant's call order. A recorded `tool/call` without a result receives `TOOL_OUTCOME_UNKNOWN`; its text warns against blindly retrying an operation with possible side effects. An assistant request with no call record receives `TOOL_NOT_STARTED`. A call record precedes preparation and does not establish that the tool body ran, so unknown outcomes remain conservative. + +This decision supersedes only the missing-result policy in [parallel tool-call execution](../feature/2026-07-10-parallel-tool-call-execution.md). Its classification, concurrency limit, ordering, and quiescence rules remain applicable. + +## Alternatives considered + +**Leave calls unanswered to avoid inventing tool outcomes.** An unanswered call invalidates later provider requests. An explicit unknown-outcome error records the limit of the available evidence without claiming success, cancellation, or absence of side effects. + +**Repair only when resuming or forking.** The ordinary failure path already records `step/end` and `turn/end`; open-tail repair intentionally preserves closed history. Recovery belongs before those records, while the driver owns the failed step. + +**Repair inside the tool scheduler alone.** Failure can occur after the assistant message is recorded but before scheduling starts. The step driver covers that interval and reuses the existing session repair semantics rather than adding a second implementation of call/result matching. + +**Retry automatically.** A dispatch can have side effects even when its result is missing. The error result supplies retry guidance, while the turn retains the original failure and waits for later work. + +## Consequences + +Later requests, resume, and fork retain a usable call/result history after a live step failure. Recovery appends results; it does not remove assistant messages, replace completed results, execute tools, or convert the failed turn into success. The log uses existing event types and recovery codes, with no format change. + +Already-closed inconsistent historical turns remain unchanged. The live recovery path requires the session to accept the missing result records; it cannot promise recovery from a failed log append. diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.zh.md b/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.zh.md new file mode 100644 index 0000000000..7d69b46a9a --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.zh.md @@ -0,0 +1,33 @@ +# Agent Note: 在关闭失败步骤前补齐工具结果 + +Status: implemented + +[English](2026-09-19-failed-step-tool-results.md) | 中文 + +## 问题 + +已提交的 assistant 消息可能请求多个工具,而调度器在全部结果记录完成之前失败。如果关闭步骤和轮次时仍有调用尚无结果,后续模型请求就不满足要求工具调用与结果配对的提供方。错误轮次已经关闭,因此恢复会话时的崩溃尾部修复无法恢复它。 + +## 决策 + +AgentLoop 驱动器在关闭失败步骤之前,为尚无结果的调用记录恢复结果,随后传播原始失败。实时步骤恢复和 `interruptedTurnClosers` 共享增量的待处理调用跟踪与结果构造;实时驱动器仍拥有自己的步骤和轮次事件。步骤本地观察器从已提交事件中保留待处理调用标识,不复制会话历史。调度器在拒绝之前停止补充池并排空已启动的派发,因此结果恢复不会与运行中的派发竞争。 + +恢复保留已提交的结果,并遵循 assistant 的调用顺序。已有 `tool/call` 记录但尚无结果的调用获得 `TOOL_OUTCOME_UNKNOWN`;其文本警告不要盲目重试可能存在副作用的操作。没有调用记录的 assistant 请求获得 `TOOL_NOT_STARTED`。调用记录先于准备阶段,不能证明工具主体已经运行,因此未知结果保持保守。 + +本决策仅取代[并行工具调用执行](../feature/2026-07-10-parallel-tool-call-execution.zh.md)中的缺失结果策略。其中的分类、并发上限、顺序和完全停稳规则继续适用。 + +## 备选方案 + +**让调用保持无结果,避免虚构工具执行结果。** 尚无结果的调用会使后续提供方请求无效。显式的未知结果错误记录可用证据的局限,而不声称成功、取消或没有副作用。 + +**仅在恢复或 fork 时修复。** 普通失败路径已经记录 `step/end` 和 `turn/end`;开放尾部修复有意保留已关闭历史。恢复应在这些记录之前执行,此时驱动器仍拥有失败步骤。 + +**仅在工具调度器内部修复。** 失败可能发生在 assistant 消息记录之后、调度开始之前。步骤驱动器覆盖这段区间,并复用已有的会话修复语义,避免另行实现调用/结果匹配。 + +**自动重试。** 即使结果缺失,派发也可能产生副作用。错误结果提供重试指引,而轮次保留原始失败并等待后续工作。 + +## 影响 + +实时步骤失败后,后续请求、恢复和 fork 会保留可用的调用/结果历史。恢复追加结果;它不移除 assistant 消息、不替换已完成结果、不执行工具,也不将失败轮次改为成功。日志使用已有事件类型和恢复代码,无需格式变更。 + +此前已关闭的不一致历史轮次保持不变。实时恢复路径要求会话能够接纳缺失结果记录;它不能保证从日志追加失败中恢复。 diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml index 4b82a34a31..5867da4d43 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml @@ -12,8 +12,8 @@ en: 532bb7268d9b93ce zh: ad47162233222ab0 /agent-note-parallel-tool-call-execution-by-per-call-safety/scheduling-and-ordering: - en: f0906d03fef98e7b - zh: f9e43c4c832c9a05 + en: a485c530ea4366de + zh: 6f9915d726de9ac7 /agent-note-parallel-tool-call-execution-by-per-call-safety/safety-contract: en: 16c4ee08b65ead23 zh: df80d6e49982615b @@ -24,8 +24,8 @@ en: eb5a6aa84980f0ab zh: eed2680a160e8ed2 /agent-note-parallel-tool-call-execution-by-per-call-safety/alternatives-considered: - en: 44f8fb5851ba4e39 - zh: 4d75c937ac5277f7 + en: 17becc038bb7df7c + zh: 673d9a9bdfa39edf /agent-note-parallel-tool-call-execution-by-per-call-safety/consequences: - en: 5e5b2a1c7d850f83 - zh: b63311f28eb69366 + en: a5508057bc03f4f6 + zh: 96b46c59402474c3 diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md index d5d26e03a2..004d725bfd 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md @@ -46,7 +46,7 @@ Only dispatch and the tool body overlap. `tools/pre-execute` and `tools/post-exe Each started call appends `tool/call` immediately before its pre-execute gate. Completed dispatches occupy model-order slots, and a commit cursor appends `tool/result` and collects `additionalContexts` only when the next slot is ready. Live surfaces may show several pending calls, but results and post-tool context remain model-ordered. -An abort before a group starts records no calls from that group. An abort during a group stops replenishment, waits for already-started calls, commits their results in order, drains accepted batch context after those results, and then ends the step through the existing abort path. Calls that never start have no audit event. An unexpected scheduler failure stops new dispatches, waits for every already-started dispatch to settle, and rethrows the first failure. Because that failure is terminal internal state rather than a tool outcome, the loop does not invent tool results for rejected or uncommitted calls. +An abort stops replenishment, waits for already-started calls, commits their results in order, drains accepted batch context after those results, and records synthetic aborted call/result pairs for undispatched calls. An unexpected scheduler failure stops new dispatches, waits for every already-started dispatch to settle, and rethrows the first failure. The driver records missing results before closing the failed step under the [failed-step recovery decision](../bug-fix/2026-09-19-failed-step-tool-results.md). PTC mode remains outside this scheduler because the model emits one native `run_code` call. `run_code` and its internal dispatch queue remain serial; native sibling calls in `mode: 'both'` use the normal scheduler. @@ -84,7 +84,7 @@ Snapshot coverage pins the visible multi-call transcript: pending calls may over **Expose staged methods or a scheduling waterfall.** Public `prepare` / `dispatch` / `finalize` methods or a `tools/execution-mode` event add extension surface before another consumer needs it. The loop uses an internal scheduler view, while `executionMode(exec)` leaves an insertion point for a policy hook. -**Convert scheduler failures into tool results.** AgentLoop cannot determine whether a rejected dispatch invoked the tool body; ToolRuntime owns body-invocation state and typed tool outcomes. Internal scheduler failures therefore remain terminal instead of being reclassified as `ABORTED` results. +**Convert scheduler failures into ordinary tool results.** AgentLoop cannot determine whether a rejected dispatch invoked the tool body; ToolRuntime owns body-invocation state and typed tool outcomes. Internal scheduler failures remain terminal instead of being reclassified as `ABORTED` results. Explicit recovery results preserve this uncertainty without leaving unanswered calls. **Start calls while the model streams.** This may reduce latency further but changes assistant-message authority, replay, and call/result pairing. The scheduler starts only after the assistant message is complete. @@ -96,7 +96,7 @@ Snapshot coverage pins the visible multi-call transcript: pending calls may over The design is fail-closed and simple for tool authors, but it cannot exploit concurrency whose safety depends on comparing siblings. A tool that opts in too broadly can expose latent shared-state races. -Parallel calls may begin in cases where serial execution would have aborted before reaching them. The scheduler therefore records only started calls, drains them on abort, and never starts replacements after cancellation. +Parallel calls may begin in cases where serial execution would have aborted before reaching them. The scheduler drains them on abort and never starts replacements after cancellation; undispatched calls receive synthetic aborted call/result pairs. Ordered commits may hold a fast result behind a slow earlier sibling. This preserves replay and model-history order while live surfaces still show pending progress. @@ -104,4 +104,4 @@ Concurrent external calls can compete for quota or process capacity. Providers o Tool registration is a scheduling boundary. Registry mutations affect not-yet-started calls because the scheduler reclassifies after each barrier and before every pool replenishment. Already-started calls retain the scheduling decision under which they entered the pool. -A terminal scheduler failure may leave recorded calls without results before the failed step closes. Waiting for live dispatches preserves quiescence without misreporting those internal failures as tool outcomes. +A terminal scheduler failure reaches step recovery only after live dispatches settle. Recovery reports uncommitted outcomes as unknown, preserving quiescence and the original failure while keeping later model history usable. diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md index 707428985c..e78f56e07c 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md @@ -46,7 +46,7 @@ Status: implemented 每个已启动的调用都会在进入 pre-execute 门禁之前立即追加 `tool/call`。已完成的派发占据模型顺序的槽位;提交游标只有在下一个槽位就绪时,才会追加 `tool/result` 并收集 `additionalContexts`。实时界面可以显示多个待处理调用,但结果和工具执行后的上下文仍按模型顺序排列。 -如果在一组启动前中止,系统不会记录该组的任何调用。如果在一组执行期间中止,系统会停止补充池,等待已启动的调用,按顺序提交其结果,在这些结果之后排空已接受的批次上下文,然后通过现有中止路径结束该步骤。从未启动的调用没有审计事件。调度器发生意外故障时,会停止新的派发,等待每项已启动的派发结算,并重新抛出第一个故障。由于该故障是内部终态,而非工具结果,循环不会为被拒绝或未提交的调用虚构工具结果。 +中止会停止补充池,等待已启动的调用,按顺序提交其结果,在这些结果之后排空已接受的批次上下文,并为未派发的调用记录合成的中止调用/结果对。调度器发生意外故障时,会停止新的派发,等待每项已启动的派发结算,并重新抛出第一个故障。驱动器按照[失败步骤恢复决策](../bug-fix/2026-09-19-failed-step-tool-results.zh.md),在关闭失败步骤之前记录缺失结果。 PTC mode 仍不使用此调度器,因为模型只会发出一个原生 `run_code` 调用。`run_code` 及其内部派发队列仍按串行方式执行;`mode: 'both'` 中的原生并列调用使用常规调度器。 @@ -84,7 +84,7 @@ PTC mode 仍不使用此调度器,因为模型只会发出一个原生 `run_co **公开分阶段方法或调度 waterfall(瀑布式事件)。** 公开的 `prepare` / `dispatch` / `finalize` 方法或 `tools/execution-mode` 事件,会在另一个消费方需要它之前扩大扩展接口。循环使用内部调度器视图,而 `executionMode(exec)` 为策略钩子保留了插入点。 -**将调度器故障转换为工具结果。** AgentLoop 无法判断被拒绝的派发是否已调用工具主体;ToolRuntime 负责工具主体调用状态和类型化工具结果。因此,内部调度器故障保持为终态,而不会被重新分类为 `ABORTED` 结果。 +**将调度器故障转换为普通工具结果。** AgentLoop 无法判断被拒绝的派发是否已调用工具主体;ToolRuntime 负责工具主体调用状态和类型化工具结果。内部调度器故障保持为终态,而不会被重新分类为 `ABORTED` 结果。显式恢复结果保留这种不确定性,同时避免留下尚无结果的调用。 **在模型流式输出时启动调用。** 这可能进一步降低延迟,但会改变 assistant 消息的权威性、回放以及调用/结果配对。调度器只在 assistant 消息完成后才启动。 @@ -96,7 +96,7 @@ PTC mode 仍不使用此调度器,因为模型只会发出一个原生 `run_co 该设计遵循安全侧原则,对工具作者而言也很简单,但无法利用必须通过比较并列调用才能确认安全的并发性。工具过于宽泛地选择并行,可能暴露潜在的共享状态竞态。 -在某些情形下,并行调用会先行启动,而串行执行原本会在轮到这些调用之前中止。因此,调度器只记录已启动的调用,在中止时将其排空,且取消后绝不启动替换调用。 +在某些情形下,并行调用会先行启动,而串行执行原本会在轮到这些调用之前中止。调度器在中止时将其排空,且取消后绝不启动替换调用;未派发的调用获得合成的中止调用/结果对。 有序提交可能会让快速结果等待较慢的早期并列调用。这保留了回放和模型历史顺序,同时实时界面仍可显示待处理进度。 @@ -104,4 +104,4 @@ PTC mode 仍不使用此调度器,因为模型只会发出一个原生 `run_co 工具注册是调度边界。调度器会在每个屏障之后以及每次补充池之前重新分类,因此注册表变更会影响尚未启动的调用。已启动的调用保留它们进入池时所依据的调度决策。 -终态调度器故障可能会在故障步骤关闭前留下已记录但没有结果的调用。等待仍在运行的派发可确保完全停稳,而不会将这些内部故障误报为工具结果。 +终态调度器故障只有在运行中的派发结算后才会进入步骤恢复。恢复将未提交的执行结果报告为未知,保留完全停稳与原始失败,同时让后续模型历史保持可用。 diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 8bac404b2d..6cd25b059d 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -35,7 +35,7 @@ env: jobs: build: - runs-on: ubuntu-latest + runs-on: blacksmith-8vcpu-ubuntu-2404 permissions: contents: read pages: read diff --git a/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts b/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts index a9d9821ec4..eef3fb2908 100644 --- a/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts +++ b/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts @@ -29,15 +29,13 @@ it('boots default Web without experimental modules, scheduling, time context, or expect(roster.plugins.length).toBeGreaterThan(roster.entries.length) expect(roster.modules.some(url => modulePackage(url) === '@deepseek-ai/dsh')).toBe(true) expect(roster.client.entries.length).toBeGreaterThan(0) - for (const name of [ - '@deepseek-ai/dsh-client-ui-sidebar-browser', - '@deepseek-ai/dsh-time-context', - '@deepseek-ai/dsh-schedule', - '@deepseek-ai/dsh-client-ui-schedule', - ]) { - const entry = roster.entries.find(entry => entry.name === name) - expect(entry, name).toBeDefined() - expect(entry!.state, name).toBeUndefined() + const browser = roster.entries.find(entry => entry.name === '@deepseek-ai/dsh-client-ui-sidebar-browser') + expect(browser).toBeDefined() + expect(browser!.state).toBeUndefined() + expect(delivered.entries.some(entry => entry.id === '@deepseek-ai/dsh-client-ui-sidebar-browser')).toBe(false) + // The optional Schedule bundle inserts these rows; the shipped composition carries none of them. + for (const name of ['@deepseek-ai/dsh-time-context', '@deepseek-ai/dsh-schedule', '@deepseek-ai/dsh-client-ui-schedule']) { + expect(roster.entries.some(entry => entry.name === name), name).toBe(false) expect(delivered.entries.some(entry => entry.id === name), name).toBe(false) } expect(experimentalRuntimeReferences(roster)).toEqual([]) diff --git a/apps/desktop/README.i18n.yaml b/apps/desktop/README.i18n.yaml index 283daee44b..93f5641fb1 100644 --- a/apps/desktop/README.i18n.yaml +++ b/apps/desktop/README.i18n.yaml @@ -21,8 +21,8 @@ en: 0d11af8639977a6d zh: 86b086b12d2210d3 /deepseek-harness-desktop/develop: - en: 9b9b362a9865e763 - zh: c748901168e2db8c + en: 40d2ad4014bf445b + zh: 6a08de06df9ed042 /deepseek-harness-desktop/develop/startup-onboarding: en: a51346b8d880ddba zh: 7913214c1c66063c diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 3521798b31..cf780cddc6 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -136,7 +136,7 @@ pnpm run start:desktop The Web counterparts are `pnpm run dev:web` and `pnpm run start:web`, documented in the [development guide](../../docs/development.md). Workspace development runs the current CLI and private Desktop Host packages under Electron RunAsNode. Plugin management and recovery use `$DSH_HOME/profiles/desktop`, separate from the disposable workspace runtime. The Host uses runtime module resolution in both development and packaged builds without creating official-package fallback links; developer-installed packages, including links, retain native priority. Use an unpacked application to exercise Electron RunAsNode, bundled pnpm, bundled dsh resources, plugin installation and repair paths. -The [native/renderer keyboard tests](tests/keyboard.spec.ts) compile in [their own Client test project](../../tsconfig.desktop-keyboard-tests.json), reached by the repository Client typecheck. Its Desktop imports are limited to Cordis-free input, persistence, IPC, browser-guest, and overlay modules. +The [native/renderer keyboard tests](tests/keyboard.spec.ts) compile as part of the repository Client typecheck. Their Desktop imports are limited to Cordis-free input, persistence, IPC, browser-guest, and overlay modules. ### Startup onboarding diff --git a/apps/desktop/README.zh.md b/apps/desktop/README.zh.md index 372c19dba7..a64cbd2246 100644 --- a/apps/desktop/README.zh.md +++ b/apps/desktop/README.zh.md @@ -138,7 +138,7 @@ pnpm run start:desktop Web 侧的对应命令是 `pnpm run dev:web` 与 `pnpm run start:web`,见[开发指南](../../docs/development.zh.md)。Workspace 开发使用 Electron RunAsNode 运行当前 CLI 与私有 Desktop Host 包,插件管理和恢复使用 `$DSH_HOME/profiles/desktop`,与一次性工作区运行时分离。Host 在开发与打包构建中都使用 runtime 模块解析,不创建官方包的 fallback 链接;开发者安装的包(包括链接)保留原生优先级。需要验证 Electron RunAsNode、内置 pnpm、内置 dsh 资源、插件安装和修复时,应运行未封装安装器的应用目录。 -[原生输入与渲染进程键盘测试](tests/keyboard.spec.ts)在[独立的 Client 测试项目](../../tsconfig.desktop-keyboard-tests.json)中编译,由仓库 Client 类型检查纳入。它只导入不依赖 Cordis 的 Desktop 输入、持久化、IPC、浏览器 guest 和蒙层模块。 +[原生输入与渲染进程键盘测试](tests/keyboard.spec.ts)直接纳入仓库 Client 类型检查。它只导入不依赖 Cordis 的 Desktop 输入、持久化、IPC、浏览器 guest 和蒙层模块。 ### 启动引导 diff --git a/apps/web/tests/expected/plugin-config/official.expected.md b/apps/web/tests/expected/plugin-config/official.expected.md index 247d6ac308..dddc056ff2 100644 --- a/apps/web/tests/expected/plugin-config/official.expected.md +++ b/apps/web/tests/expected/plugin-config/official.expected.md @@ -14,9 +14,9 @@ - text: 实验性 提供自动审查权限模式,由模型在每次工具调用前判断是否授权。 - switch "启用 自动授权审查" - listitem: - - button "查看 定时与时间上下文": 定时与时间上下文 - - text: 实验性 启用持久提醒、自动化任务页面与逐步时钟上下文。 - - switch "启用 定时与时间上下文" + - button "查看 自动化任务": 自动化任务 + - text: 实验性 按设定的时间或周期,在会话中自动执行任务。 + - switch "启用 自动化任务" - listitem: - button "查看 语音输入": 语音输入 - text: 实验性 在本机使用 SenseVoice 转写录音;首次使用需安装依赖 diff --git a/apps/web/tests/expected/plugin-manager/live-enabled.expected.md b/apps/web/tests/expected/plugin-manager/live-enabled.expected.md index 4bd8ed064e..086bfee63c 100644 --- a/apps/web/tests/expected/plugin-manager/live-enabled.expected.md +++ b/apps/web/tests/expected/plugin-manager/live-enabled.expected.md @@ -14,9 +14,9 @@ - text: 实验性 提供自动审查权限模式,由模型在每次工具调用前判断是否授权。 - switch "启用 自动授权审查" - listitem: - - button "查看 定时与时间上下文": 定时与时间上下文 - - text: 实验性 启用持久提醒、自动化任务页面与逐步时钟上下文。 - - switch "启用 定时与时间上下文" + - button "查看 自动化任务": 自动化任务 + - text: 实验性 按设定的时间或周期,在会话中自动执行任务。 + - switch "启用 自动化任务" - listitem: - button "查看 语音输入": 语音输入 - text: 实验性 在本机使用 SenseVoice 转写录音;首次使用需安装依赖 diff --git a/apps/web/tests/expected/plugin-manager/manager.expected.md b/apps/web/tests/expected/plugin-manager/manager.expected.md index b188be8787..90f137a80e 100644 --- a/apps/web/tests/expected/plugin-manager/manager.expected.md +++ b/apps/web/tests/expected/plugin-manager/manager.expected.md @@ -14,9 +14,9 @@ - text: 实验性 提供自动审查权限模式,由模型在每次工具调用前判断是否授权。 - switch "启用 自动授权审查" - listitem: - - button "查看 定时与时间上下文": 定时与时间上下文 - - text: 实验性 启用持久提醒、自动化任务页面与逐步时钟上下文。 - - switch "启用 定时与时间上下文" + - button "查看 自动化任务": 自动化任务 + - text: 实验性 按设定的时间或周期,在会话中自动执行任务。 + - switch "启用 自动化任务" - listitem: - button "查看 语音输入": 语音输入 - text: 实验性 在本机使用 SenseVoice 转写录音;首次使用需安装依赖 diff --git a/apps/web/tests/expected/settings-chrome/plugin-instances.expected.md b/apps/web/tests/expected/settings-chrome/plugin-instances.expected.md index 612291fe84..16d2109528 100644 --- a/apps/web/tests/expected/settings-chrome/plugin-instances.expected.md +++ b/apps/web/tests/expected/settings-chrome/plugin-instances.expected.md @@ -1,23 +1,20 @@ - list: - listitem: - - button "tool-subagent-control, tool-subagent-control, 已启用": + - button "tool-subagent-control, 已启用": - strong: tool-subagent-control - - text: 已启用 Globally named send_message, interrupt_agent, and list_agents tools over ctx.subagents continuations - - code: tool-subagent-control + - text: Globally named send_message, interrupt_agent, and list_agents tools over ctx.subagents continuations - listitem: - button "tool-subagent-control/list-agents, tool-subagent-list-agents, 已启用": - strong: tool-subagent-control/list-agents - - text: 已启用 - code: tool-subagent-list-agents - listitem: - - button "tool-subagent, tool-subagent, 已启用": + - button "tool-subagent, 已启用": - strong: tool-subagent - - text: 已启用 Model-facing subagent delegation tool over the ctx.subagents seam - - code: tool-subagent + - text: Model-facing subagent delegation tool over the ctx.subagents seam - listitem: - button "tool-subagent, tool-subagent-fork, 已启用": - strong: tool-subagent - - text: 已启用 Model-facing subagent delegation tool over the ctx.subagents seam + - text: Model-facing subagent delegation tool over the ctx.subagents seam - code: tool-subagent-fork - listitem: - button "tool-subagent, tool-subagent-codex, 已停用": diff --git a/apps/web/tests/expected/settings-chrome/plugins.expected.md b/apps/web/tests/expected/settings-chrome/plugins.expected.md index 7cfe33f5f0..20e096a308 100644 --- a/apps/web/tests/expected/settings-chrome/plugins.expected.md +++ b/apps/web/tests/expected/settings-chrome/plugins.expected.md @@ -1,5 +1,4 @@ - listitem: - - button "tool-subagent, tool-subagent, 已启用": + - button "tool-subagent, 已启用": - strong: tool-subagent - - text: 已启用 Model-facing subagent delegation tool over the ctx.subagents seam - - code: tool-subagent + - text: Model-facing subagent delegation tool over the ctx.subagents seam diff --git a/apps/web/tests/plugin-manager.e2e.ts b/apps/web/tests/plugin-manager.e2e.ts index cfa6a9a763..01523cc13e 100644 --- a/apps/web/tests/plugin-manager.e2e.ts +++ b/apps/web/tests/plugin-manager.e2e.ts @@ -239,6 +239,8 @@ describe('web e2e: plugin manager', () => { await panel.getByRole('button', { name: 'Back to plugins' }).click() await panel.getByRole('button', { name: 'View Agent Teams', exact: true }).waitFor() expect(await panel.getByRole('switch', { name: 'Enable Agent Teams', exact: true }).count()).toBe(1) + await panel.getByRole('button', { name: 'View Automation tasks', exact: true }).waitFor() + expect(await panel.getByText('Run tasks in your sessions at a set time or on a repeating schedule.', { exact: true }).count()).toBe(1) // The official configuration pages follow the language too, from their own dictionary. for (const title of ['Shell', 'Agent loop', 'Subagent', 'Web search']) { await panel.getByRole('button', { name: `View ${title}`, exact: true }).waitFor() @@ -312,6 +314,37 @@ describe('web e2e: plugin manager', () => { expect(tripwire.pageErrors).toEqual([]) }, 60_000) + it('lists the Automation tasks rows and mounts them with the bundle switch', async () => { + onTestFailed(() => saveFailureShot(page, 'web-e2e-plugin-manager-schedule')) + const panel = await openPluginsPanel() + const scheduleRows = () => [...scaffold.ctx.loader.entries()] + .filter(entry => ['time-context', 'schedule', 'ui-schedule'].includes(entry.options.id)) + const running = () => scheduleRows().filter(entry => entry.fiber?.state === FiberState.ACTIVE).length + expect(running()).toBe(0) + await panel.getByRole('button', { name: '查看 自动化任务', exact: true }).click() + const rows = panel.locator('[data-plugin-rows]') + for (const title of ['时间感知', '任务调度', '任务界面']) { + await rows.locator('[data-plugin-row]', { hasText: title }).waitFor() + } + // A bundle that is off offers no row switches. + expect(await rows.getByRole('switch').count()).toBe(0) + const toggle = panel.getByRole('switch', { name: '启用 自动化任务', exact: true }) + await toggle.click() + try { + await expect.poll(running, { timeout: 20_000 }).toBe(3) + await expect.poll(() => rows.locator('[data-plugin-row]', { hasText: '运行中' }).count(), { timeout: 20_000 }).toBe(3) + for (const title of ['时间感知', '任务调度', '任务界面']) { + await rows.getByRole('switch', { name: `启用组件 ${title}`, exact: true }).waitFor() + } + await page.getByRole('navigation', { name: '全局面板' }).getByRole('button', { name: '自动化任务', exact: true }).waitFor() + } finally { + if (await toggle.getAttribute('aria-checked') === 'true') await toggle.click() + await expect.poll(running, { timeout: 20_000 }).toBe(0) + await panel.getByRole('button', { name: '返回插件列表' }).click() + } + expect(tripwire.pageErrors).toEqual([]) + }, 60_000) + it('checks a spec before installing it and words what the check refused', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-plugin-manager-install')) const panel = await openPluginsPanel() diff --git a/apps/web/tests/schedule-after.e2e.ts b/apps/web/tests/schedule-after.e2e.ts index c0f131f3d3..efa4f86ccb 100644 --- a/apps/web/tests/schedule-after.e2e.ts +++ b/apps/web/tests/schedule-after.e2e.ts @@ -839,19 +839,20 @@ describe.skipIf(MODE === 'record')('web e2e: active Schedule catalog', () => { it('reads stored tasks without a live Session and deletes them through the catalog', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-schedule-catalog')) - const shipped = composeEntries([ + const layers = [ loadOverlayPatches('Schedule catalog shipped roster', BASE_PATCH), ...WEB_PATCHES.map(file => loadOverlayPatches('Schedule catalog shipped roster', file)), - ]) - expect(shipped.find(entry => entry.id === 'ui-schedule')).toMatchObject({ - name: '@deepseek-ai/dsh-client-ui-schedule', - }) - expect(shipped.find(entry => entry.id === 'ui-schedule')?.disabled).toBe(true) + ] + const shipped = composeEntries(layers) + const withBundle = composeEntries([...layers, loadOverlayPatches('Schedule catalog bundle', SCHEDULE_BUNDLE)]) for (const row of [ { id: 'time-context', name: '@deepseek-ai/dsh-time-context' }, { id: 'schedule', name: '@deepseek-ai/dsh-schedule' }, + { id: 'ui-schedule', name: '@deepseek-ai/dsh-client-ui-schedule' }, ]) { - expect(shipped.filter(entry => entry.id === row.id && entry.name === row.name)).toHaveLength(1) + // The shipped Web composition carries none of the rows; the bundle inserts each once, switched on. + expect(shipped.some(entry => entry.id === row.id)).toBe(false) + expect(withBundle.filter(entry => entry.id === row.id && entry.name === row.name && entry.disabled !== true)).toHaveLength(1) } await page.getByRole('button', { name: 'Automation tasks', exact: true }).click() diff --git a/apps/web/tests/settings-chrome.e2e.ts b/apps/web/tests/settings-chrome.e2e.ts index 14278995c4..54b5e5f6f8 100644 --- a/apps/web/tests/settings-chrome.e2e.ts +++ b/apps/web/tests/settings-chrome.e2e.ts @@ -111,16 +111,17 @@ describe('web e2e: settings modal and General preferences', () => { // rewrite this surface's golden. await dialog.getByRole('button', { name: '内置插件', exact: true }).click() await dialog.getByRole('heading', { name: '内置插件', exact: true }).waitFor({ timeout: 10_000 }) - // Both groups start collapsed; the preset group's header still carries its display-only switcher. + // The preset group starts open under its display-only switcher; the global group starts folded. const presetSwitcher = dialog.getByRole('button', { name: '选择要查看的 Agent 预设' }) await presetSwitcher.waitFor({ timeout: 10_000 }) // The shipped default's zh display name comes from the zh dictionaries. expect(await presetSwitcher.textContent()).toBe('标准模式(默认)') const presetToggle = dialog.getByRole('button', { name: '会话插件', exact: true }) - expect(await presetToggle.getAttribute('aria-expanded')).toBe('false') - expect(await dialog.locator('[data-plugin-scope="preset"] [data-plugin-entry]').count()).toBe(0) - await presetToggle.click() - await dialog.getByRole('button', { name: /^全局/ }).click() + expect(await presetToggle.getAttribute('aria-expanded')).toBe('true') + expect(await dialog.locator('[data-plugin-scope="preset"] [data-plugin-entry]').count()).toBeGreaterThan(0) + const globalToggle = dialog.getByRole('button', { name: /^全局/ }) + expect(await globalToggle.getAttribute('aria-expanded')).toBe('false') + await globalToggle.click() const pluginRow = dialog.locator(PLUGIN_ROW_SELECTOR) await pluginRow.waitFor({ timeout: 10_000 }) const expectedPluginCount = [...scaffold.ctx.loader.entries()] @@ -132,11 +133,12 @@ describe('web e2e: settings modal and General preferences', () => { // presets took over included, preset compositions excluded. expect(await dialog.locator('[data-plugin-scope="global"] [data-plugin-entry]').count()) .toBe(expectedPluginCount) - // The enablement tag is the row's collapsed status: an active fiber draws no - // dot, so no global row names the active phase. Guard the assertion against - // matching nothing because no row is enabled. - expect(await dialog.locator('[data-plugin-scope="global"] [data-plugin-entry] button[aria-label$="已启用"]').count()) - .toBeGreaterThan(0) + // A plainly enabled row states its status only in its accessible name: it + // carries no tag, and an active fiber draws no dot, so no global row shows + // either. Guard the assertions against matching nothing because no row is enabled. + const enabledRows = dialog.locator('[data-plugin-scope="global"] [data-plugin-entry] button[aria-label$="已启用"]') + expect(await enabledRows.count()).toBeGreaterThan(0) + expect(await enabledRows.getByText('已启用', { exact: true }).count()).toBe(0) expect(await dialog.locator('[data-plugin-scope="global"] [role="img"][aria-label="运行中"]').count()).toBe(0) expect(await dialog.locator('[data-plugin-count]').getAttribute('data-plugin-count')) .toBe(String(expectedPluginCount)) @@ -159,10 +161,17 @@ describe('web e2e: settings modal and General preferences', () => { ] as const for (const [entryId, status] of instanceRows) { const row = dialog.locator(`[data-plugin-scope="preset"] [data-plugin-entry="${entryId}"]`) - const trigger = row.getByRole('button', { name: `tool-subagent, ${entryId}, ${status}`, exact: true }) + // An id that repeats the title adds nothing, so its card shows no chip and its name omits it. + const repeatsTitle = entryId === 'tool-subagent' + const name = repeatsTitle ? `tool-subagent, ${status}` : `tool-subagent, ${entryId}, ${status}` + const trigger = row.getByRole('button', { name, exact: true }) await trigger.waitFor({ timeout: 10_000 }) expect(await trigger.getAttribute('aria-expanded')).toBe('false') const identity = row.locator('code') + if (repeatsTitle) { + expect(await identity.count()).toBe(0) + continue + } expect(await identity.textContent()).toBe(entryId) expect(await identity.getAttribute('title')).toBe(entryId) } diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index efc66e50ef..06758c387e 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -24,8 +24,8 @@ en: 214c3c81c21df6f9 zh: 75d2cdc3c9fc9930 /deepseek-harness-architecture/turn-flow: - en: e13500f6d5b614d6 - zh: 7e6acb96a520ded2 + en: badc0ffea030cdce + zh: 3241f0b37c3ec067 /deepseek-harness-architecture/session-log: en: e1e3a44140036f25 zh: 84c70c969e5ad216 diff --git a/docs/architecture.md b/docs/architecture.md index 0919d65a30..b5dfb6571b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -114,6 +114,8 @@ One inbox feeds the driver; injected context waits for a waking message. AgentLo The loop sends immutable requests with live cancellation and reuses freeze evidence for frozen identities; [agent-loop](../packages/core/agent-loop/README.md) owns construction and cancellation causes. +Failed steps [record missing tool results](../packages/core/agent-loop/README.md#understand-the-implementation). + Details: the [sequence diagram](agent-lifecycle.md), the [tool pipeline](tool-execution-pipeline.md), and [cancellation and error recovery](subsystems/core.md#the-agent-handle). ## Session log diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index bf3fb3b649..c7fdc96a90 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -118,6 +118,8 @@ turn/end 循环发送不可变请求,同时保持取消有效,仅对完全冻结的对象复用冻结证据;[agent-loop](../packages/core/agent-loop/README.zh.md) 负责请求构建与取消原因。 +失败步骤会[记录缺失的工具结果](../packages/core/agent-loop/README.zh.md#understand-the-implementation)。 + 详情见[时序图](agent-lifecycle.zh.md)、[工具流水线](tool-execution-pipeline.zh.md)和[取消与错误恢复](subsystems/core.zh.md#the-agent-handle)。 ## 会话日志 diff --git a/docs/subsystems/schedule.i18n.yaml b/docs/subsystems/schedule.i18n.yaml index 13c6d192fe..614eb31d1e 100644 --- a/docs/subsystems/schedule.i18n.yaml +++ b/docs/subsystems/schedule.i18n.yaml @@ -9,8 +9,8 @@ en: 41cd197b5c74bfa3 zh: 18ed11259036d726 /host-wide-schedule/absolute-time-input: - en: 0876a14a60ba2160 - zh: e7d4bd9099c14fb2 + en: 8186d070ab412545 + zh: 968b752df9e03b68 /host-wide-schedule/daily-wall-clock-input: en: 2d249edc3dfccb8a zh: 33fecda132b03b84 @@ -27,8 +27,8 @@ en: 0656173de50ee132 zh: 54176540371d0f28 /host-wide-schedule/web-catalog: - en: 60c88b08abde9bb0 - zh: 0a3644cfa86d014d + en: 9955c4857340103a + zh: edfa0491c8a08db7 /host-wide-schedule/timing-edits: en: ce5d2095a3ad2e82 zh: 167b3715f8eaffc1 diff --git a/docs/subsystems/schedule.md b/docs/subsystems/schedule.md index 571084d0ae..4d8f4a1c0e 100644 --- a/docs/subsystems/schedule.md +++ b/docs/subsystems/schedule.md @@ -116,7 +116,7 @@ interface LocalAtInput { type AtInput = string | LocalAtInput ``` -The shipped Web composition carries the `time-context` row disabled; the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle`, enabled from the Plugins page, mounts time-context, which samples the browser's IANA zone for every prompt. Time-context tells the model to interpret otherwise-unqualified natural-language dates and times in that request-local zone when the open turn has one unambiguous browser zone; mixed or missing browser-zone records tell the model to ask. That guidance is not a durable Session default: the model must still pass an offset in the string form or `time_zone` in the local form, and Schedule never reads browser, Session, process, or model context. +The shipped Web composition carries no `time-context` row; the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle`, enabled from the Plugins page, inserts and mounts time-context, which samples the browser's IANA zone for every prompt. Time-context tells the model to interpret otherwise-unqualified natural-language dates and times in that request-local zone when the open turn has one unambiguous browser zone; mixed or missing browser-zone records tell the model to ask. That guidance is not a durable Session default: the model must still pass an offset in the string form or `time_zone` in the local form, and Schedule never reads browser, Session, process, or model context. Schedule rejects invalid offsets and zones, offset-free strings, non-future targets, and local times inside daylight-saving gaps. A daylight-saving overlap chooses its first, earlier instant. Successful creation stores only canonical UTC `scheduledAt`, so replay never depends on ambient time-zone state. @@ -339,7 +339,7 @@ type ScheduleCatalogEntry = ScheduleRecord & { The Remote `schedule.list({ sessionId })`, model `schedule_list`, and Session-header catalog return only active tasks. A model view's derived timing `state` remains distinct from stored lifecycle `status`. Deletion uses `schedule.delete({ sessionId, id })` with the entry's original binding; a mismatched binding returns not found. Model tools supply the current Agent's Session, whereas the global user interface supplies the selected task's binding. The binding check alone does not establish caller authorization. The payload-free `schedule/changed` event invalidates client lists; reconnecting clients fetch current state again. -The shipped Web composition carries the `ui-schedule` row disabled; the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle`, enabled from the Plugins page, mounts `ui-schedule` with the Host capability. The [client package](../../packages/client/ui-schedule/README.md) owns the catalog, empty state, and deletion controls. The page separately filters all, active, and inactive tasks, retains inactive details and an original-Session control in the detail tab strip, and requires explicit confirmed deletion. Rules and Delivery records separate task settings from lazily paged saved receipts. Neither a receipt nor inactive status confirms model execution. +The shipped Web composition carries no `ui-schedule` row; the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle`, enabled from the Plugins page, inserts and mounts `ui-schedule` with the Host capability. The [client package](../../packages/client/ui-schedule/README.md) owns the catalog, empty state, and deletion controls. The page separately filters all, active, and inactive tasks, retains inactive details and an original-Session control in the detail tab strip, and requires explicit confirmed deletion. Rules and Delivery records separate task settings from lazily paged saved receipts. Neither a receipt nor inactive status confirms model execution. ## Timing edits diff --git a/docs/subsystems/schedule.zh.md b/docs/subsystems/schedule.zh.md index bd210a11a2..eff76ade44 100644 --- a/docs/subsystems/schedule.zh.md +++ b/docs/subsystems/schedule.zh.md @@ -116,7 +116,7 @@ interface LocalAtInput { type AtInput = string | LocalAtInput ``` -随发行版交付的 Web 组合把 `time-context` 行保持禁用;在插件管理页启用可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 会挂载 time-context,它为每条提示词采样浏览器的 IANA 时区。当 open turn 只有一个无歧义的浏览器时区时,Time-context 会告诉模型按该请求本地时区解释未明确限定时区的自然语言日期和时间;浏览器时区记录混合或缺失时,则告诉模型询问用户。该指引不是持久 Session 默认值:模型仍必须在字符串形式中传入偏移量,或在本地形式中传入 `time_zone`;Schedule 绝不会读取浏览器、Session、进程或模型上下文。 +随发行版交付的 Web 组合不含 `time-context` 行;在插件管理页启用可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 会插入并挂载 time-context,它为每条提示词采样浏览器的 IANA 时区。当 open turn 只有一个无歧义的浏览器时区时,Time-context 会告诉模型按该请求本地时区解释未明确限定时区的自然语言日期和时间;浏览器时区记录混合或缺失时,则告诉模型询问用户。该指引不是持久 Session 默认值:模型仍必须在字符串形式中传入偏移量,或在本地形式中传入 `time_zone`;Schedule 绝不会读取浏览器、Session、进程或模型上下文。 Schedule 会拒绝无效偏移量与时区、不带偏移量的字符串、非未来目标,以及落在夏令时缺口内的本地时间。遇到夏令时重叠时,会选择第一次出现的较早时点。创建成功后只存储规范化后的 UTC `scheduledAt`,因此回放绝不依赖环境时区状态。 @@ -339,7 +339,7 @@ type ScheduleCatalogEntry = ScheduleRecord & { Remote 方法 `schedule.list({ sessionId })`、模型 `schedule_list` 和 Session 页头目录仅返回活动任务。模型视图派生的时间 `state` 与存储的生命周期 `status` 仍是不同概念。删除通过 `schedule.delete({ sessionId, id })` 使用条目的原始绑定;绑定不匹配时返回未找到。模型工具传入当前 Agent 的 Session,全局用户界面则传入所选任务的绑定。仅校验绑定并不构成调用者鉴权。无 payload 的 `schedule/changed` 事件通知客户端刷新列表;重新连接后,客户端再次获取当前状态。 -随发行版交付的 Web 组合把 `ui-schedule` 行保持禁用;在插件管理页启用可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 会将它与宿主能力一起挂载。[客户端包](../../packages/client/ui-schedule/README.zh.md) 负责目录、空状态与删除控件。页面单独筛选全部、活动和未运行任务,保留未运行任务详情及详情页签条内的原 Session 入口,并要求显式确认删除。“规则”和“发送记录”将任务设置与按需分页加载的已保存回执分开。回执与未运行状态均不确认模型执行。 +随发行版交付的 Web 组合不含 `ui-schedule` 行;在插件管理页启用可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 会插入它并与宿主能力一起挂载。[客户端包](../../packages/client/ui-schedule/README.zh.md) 负责目录、空状态与删除控件。页面单独筛选全部、活动和未运行任务,保留未运行任务详情及详情页签条内的原 Session 入口,并要求显式确认删除。“规则”和“发送记录”将任务设置与按需分页加载的已保存回执分开。回执与未运行状态均不确认模型执行。 ## 修改时间 diff --git a/docs/user/guide/schedule.i18n.yaml b/docs/user/guide/schedule.i18n.yaml index 1bf88dec7c..1a1a1c08f9 100644 --- a/docs/user/guide/schedule.i18n.yaml +++ b/docs/user/guide/schedule.i18n.yaml @@ -12,8 +12,8 @@ en: b3b2a6e0ce02bdee zh: 2ddb168de79fd19c /schedule-reminders/create-reminders: - en: c76ee99f59529942 - zh: be1d75c226231dd7 + en: 68763ffeedaeea3c + zh: 739af012a337d448 /schedule-reminders/inspect-and-delete-tasks: en: a44fc1aabc2ab957 zh: 31f444d4c7fd5c7a diff --git a/docs/user/guide/schedule.md b/docs/user/guide/schedule.md index abb86b7f8e..97db8089b0 100644 --- a/docs/user/guide/schedule.md +++ b/docs/user/guide/schedule.md @@ -17,7 +17,7 @@ Create reminders in a conversation, then inspect active and inactive tasks and e ## Create reminders -The shipped Web profile carries the `time-context`, `schedule`, and `ui-schedule` rows disabled. Open Plugins and enable `Scheduling and time context` in the Official group; the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle` then mounts Schedule with the clock context that gives the model the current time and the browser's zone. Configure a model provider before asking it to create reminders. +The shipped Web profile does not include Schedule. Open Plugins and enable Automation tasks in the Official group; the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle` then adds Schedule with the clock context that gives the model the current time and the browser's zone. Configure a model provider before asking it to create reminders. Ask the model to create, list, edit, or delete reminders. It uses `schedule_create`, `schedule_list`, `schedule_update`, and `schedule_delete` (update changes one reminder in place and keeps its id and saved delivery records); the Automation tasks page's New action opens a New Session for a creation instead, with the request already written in its composer. Supported choices are a one-time delay in positive whole seconds, an absolute date and time, a fixed interval of at least one minute, a daily local time with an IANA time zone, a weekly local time with an IANA time zone and ISO weekdays from Monday 1 through Sunday 7, or a five-field cron expression with an explicit IANA time zone, stored in canonical form. diff --git a/docs/user/guide/schedule.zh.md b/docs/user/guide/schedule.zh.md index bd10a06fe5..fcfbfc1082 100644 --- a/docs/user/guide/schedule.zh.md +++ b/docs/user/guide/schedule.zh.md @@ -17,7 +17,7 @@ ## 创建提醒 -发布的 Web profile 把 `time-context`、`schedule` 与 `ui-schedule` 三行保持禁用。打开插件管理页,在 Official 分组中启用“定时与时间上下文”;可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 随即挂载 Schedule 及其时钟上下文,后者把当前时间和浏览器时区交给模型。在请求模型创建提醒前,先配置模型 provider。 +发布的 Web profile 不含 Schedule。打开插件管理页,在“官方”分组中启用“自动化任务”;可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 随即加入 Schedule 及其时钟上下文,后者把当前时间和浏览器时区交给模型。在请求模型创建提醒前,先配置模型 provider。 向模型提出创建、列出、修改或删除提醒的请求。模型使用 `schedule_create`、`schedule_list`、`schedule_update` 和 `schedule_delete`(更新原地修改一条提醒,保留其 id 与已保存的投递记录);「自动化任务」页面的「新建」操作则为创建打开一个新会话,其 composer 已写好该请求。支持正整数秒的一次性延迟、绝对日期时间、至少一分钟的固定间隔、带 IANA 时区的每日本地时间、带 IANA 时区和 ISO 星期(周一 1 至周日 7)的每周本地时间,以及带显式 IANA 时区的五字段 cron 表达式(表达式以规范化形式保存)。 diff --git a/package.json b/package.json index 81a370d4c4..4317f7dc94 100644 --- a/package.json +++ b/package.json @@ -181,8 +181,8 @@ "gen-third-party-notices": "tsx scripts/gen-third-party-notices.ts", "verify-third-party-notices": "tsx scripts/gen-third-party-notices.ts --check", "gen-module-graph": "tsx scripts/gen-module-graph.ts", - "gen-scoped-events": "tsx scripts/gen-scoped-events.ts", - "verify-scoped-events": "tsx scripts/gen-scoped-events.ts --check", + "gen-scoped-events": "node --max-old-space-size=4096 --import tsx/esm scripts/gen-scoped-events.ts", + "verify-scoped-events": "node --max-old-space-size=4096 --import tsx/esm scripts/gen-scoped-events.ts --check", "verify-module-graph": "tsx scripts/gen-module-graph.ts --check", "constraints": "tsx scripts/check-workspace-constraints.ts", "doc-sync": "tsx scripts/run-gates.ts doc-sync", diff --git a/packages/bundle/web-app/README.i18n.yaml b/packages/bundle/web-app/README.i18n.yaml index f69476f693..161739e50d 100644 --- a/packages/bundle/web-app/README.i18n.yaml +++ b/packages/bundle/web-app/README.i18n.yaml @@ -21,8 +21,8 @@ en: b7ab01cfe9fed40f zh: 23703ab7727c3ab5 /deepseek-ai-dsh-web-app/use-this-package/configuration: - en: cea7ecccb2a03b79 - zh: 0af72c22e46045d3 + en: a93760e9e451faa6 + zh: 5dab82dd20aa952f /deepseek-ai-dsh-web-app/use-this-package/lan-access-and-trusted-hosts: en: 2522c806db384ddc zh: 7e96f11194af9f9f diff --git a/packages/bundle/web-app/README.md b/packages/bundle/web-app/README.md index 42370ff203..952ff9cafa 100644 --- a/packages/bundle/web-app/README.md +++ b/packages/bundle/web-app/README.md @@ -53,7 +53,7 @@ Most users never set these; the command-line flags feed the four settings below | `surfaceContext` | `true` | Give the agent GUI-orientation context and expose `DSH_WEB_URL` to its shell commands | | `trustedHosts` | `[]` | Extra hosts allowed to reach the GUI from the network | -The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-web-app) is the exhaustive source for every accepted field and its JSDoc. The shipped composition disables `schedule`, `ui-schedule`, and `time-context` by default, and the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle` switches those three rows on from the Plugins page. +The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-web-app) is the exhaustive source for every accepted field and its JSDoc. The shipped composition carries no `time-context`, `schedule`, or `ui-schedule` row; the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle` inserts those three rows from the Plugins page. ### LAN access and trusted hosts diff --git a/packages/bundle/web-app/README.zh.md b/packages/bundle/web-app/README.zh.md index fe276317be..a82057b09f 100644 --- a/packages/bundle/web-app/README.zh.md +++ b/packages/bundle/web-app/README.zh.md @@ -53,7 +53,7 @@ dsh --profile web --no-open --port 8080 | `surfaceContext` | `true` | 给 agent 提供 GUI 定位上下文,并把 `DSH_WEB_URL` 暴露给其 shell 命令 | | `trustedHosts` | `[]` | 允许从网络访问 GUI 的额外主机 | -生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-web-app)是每个受支持字段及其 JSDoc 的穷尽式真源。 随发行版交付的组合默认禁用 `schedule`、`ui-schedule` 和 `time-context`,可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 可在插件管理页把这三行打开。 +生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-web-app)是每个受支持字段及其 JSDoc 的穷尽式真源。 随发行版交付的组合不含 `time-context`、`schedule` 和 `ui-schedule` 行,可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 可在插件管理页插入这三行。 ### LAN 访问与可信主机 diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index cf2d277132..0659d73674 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -131,19 +131,6 @@ - id: workspace-files name: '@deepseek-ai/dsh-api-workspace-files' - # Durable Host-wide reminders. The Schedule service owns the task store and - # delivers each due occurrence as a follow-up in its original Session; - # `time-context` supplies the current time, browser zone, and elapsed time - # the model needs to resolve an unqualified date or time. The browser half - # is the `ui-schedule` row below. - - id: time-context - name: '@deepseek-ai/dsh-time-context' - disabled: true - - - id: schedule - name: '@deepseek-ai/dsh-schedule' - disabled: true - # Configuration-surface reads and writes over Typert Remote. Each method # reports an actionable error when its settings-domain provider is absent. - id: ui-settings-account @@ -383,12 +370,6 @@ - id: ui-jobs name: '@deepseek-ai/dsh-client-ui-jobs' - # Task management and Session reminder catalog over the Host Schedule - # service mounted above. - - id: ui-schedule - name: '@deepseek-ai/dsh-client-ui-schedule' - disabled: true - # Goal surface: GoalBar in the input dock over the goal session projection. - id: ui-goal name: '@deepseek-ai/dsh-client-ui-goal' diff --git a/packages/bundle/web-app/package.json b/packages/bundle/web-app/package.json index 35c558314a..accaa7132e 100644 --- a/packages/bundle/web-app/package.json +++ b/packages/bundle/web-app/package.json @@ -91,7 +91,6 @@ "@deepseek-ai/dsh-client-ui-plugin-manager": "workspace:*", "@deepseek-ai/dsh-client-ui-reference": "workspace:*", "@deepseek-ai/dsh-client-ui-renderer": "workspace:*", - "@deepseek-ai/dsh-client-ui-schedule": "workspace:*", "@deepseek-ai/dsh-client-ui-session": "workspace:*", "@deepseek-ai/dsh-client-ui-settings": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-general": "workspace:*", @@ -138,7 +137,6 @@ "@deepseek-ai/dsh-persona": "workspace:*", "@deepseek-ai/dsh-plan-mode": "workspace:*", "@deepseek-ai/dsh-plugin-manager": "workspace:*", - "@deepseek-ai/dsh-schedule": "workspace:*", "@deepseek-ai/dsh-session-log-export": "workspace:*", "@deepseek-ai/dsh-session-reference": "workspace:*", "@deepseek-ai/dsh-session-stats": "workspace:*", @@ -147,7 +145,6 @@ "@deepseek-ai/dsh-subprocess": "workspace:*", "@deepseek-ai/dsh-terminal": "workspace:*", "@deepseek-ai/dsh-terminal-bash": "workspace:*", - "@deepseek-ai/dsh-time-context": "workspace:*", "@deepseek-ai/dsh-tool-ask-user": "workspace:*", "@deepseek-ai/dsh-tool-bash": "workspace:*", "@deepseek-ai/dsh-tool-bash-persistent": "workspace:*", diff --git a/packages/client/ui-schedule/README.i18n.yaml b/packages/client/ui-schedule/README.i18n.yaml index ec785c3dda..8866d6e127 100644 --- a/packages/client/ui-schedule/README.i18n.yaml +++ b/packages/client/ui-schedule/README.i18n.yaml @@ -15,8 +15,8 @@ en: d152484eb41ac6b4 zh: 405b30feaf48c087 /deepseek-ai-dsh-client-ui-schedule/use-this-package: - en: bf8096e6e576ac07 - zh: 1936c0a75dc5cd4b + en: c129ae5d0a150a5d + zh: d81976f2840cb059 /deepseek-ai-dsh-client-ui-schedule/use-this-package/time-zone-data-source: en: 6bd6882dbbbefcb0 zh: 03aa15f1a65d13dc diff --git a/packages/client/ui-schedule/README.md b/packages/client/ui-schedule/README.md index 54b0b172b5..ed9431a6b5 100644 --- a/packages/client/ui-schedule/README.md +++ b/packages/client/ui-schedule/README.md @@ -25,7 +25,7 @@ Manage active and inactive tasks from the Automation tasks page and the right Si ## Use this package -The shipped Web composition carries the `ui-schedule` row disabled; enable the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle` from the Plugins page (Official group), or list it in a profile's `dsh.profile.bundles`, to mount `ui-schedule` with the Host Schedule service and the clock context that supplies the model's current time. The Automation tasks sidebar entry opens a global page without selecting or activating a Session. The page header's New action starts a new Session, where the reminder is created by asking the model; the page itself has no creation form. Its task list supports text search and one All, Enabled, and Inactive status filter row; search matches the stored task name, the instruction, and the internal Session id, not a resolved Session title, and the empty state keeps the heading's New action beside the notice. Selecting an active or inactive task opens a second detail level with Rules and Delivery records tabs. Every list row shows the task's stored title with its status, frequency, and next run and never the original Session name; the detail's name control shows the stored title. Rules contains the complete reminder, status, next target for active tasks, original recurrence rule, and task id. Daily rule labels show the saved local time and exact stored IANA zone, preserving nonzero seconds and milliseconds rather than substituting the browser's zone. The Linked session entry in the detail tab strip names the linked Session by its catalog title, falling back to the Session id when no title is available. It opens an available, unarchived Session after metadata loads; pending Session or Workspace metadata, an archived Session, missing Session membership, or a Workspace query error keeps it disabled with an explanation. The click rechecks availability; browsing does not unarchive the Session. Only this action requests Session activation. Deletion is available from the More task actions menu in the detail's top strip, separate from navigation. +The shipped Web composition carries no `ui-schedule` row; enable the optional experimental bundle `@deepseek-ai/dsh-experimental-schedule-bundle` from the Plugins page (Official group), or list it in a profile's `dsh.profile.bundles`, to insert and mount `ui-schedule` with the Host Schedule service and the clock context that supplies the model's current time. The Automation tasks sidebar entry opens a global page without selecting or activating a Session. The page header's New action starts a new Session, where the reminder is created by asking the model; the page itself has no creation form. Its task list supports text search and one All, Enabled, and Inactive status filter row; search matches the stored task name, the instruction, and the internal Session id, not a resolved Session title, and the empty state keeps the heading's New action beside the notice. Selecting an active or inactive task opens a second detail level with Rules and Delivery records tabs. Every list row shows the task's stored title with its status, frequency, and next run and never the original Session name; the detail's name control shows the stored title. Rules contains the complete reminder, status, next target for active tasks, original recurrence rule, and task id. Daily rule labels show the saved local time and exact stored IANA zone, preserving nonzero seconds and milliseconds rather than substituting the browser's zone. The Linked session entry in the detail tab strip names the linked Session by its catalog title, falling back to the Session id when no title is available. It opens an available, unarchived Session after metadata loads; pending Session or Workspace metadata, an archived Session, missing Session membership, or a Workspace query error keeps it disabled with an explanation. The click rechecks availability; browsing does not unarchive the Session. Only this action requests Session activation. Deletion is available from the More task actions menu in the detail's top strip, separate from navigation. Selecting a task from the list opens Rules; refreshing that task preserves the selected tab. Rules edits an active task's name, instruction, and run time; the edits stay in a local draft until saved, and inactive tasks remain read-only. Unmodified Left/Right arrows cycle the tabs, Home/End select the first/last tab, and keyboard focus follows the selection. The tabs do not consume these keys with Alt, Ctrl, Meta, or Shift. The linked-Session control in the detail tab strip stays available on the Rules tab, and confirmed deletion is available from either view. diff --git a/packages/client/ui-schedule/README.zh.md b/packages/client/ui-schedule/README.zh.md index 4a4fab6b96..d03aefa658 100644 --- a/packages/client/ui-schedule/README.zh.md +++ b/packages/client/ui-schedule/README.zh.md @@ -25,7 +25,7 @@ kind: "package-reference" ## 使用本包 -随发行版交付的 Web 组合把 `ui-schedule` 行保持禁用;在插件管理页(Official 分组)启用可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle`,或把它列进 profile 的 `dsh.profile.bundles`,即可将 `ui-schedule` 与 Host Schedule 服务、以及为模型提供当前时间的时钟上下文一起挂载。侧栏的自动化任务入口打开全局页面,不选择或激活会话。页面页头的“新建”操作会打开一个新会话,提醒由模型在该会话中创建;页面本身没有创建表单。列表支持文本搜索和一行“全部/已开启/未运行”状态筛选;搜索匹配任务已存储的名称、指令和内部会话 id,不匹配解析出的会话标题,空状态在提示旁保留页头的“新建”操作。选择活动或未运行任务后,进入含“规则”和“任务运行记录”页签的第二层详情。每条列表行显示任务已存储的标题及其状态、频率和下次运行,不显示原会话名称;详情中的名称输入框显示任务已存储的标题。“规则”显示完整提醒、状态、活动任务的下一目标时间、原始重复规则和任务 id。每天的规则标签显示已保存的本地时间与精确的存储 IANA 时区,保留非零秒与毫秒,而不替换为浏览器时区。详情页签条内的“关联会话”入口按会话标题显示,取不到标题时回退显示会话 id。元数据加载后,它可打开可用且未归档的会话;会话或 Workspace 元数据尚未就绪、会话已归档、会话不在列表中或 Workspace 查询失败时,入口保持禁用并显示原因。点击时会重新检查可用性;浏览不会取消会话归档。仅此操作请求激活会话。删除位于详情顶部条带的“更多任务操作”菜单。 +随发行版交付的 Web 组合不含 `ui-schedule` 行;在插件管理页的“官方”分组中启用可选实验性 bundle `@deepseek-ai/dsh-experimental-schedule-bundle`,或把它列进 profile 的 `dsh.profile.bundles`,即可插入 `ui-schedule` 并将它与 Host Schedule 服务、以及为模型提供当前时间的时钟上下文一起挂载。侧栏的自动化任务入口打开全局页面,不选择或激活会话。页面页头的“新建”操作会打开一个新会话,提醒由模型在该会话中创建;页面本身没有创建表单。列表支持文本搜索和一行“全部/已开启/未运行”状态筛选;搜索匹配任务已存储的名称、指令和内部会话 id,不匹配解析出的会话标题,空状态在提示旁保留页头的“新建”操作。选择活动或未运行任务后,进入含“规则”和“任务运行记录”页签的第二层详情。每条列表行显示任务已存储的标题及其状态、频率和下次运行,不显示原会话名称;详情中的名称输入框显示任务已存储的标题。“规则”显示完整提醒、状态、活动任务的下一目标时间、原始重复规则和任务 id。每天的规则标签显示已保存的本地时间与精确的存储 IANA 时区,保留非零秒与毫秒,而不替换为浏览器时区。详情页签条内的“关联会话”入口按会话标题显示,取不到标题时回退显示会话 id。元数据加载后,它可打开可用且未归档的会话;会话或 Workspace 元数据尚未就绪、会话已归档、会话不在列表中或 Workspace 查询失败时,入口保持禁用并显示原因。点击时会重新检查可用性;浏览不会取消会话归档。仅此操作请求激活会话。删除位于详情顶部条带的“更多任务操作”菜单。 从列表选择任务时默认打开“规则”;刷新同一任务会保留当前页签。活动任务的“规则”修改名称、指令和运行时间;这些修改保留在本地草稿中直到保存,未运行任务保持只读。不带修饰键的左右方向键循环切换页签,Home/End 选择第一个/最后一个页签,键盘焦点随选择移动。页签不会拦截这些键与 Alt、Ctrl、Meta 或 Shift 的组合。共用的会话入口位于详情页签条内,在“规则”页签可见;确认删除操作在两种页签下都可用。 diff --git a/packages/client/ui-schedule/locale/en.json b/packages/client/ui-schedule/locale/en.json new file mode 100644 index 0000000000..7564e0fff7 --- /dev/null +++ b/packages/client/ui-schedule/locale/en.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Task interface", + "description": "The Automation tasks page in the sidebar, plus task cards and reminder marks in sessions." + } +} diff --git a/packages/client/ui-schedule/locale/zh.json b/packages/client/ui-schedule/locale/zh.json new file mode 100644 index 0000000000..b9b7495eaa --- /dev/null +++ b/packages/client/ui-schedule/locale/zh.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "任务界面", + "description": "侧栏的自动化任务页面,以及会话里的任务卡片和提醒标记。" + } +} diff --git a/packages/client/ui-schedule/package.json b/packages/client/ui-schedule/package.json index f4979f5921..665d1bca90 100644 --- a/packages/client/ui-schedule/package.json +++ b/packages/client/ui-schedule/package.json @@ -15,7 +15,8 @@ "default": "./lib/client.js" }, "./src/*": "./src/*", - "./package.json": "./package.json" + "./package.json": "./package.json", + "./locale/*.json": "./locale/*.json" }, "dsh": { "client": { @@ -81,6 +82,7 @@ "react-dom": "^18.2.0" }, "files": [ + "locale/*.json", "lib/index.js", "lib/client.js", "lib/types/**/*.d.ts" diff --git a/packages/client/ui-settings-plugin-inventory/README.i18n.yaml b/packages/client/ui-settings-plugin-inventory/README.i18n.yaml index 6b7ff6a2bf..b30b1c29f8 100644 --- a/packages/client/ui-settings-plugin-inventory/README.i18n.yaml +++ b/packages/client/ui-settings-plugin-inventory/README.i18n.yaml @@ -9,8 +9,8 @@ en: 463eecf002ec0f54 zh: b8dc6003ddccd14a /deepseek-ai-dsh-client-ui-settings-plugin-inventory/summary: - en: 56f5349b6e502525 - zh: 691a0fe90b739e9d + en: f52353cf99816eb8 + zh: e172c1751289882f /deepseek-ai-dsh-client-ui-settings-plugin-inventory/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb @@ -18,14 +18,14 @@ en: 67637808e7940157 zh: d4d0716ea4d33e4a /deepseek-ai-dsh-client-ui-settings-plugin-inventory/use-this-package/reading-a-card: - en: e615aaa891b5055f - zh: 38769e763c8e54ca + en: 141d6a73e99fee51 + zh: 2aa28b174867c967 /deepseek-ai-dsh-client-ui-settings-plugin-inventory/use-this-package/the-preset-switcher: en: 84e5938329199a22 zh: 6f5cfaac56433e8b /deepseek-ai-dsh-client-ui-settings-plugin-inventory/use-this-package/retrying-a-failed-read: - en: 2859449aaae5ab59 - zh: 06380059ae429026 + en: 673c7574e14078be + zh: b093e897f0341973 /deepseek-ai-dsh-client-ui-settings-plugin-inventory/understand-the-implementation: en: cab883d26642c932 zh: 54e7321bd781b400 @@ -33,8 +33,8 @@ en: e4792880c5916ce0 zh: 4c8171a446dd153f /deepseek-ai-dsh-client-ui-settings-plugin-inventory/understand-the-implementation/rendering: - en: b9aa850e5f101b68 - zh: 8a29025f19f6ce37 + en: 779146e3d3732803 + zh: 0dfafa328dfeda75 /deepseek-ai-dsh-client-ui-settings-plugin-inventory/further-exploration: en: ae3819f0c5da95bd zh: 40972ab96d5c95e2 diff --git a/packages/client/ui-settings-plugin-inventory/README.md b/packages/client/ui-settings-plugin-inventory/README.md index 7353f14d11..f6aa2312b8 100644 --- a/packages/client/ui-settings-plugin-inventory/README.md +++ b/packages/client/ui-settings-plugin-inventory/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -The **Plugin list** tab lets Web users inspect plugins without changing their configuration. It lists agent presets, then the global inventory; both start collapsed and open on demand or during a search. Cards show localized titles and descriptions when available, identify instances by stable entry id, and expose enablement, source details, runtime status, disabled conditions, and discovery failures; preset-provided global entries name their presets. Search covers both groups and points to matches in other presets. The tab handles loading, empty, no-match, failure, and retry states without exposing transport details, and still shows the global inventory without a preset roster. +The **Plugin list** tab lets Web users inspect plugins without changing their configuration. It lists agent presets, open by default, then the collapsed global inventory; a search opens both. Cards show localized titles and descriptions when available, identify instances by stable entry id, and expose enablement, source details, runtime status, disabled conditions, and discovery failures; preset-provided global entries name their presets. Search covers both groups and points to matches in other presets. The tab handles loading, empty, no-match, failure, and retry states without exposing transport details, and still shows the global inventory without a preset roster. ## Table of Contents @@ -31,9 +31,9 @@ A failed installation with pending pnpm build permissions offers **Allow these s ### Reading a card -Each collapsed card uses the plugin's localized title and description when available. Settings shortens literal package-name or module-name fallbacks by removing the npm scope and the `cordis:`, `cordis-plugin-`, or `dsh-`/`dsh-host-`/`dsh-client-` prefix; locale titles remain unchanged. The Host supplies locale and package-field fallbacks in `meta`; the Client selects the current language. Full module specifiers remain available in details and search. Missing descriptions are omitted, and metadata diagnostics appear with the card. This compact naming applies only to Settings, not the sidebar's Plugins page. +Each collapsed card uses the plugin's localized title and description when available; a description shows at most two lines until its card is expanded. Settings shortens literal package-name or module-name fallbacks by removing the npm scope and the `cordis:`, `cordis-plugin-`, or `dsh-`/`dsh-host-`/`dsh-client-` prefix; locale titles remain unchanged. The Host supplies locale and package-field fallbacks in `meta`; the Client selects the current language. Full module specifiers remain available in details and search. Missing descriptions are omitted, and metadata diagnostics appear with the card. This compact naming applies only to Settings, not the sidebar's Plugins page. -The stable entry id appears underneath the title, alongside a small enablement tag; a colored root-fiber status dot marks only the phases the tag cannot state: `pending` maps to idle, while `loading` and `unloading` map to ongoing. An `active` or `failed` fiber shows its tag without a dot. A composition-generated subtitle omits its leading `include:` marker, while hover, search, the accessible name, and expanded details retain the complete id. Long entry ids truncate in the row and remain available on hover. Expanding one card reveals the declared entry id, the full module specifier, and the state facts: a preset row names the preset it comes from, its runtime status when the composition is live, and its disable condition when it carries one; a preset-provided global row explains that agent presets provide it per session, names the presets that enable it, and offers a jump into the preset group. Preset names resolve through the shared `presetDisplayText` fold (`dsh-agent-preset-registry/display`) over [`ui-agent-preset`](../ui-agent-preset/README.md)'s dictionaries: shipped presets follow the active locale while user-authored ones keep their own metadata, so an English surface never echoes the preset declarations' Chinese names. Search filters both groups by localized title and description, module name, and entry id. +A small enablement tag marks only the states that differ from plainly enabled: disabled, conditional, provided by presets, and failed; a plainly enabled row carries no tag. A colored root-fiber status dot marks the live phases no tag states: `pending` maps to idle, while `loading` and `unloading` map to ongoing; an `active` or `failed` fiber shows no dot. The stable entry id appears in a code chip at the card's bottom-left, in line with the title, only when it differs from the title: a composition-generated id drops its leading `include:` marker there, and an id that then repeats the title is omitted. Whenever the chip shows, its hover text and the card's accessible name carry the complete id; search and expanded details always retain it. Long entry ids truncate in the chip and remain available on hover. Expanding one card reveals its full description, the declared entry id, the full module specifier, and the state facts: a preset row names the preset it comes from, its runtime status when the composition is live, and its disable condition when it carries one; a preset-provided global row explains that agent presets provide it per session, names the presets that enable it, and offers a jump into the preset group. Preset names resolve through the shared `presetDisplayText` fold (`dsh-agent-preset-registry/display`) over [`ui-agent-preset`](../ui-agent-preset/README.md)'s dictionaries: shipped presets follow the active locale while user-authored ones keep their own metadata, so an English surface never echoes the preset declarations' Chinese names. Search filters both groups by localized title and description, module name, and entry id. ### The preset switcher @@ -41,7 +41,7 @@ The switcher is the same selector-pill-plus-menu control the General settings ro ### Retrying a failed read -A failed read renders the shared error marker inside the tab; loading and current-page synchronization use the shared ongoing loader. Retrying re-runs the lazy `list()` call without exposing transport details. +A failed read renders the shared error marker inside the tab. Loading shows skeleton cards in the card grid, and current-page synchronization uses the shared ongoing loader. Retrying re-runs the lazy `list()` call without exposing transport details. The Plugin list also shows synchronization failures on the current page. Its retry reapplies the latest client graph without changing Host enablement or refreshing the page. @@ -61,7 +61,7 @@ The browser plugin registers one localized `settings.plugins.tab` contribution w ### Rendering -Row keys are scope-qualified (`global:`, `preset::`), so one module appearing in both scopes keeps distinct disclosure state; a declared entry id appears in expanded details and supplies the collapsed subtitle after removal of a leading composition `include:` marker, while a row without one stays unlabeled. The preset-provided marking is derived client-side: a global entry carries it when it is disabled there while at least one preset row for the same module specifier is actually enabled, so a module every preset gates off (or declares only conditionally) stays plainly disabled rather than over-claiming provision. +Row keys are scope-qualified (`global:`, `preset::`), so one module appearing in both scopes keeps distinct disclosure state; a declared entry id appears in expanded details and, after removal of a leading composition `include:` marker, supplies the collapsed id chip when the result differs from the title, while a row without one stays unlabeled. The preset-provided marking is derived client-side: a global entry carries it when it is disabled there while at least one preset row for the same module specifier is actually enabled, so a module every preset gates off (or declares only conditionally) stays plainly disabled rather than over-claiming provision. diff --git a/packages/client/ui-settings-plugin-inventory/README.zh.md b/packages/client/ui-settings-plugin-inventory/README.zh.md index 9078f8074a..cb0c2ce723 100644 --- a/packages/client/ui-settings-plugin-inventory/README.zh.md +++ b/packages/client/ui-settings-plugin-inventory/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -**插件列表**标签页让 Web 用户查看插件,而不改变其配置。它先列 agent(智能体)预设分组,再列全局清单;两个分组默认都收起,按需展开,搜索期间保持展开。卡片显示可用的本地化标题与描述,以稳定的条目 id 标识实例,并展示启停状态、出处、运行状态、禁用条件与发现失败;由预设提供的全局条目会列出对应预设。搜索覆盖两个分组,并指出其他预设中的匹配。标签页处理加载、空结果、无匹配、失败与重试状态,且不暴露传输细节;没有预设 roster 时仍会展示全局清单。 +**插件列表**标签页让 Web 用户查看插件,而不改变其配置。它先列 agent(智能体)预设分组,再列全局清单;预设分组默认展开,全局分组默认收起,搜索期间两组都展开。卡片显示可用的本地化标题与描述,以稳定的条目 id 标识实例,并展示启停状态、出处、运行状态、禁用条件与发现失败;由预设提供的全局条目会列出对应预设。搜索覆盖两个分组,并指出其他预设中的匹配。标签页处理加载、空结果、无匹配、失败与重试状态,且不暴露传输细节;没有预设 roster 时仍会展示全局清单。 ## 目录 @@ -31,9 +31,9 @@ kind: "package-reference" ### 阅读卡片 -每张收起的卡片使用插件可用的本地化标题与描述。设置页会缩短字面的包名或模块名回退值,移除 npm scope 以及 `cordis:`、`cordis-plugin-` 或 `dsh-`/`dsh-host-`/`dsh-client-` 前缀;locale 标题保持原样。Host 在 `meta` 中提供 locale 与包字段的回退结果,Client 选择当前语言。详情与搜索仍保留完整模块标识。缺失的描述不显示,元信息诊断随卡片展示。这种短名显示仅用于设置页,不影响侧栏的插件页。 +每张收起的卡片使用插件可用的本地化标题与描述;描述最多显示两行,展开卡片后显示全文。设置页会缩短字面的包名或模块名回退值,移除 npm scope 以及 `cordis:`、`cordis-plugin-` 或 `dsh-`/`dsh-host-`/`dsh-client-` 前缀;locale 标题保持原样。Host 在 `meta` 中提供 locale 与包字段的回退结果,Client 选择当前语言。详情与搜索仍保留完整模块标识。缺失的描述不显示,元信息诊断随卡片展示。这种短名显示仅用于设置页,不影响侧栏的插件页。 -稳定的条目 id 显示在标题下方,卡片带有表示启停状态的小标签;彩色根 fiber 状态圆点只标记标签无法表达的阶段:`pending` 映射为 idle,`loading` 与 `unloading` 映射为 ongoing。`active` 或 `failed` fiber 只显示标签,不显示圆点。组合生成的次标题省略开头的 `include:` 标记;悬停、搜索、无障碍名称与展开详情仍保留完整 id。长条目 id 会在行内截断,悬停时仍可查看完整值。展开卡片后会显示声明的条目 id、完整模块标识与状态事实:预设行说明它来自哪个预设、组合存活时的运行状态,以及它携带的禁用条件;被预设提供的全局行说明它由 Agent 预设按会话提供、列出启用它的预设,并提供跳转到预设组的入口。预设名经共享的 `presetDisplayText` 纯函数(`dsh-agent-preset-registry/display`)叠在 [`ui-agent-preset`](../ui-agent-preset/README.zh.md) 的字典上解析:内置预设走当前语言,用户自建预设保留自己的元数据,因此英文界面不会回显预设声明里的中文名。搜索按本地化标题与描述、模块名称及条目 id 过滤两组。 +表示启停状态的小标签只标记与普通启用不同的状态:已停用、条件启用、由预设提供与启动失败;普通启用的行不带标签。彩色根 fiber 状态圆点标记没有标签表达的存活阶段:`pending` 映射为 idle,`loading` 与 `unloading` 映射为 ongoing;`active` 或 `failed` fiber 不显示圆点。稳定的条目 id 仅在与标题不同时显示,位于卡片左下角的代码标签中,与标题左对齐:组合生成的 id 在此省略开头的 `include:` 标记,省略后与标题相同的 id 不再重复显示。显示代码标签时,其悬停提示与卡片的无障碍名称带完整 id;搜索与展开详情始终保留完整 id。长条目 id 会在代码标签内截断,悬停时仍可查看完整值。展开卡片后会显示完整描述、声明的条目 id、完整模块标识与状态事实:预设行说明它来自哪个预设、组合存活时的运行状态,以及它携带的禁用条件;被预设提供的全局行说明它由 Agent 预设按会话提供、列出启用它的预设,并提供跳转到预设组的入口。预设名经共享的 `presetDisplayText` 纯函数(`dsh-agent-preset-registry/display`)叠在 [`ui-agent-preset`](../ui-agent-preset/README.zh.md) 的字典上解析:内置预设走当前语言,用户自建预设保留自己的元数据,因此英文界面不会回显预设声明里的中文名。搜索按本地化标题与描述、模块名称及条目 id 过滤两组。 ### 预设切换器 @@ -41,7 +41,7 @@ kind: "package-reference" ### 重试失败的读取 -读取失败会在标签页内渲染共享 error 标记;加载与当前页面同步使用共享 ongoing loading。重试会重新执行懒 `list()` 调用,且不会暴露传输细节。 +读取失败会在标签页内渲染共享 error 标记。加载时在卡片网格中显示骨架卡片,当前页面同步使用共享 ongoing loading。重试会重新执行懒 `list()` 调用,且不会暴露传输细节。 插件列表还显示当前页面的同步失败。重试会重新应用最新客户端图,不改变 Host 启用状态,也不刷新页面。 @@ -61,7 +61,7 @@ kind: "package-reference" ### 渲染 -行 key 按作用域限定(`global:`、`preset::`),因此同一模块出现在两个作用域时保持各自的展开状态;声明的条目 id 出现在展开详情中,并在去掉开头的组合 `include:` 标记后作为收起次标题,没有 id 的行不显示次级标签。预设提供标记在客户端推导:一个全局条目在全局被停用、且至少一个预设行对同一模块标识实际启用时才携带它,因此被所有预设关掉(或仅条件声明)的模块保持单纯的已停用,而不是夸大提供关系。 +行 key 按作用域限定(`global:`、`preset::`),因此同一模块出现在两个作用域时保持各自的展开状态;声明的条目 id 出现在展开详情中;去掉开头的组合 `include:` 标记后若与标题不同,则作为收起卡片的 id 代码标签,没有 id 的行不显示次级标签。预设提供标记在客户端推导:一个全局条目在全局被停用、且至少一个预设行对同一模块标识实际启用时才携带它,因此被所有预设关掉(或仅条件声明)的模块保持单纯的已停用,而不是夸大提供关系。 diff --git a/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css b/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css index 87b29d442f..be3aa42773 100644 --- a/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css +++ b/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css @@ -1,4 +1,6 @@ +/* The tab's own width, not the window's, decides the card columns below. */ .section { + container: plugin-inventory / inline-size; display: flex; flex-direction: column; gap: 14px; @@ -7,7 +9,6 @@ color: var(--dsw-alias-label-primary); } -.catalogHeading h3, .status, .failure p { margin: 0; @@ -85,37 +86,21 @@ box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)) 18%, transparent); } -.catalogHeading { - display: flex; - align-items: baseline; - gap: 7px; - padding: 0 2px; -} - -.catalogHeading h3 { - font-size: 13px; - line-height: 20px; - font-weight: 600; -} - -.catalogHeading span { - font-size: 12px; - line-height: 18px; - color: var(--dsw-alias-label-tertiary); - font-variant-numeric: tabular-nums; -} - .cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - align-items: start; gap: 10px; margin: 0; padding: 0; list-style: none; } +/* Cards stretch to their grid row's height. The button fills a stretched card: + the title row and description stay at the top, in line with the other card's, + and the spare height sits above the entry id chip or at the bottom. */ .card { + display: flex; + flex-direction: column; min-width: 0; overflow: hidden; border: 0.5px solid var(--dsw-alias-settings-card-stroke); @@ -127,11 +112,22 @@ border-color: var(--dsw-alias-border-l3); } +/* The other card in an expanded card's row keeps its own height instead of + stretching alongside the details. The selectors assume the two columns + `.cards` declares, where each row starts at an odd position, and must change + with that count; in one column the matched card sits alone in its row. */ +.card:nth-child(odd)[data-open='true'] + .card, +.card:nth-child(odd):has(+ .card[data-open='true']) { + align-self: start; +} + .cardContent { box-sizing: border-box; display: flex; + flex: 1 1 auto; flex-direction: column; align-items: stretch; + gap: 2px; width: 100%; min-height: 52px; border: 0; @@ -173,11 +169,35 @@ min-width: 0; font-size: 14px; line-height: 20px; - font-weight: 600; + font-weight: 500; } -/* The Loader entry id keeps one size in the card subtitle and the row it expands into; - the collapsed subtitle stays on the quieter label tier, while the expanded row carries +/* Two lines at most while collapsed; the expanded card shows the full text. */ +.cardDescription { + display: -webkit-box; + overflow: hidden; + color: var(--dsw-alias-label-tertiary); + font-size: 12px; + line-height: 18px; + text-wrap: pretty; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.card[data-open='true'] .cardDescription { + display: block; +} + +/* The entry id is metadata, not prose: a code chip in the card's bottom-left + corner whose left edge lines up with the title. */ +.cardMeta { + display: flex; + margin-top: auto; + padding-top: 6px; +} + +/* The Loader entry id keeps one size in the card chip and the row it expands into; + the collapsed chip stays on the quieter label tier, while the expanded row carries the card's primary value. */ .cardIdentity, .entryValue { @@ -188,15 +208,19 @@ .cardIdentity { display: block; - width: 100%; - color: var(--dsw-alias-label-tertiary); + box-sizing: border-box; + max-width: 100%; + border-radius: var(--dsw-radius-xs); + padding: 1px 6px; + background: var(--dsw-alias-bg-module-platform); + color: var(--dsw-alias-label-secondary); } .cardTrailing { display: inline-flex; flex: none; align-items: center; - gap: 7px; + gap: 8px; color: var(--dsw-alias-label-tertiary); } @@ -213,11 +237,13 @@ gap: 10px; } +/* Every group header takes the preset switcher's 36px height, so each subtitle + sits the same distance below its title. */ .groupTitleRow { display: flex; align-items: center; gap: 8px; - min-height: 32px; + min-height: 36px; } .group + .group { @@ -266,7 +292,7 @@ .groupSub { display: flex; flex-wrap: wrap; - gap: 4px 8px; + row-gap: 4px; margin: -6px 0 0 20px; color: var(--dsw-alias-label-tertiary); font-size: 12px; @@ -274,6 +300,13 @@ font-variant-numeric: tabular-nums; } +/* A middle dot with equal space on both sides separates the subtitle's parts. */ +.groupSub > * + *::before { + content: '·'; + padding: 0 6px; + color: var(--dsw-alias-label-tertiary); +} + .failedCount { color: var(--dsw-alias-state-error-primary); } @@ -330,7 +363,7 @@ padding: 8px 10px; background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent); color: var(--dsw-alias-state-error-primary); - font-size: 12.5px; + font-size: 12px; line-height: 18px; overflow-wrap: anywhere; white-space: pre-line; @@ -343,7 +376,7 @@ gap: 4px 8px; margin: 0; color: var(--dsw-alias-label-tertiary); - font-size: 12.5px; + font-size: 12px; line-height: 18px; } @@ -353,10 +386,34 @@ background: transparent; color: var(--dsw-alias-state-business-primary); font: inherit; - font-size: 12.5px; + font-size: 12px; cursor: pointer; } +/* Loading placeholder in the cards grid, sized like a card with one description + line; its bars pulse through the shared skeleton token as the input-trigger + menu's pending rows do. */ +.skeletonCard { + display: flex; + flex-direction: column; + gap: 8px; + border: 0.5px solid var(--dsw-alias-settings-card-stroke); + border-radius: var(--dsw-radius-xl); + padding: 15px 14px; + background: var(--dsw-alias-settings-card-fill); +} + +.skeletonBar { + width: 40%; + height: 14px; + border-radius: var(--dsw-radius-xs); + background: var(--dsw-alias-bg-skeleton); +} + +.skeletonBar + .skeletonBar { + width: 80%; + height: 12px; +} .enabledIn { display: flex; @@ -430,9 +487,19 @@ .chevron { transition: transform 140ms var(--ds-ease-in-out); } + + .skeletonBar { + animation: plugin-inventory-skeleton 2s cubic-bezier(0.36, 0, 0.64, 1) infinite; + } } -@media (max-width: 680px) { +@keyframes plugin-inventory-skeleton { + 0% { opacity: 1; } + 40% { opacity: 0.6; } + 80%, 100% { opacity: 1; } +} + +@container plugin-inventory (max-width: 520px) { .cards { grid-template-columns: minmax(0, 1fr); } diff --git a/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx b/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx index c5064e5441..57810b993a 100644 --- a/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx +++ b/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx @@ -58,6 +58,9 @@ const PHASE_KEYS = { unloading: 'unloading', } satisfies Record, PluginInventoryLocaleKey> +/** Placeholder cards the loading skeleton lays out in the cards grid. */ +const SKELETON_CARDS = [0, 1, 2, 3] as const + /** Localized accessible label for one root Fiber phase. */ function phaseLabel(phase: PluginFiberPhase, t: Translate): string { return phase === null ? t('unobserved') : t(PHASE_KEYS[phase]) @@ -77,6 +80,16 @@ function entrySubtitle(entryId: string): string { return entryId.replace(/^include:/, '') } +/** Whether a card shows its entry id: the id exists and, without its `include:` marker, differs from the title. */ +function idAddsToTitle(entryId: string | null, title: string): entryId is string { + return entryId !== null && entrySubtitle(entryId) !== title +} + +/** Accessible card name: the title, the complete entry id when the card shows one, then the enablement state. */ +function cardLabel(title: string, entryId: string | null, state: string): string { + return idAddsToTitle(entryId, title) ? `${title}, ${entryId}, ${state}` : `${title}, ${state}` +} + /** Preserve translated titles and shorten literal package or module name fallbacks in Settings. */ function pluginText(row: PluginInventoryEntry | AgentPresetRow, resolveText: PluginInventorySettingsTabInjected['resolveText']) { const title = row.meta?.title @@ -151,8 +164,12 @@ function PluginCard({