Merge origin/master into xtr/runtime-context-reminders
This commit is contained in:
commit
beb5186e36
1948 files changed
+34090
-11121
No files matched your search
+2
-2
@@ -36,8 +36,8 @@
|
||||
en: 4479d41466723100
|
||||
zh: 3a7f398e0d0070a5
|
||||
/agent-note-typert-gateway-targeted-method-calls/the-remote-package-entry:
|
||||
en: 75e977fe0fb71d59
|
||||
zh: 00760c79da173cb7
|
||||
en: 91033aab32e4d8eb
|
||||
zh: a11ada70e047af55
|
||||
/agent-note-typert-gateway-targeted-method-calls/strict-consumer-api-types:
|
||||
en: 983f55c0ed4a8edc
|
||||
zh: 5a736e00e4b57e6a
|
||||
|
||||
@@ -232,7 +232,7 @@ import goalsRemote from '@deepseek-ai/dsh-goal/remote'
|
||||
|
||||
This import brings the `.d.ts` map augmentation into the current TypeScript project while supplying the JS descriptor for the same contract as a value to the runtime. A business package that is not imported does not extend the current project's Remote API types.
|
||||
|
||||
The business package's published files must include `lib/typert.remote-client.d.ts.map`. The generated DTS refers to its adjacent map with `//# sourceMappingURL=typert.remote-client.d.ts.map`; the map source points from `lib` to the business source by a relative path such as `../src/index.ts`. The `/remote` export does not list the map separately; the package `files` field publishes it. That target is a development-time path: a workspace consumer resolves it through the package link, so the published payload keeps excluding `src` and a published map simply resolves nothing.
|
||||
The business package's published files must include `lib/typert.remote-client.d.ts.map`. The generated DTS refers to its adjacent map with `//# sourceMappingURL=typert.remote-client.d.ts.map`; the map source points from `lib` to the business source by a relative path such as `../src/index.ts`. The `/remote` export does not list the map separately; the package `files` field publishes it. That target is a relative path inside the same package, and the payload publishes `src` beside the declaration map, so the navigation resolves for an installed consumer ([rationale](../process/2026-10-08-published-source-and-docs.md)).
|
||||
|
||||
Code that needs only static types may use `import type {} from '@deepseek-ai/dsh-goal/remote'`. This import is erased at runtime, loads no JS, and cannot trigger runtime registration. An environment that makes real calls must pass the contribution from a normal value import to the Client Remote Service.
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ import goalsRemote from '@deepseek-ai/dsh-goal/remote'
|
||||
|
||||
该 import 让 `.d.ts` 的 map augmentation 进入当前 TypeScript project,同时把同一约定的 JS descriptor 作为值交给运行时。未 import 的业务包不会扩展当前 project 的 Remote API 类型。
|
||||
|
||||
业务 package 的发布文件必须包含 `lib/typert.remote-client.d.ts.map`。生成 DTS 以 `//# sourceMappingURL=typert.remote-client.d.ts.map` 引用相邻 map;map 中的 source 从 `lib` 相对指向业务源码,例如 `../src/index.ts`。`/remote` export 不单独列出 map,package `files` 负责发布它。该目标是开发期路径:workspace 消费者经 package link 解析它,因此发布产物仍然不含 `src`,已发布的 map 只是解析不到东西。
|
||||
业务 package 的发布文件必须包含 `lib/typert.remote-client.d.ts.map`。生成 DTS 以 `//# sourceMappingURL=typert.remote-client.d.ts.map` 引用相邻 map;map 中的 source 从 `lib` 相对指向业务源码,例如 `../src/index.ts`。`/remote` export 不单独列出 map,package `files` 负责发布它。该目标是同一 package 内的相对路径,发布产物在声明映射旁一并发布 `src`,因此已安装的消费方也能完成这次导航([理由](../process/2026-10-08-published-source-and-docs.zh.md))。
|
||||
|
||||
仅需要静态类型时可以使用 `import type {} from '@deepseek-ai/dsh-goal/remote'`;这种 import 在运行时会被擦除,不会加载 JS,也不能触发任何运行时注册。需要真实调用的环境必须把普通 value import 得到的 contribution 交给 Client Remote Service。
|
||||
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@
|
||||
en: 29def175ab33b7d6
|
||||
zh: 87e55ec28c20c7c1
|
||||
/agent-note-package-and-update-the-electron-desktop-application/updates-and-recovery:
|
||||
en: 4cb7014acfe71762
|
||||
zh: 2178e0337cba0324
|
||||
en: 61a02870beb953ec
|
||||
zh: 205ecce330959e9a
|
||||
/agent-note-package-and-update-the-electron-desktop-application/security-and-release-policy:
|
||||
en: 5faaa53b58121311
|
||||
zh: e1c3fc8d21a27d1a
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ Electron update uses one `electron-updater` release stream and signed `electron-
|
||||
|
||||
The [immediate-window reference](../../../../apps/desktop/README.md) owns the local loading page, direct Host startup, and recovery in the main window. Profile reconciliation follows the [bundled-runtime decision](2026-09-08-desktop-bundled-runtime-and-external-plugins.md).
|
||||
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` selects the test deployment by default or the production deployment for both the target-specific generic-provider URL and COS destination. Release automation supplies the test HTTPS origin through `DOWNLOAD_TEST_ORIGIN` and each deployment's bucket through `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`; keeping mutable test routing and COS storage identities out of source lets deployment infrastructure change without a code release, while the public production origin remains fixed. Packaging resolves only the public updater URL, disables electron-builder publishing, removes every COS credential field from its subprocess environment, and writes a completion record only after electron-builder and every signing or notarization hook succeeds. Target upload additionally requires the selected bucket, then requires the completion record, root dsh version, Desktop version, version-derived channel metadata, artifact names, sizes, and SHA-512 values to agree before it reads the selected credentials or sends data. It uploads immutable versioned updater payloads and any separate blockmaps before replacing the channel metadata emitted by electron-builder, and it never deletes historical objects. Stable versions use the `latest` metadata name; prereleases use the first semantic-version prerelease identifier. NSIS embeds its blockmap in the signed executable; the macOS ZIP carries a separate blockmap. Both let electron-updater download changed blocks when supported, while application replacement and the local pnpm package operation remain separate operations.
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` selects the test deployment by default or the production deployment for both the target-specific generic-provider URL and COS destination. Release automation supplies the test HTTPS origin through `DOWNLOAD_TEST_ORIGIN` and each deployment's bucket through `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`; keeping mutable test routing and COS storage identities out of source lets deployment infrastructure change without a code release, while the public production origin remains fixed. Packaging resolves only the public updater URL, disables electron-builder publishing, removes every COS credential field from its subprocess environment, and writes a completion record only after electron-builder and every signing or notarization hook succeeds. Target upload additionally requires the selected bucket, then requires the completion record, root dsh version, Desktop version, channel-selected metadata, artifact names, sizes, and SHA-512 values to agree before it reads the selected credentials or sends data. It uploads immutable versioned updater payloads and any separate blockmaps before replacing the channel metadata emitted by electron-builder, and it never deletes historical objects. The selected channel determines the metadata name: `stable.yml` or `nightly.yml`, with `-mac` before the extension for macOS; version suffixes do not select the channel. NSIS embeds its blockmap in the signed executable; the macOS ZIP carries a separate blockmap. Both let electron-updater download changed blocks when supported, while application replacement and the local pnpm package operation remain separate operations.
|
||||
|
||||
Test releases require `DOWNLOAD_TEST_RELEASE_ID` from the platform dotenv file. Their feeds and binaries share `dsh-desk/<release-id>/`; production retains its fixed directories. The ID is part of the packaged update URL, so upload rejects a different ID. Internal distribution can supply a new installer after rotation; installed clients retain their existing feed. Random directories reduce guessing but provide no authorization for link holders. The [release configuration](../../../../apps/desktop/publishing/README.md#upload-updates) defines generation and reuse.
|
||||
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ Electron 更新只使用一个 `electron-updater` 发布流和签名 `electron-b
|
||||
|
||||
[立即显示窗口参考](../../../../apps/desktop/README.zh.md)负责本地加载页、直接启动 Host 和主窗口恢复。profile 协调遵循[内置运行时决策](2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md)。
|
||||
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` 默认为测试部署,也可以选择生产部署,并同时决定目标专用的 generic-provider URL 与 COS 目标。发布自动化通过 `DOWNLOAD_TEST_ORIGIN` 提供测试 HTTPS origin,并通过 `DOWNLOAD_TEST_COS_BUCKET` 或 `DOWNLOAD_PROD_COS_BUCKET` 提供各部署的 bucket;可变的测试路由与 COS 存储身份不写入源码,部署基础设施变更时无需发布新代码,而公开的生产 origin 仍固定。打包只解析公开更新 URL、禁止 electron-builder 发布、从子进程环境中删除每个 COS 凭据字段,并且只有在 electron-builder 以及每个签名或公证 hook 成功后才写入完成记录。目标上传还必须提供所选 bucket,随后会先要求完成记录、根 dsh 版本、Desktop 版本、根据版本得出的频道元数据、产物名称、大小与 SHA-512 全部一致,再读取所选凭据或发送数据。它先上传不可变且带版本的更新载荷与所有独立 blockmap,最后替换 electron-builder 生成的频道元数据,并且不会删除历史对象。稳定版本使用 `latest` 元数据名称,预发布版本则使用语义化版本的第一个预发布标识符。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm 包操作仍是两个独立操作。
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` 默认为测试部署,也可以选择生产部署,并同时决定目标专用的 generic-provider URL 与 COS 目标。发布自动化通过 `DOWNLOAD_TEST_ORIGIN` 提供测试 HTTPS origin,并通过 `DOWNLOAD_TEST_COS_BUCKET` 或 `DOWNLOAD_PROD_COS_BUCKET` 提供各部署的 bucket;可变的测试路由与 COS 存储身份不写入源码,部署基础设施变更时无需发布新代码,而公开的生产 origin 仍固定。打包只解析公开更新 URL、禁止 electron-builder 发布、从子进程环境中删除每个 COS 凭据字段,并且只有在 electron-builder 以及每个签名或公证 hook 成功后才写入完成记录。目标上传还必须提供所选 bucket,随后会先要求完成记录、根 dsh 版本、Desktop 版本、根据所选渠道确定的元数据、产物名称、大小与 SHA-512 全部一致,再读取所选凭据或发送数据。它先上传不可变且带版本的更新载荷与所有独立 blockmap,最后替换 electron-builder 生成的频道元数据,并且不会删除历史对象。所选渠道决定元数据名称:`stable.yml` 或 `nightly.yml`,macOS 在扩展名前加 `-mac`;版本后缀不决定渠道。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm 包操作仍是两个独立操作。
|
||||
|
||||
测试发布必须从平台 dotenv 文件提供 `DOWNLOAD_TEST_RELEASE_ID`。清单和安装包共用 `dsh-desk/<release-id>/`,生产环境保留固定目录。ID 是打包更新 URL 的一部分,因此上传会拒绝不同的 ID。轮换后可通过内部分发提供新安装包,已安装客户端保留原清单地址。随机目录降低被猜中的概率,但不对持有链接者进行鉴权。[发布配置](../../../../apps/desktop/publishing/README.zh.md#upload-updates)定义生成与复用方式。
|
||||
|
||||
|
||||
+4
-4
@@ -15,11 +15,11 @@
|
||||
en: 62284bd7dd804188
|
||||
zh: b3a8dfebffc01bdb
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/decision/complete-removal-and-reintroduction-condition:
|
||||
en: 8c6d355d91ec83d2
|
||||
zh: fcc74d1e6ef477bf
|
||||
en: 8cd78e21602f17f0
|
||||
zh: 3843fea07e9b93a3
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/alternatives-considered:
|
||||
en: 7386e88e7051dce7
|
||||
zh: dcea5b9e30374be3
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/consequences:
|
||||
en: 9c23dd6ace029ec3
|
||||
zh: f08ef3f142534843
|
||||
en: 22a3af4c51aee23e
|
||||
zh: 9d59315313d0f761
|
||||
@@ -40,7 +40,7 @@ The globally registered model tool is direction-neutral and has one fixed schema
|
||||
|
||||
```ts
|
||||
interface SendMessageInput {
|
||||
readonly agent_id: string
|
||||
readonly agent: string
|
||||
readonly message: string
|
||||
}
|
||||
```
|
||||
@@ -53,7 +53,7 @@ Human browser prompts are not model-authored Agent messages. The remote prompt p
|
||||
|
||||
### Complete removal and reintroduction condition
|
||||
|
||||
The standalone `@deepseek-ai/dsh-tool-subagent-report` package, `report` schema, `tool:report` prompt section, `reportDelivery` configuration, report-specific message source, catalog entries, composition rows, and supported-behavior snapshots are absent. The unified tool gives up the recipient-free child shortcut and the old ability for a structural return tool to survive an explicit child allow-list. Those capabilities return only if a concrete use case requires semantics that a tree member’s `agent_id` and fixed Steer cannot express; reintroducing them requires a distinct model operation and prefix-cost evidence, not an alias over `sendMessage()`.
|
||||
The standalone `@deepseek-ai/dsh-tool-subagent-report` package, `report` schema, `tool:report` prompt section, `reportDelivery` configuration, report-specific message source, catalog entries, composition rows, and supported-behavior snapshots are absent. The unified tool gives up the recipient-free child shortcut and the old ability for a structural return tool to survive an explicit child allow-list. Those capabilities return only if a concrete use case requires semantics that a tree member’s `agent` reference and fixed Steer cannot express; reintroducing them requires a distinct model operation and prefix-cost evidence, not an alias over `sendMessage()`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -71,7 +71,7 @@ The standalone `@deepseek-ai/dsh-tool-subagent-report` package, `report` schema,
|
||||
|
||||
## Consequences
|
||||
|
||||
- Model consumers expose one `send_message({ agent_id, message })` definition to parents and children, with no model-selected Queue versus Steer parameter.
|
||||
- Model consumers expose one `send_message({ agent, message })` definition to parents and children, with no model-selected Queue versus Steer parameter.
|
||||
- The continuation manager remains the sole owner of tree authorization, residency, cold resume, waking admission, and teardown races.
|
||||
- Accepted messages may extend a running target's current turn; messages waiting together share next-step FIFO ordering.
|
||||
- Caller cancellation owns work only until inbox acceptance and does not retract an accepted message or dispose the target.
|
||||
|
||||
+3
-3
@@ -40,7 +40,7 @@ interface AgentMessageSource {
|
||||
|
||||
```ts
|
||||
interface SendMessageInput {
|
||||
readonly agent_id: string
|
||||
readonly agent: string
|
||||
readonly message: string
|
||||
}
|
||||
```
|
||||
@@ -53,7 +53,7 @@ parent 与 child 以相同注册表顺序继承相同定义。标准定义携带
|
||||
|
||||
### 完整移除与重新引入条件
|
||||
|
||||
独立的 `@deepseek-ai/dsh-tool-subagent-report` 包、`report` schema、`tool:report` 提示词 section、`reportDelivery` 配置、report 专属消息来源、目录项、组合行和受支持行为快照均已不存在。统一工具放弃了无需接收方的 child 快捷方式,也放弃了让结构性返回工具绕过显式 child allow-list 的旧能力。只有具体用例需要树成员的 `agent_id` 与固定 Steer 无法表达的语义时,这些能力才会重新出现;重新引入需要独立的模型操作与前缀成本证据,而非 `sendMessage()` 之上的别名。
|
||||
独立的 `@deepseek-ai/dsh-tool-subagent-report` 包、`report` schema、`tool:report` 提示词 section、`reportDelivery` 配置、report 专属消息来源、目录项、组合行和受支持行为快照均已不存在。统一工具放弃了无需接收方的 child 快捷方式,也放弃了让结构性返回工具绕过显式 child allow-list 的旧能力。只有具体用例需要树成员的 `agent` 引用与固定 Steer 无法表达的语义时,这些能力才会重新出现;重新引入需要独立的模型操作与前缀成本证据,而非 `sendMessage()` 之上的别名。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -71,7 +71,7 @@ parent 与 child 以相同注册表顺序继承相同定义。标准定义携带
|
||||
|
||||
## 后果
|
||||
|
||||
- 模型 Consumer 向 parent 与 child 公开一个 `send_message({ agent_id, message })` 定义,不提供模型选择的 Queue 与 Steer 参数。
|
||||
- 模型 Consumer 向 parent 与 child 公开一个 `send_message({ agent, message })` 定义,不提供模型选择的 Queue 与 Steer 参数。
|
||||
- 继续执行管理器仍是整树授权、驻留、冷恢复、唤醒准入与拆卸竞态的唯一所有者。
|
||||
- 被接受的消息可以延长运行中目标的当前轮次;一起等待的消息共享 next-step FIFO 顺序。
|
||||
- 调用方取消只在 inbox 接受前掌管工作,不会撤回已接受消息或 dispose(资源释放)目标。
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 5a991fec31578cea
|
||||
zh: 13b75aa36eb22907
|
||||
/agent-note-parent-owned-subagent-catalog-events/decision:
|
||||
en: 4319c502038ab34d
|
||||
zh: b11825ff9d744822
|
||||
en: d2290d8e70d42054
|
||||
zh: 64a2d1d0915f6c3e
|
||||
/agent-note-parent-owned-subagent-catalog-events/alternatives-considered:
|
||||
en: 7a3d4b9f69c243d8
|
||||
zh: 82b03d16ccc94dd2
|
||||
|
||||
@@ -14,7 +14,7 @@ The child descriptor remains necessary for recovery and composition, but it cann
|
||||
|
||||
The shared activation entry point, external execution ownership, and caller-versus-parent result delivery are governed by [Unified subagent activations](../simplification/2026-09-17-unified-subagent-activations.md). This record retains the independent rationale described below.
|
||||
|
||||
The parent Session's required `subagent/catalog` events are the persistent authority for direct-child discovery. Every local child enters the catalog independently of its result recipient; external children enter only with parent delivery. Workflow membership records describe execution progress and do not replace local Session discovery. Each event contains `childId`, `childCreatedAt`, mode, and the mode-discriminated label. Local children use `continuable`; external executions use `external` and cannot be opened as local Sessions. Historical `one-shot` and `unknown` entries remain readable. Invalid own facts, including unsupported payload versions, reject projection restoration because silently dropping a required fact would return an incomplete catalog.
|
||||
The parent Session's required `subagent/catalog` events are the persistent authority for direct-child discovery. Every local child enters the catalog independently of its result recipient; external children enter only with parent delivery. Workflow membership records describe execution progress and do not replace local Session discovery. Each event contains `childId`, `childCreatedAt`, mode, the mode-discriminated label, and, from payload v3, the child's [name](2026-11-02-subagent-catalog-names.md). Local children use `continuable`; external executions use `external` and cannot be opened as local Sessions. Historical `one-shot` and `unknown` entries remain readable. Invalid own facts, including unsupported payload versions, reject projection restoration because silently dropping a required fact would return an incomplete catalog.
|
||||
|
||||
Creation publishes only successful facts. A local activation admits the initial prompt before appending its catalog event. An external activation appends its event after provider startup and before returning the receipt. Failed admission or publication releases the unpublished execution; there is no compensating catalog event or rollback protocol.
|
||||
|
||||
@@ -22,7 +22,7 @@ The child header and `subagent/descriptor` remain authoritative for recovery and
|
||||
|
||||
The registered `subagentCatalog` projection materializes the parent facts. It delegates storage, append, iteration, and checkpoint validation to [`dsh-chunked-list`](../../../../packages/util/chunked-list/README.md), which stores facts in a persistent stack of 64-entry chunks, so an append copies at most the head chunk in bounded O(1) work. Materialization visits chunks from oldest to newest and preserves parent catalog event order in O(D) time for D facts. Concurrent creation is ordered by successful catalog append, independent of child timestamps and ids. A projection checkpoint clones the state once in O(D); projection-cache writes remain asynchronous and use the existing mandatory creation, turn-end, and disposal points.
|
||||
|
||||
The utility owns chunk layout and its shared capacity constant; the catalog owns event validation, fork filtering, and row conversion. Catalog projection state version 6 accepts external membership, so the projection registry rebuilds incompatible caches from Session events. Payload v0 and v1 remain unchanged; v2 records external executions.
|
||||
The utility owns chunk layout and its shared capacity constant; the catalog owns event validation, fork filtering, and row conversion. Catalog projection state version 7 accepts external membership and child names, so the projection registry rebuilds incompatible caches from Session events. Payload v0 and v1 remain unchanged; v2 records external executions; v3 adds names to continuable and external entries.
|
||||
|
||||
Fork isolation uses the exact `Session.inheritedEventCount` supplied to projection initialization. The fold ignores `subagent/catalog` events below that offset. The state stores the inherited offset but not each event seq because acceptance is decided during folding.
|
||||
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ child descriptor 对恢复与 composition 仍然必要,但它不能作为 disc
|
||||
|
||||
共享 activation 入口、外部执行所有权,以及调用方与父级之间的结果投递选择由[统一 subagent activation](../simplification/2026-09-17-unified-subagent-activations.zh.md) 决策拥有。本记录保留下述独立理由。
|
||||
|
||||
父级 Session 的必读 `subagent/catalog` 事件是发现直接子级的持久化权威。每个本地子级都会进入目录,与结果接收方无关;外部子级仅在 parent 投递时进入目录。工作流成员记录描述执行进度,不替代本地 Session 发现。每个事件包含 `childId`、`childCreatedAt`、mode 与按 mode 区分的 label。本地子级使用 `continuable`;外部执行使用 `external`,不能作为本地 Session 打开。历史 `one-shot` 与 `unknown` 条目仍可读取。无效的自身事实(包括不支持的载荷版本)会使投影恢复失败,因为静默丢弃必读事实将返回不完整的 catalog。
|
||||
父级 Session 的必读 `subagent/catalog` 事件是发现直接子级的持久化权威。每个本地子级都会进入目录,与结果接收方无关;外部子级仅在 parent 投递时进入目录。工作流成员记录描述执行进度,不替代本地 Session 发现。每个事件包含 `childId`、`childCreatedAt`、mode、按 mode 区分的 label,以及从载荷 v3 起的子级[名称](2026-11-02-subagent-catalog-names.zh.md)。本地子级使用 `continuable`;外部执行使用 `external`,不能作为本地 Session 打开。历史 `one-shot` 与 `unknown` 条目仍可读取。无效的自身事实(包括不支持的载荷版本)会使投影恢复失败,因为静默丢弃必读事实将返回不完整的 catalog。
|
||||
|
||||
创建只发布成功事实。本地 activation 先接纳初始提示词,再追加 catalog 事件。外部 activation 在提供方启动后、返回回执前追加事件。准入或发布失败会释放未发布的执行;不存在补偿 catalog 事件或回滚协议。
|
||||
|
||||
@@ -22,7 +22,7 @@ child header 与 `subagent/descriptor` 继续拥有恢复与 composition 权威
|
||||
|
||||
注册的 `subagentCatalog` projection 物化 parent fact。它将存储、追加、迭代和检查点校验交给 [`dsh-chunked-list`](../../../../packages/util/chunked-list/README.zh.md),后者以每块 64 项的持久 stack 保存事实,因此 append 最多复制 head chunk,以有界 O(1) 工作完成。materialization 从旧到新访问 chunk,对 D 条事实以 O(D) 时间保留父目录事件顺序。并发创建按目录成功追加的顺序排列,与 child 时间戳和 id 无关。projection checkpoint 以 O(D) 克隆 state;projection-cache 继续异步写入,并使用既有创建、turn-end 与 disposal 强制点。
|
||||
|
||||
工具库拥有 chunk 布局与共享容量常量;catalog 拥有事件校验、fork 过滤和行转换。catalog 投影状态版本 6 接受外部成员,因此投影注册表会从 Session 事件重建不兼容缓存。载荷 v0 与 v1 保持不变;v2 记录外部执行。
|
||||
工具库拥有 chunk 布局与共享容量常量;catalog 拥有事件校验、fork 过滤和行转换。catalog 投影状态版本 7 接受外部成员和子级名称,因此投影注册表会从 Session 事件重建不兼容缓存。载荷 v0 与 v1 保持不变;v2 记录外部执行;v3 为可继续和外部条目增加名称。
|
||||
|
||||
fork 隔离使用 projection 初始化时提供的精确 `Session.inheritedEventCount`。fold 忽略该 offset 之前的 `subagent/catalog` 事件。state 保存 inherited offset,但不保存每条 event seq,因为接受判定已在 fold 时完成。
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 2fc4ac7bdd4a3474
|
||||
zh: 21ebdd3ac9264132
|
||||
/agent-note-independent-libreoffice-kit-ownership/decision:
|
||||
en: 0d8e05bcb940f88f
|
||||
zh: b5b55f4ee82cf6a3
|
||||
en: 2ecc1fd680d8ec31
|
||||
zh: 1f5edeead8dceb44
|
||||
/agent-note-independent-libreoffice-kit-ownership/alternatives-considered:
|
||||
en: 55175bb30630bf0c
|
||||
zh: 788853ddd5f7a4e9
|
||||
|
||||
@@ -10,11 +10,11 @@ LibreOffice compilation, source patches, platform qualification, and large binar
|
||||
|
||||
## Decision
|
||||
|
||||
The `deepseek-harness/libreoffice-kit` repository owns the reusable `@deepseek-ai/libreoffice-kit` Node API, its Worker, font handling, engine selection, build recipes, patches, tests, and releases. The API has no Cordis dependency. Harness owns the adapter from its `OfficeToPdf` service to this API, Session authorization, conversion lifetime, transport, Web UI, and application packaging.
|
||||
The `deepseek-harness/libreoffice-kit` repository owns the reusable `@deepseek-ai/libreoffice-kit` Node API, its Worker, font handling, engine selection, build recipes, patches, tests, and releases. The API has no Cordis dependency. Harness owns the adapter from its `OfficeKit` service to this API, Session authorization, conversion lifetime, transport, Web UI, and application packaging.
|
||||
|
||||
Kit releases run independently of Harness releases. The kit repository qualifies and publishes the Node API and engine npm packages at a shared version, starting at `0.0.1`. Harness consumes an exact npm version and commits its dependency resolution in `pnpm-lock.yaml`; Harness releases neither build nor publish kit packages.
|
||||
|
||||
The upstream API declares platform engines as optional dependencies. The [platform engine reference](../../../../packages/document/office-to-pdf/README.md) supersedes the original required-WASM fallback policy. Desktop installs the kit as an external npm dependency. Python sidecars keep the Worker, selected engine, and their dependency closure on the real filesystem, outside the executable’s virtual filesystem. Conversion requires neither downloads nor GitHub credentials.
|
||||
The upstream API declares platform engines as optional dependencies. The [platform engine reference](../../../../packages/document/office-kit/README.md) supersedes the original required-WASM fallback policy. Desktop installs the kit as an external npm dependency. Python sidecars keep the Worker, selected engine, and their dependency closure on the real filesystem, outside the executable’s virtual filesystem. Conversion requires neither downloads nor GitHub credentials.
|
||||
|
||||
The kit repository owns engine qualification and corresponding source materials. MPL-2.0 declarations, source availability, and redistribution notices accompany the API and engines; Harness retains these materials when packaging them. The notices check accepts the exact API, WASM, macOS ARM64/x64, and Windows ARM64/x64 package identities only at MPL-2.0; unrelated packages and changed license terms still reject.
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ LibreOffice 编译、源码补丁、平台资格验证和大型二进制发布
|
||||
|
||||
## Decision
|
||||
|
||||
`deepseek-harness/libreoffice-kit` 仓库维护可复用的 `@deepseek-ai/libreoffice-kit` Node API、Worker、字体处理、引擎选择、构建配方、补丁、测试和发布。API 不依赖 Cordis。Harness 负责将自己的 `OfficeToPdf` 服务适配到此 API,以及 Session 授权、转换生命周期、传输、Web UI 和应用打包。
|
||||
`deepseek-harness/libreoffice-kit` 仓库维护可复用的 `@deepseek-ai/libreoffice-kit` Node API、Worker、字体处理、引擎选择、构建配方、补丁、测试和发布。API 不依赖 Cordis。Harness 负责将自己的 `OfficeKit` 服务适配到此 API,以及 Session 授权、转换生命周期、传输、Web UI 和应用打包。
|
||||
|
||||
kit 发布流程独立于 Harness 发布流程。kit 仓库验证并以统一版本发布 Node API 和引擎 npm 包,起始版本为 `0.0.1`。Harness 消费精确的 npm 版本,并在 `pnpm-lock.yaml` 中提交依赖解析结果;Harness 发布既不构建也不发布 kit 包。
|
||||
|
||||
上游 API 将平台引擎声明为可选依赖。[平台引擎参考](../../../../packages/document/office-to-pdf/README.zh.md)取代最初强制携带 WASM 回退引擎的策略。Desktop 将 kit 作为外部 npm 依赖安装。Python sidecar 将 Worker、所选引擎及其依赖闭包保留在真实文件系统中,位于可执行文件的虚拟文件系统之外。转换无需下载或 GitHub 凭据。
|
||||
上游 API 将平台引擎声明为可选依赖。[平台引擎参考](../../../../packages/document/office-kit/README.zh.md)取代最初强制携带 WASM 回退引擎的策略。Desktop 将 kit 作为外部 npm 依赖安装。Python sidecar 将 Worker、所选引擎及其依赖闭包保留在真实文件系统中,位于可执行文件的虚拟文件系统之外。转换无需下载或 GitHub 凭据。
|
||||
|
||||
kit 仓库负责引擎资格验证和对应源码材料。API 和引擎携带 MPL-2.0 声明、可访问源码及再分发声明;Harness 打包时保留这些材料。再分发声明检查仅在许可为 MPL-2.0 时接受 API、WASM、macOS ARM64/x64 和 Windows ARM64/x64 的精确包标识;无关包和改变后的许可条款仍被拒绝。
|
||||
|
||||
|
||||
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: b305613daff5a763
|
||||
zh: 1aba13289664f65c
|
||||
/agent-note-bounded-shared-office-conversion/decision:
|
||||
en: bcd0780f95f173bf
|
||||
zh: 2a7f49db2575c59d
|
||||
en: d3c8181e29c1ff3b
|
||||
zh: a6f15b85e58907e5
|
||||
/agent-note-bounded-shared-office-conversion/alternatives-considered:
|
||||
en: 9d5cfb7fa5f66bd6
|
||||
zh: 4f4133daa2f02f43
|
||||
en: 73888970b0fc7f6b
|
||||
zh: 9bcccf774116f849
|
||||
/agent-note-bounded-shared-office-conversion/consequences:
|
||||
en: c2800ff56c8f89d1
|
||||
zh: 3b3710072b59c443
|
||||
|
||||
@@ -10,9 +10,9 @@ Office preview and explicit document inspection can request the same conversion.
|
||||
|
||||
## Decision
|
||||
|
||||
The `office-to-pdf` service returns complete PDF bytes. Its Remote file entry authorizes Session files through `workspaceFiles`, while its in-process conversion accepts authorized deferred reads without requiring that service. The `api/remotes` assembly owns Client namespace mounting. Page rasterization and user presentation remain separate consumers, so conversion naming does not imply image rendering or preview UI.
|
||||
The `office-kit` service returns complete PDF bytes. Its Remote file entry authorizes Session files through `workspaceFiles`, while its in-process conversion accepts authorized deferred reads without requiring that service. The `api/remotes` assembly owns Client namespace mounting. Page rasterization and user presentation remain separate consumers, so conversion naming does not imply image rendering or preview UI.
|
||||
|
||||
The [Host provider](../../../../packages/document/office-to-pdf/README.md) owns a shared conversion queue and transient content cache. Authorized source metadata enters admission before source bytes are loaded. The source callback receives reserved byte capacity and returns its read version; changed sources fail without publishing aliases. Exact source bytes and Office extension determine the digest. Each converter lifetime adds a generation so engine/font/configuration replacement invalidates reuse.
|
||||
The [Host provider](../../../../packages/document/office-kit/README.md) owns a shared conversion queue and transient content cache. Authorized source metadata enters admission before source bytes are loaded. The source callback receives reserved byte capacity and returns its read version; changed sources fail without publishing aliases. Exact source bytes and Office extension determine the digest. Each converter lifetime adds a generation so engine/font/configuration replacement invalidates reuse.
|
||||
|
||||
A bounded source-version index avoids repeated reads after authorization; the digest remains the identity for sharing conversion across distinct paths. Ready PDFs use an entry/byte-bounded LRU. Queued jobs contain metadata and deferred callbacks. Reader, queue, source-byte, and conversion limits also apply before work completes. Each active source locator belongs to live readers, so cancellation cannot grow retained source metadata independently of reader admission. Unknown source sizes reserve the input cap; cancellation retains active capacity until actual read/conversion cleanup settles.
|
||||
|
||||
@@ -28,7 +28,7 @@ Foreground preview and explicit QA requests precede background work. Disabling b
|
||||
|
||||
**Build a second prewarm or QA converter.** Independent queues duplicate resource ownership and cannot prioritize shared foreground work.
|
||||
|
||||
**Separate service-definition and provider packages for the sole LibreOffice implementation.** They evolve together and have no independent alternative implementation. One `office-to-pdf` package supplies the mountable service without duplicated package, dependency, and release configuration. Native and WASM engine selection remains inside the kit; a second independent implementation can justify extracting an interface from actual consumer needs.
|
||||
**Separate service-definition and provider packages for the sole LibreOffice implementation.** They evolve together and have no independent alternative implementation. One `office-kit` package supplies the mountable service without duplicated package, dependency, and release configuration. Native and WASM engine selection remains inside the kit; a second independent implementation can justify extracting an interface from actual consumer needs.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ Office 预览和显式文档检查可能请求相同转换。仅缓存已完成
|
||||
|
||||
## 决策
|
||||
|
||||
`office-to-pdf` 服务返回完整 PDF 字节。其 Remote 文件入口通过 `workspaceFiles` 授权 Session 文件,进程内转换则接受已授权的延迟读取,不要求该服务。`api/remotes` 装配负责 Client 命名空间挂载。页面栅格化和用户展示由独立消费方负责,因此转换命名不隐含图片渲染或预览 UI。
|
||||
`office-kit` 服务返回完整 PDF 字节。其 Remote 文件入口通过 `workspaceFiles` 授权 Session 文件,进程内转换则接受已授权的延迟读取,不要求该服务。`api/remotes` 装配负责 Client 命名空间挂载。页面栅格化和用户展示由独立消费方负责,因此转换命名不隐含图片渲染或预览 UI。
|
||||
|
||||
[宿主提供方](../../../../packages/document/office-to-pdf/README.zh.md)拥有共享转换队列和临时内容缓存。已授权的源文件元数据在加载字节之前进入准入流程。源回调接收预留的字节容量并返回读取版本;源文件变化会导致失败,不发布别名。确切的源字节和 Office 扩展名决定摘要。每个转换器生命周期附加代次,因此引擎、字体或配置替换会使复用失效。
|
||||
[宿主提供方](../../../../packages/document/office-kit/README.zh.md)拥有共享转换队列和临时内容缓存。已授权的源文件元数据在加载字节之前进入准入流程。源回调接收预留的字节容量并返回读取版本;源文件变化会导致失败,不发布别名。确切的源字节和 Office 扩展名决定摘要。每个转换器生命周期附加代次,因此引擎、字体或配置替换会使复用失效。
|
||||
|
||||
有界的源版本索引在授权后避免重复读取;摘要仍是不同路径间共享转换的身份。已就绪 PDF 使用按条目与字节限制的 LRU。排队任务包含元数据和延迟回调。读取方、队列、源字节与转换限制在工作完成前也适用。每个在途源定位信息归属于活跃读取方,因此取消操作不能让保留的源元数据脱离读取方准入限制增长。未知源大小预留输入上限;取消后仍保留活动容量,直至实际读取、转换与清理结束。
|
||||
|
||||
@@ -28,7 +28,7 @@ Office 预览和显式文档检查可能请求相同转换。仅缓存已完成
|
||||
|
||||
**另建预热或 QA 转换器。** 独立队列重复拥有资源,无法优先调度共享前台工作。
|
||||
|
||||
**为唯一的 LibreOffice 实现拆分服务定义和提供方包。** 两者共同演化,没有独立的替代实现。单个 `office-to-pdf` 包直接提供可挂载服务,避免重复维护包、依赖和发布配置。原生与 WASM 引擎选择仍由 kit 负责;若出现独立的第二种实现,再根据实际消费者提取接口。
|
||||
**为唯一的 LibreOffice 实现拆分服务定义和提供方包。** 两者共同演化,没有独立的替代实现。单个 `office-kit` 包直接提供可挂载服务,避免重复维护包、依赖和发布配置。原生与 WASM 引擎选择仍由 kit 负责;若出现独立的第二种实现,再根据实际消费者提取接口。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: a9984a594cdac98d
|
||||
zh: 38768e7699bc8d39
|
||||
/agent-note-progressive-office-skills-with-ordinary-shell-execution/decision:
|
||||
en: d057c558d6b9803c
|
||||
zh: acf3fdbc4cd7cee3
|
||||
en: 391c5d75994ab7e7
|
||||
zh: f820f407eb2d8769
|
||||
/agent-note-progressive-office-skills-with-ordinary-shell-execution/alternatives-considered:
|
||||
en: 8bca955255e6d982
|
||||
zh: bc313a4bcab3f6f9
|
||||
en: 0689e709dc1ff48d
|
||||
zh: 5d2108ccdd13f093
|
||||
/agent-note-progressive-office-skills-with-ordinary-shell-execution/consequences:
|
||||
en: cd4ff2423b2f5f9e
|
||||
zh: 63135aeae9c46415
|
||||
@@ -11,17 +11,17 @@ Combined authoring and rendering instructions spend context on operations unrela
|
||||
## Decision
|
||||
|
||||
|
||||
The [Office provider](../../../../packages/skill/skill-office/README.md) lists a compact router and six format-specific authoring or operation skills. The model loads full instructions by name and selects rendering for appearance-focused changes unless the user explicitly skips it. Rendering uses a private, randomly named temporary directory inside the task workspace and removes it after inspection. Skills invoke the shared Python checker and configured LibreOffice Kit CLI through ordinary Bash or PowerShell. SDK and Desktop own their Office composition separately; ordinary Web and the shared base do not mount these plugins.
|
||||
The [Office provider](../../../../packages/skill/skill-office/README.md) lists a compact router and three format-specific authoring skills; the operation skills this decision introduced are replaced by the [on-demand Office tools](2026-10-31-native-office-tool.md). The model loads full instructions by name and selects rendering for appearance-focused changes unless the user explicitly skips it. Rendering uses a private, randomly named temporary directory inside the task workspace and removes it after inspection. Skills invoke the shared Python checker and configured LibreOffice Kit CLI through ordinary Bash or PowerShell. SDK and Desktop own their Office composition separately; ordinary Web and the shared base do not mount these plugins. [Office skills in every deployment](2026-10-26-cross-deployment-office-kit.md) supersedes this mounting decision, and the [on-demand Office tools](2026-10-31-native-office-tool.md) supersede the shell execution and renderer-tool rejection.
|
||||
|
||||
Carriers own interpreter and resource locations. Operation skills append the configured Node and CLI paths only when their bodies are loaded; authoring skills use the selected Python and shared checker resource. Loading instructions does not prepare engines or authoring payloads. Ordinary shell execution retains its existing permissions and process management.
|
||||
|
||||
Office dependency ranges allow patch updates; the root lock records the resolved adapter and engine versions.
|
||||
LibreOffice Kit dependencies declare one exact version in every manifest; the root lock records the resolved adapter and engine versions.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep a dedicated renderer tool.** A small typed schema constrains rendering arguments, but requires its own execution configuration, scoped registration, projection, and activation lifecycle. The CLI operation skills accept shell quoting and model-selected arguments in exchange for reusing the existing approval, sandbox, cancellation, and output mechanisms.
|
||||
|
||||
**Hide subskills from the catalog.** This requires a generic visibility policy independent of invocation permission. Listing seven short summaries preserves the existing skill API while full instruction bodies remain demand-loaded.
|
||||
**Hide subskills from the catalog.** This requires a generic visibility policy independent of invocation permission. Listing four short summaries preserves the existing skill API while full instruction bodies remain demand-loaded.
|
||||
|
||||
**Automatically inject guides for file extensions.** Extensions do not distinguish text edits, recalculation, conversion, and requested visual checks. Model-selected routing keeps unrelated operations out of context and permits explicit direct invocation.
|
||||
|
||||
|
||||
@@ -11,17 +11,17 @@ Status: implemented
|
||||
## 决策
|
||||
|
||||
|
||||
[Office 提供者](../../../../packages/skill/skill-office/README.zh.md) 列出精简路由和六个各格式创作或操作技能。模型按名称加载完整说明;以外观为目标的修改会选择渲染,除非用户明确要求跳过。渲染使用任务工作区内随机命名的私有临时目录,并在检查后删除。技能通过普通 Bash 或 PowerShell 调用共享 Python 检查器和配置的 LibreOffice Kit CLI。SDK 和 Desktop 分别拥有 Office 组合;普通 Web 和共享 base 不挂载这些插件。
|
||||
[Office 提供者](../../../../packages/skill/skill-office/README.zh.md) 列出精简路由和三个各格式创作技能;本决策引入的操作技能已由[按需激活的 Office 工具](2026-10-31-native-office-tool.zh.md)取代。模型按名称加载完整说明;以外观为目标的修改会选择渲染,除非用户明确要求跳过。渲染使用任务工作区内随机命名的私有临时目录,并在检查后删除。技能通过普通 Bash 或 PowerShell 调用共享 Python 检查器和配置的 LibreOffice Kit CLI。SDK 和 Desktop 分别拥有 Office 组合;普通 Web 和共享 base 不挂载这些插件。[Office 技能在所有部署中可用](2026-10-26-cross-deployment-office-kit.zh.md)取代此挂载决策,[按需激活的 Office 工具](2026-10-31-native-office-tool.zh.md)取代其 shell 执行方式和对渲染工具的否决。
|
||||
|
||||
载体负责解释器和资源位置。操作技能仅在加载正文时附加配置的 Node 与 CLI 路径;创作技能使用选定的 Python 和共享检查器资源。加载说明不准备引擎或创作 payload。普通 shell 执行保留现有权限和进程管理。
|
||||
|
||||
Office 依赖范围允许补丁版本更新;根锁文件记录实际解析的适配器与引擎版本。
|
||||
LibreOffice Kit 依赖在每个清单中声明同一个精确版本;根锁文件记录实际解析的适配器与引擎版本。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留专属渲染工具。** 小型类型化 schema 能约束渲染参数,但需要专属执行配置、作用域注册、投影和激活生命周期。CLI 操作技能接受 shell 引用和模型选择参数的成本,以复用现有审批、沙箱、取消及输出机制。
|
||||
|
||||
**从目录隐藏子技能。** 这需要独立于调用权限的通用可见性策略。列出七条简短摘要可保留现有技能 API,完整说明仍按需加载。
|
||||
**从目录隐藏子技能。** 这需要独立于调用权限的通用可见性策略。列出四条简短摘要可保留现有技能 API,完整说明仍按需加载。
|
||||
|
||||
**按文件扩展名自动注入指南。** 扩展名无法区分文本编辑、重计算、转换和用户要求的视觉检查。模型选路使无关操作不进入上下文,并允许显式直接调用。
|
||||
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
en: 10bebc7f982cf7f4
|
||||
zh: 9247a2ba57921a11
|
||||
/agent-note-retained-ptc-processes-follow-file-policy/consequences:
|
||||
en: b3ddf273158d3754
|
||||
zh: 916356f4074b3622
|
||||
en: 1f94ca28e0cb7564
|
||||
zh: d8a918c0c5dfc6d8
|
||||
@@ -28,4 +28,4 @@ An approved one-time escalation omits the state owner and receives a fresh tempo
|
||||
|
||||
## Consequences
|
||||
|
||||
Normal calls retain variables without changing file permissions. Changing sandbox mode discards variables. Escalated programs must be self-contained. Runtime consumers must release a retained owner before supplying a different policy without a Session mode change; the provider rejects inconsistent reuse. The sandbox inherits the existing backends’ file-effect coverage and platform limitations.
|
||||
Normal calls retain variables without changing file permissions. Changing sandbox mode discards variables. Escalated programs must be self-contained. Runtime consumers must release a retained owner before changing its mode, workspace root or Session identity; the provider rejects inconsistent reuse. [Directory-effect lifetimes](2026-10-26-session-writable-directory-effects.md) permit extra-directory changes while retained processes keep their launch grants. The sandbox inherits the existing backends’ file-effect coverage and platform limitations.
|
||||
@@ -28,4 +28,4 @@ Status: implemented
|
||||
|
||||
## 影响
|
||||
|
||||
普通调用保留变量,同时维持原文件权限。切换沙箱模式会丢弃变量。提权程序必须包含执行所需的完整代码。未发生 Session 模式变化时,运行时消费者在传入不同策略前必须释放保留状态的所有者;提供方拒绝策略不一致的复用。沙箱继承现有后端的文件约束范围与平台限制。
|
||||
普通调用保留变量,同时维持原文件权限。切换沙箱模式会丢弃变量。提权程序必须包含执行所需的完整代码。运行时消费者在更改模式、工作区根目录或 Session 身份前必须释放保留状态的所有者;提供方拒绝策略不一致的复用。[目录 effect 生命周期](2026-10-26-session-writable-directory-effects.zh.md)允许额外目录发生变化,同时保留进程继续使用启动时的授权。沙箱继承现有后端的文件约束范围与平台限制。
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-13-team-identity-runtime-context.md
|
||||
/agent-note-subagent-identity-in-required-runtime-context:
|
||||
en: ce8f378b6e7b57fa
|
||||
zh: 26bd0415b4dba94a
|
||||
en: 3da6e33c5dc2c917
|
||||
zh: 9c8d4542cda63fc4
|
||||
/agent-note-subagent-identity-in-required-runtime-context/problem:
|
||||
en: f944254565541454
|
||||
zh: 27fa9f67c671dec7
|
||||
|
||||
@@ -4,6 +4,8 @@ Status: implemented
|
||||
|
||||
English | [中文](2026-10-13-team-identity-runtime-context.zh.md)
|
||||
|
||||
[Catalog names](2026-11-02-subagent-catalog-names.md) supersede the Session-id identity text below: agents see their own, direct parent, and root names. The runtime-context retention decision remains active.
|
||||
|
||||
## Problem
|
||||
|
||||
A teammate identity embedded in its initial task depends on ordinary history retention. Compaction can remove it, and an ordinary fork from that teammate can retain the old role even though the fork is an independent Lead. Identity must reflect the current subagent tree while keeping shared system instructions uniform.
|
||||
|
||||
@@ -4,6 +4,8 @@ Status: implemented
|
||||
|
||||
[English](2026-10-13-team-identity-runtime-context.md) | 中文
|
||||
|
||||
[目录名称](2026-11-02-subagent-catalog-names.zh.md)取代下文以 Session id 表述身份的文本:agent 看到自身、直接父级和根的名称。runtime context 保留决策仍然有效。
|
||||
|
||||
## Problem
|
||||
|
||||
嵌入初始任务的 teammate 身份依赖普通历史保留。压缩可能移除它;从 teammate 创建的普通 fork 即使已成为独立 Lead,也可能保留旧角色。身份必须反映当前 subagent 树,同时保持共享系统指令一致。
|
||||
|
||||
+18
-18
@@ -2,21 +2,21 @@
|
||||
# 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/architecture/2026-10-14-registry-bundle-updates.md
|
||||
/agent-note-registry-bundle-updates:
|
||||
en: 9b55699b0d5b3c7b
|
||||
zh: 729a564af144048d
|
||||
/agent-note-registry-bundle-updates/problem:
|
||||
en: 47524f94e08192b5
|
||||
zh: 335104b6fda38f03
|
||||
/agent-note-registry-bundle-updates/decision:
|
||||
en: 9201e6cc6cad6b8d
|
||||
zh: be2332897037c43a
|
||||
/agent-note-registry-bundle-updates/alternatives-considered:
|
||||
en: 299b8e211b9c21bf
|
||||
zh: 72bd106e96b42605
|
||||
/agent-note-registry-bundle-updates/consequences:
|
||||
en: 57c30a10a233c3e0
|
||||
zh: 87cd51e2df8696bd
|
||||
/agent-note-registry-bundle-updates/verification:
|
||||
en: ab3442415bff92a6
|
||||
zh: 89d477c7d14f6098
|
||||
/agent-note-registry-and-git-bundle-updates:
|
||||
en: 970e28efc1c454c5
|
||||
zh: 397ea3ace1d5b172
|
||||
/agent-note-registry-and-git-bundle-updates/problem:
|
||||
en: b1cf4a8c1135321c
|
||||
zh: e60ab2a2d876fc72
|
||||
/agent-note-registry-and-git-bundle-updates/decision:
|
||||
en: 29e891007e8efe08
|
||||
zh: ac038ad0c2e17618
|
||||
/agent-note-registry-and-git-bundle-updates/alternatives-considered:
|
||||
en: ba19c772e06f1f7d
|
||||
zh: 65d18f2d89ac3adc
|
||||
/agent-note-registry-and-git-bundle-updates/consequences:
|
||||
en: aa716f92ddbdd547
|
||||
zh: 01c29b7f21db33b4
|
||||
/agent-note-registry-and-git-bundle-updates/verification:
|
||||
en: b0ac6f7faff0f5d5
|
||||
zh: 5bbc3d5c16d4f2f1
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: Registry bundle updates
|
||||
# Agent Note: Registry and Git bundle updates
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,15 +6,17 @@ English | [中文](2026-10-14-registry-bundle-updates.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Third-party bundles installed from a registry had no update path. The plugin page offered **Update** only for Official bundles, whose target is the running DSH version, so a person learned of a newer third-party release outside DSH and replaced it by uninstalling and installing again. With HMR an install over an installed package is refused, and a manual removal followed by a failed installation leaves the plugin uninstalled.
|
||||
Third-party bundles installed from a registry had no update path. The plugin page offered **Update** only for Official bundles, whose target is the running DSH version, so a person learned of a newer third-party release outside DSH and replaced it by uninstalling and installing again. With HMR an install over an installed package is refused, and a manual removal followed by a failed installation leaves the plugin uninstalled. A bundle installed from Git had no update path either: a Git repository publishes no version list, and installing a newer commit from a plugin index over the installed copy resolves the package to another directory, which HMR cannot replace live, so the change waited for a restart and nothing reinstalled the old commit when it failed.
|
||||
|
||||
## Decision
|
||||
|
||||
**Versions are read through `pnpm view` in the profile directory.** `listVersions` runs `pnpm view <name> versions time dist-tags` over the same registry plan as `inspect`, so the `.npmrc` chain, proxy, and authentication that the installation uses also decide what the list shows, and a registry that cannot be reached moves the lookup to the next one. pnpm prints a range lookup as its highest match only, so each listed version takes its own `pnpm view <name>@<version> name version engines peerDependencies deprecated dsh` on the registry that answered. `versionListLimit` bounds how many newer versions are read and `versionLookupConcurrency` bounds the subprocesses in flight. Compatibility reuses `evaluatePluginCompatibility` with the profile's exemptions, so the list and the installation judge peers the same way.
|
||||
|
||||
**The Host owns the update and its recovery.** `updateBundle` views the target first and refuses a missing version, one that declares no bundle, or one whose `engines.dsh` or DSH peers reject the running version before anything changes. With HMR it then calls `removeBundle` and `installBundle(name@version, { saveExact, registry, enabled })`; when that installation fails, is cancelled, or installs but cannot start, it removes a target that stayed and installs the removed version again from the registry it came from, else over the configured plan. The reinstallation carries no request id, so a cancellation of the update cannot stop it. Without HMR it installs over the running version, which `installBundle` allows there and reports as `restart-required`. `plugin-manager/update-state` announces each step for the dialog's progress list.
|
||||
**The Host owns the update and its recovery.** For a registry copy, `updateBundle` views the target first and refuses a missing version, one that declares no bundle, or one whose `engines.dsh` or DSH peers reject the running version before anything changes. With HMR it then calls `removeBundle` and `installBundle(name@version, { saveExact, registry, enabled, expected })`, where `expected` makes the installation undo itself, modules included, unless it installs `name` at `version`; when that installation fails, is cancelled, or installs but cannot start, it removes a target that stayed and installs the removed version again from the registry it came from, else over the configured plan. The reinstallation carries no request id, so a cancellation of the update cannot stop it. Without HMR it installs over the running version, which `installBundle` allows there and reports as `restart-required`. `plugin-manager/update-state` announces each step for the dialog's progress list.
|
||||
|
||||
**Eligibility is a listed fact.** `BundleInfo.updatable` marks a third-party profile dependency saved as a registry spec under its own name and not as an `npm:` alias, with a semver version and no plugin management; the page shows the Versions section only for those, and both methods refuse every other bundle. The `plugin_manager` tool, which has no update action, omits the field.
|
||||
**Eligibility is a listed fact.** `BundleInfo.update` is `registry` for a third-party profile dependency saved as a registry spec under its own name and not as an `npm:` alias, and `git` for one saved as a Git spec pinned to a full commit, whose reinstallation brings back the removed version; either needs a semver version and no plugin management. The page shows the Versions section only for `registry` bundles, `listVersions` refuses every other bundle, and `updateBundle` refuses every bundle without `update`. The `plugin_manager` tool, which has no update action, omits the field.
|
||||
|
||||
**A Git copy updates to a version a plugin index lists, from the repository directory it came from.** A Git repository has no version list, while a plugin index pins each Git version to a commit and a package directory. The `pluginMarket` Host service marks a version with `update` when it is newer than a `git` copy and pins a commit of the same repository and `path:` directory; Discover offers **Update** for it and hands the package, version, and spec to the Plugins page, whose update dialog calls `updateBundle(name, version, { spec })`. The Host requires such a spec for a `git` copy and refuses one for a `registry` copy; `pinnedGitOrigin`, which both services import from `@deepseek-ai/dsh-plugin-manager`, compares origins regardless of the form pnpm saved, because a profile saves the SSH form of an HTTPS repository it cannot read and the `github:` shortcut of a GitHub URL. pnpm reads a Git package only while installing it, so before the removal the Host checks only that the network reaches the target's repository host; an unreachable host would fail the target and the reinstallation alike. A check Git cannot run, or one refused for credentials, is left to pnpm, which installs a public hosted commit as a tarball without Git. The rest is checked during the installation: compatibility as for every installation, and `expected` undoes a target that installs another package or version, or no bundle, before its bundle loads, failing with `unexpected-package` or `not-bundle`. The removed copy is reinstalled from the spec the profile saved, and Discover never installs over an installed copy.
|
||||
|
||||
**Updates only move forward.** The page lists versions newer than the installed one and `updateBundle` refuses any other, so an update never asks a plugin to read data a newer version wrote.
|
||||
|
||||
@@ -28,13 +30,26 @@ Third-party bundles installed from a registry had no update path. The plugin pag
|
||||
|
||||
**List versions while reading bundles.** `listBundles` is offline; adding registry lookups would make opening the plugin page a network operation with one subprocess per version.
|
||||
|
||||
**Read Git versions from repository tags in the Host.** A tag names no package directory in a monorepo and says nothing about the version an index vetted; the plugin index already pins commit, directory, and version.
|
||||
|
||||
**Install the newer commit over the installed copy.** It needs no removal, but with HMR the package resolves to another directory, so the live replacement is refused until a restart, and a failed installation reinstalls nothing.
|
||||
|
||||
**Check the package name before removing the installed copy.** pnpm reads a Git spec's manifest only by fetching it, and a second fetch before the installation would double the download; checking the installed package inside the run keeps one fetch, and the reinstallation recovers a wrong target.
|
||||
|
||||
**Remove a target that installed another package after the installation.** Its bundle would already have loaded and run, and when the other package was an existing dependency, `pnpm add` would have replaced it, so removing it would uninstall a plugin the person kept. Undoing the run with its lockfile restores that dependency too.
|
||||
|
||||
**Accept a target from another repository.** Index content is unsigned, and the update dialog carries no trust notice of its own, so any configured index could replace a plugin's code with code from a source the person never chose; a plugin that moved to another repository is uninstalled and installed again, with the install confirmation.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Checking a package costs one `pnpm view` plus one per listed version, each bounded by `inspectTimeoutMs`.
|
||||
- A target saved by an update, and a reinstallation after a failed one, are exact versions even when the profile had saved a range; with HMR the bundle joins the end of the selection.
|
||||
- A registry target saved by an update, and a reinstallation after a failed one, are exact versions even when the profile had saved a range; with HMR the bundle joins the end of the selection.
|
||||
- Removal and installation take the profile lock separately, so another operation can run between them; an installation that rejects, for example on a lock timeout, still triggers the reinstallation.
|
||||
- A version whose dependencies need a new build-script approval fails to install and is reinstalled at the previous version; its scripts are approved through an ordinary installation.
|
||||
- A reinstallation that also fails leaves the package uninstalled, and the dialog says so.
|
||||
- A Git target whose commit is missing, that declares no bundle, rejects the running DSH, or installs another package or version is found only after the installed copy was removed; the reinstallation recovers it at the cost of a stop and a second installation.
|
||||
- A Git copy saved with credentials in its URL is reinstalled from the saved spec without them, so a private repository needs a Git credential helper.
|
||||
- A Git copy saved with a branch, a tag, or a range, or one whose plugin moved to another repository, updates only by uninstalling and installing again.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -43,5 +58,11 @@ Third-party bundles installed from a registry had no update path. The plugin pag
|
||||
| Listing, registry fallback, refusals, compatibility, prereleases, deprecation, and the version limit | `packages/boot/plugin-manager/tests/manager.spec.ts` |
|
||||
| Update through removal and installation, reinstallation after a failed, cancelled, or rejected target, a failed reinstallation, refusal before any change, removal failure, and the no-HMR path | `packages/boot/plugin-manager/tests/manager.spec.ts` |
|
||||
| `pnpm view` answer parsing | `packages/boot/plugin-manager/tests/versions.spec.ts` |
|
||||
| Version checks, update state, and step progress in the store | `packages/client/ui-plugin-manager/tests/manager-store.client.spec.ts` |
|
||||
| Versions section, toolbar check, and every update outcome | `packages/client/ui-plugin-manager/tests/components.client.spec.tsx` |
|
||||
| Git eligibility, update through a commit of the same directory, reinstallation from the saved spec, a target that installs another package or version, the no-HMR path with a target without a bundle, an unreachable repository host, and spec refusals | `packages/boot/plugin-manager/tests/manager.spec.ts` |
|
||||
| Git origin across the forms pnpm saves | `packages/boot/plugin-manager/tests/install-spec.spec.ts` |
|
||||
| A run undone with its modules when it installs another package, another version, or no bundle where one is expected | `packages/boot/plugin-manager/tests/operations.spec.ts` |
|
||||
| The connection check of a Git repository on any host | `packages/boot/plugin-manager/tests/github-connection.spec.ts` |
|
||||
| Git versions of the installed copy's directory marked as updates in an index listing | `packages/boot/plugin-market/tests/market.spec.ts` |
|
||||
| Version checks, update state, and step progress in the store, including a Git update another tab names | `packages/client/ui-plugin-manager/tests/manager-store.client.spec.ts` |
|
||||
| Versions section, toolbar check, every update outcome, and a Git target's repository in the dialog | `packages/client/ui-plugin-manager/tests/components.client.spec.tsx` |
|
||||
| Discover's **Update** on cards and version rows, and no installation over an installed copy | `packages/client/ui-plugin-market/tests/discover.client.spec.tsx` |
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 注册表组合包更新
|
||||
# Agent Note: 注册表与 Git 组合包更新
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,15 +6,17 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
从注册表安装的第三方组合包没有更新途径。插件页只为官方组合包提供 **更新**,其目标是当前运行的 DSH 版本,因此用户只能在 DSH 之外得知第三方新版本,并通过卸载再安装来替换。有 HMR 时,安装到已安装的包之上会被拒绝;手动移除后安装失败,插件就处于未安装状态。
|
||||
从注册表安装的第三方组合包没有更新途径。插件页只为官方组合包提供 **更新**,其目标是当前运行的 DSH 版本,因此用户只能在 DSH 之外得知第三方新版本,并通过卸载再安装来替换。有 HMR 时,安装到已安装的包之上会被拒绝;手动移除后安装失败,插件就处于未安装状态。从 Git 安装的组合包同样没有更新途径:Git 仓库不发布版本列表;从插件索引把较新的提交安装到已安装副本之上时,包会解析到另一个目录,HMR 无法在运行中替换,因此改动要等到重启,安装失败时也没有任何机制重新安装旧提交。
|
||||
|
||||
## 决策
|
||||
|
||||
**版本通过在 profile 目录中运行 `pnpm view` 读取。** `listVersions` 按与 `inspect` 相同的注册表计划运行 `pnpm view <name> versions time dist-tags`,因此安装所用的 `.npmrc` 链、代理与认证同样决定列表显示什么,连不上的注册表会使查询转向下一个。pnpm 对范围查询只打印最高的匹配版本,因此每个列出的版本在作答的注册表上单独运行一次 `pnpm view <name>@<version> name version engines peerDependencies deprecated dsh`。`versionListLimit` 限制读取的新版本个数,`versionLookupConcurrency` 限制同时运行的子进程个数。兼容性复用 `evaluatePluginCompatibility` 与 profile 的豁免,因此列表与安装以同样方式判断 peer。
|
||||
|
||||
**更新及其恢复由 Host 负责。** `updateBundle` 先查询目标版本,在任何改动之前拒绝不存在的版本、未声明组合包的版本,以及 `engines.dsh` 或 DSH peer 拒绝当前运行版本的版本。有 HMR 时,它随后调用 `removeBundle` 与 `installBundle(name@version, { saveExact, registry, enabled })`;该安装失败、被取消,或已安装但无法启动时,先移除仍留下的目标版本,再从被移除版本原来的注册表重新安装它,没有记录时按配置的计划安装。重新安装不带请求 id,因此取消更新无法中止它。没有 HMR 时,它直接安装在运行中的版本之上,`installBundle` 在这种情况下允许这样做并报告 `restart-required`。`plugin-manager/update-state` 通告每一步,供对话框的进度列表使用。
|
||||
**更新及其恢复由 Host 负责。** 对注册表副本,`updateBundle` 先查询目标版本,在任何改动之前拒绝不存在的版本、未声明组合包的版本,以及 `engines.dsh` 或 DSH peer 拒绝当前运行版本的版本。有 HMR 时,它随后调用 `removeBundle` 与 `installBundle(name@version, { saveExact, registry, enabled, expected })`,其中 `expected` 让安装在装入的不是 `version` 版本的 `name` 时连同模块一起撤销自身;该安装失败、被取消,或已安装但无法启动时,先移除仍留下的目标版本,再从被移除版本原来的注册表重新安装它,没有记录时按配置的计划安装。重新安装不带请求 id,因此取消更新无法中止它。没有 HMR 时,它直接安装在运行中的版本之上,`installBundle` 在这种情况下允许这样做并报告 `restart-required`。`plugin-manager/update-state` 通告每一步,供对话框的进度列表使用。
|
||||
|
||||
**更新资格是列出的事实。** `BundleInfo.updatable` 标记以自身包名、按注册表 spec 保存(不是 `npm:` 别名)、带 semver 版本且不承载插件管理的第三方 profile 依赖;页面只为这些组合包显示版本区块,两个方法都拒绝其他组合包。没有更新操作的 `plugin_manager` 工具省略该字段。
|
||||
**更新资格是列出的事实。** 以自身包名、按注册表 spec 保存(不是 `npm:` 别名)的第三方 profile 依赖,其 `BundleInfo.update` 为 `registry`;按固定到完整提交的 Git spec 保存的为 `git`,其重新安装能恢复被移除的版本;两者都要求 semver 版本且不承载插件管理。页面只为 `registry` 组合包显示版本区块,`listVersions` 拒绝其他所有组合包,`updateBundle` 拒绝所有没有 `update` 的组合包。没有更新操作的 `plugin_manager` 工具省略该字段。
|
||||
|
||||
**Git 副本从其来源的仓库目录更新到插件索引列出的版本。** Git 仓库没有版本列表,而插件索引把每个 Git 版本固定到一个提交和一个包目录。版本比 `git` 副本新、且固定到同一仓库与 `path:` 目录中的一个提交时,`pluginMarket` Host 服务为其标上 `update`;「发现」为其提供 **更新**,并把包、版本和 spec 交给插件页,由插件页的更新对话框调用 `updateBundle(name, version, { spec })`。Host 要求 `git` 副本提供这样的 spec,并拒绝为 `registry` 副本提供的 spec;两个服务都从 `@deepseek-ai/dsh-plugin-manager` 导入 `pinnedGitOrigin`,它比较仓库来源时不考虑 pnpm 保存的形式,因为 profile 会为它无法读取的 HTTPS 仓库保存 SSH 形式,并为 GitHub URL 保存 `github:` 简写。pnpm 只在安装时读取 Git 包,因此 Host 在移除之前只检查网络能否到达目标仓库所在的主机;主机不可达时,目标的安装与重新安装同样会失败。Git 无法运行的检查,或因需要凭据而被拒绝的检查,交给 pnpm 处理,pnpm 无需 Git 即可将公开托管仓库中的提交作为 tarball 安装。其余检查在安装过程中进行:兼容性的检查与每次安装相同,`expected` 在组合包加载前撤销安装了另一个包或版本、或不含组合包的目标,并以 `unexpected-package` 或 `not-bundle` 失败。被移除的副本从 profile 保存的 spec 重新安装,「发现」从不在已安装副本之上安装。
|
||||
|
||||
**更新只向前。** 页面只列出比已安装版本新的版本,`updateBundle` 拒绝其他版本,因此更新不会让插件读取更新版本写入的数据。
|
||||
|
||||
@@ -28,13 +30,26 @@ Status: implemented
|
||||
|
||||
**在读取组合包时列出版本。** `listBundles` 不联网;加入注册表查询会让打开插件页变成网络操作,且每个版本一个子进程。
|
||||
|
||||
**由 Host 从仓库 tag 读取 Git 版本。** 在 monorepo 中,tag 不指明包目录,也不说明索引审核过哪个版本;插件索引已经固定了提交、目录与版本。
|
||||
|
||||
**把较新的提交安装到已安装副本之上。** 这样无需移除,但有 HMR 时包会解析到另一个目录,因此运行中替换会被拒绝,直到重启;安装失败时也不会重新安装任何版本。
|
||||
|
||||
**在移除已安装副本之前检查包名。** pnpm 只有拉取 Git spec 才能读取其 manifest(元数据清单),在安装前再拉取一次会使下载量翻倍;在本次运行中检查安装的包只需拉取一次,目标不对时由重新安装恢复。
|
||||
|
||||
**在安装之后移除安装了另一个包的目标。** 那时其组合包已经加载并运行;若另一个包是已有依赖,`pnpm add` 已将其替换,移除它会卸载用户保留的插件。连同锁文件撤销本次运行,也会恢复该依赖。
|
||||
|
||||
**接受来自其他仓库的目标。** 索引内容没有签名,更新对话框也不带自己的信任提示,因此任何已配置的索引都能用来自用户从未选择的来源的代码替换插件的代码;迁移到另一个仓库的插件需要先卸载再安装,并经过安装确认。
|
||||
|
||||
## 后果
|
||||
|
||||
- 检查一个包需要一次 `pnpm view`,再加上每个列出版本一次,每次都受 `inspectTimeoutMs` 限制。
|
||||
- 更新保存的目标版本,以及失败后重新安装的版本,都是确切版本,即使 profile 原先保存的是范围;有 HMR 时,组合包排到选中列表末尾。
|
||||
- 更新保存的注册表目标版本,以及失败后重新安装的版本,都是确切版本,即使 profile 原先保存的是范围;有 HMR 时,组合包排到选中列表末尾。
|
||||
- 移除与安装分别获取 profile 锁,其他操作可能在两者之间运行;安装被拒绝(例如锁等待超时)时,同样会触发重新安装。
|
||||
- 依赖需要新的构建脚本批准的版本会安装失败,并重新安装为原版本;其脚本通过普通安装批准。
|
||||
- 重新安装也失败时,包处于未安装状态,对话框会如实说明。
|
||||
- 提交不存在、未声明组合包、拒绝当前运行 DSH,或安装了另一个包或版本的 Git 目标,只有在已安装副本被移除之后才会被发现;重新安装可以恢复,代价是插件停止一次并多安装一次。
|
||||
- URL 中带凭据保存的 Git 副本,从已保存的 spec 重新安装时不带这些凭据,因此私有仓库需要 Git 凭据助手。
|
||||
- 以分支、标签或范围保存的 Git 副本,或其插件已迁移到另一个仓库的 Git 副本,只能通过先卸载再安装来更新。
|
||||
|
||||
## 验证
|
||||
|
||||
@@ -43,5 +58,11 @@ Status: implemented
|
||||
| 列表、注册表回退、拒绝、兼容性、预发布、弃用与版本上限 | `packages/boot/plugin-manager/tests/manager.spec.ts` |
|
||||
| 经移除与安装的更新、目标失败、取消或被拒绝后的重新安装、重新安装失败、改动前的拒绝、移除失败与无 HMR 路径 | `packages/boot/plugin-manager/tests/manager.spec.ts` |
|
||||
| `pnpm view` 答复解析 | `packages/boot/plugin-manager/tests/versions.spec.ts` |
|
||||
| store 中的版本检查、更新状态与步骤进度 | `packages/client/ui-plugin-manager/tests/manager-store.client.spec.ts` |
|
||||
| 版本区块、工具栏检查与每种更新结果 | `packages/client/ui-plugin-manager/tests/components.client.spec.tsx` |
|
||||
| Git 更新资格、经同一目录中某个提交的更新、从已保存 spec 重新安装、安装另一个包或版本的目标、目标不含组合包时的无 HMR 路径、不可达的仓库主机,以及对 spec 的拒绝 | `packages/boot/plugin-manager/tests/manager.spec.ts` |
|
||||
| pnpm 保存的各种形式下的 Git 来源 | `packages/boot/plugin-manager/tests/install-spec.spec.ts` |
|
||||
| 安装了另一个包、另一个版本,或缺少预期的组合包时,连同模块一起撤销的运行 | `packages/boot/plugin-manager/tests/operations.spec.ts` |
|
||||
| 对任意主机上 Git 仓库的连接检查 | `packages/boot/plugin-manager/tests/github-connection.spec.ts` |
|
||||
| 索引列表中被标为更新的、已安装副本所在目录的 Git 版本 | `packages/boot/plugin-market/tests/market.spec.ts` |
|
||||
| store 中的版本检查、更新状态与步骤进度,包括另一个标签页指定的 Git 更新 | `packages/client/ui-plugin-manager/tests/manager-store.client.spec.ts` |
|
||||
| 版本区块、工具栏检查、每种更新结果,以及对话框中 Git 目标的仓库 | `packages/client/ui-plugin-manager/tests/components.client.spec.tsx` |
|
||||
| 「发现」卡片和版本行上的 **更新**,以及不在已安装副本之上安装 | `packages/client/ui-plugin-market/tests/discover.client.spec.tsx` |
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.md
|
||||
/agent-note-agent-teams-teammates-are-subagents:
|
||||
en: 51e2fc51a6e1622a
|
||||
zh: 7e09ea6292e5921d
|
||||
en: f49f9e7ef588e612
|
||||
zh: 7da804056221af51
|
||||
/agent-note-agent-teams-teammates-are-subagents/problem:
|
||||
en: ca2848e3bbc3a009
|
||||
zh: 0551e32ff6d187d0
|
||||
|
||||
@@ -4,7 +4,7 @@ Status: implemented
|
||||
|
||||
English | [中文](2026-10-15-agent-teams-as-subagents.zh.md)
|
||||
|
||||
The adjacency and parent-lifetime restrictions are superseded by [tree-wide communication](2026-10-16-tree-wide-agent-communication.md). [Task-board inbox coordination](2026-10-19-team-task-board-inbox.md) supersedes the separate roster and Team-owned wait; the single subagent identity decision remains active.
|
||||
The adjacency and parent-lifetime restrictions are superseded by [tree-wide communication](2026-10-16-tree-wide-agent-communication.md). [Task-board inbox coordination](2026-10-19-team-task-board-inbox.md) supersedes the separate roster and Team-owned wait; the single subagent identity decision remains active. [Catalog names](2026-11-02-subagent-catalog-names.md) supersede the statement that creation accepts no member name and the rejection of unique names below.
|
||||
|
||||
## Problem
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Status: implemented
|
||||
|
||||
[English](2026-10-15-agent-teams-as-subagents.md) | 中文
|
||||
|
||||
邻接和父级生命周期限制由[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)取代。[任务板收件箱协调](2026-10-19-team-task-board-inbox.zh.md)取代独立名册和 Team 所有的等待;单一 subagent 身份决策仍有效。
|
||||
邻接和父级生命周期限制由[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)取代。[任务板收件箱协调](2026-10-19-team-task-board-inbox.zh.md)取代独立名册和 Team 所有的等待;单一 subagent 身份决策仍有效。[目录名称](2026-11-02-subagent-catalog-names.zh.md)取代下文“创建不接收成员名称”的表述及对唯一名称的否决。
|
||||
|
||||
## Problem
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: c583c3cdb894e417
|
||||
zh: 6edc0354b79f4f26
|
||||
/agent-note-communication-follows-the-subagent-tree/decision:
|
||||
en: da35a75e069fa1e4
|
||||
zh: 7edf62ad66ab04ec
|
||||
en: 767b4b4dfd84f72e
|
||||
zh: 36617ec0dab777ee
|
||||
/agent-note-communication-follows-the-subagent-tree/alternatives-considered:
|
||||
en: 470f9cf8c3e32af9
|
||||
zh: 73bbe29767c5d02a
|
||||
|
||||
@@ -10,13 +10,13 @@ A Team shares one task board across the root's entire subagent tree. Restricting
|
||||
|
||||
## Decision
|
||||
|
||||
`list_agents` starts at the caller's live root and lists that root and all reachable continuable descendants, including the caller. Parent ids and depth remain relative to the root. The existing parent catalogs remain authoritative; no Team directory is stored separately.
|
||||
`list_agents` starts at the caller's live root and lists that root and all reachable continuable descendants, including the caller. Full names remain relative to the root. The existing parent catalogs remain authoritative; no Team directory is stored separately.
|
||||
|
||||
`send_message` accepts another root or continuable member of the same tree. The sender must be the exact live root or a resident continuable subagent. Cross-branch delivery verifies the target descriptor and creation catalogs, then restores only the target under the Team root. Final inbox admission rechecks the sender and root. Cancellation or rejection before acceptance releases the new target instance; accepted input follows normal Agent persistence.
|
||||
|
||||
The existing Agent registry records runtime ownership by the Team root. Creation parent ids and catalogs preserve ancestry for discovery and interrupt authority. A member finishing or being released affects only its own instance; Team-root shutdown joins all startup and cleanup. This removes ancestor restoration, parent retention, and recursive disposal without adding a Team runtime registry. Saved target presets and permission events supply restoration configuration.
|
||||
|
||||
This supersedes the adjacency, discovery, and parent-lifetime restrictions in [adjacent messaging](2026-08-27-adjacent-agent-steer-messaging.md) and [subagent teammates](2026-10-15-agent-teams-as-subagents.md). Their shared inbox, scheduling, attribution, and Team reuse decisions remain active. Members use Session ids for addressing; ordinary Session forks lead independent Teams.
|
||||
This supersedes the adjacency, discovery, and parent-lifetime restrictions in [adjacent messaging](2026-08-27-adjacent-agent-steer-messaging.md) and [subagent teammates](2026-10-15-agent-teams-as-subagents.md). Their shared inbox, scheduling, attribution, and Team reuse decisions remain active. Members address one another by the names that [catalog names](2026-11-02-subagent-catalog-names.md) define, or by Session id; ordinary Session forks lead independent Teams.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+2
-2
@@ -10,13 +10,13 @@ Status: implemented
|
||||
|
||||
## Decision
|
||||
|
||||
`list_agents` 从调用方的在线根开始,列出该根与所有可达的可继续后代,包括调用方。父级 id 和深度始终相对根计算。现有父级目录仍是权威来源;不单独存储 Team 目录。
|
||||
`list_agents` 从调用方的在线根开始,列出该根与所有可达的可继续后代,包括调用方。完整名称始终相对根计算。现有父级目录仍是权威来源;不单独存储 Team 目录。
|
||||
|
||||
`send_message` 接受同一棵树中的其他根或可继续成员。发送者必须是确切在线根或驻留的可继续子代理。跨分支投递验证目标描述符和创建目录后,仅在 Team 根下恢复目标。最终收件箱准入重新检查发送者与根。接受前的取消或拒绝释放新建目标实例;已接受输入遵循普通 Agent 持久化。
|
||||
|
||||
现有 Agent 注册表记录 Team 根对运行时实例的所有权。创建父级 id 和目录保留用于发现及中断授权的祖先关系。成员完成或释放只影响自身实例;关闭 Team 根会等待全部启动和清理。这删除了祖先恢复、父级保留和递归释放,无需增加 Team 运行时注册表。目标保存的 preset 和权限事件提供恢复配置。
|
||||
|
||||
本决策替代[相邻消息](2026-08-27-adjacent-agent-steer-messaging.zh.md)与[子代理队友](2026-10-15-agent-teams-as-subagents.zh.md)中的邻接、发现及父子生命周期限制。它们的共享收件箱、调度、发送者归属和 Team 复用决策仍有效。成员使用 Session id 寻址;普通 Session 分叉领导独立 Team。
|
||||
本决策替代[相邻消息](2026-08-27-adjacent-agent-steer-messaging.zh.md)与[子代理队友](2026-10-15-agent-teams-as-subagents.zh.md)中的邻接、发现及父子生命周期限制。它们的共享收件箱、调度、发送者归属和 Team 复用决策仍有效。成员使用[目录名称](2026-11-02-subagent-catalog-names.zh.md)定义的名称或 Session id 互相寻址;普通 Session 分叉领导独立 Team。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: ac391b569d2bc7e9
|
||||
zh: a95f613aa2b330b8
|
||||
/agent-note-subagent-tools-own-communication-guidance/decision:
|
||||
en: 52fb33963bedfe07
|
||||
zh: 75fe66d80f47323f
|
||||
en: d83310e85ffcb3cf
|
||||
zh: eb1f069948399d93
|
||||
/agent-note-subagent-tools-own-communication-guidance/alternatives-considered:
|
||||
en: c55d44c6414e0c4c
|
||||
zh: 9fa2ab31843e259b
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ The continuation manager inspected a Symbol on the standard messaging tool befor
|
||||
|
||||
## Decision
|
||||
|
||||
`tool-subagent-control` owns messaging instructions and parent addressing. Its logged runtime context gives delegated children their direct creator’s id from the Session header, explains when to send findings, and states that sending does not finish a turn. The context is emitted only for delegated children when the plugin’s own tool definition is admitted in the current scope and step; roots and ordinary Session forks receive none. Communication guidance follows inherited history without changing the shared system prompt or tool schema.
|
||||
`tool-subagent-control` owns messaging instructions and parent addressing. Its logged runtime context gives delegated children their direct creator’s name from `ctx.subagents.namesOf()`, explains when to send findings, and states that sending does not finish a turn. The context is emitted only for delegated children when the plugin’s own tool definition is admitted in the current scope and step; roots and ordinary Session forks receive none. Communication guidance follows inherited history without changing the shared system prompt or tool schema.
|
||||
|
||||
The manager owns result delivery. Local activations with `delivery: 'parent'` receive automatic-result guidance even without messaging tools; caller-owned activations receive no parent-delivery promise. Settlement timing, recipients, output, and suppression of closing text after a final message to the parent remain unchanged. Tool visibility grants no messaging authority.
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
`tool-subagent-control` 拥有通信说明和父级寻址。持久记录的运行时上下文从 Session header 读取委派子级的直接创建者 id,说明何时发送发现,并指出发送不会结束轮次。仅当本插件的工具定义在当前作用域和 step 中可用时,才向委派子级提供上下文;根代理和普通 Session fork 不接收。通信指导位于继承历史之后,不改变共享系统提示词或工具 schema。
|
||||
`tool-subagent-control` 拥有通信说明和父级寻址。持久记录的运行时上下文通过 `ctx.subagents.namesOf()` 提供委派子级的直接创建者名称,说明何时发送发现,并指出发送不会结束轮次。仅当本插件的工具定义在当前作用域和 step 中可用时,才向委派子级提供上下文;根代理和普通 Session fork 不接收。通信指导位于继承历史之后,不改变共享系统提示词或工具 schema。
|
||||
|
||||
管理器拥有结果交付。采用 `delivery: 'parent'` 的本地 activation 即使没有通信工具,也会收到自动结果交付指导;调用方拥有的 activation 不会收到父级交付承诺。结算时序、接收方、输出,以及最后向父级发送消息后省略结束文本的规则保持不变。工具可见性不授予通信权限。
|
||||
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-26-cross-deployment-office-kit.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/architecture/2026-10-26-cross-deployment-office-kit.md
|
||||
/agent-note-office-skills-in-every-deployment-with-one-office-kit-package:
|
||||
en: aef0b589f3562990
|
||||
zh: c2227dd19e282c6f
|
||||
/agent-note-office-skills-in-every-deployment-with-one-office-kit-package/problem:
|
||||
en: 129d30508926bbc5
|
||||
zh: 15feff44556fc9b7
|
||||
/agent-note-office-skills-in-every-deployment-with-one-office-kit-package/decision:
|
||||
en: 7d70a22025ab858e
|
||||
zh: cc37cb9932e78787
|
||||
/agent-note-office-skills-in-every-deployment-with-one-office-kit-package/alternatives-considered:
|
||||
en: 167c52da0171028f
|
||||
zh: 9c87c2370d0719df
|
||||
/agent-note-office-skills-in-every-deployment-with-one-office-kit-package/consequences:
|
||||
en: ffd13aad24ca750c
|
||||
zh: a22f513e123f31b4
|
||||
@@ -0,0 +1,43 @@
|
||||
# Agent Note: Office skills in every deployment with one Office Kit package
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-26-cross-deployment-office-kit.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Office skills were mounted only by Desktop and the Python SDK, so ordinary `dsh web` users could not create or inspect Office documents with the same guidance. Each deployment located LibreOffice Kit differently: the conversion provider, the skills, and the Desktop shell each held their own lookup, and an absent Kit made the conversion row fail to load. A Python SDK executable without its downloaded Office sidecar, or a source checkout without native engines, is a deployment that lacks Kit, not a misconfigured composition; failing the row hid the provider and the skills even though most of their behavior did not need Kit.
|
||||
|
||||
## Decision
|
||||
|
||||
The [Web bundle](../../../../packages/bundle/web-app/cordis.patch.yml) and the [SDK bundle](../../../../packages/bundle/sdk-app/cordis.patch.yml) mount [`skill-office`](../../../../packages/skill/skill-office/README.md) in Desktop, ordinary `dsh web`, and the SDK. This supersedes the mounting decision of [progressive Office skills](2026-09-27-progressive-office-skills.md); its skill structure remains current, and the [on-demand Office tools](2026-10-31-native-office-tool.md) replace its shell execution.
|
||||
|
||||
Every deployment reads the Office skills from the `skill-office` package's `assets/`, which hold only `SKILL.md` files. The Host reads them with ordinary file reads: Desktop's Electron Host reads them inside `app.asar`, and the SDK executable embeds them, so no carrier copies the skills to a separate resource directory. A composition selects another directory through `assetRoot`, or overrides individual skills with same-name filesystem skills.
|
||||
|
||||
The bundled Python environment ([`tool-workspace-dependencies`](../../../../packages/skill/tool-workspace-dependencies/README.md)) mounts only in Desktop and in the SDK when a primary runtime is available. `skill-office` declares `pythonEnvironment: 'workspace-dependencies' | 'user'` explicitly; each bundle row selects the value for its deployment. The default is `'workspace-dependencies'` because older SDK patches replace the complete `config` of the `skill-office` row; such a patch omits the new key and keeps the bundled-environment instructions it was written for.
|
||||
|
||||
[`@deepseek-ai/dsh-office-kit`](../../../../packages/document/office-kit/README.md) owns Kit location and the shared conversion service. Kit location is an internal implementation of the provider and is not exported: the provider passes its own `import.meta.resolve` lookup, and native engine paths use ASAR-unpacked mapping. Location reads no environment variables. The [on-demand Office tools](2026-10-31-native-office-tool.md) run Kit operations inside the Host, so no deployment publishes Kit CLI paths or Office executable variables.
|
||||
|
||||
An absent Kit degrades instead of failing a load. The conversion service registers and rejects conversions with code `unavailable`, retaining only a successful Kit import and reporting each distinct failure once; the `office_render`, `office_convert`, and `office_recalculate` tools stay registered and return that message when called.
|
||||
|
||||
`office-to-pdf` became `office-kit` so that later Kit consumers, such as native rendering (PR #5374) and the `tool-office` package, take Kit from one package. LibreOffice Kit dependencies declare one exact version in every manifest, so the converter and packaged engine come from the same Kit release.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep Office skills out of ordinary Web.** This keeps ordinary Web free of Office guidance it might not execute, but leaves Web users without document authoring that needs only Python libraries. Skills now direct the model to the user's Python when no bundled environment exists.
|
||||
|
||||
**Default `pythonEnvironment` to `'user'`.** This matches ordinary Web, but an older SDK patch that replaces the row's `config` would silently switch Desktop-like SDK deployments to the user's Python.
|
||||
|
||||
**Read deployment environment variables inside the locator.** This removes the `cordis.yml` expressions, but hides deployment configuration inside a library and makes source, Web, Desktop, and SDK behavior depend on ambient variables. Bundles already own deployment-specific expressions.
|
||||
|
||||
**Fail the conversion row when Kit is absent.** This reports the absence at load, which the misconfiguration rule prefers, but the absence belongs to the deployment rather than the configuration, and it disables preview rendering paths and dependent skills that do not need Kit.
|
||||
|
||||
**Keep a separate locator package.** A package without Cordis dependencies isolates `skill-office` from the service, but duplicates release, dependency-policy, and documentation configuration for one closely coupled component. No consumer outside the provider needs to locate Kit, so the location logic stays inside the provider.
|
||||
|
||||
## Consequences
|
||||
|
||||
Ordinary Web and SDK sessions without a bundled environment list the four Office skills, which adds their summaries to the first context. Deployments without Kit see `unavailable` previews and `office_*` tools that report the missing Kit instead of failing at startup; skill loading does not check for Kit; a later Kit installation takes effect for conversion without reloading the provider. External compositions migrate from the `office-to-pdf` row, `ctx.officeToPdf`, and the `officeToPdf/*` Remote methods to the `office-kit` package through the [upgrade guide](../../../../docs/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.md).
|
||||
|
||||
Processes launched from the Desktop built-in terminal inherit `DSH_PRIMARY_RUNTIME`, so the SDK uses its bundled environment. The shell publishes no Kit location; child applications resolve their own Kit.
|
||||
|
||||
Verification covers Kit lookup from the provider's Kit entry ([kit-lookup.spec.ts](../../../../packages/document/office-kit/tests/kit-lookup.spec.ts)), engine resolution and ASAR-unpacked path mapping ([engine-package.spec.ts](../../../../packages/document/office-kit/tests/engine-package.spec.ts)), independent engine resolution per Kit installation ([anchors.spec.ts](../../../../packages/document/office-kit/tests/anchors.spec.ts)), conversion retry after a missing or unloadable Kit, deduplicated warnings, the `office_*` tools' `unavailable` results with an absent Kit, recorded Web and SDK sessions that list the Office skills, and Office profile end-to-end tests.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Agent Note: Office 技能在所有部署中可用,由单一 Office Kit 包提供 Kit
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-26-cross-deployment-office-kit.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
原先只有 Desktop 和 Python SDK 挂载 Office 技能,普通 `dsh web` 用户无法借助同样的指导创建或检查 Office 文档。各部署定位 LibreOffice Kit 的方式也不同:转换提供方、技能和 Desktop 壳各自查找 Kit,Kit 缺失时转换配置行加载失败。没有下载 Office sidecar 的 Python SDK 可执行程序、没有原生引擎的源码检出,都属于部署缺少 Kit,而不是组合配置错误;让配置行失败会隐藏提供方和技能,而它们的大部分行为并不需要 Kit。
|
||||
|
||||
## 决策
|
||||
|
||||
[Web bundle](../../../../packages/bundle/web-app/cordis.patch.yml) 和 [SDK bundle](../../../../packages/bundle/sdk-app/cordis.patch.yml) 在 Desktop、普通 `dsh web` 和 SDK 中都挂载 [`skill-office`](../../../../packages/skill/skill-office/README.zh.md)。这取代了[渐进式 Office 技能](2026-09-27-progressive-office-skills.zh.md)中的挂载决策;其技能结构仍然有效,[按需激活的 Office 工具](2026-10-31-native-office-tool.zh.md)取代了其 shell 执行方式。
|
||||
|
||||
所有部署都从 `skill-office` 包的 `assets/` 读取 Office 技能,该目录只包含 `SKILL.md` 文件。Host 直接读取这些文件:Desktop 的 Electron Host 在 `app.asar` 内读取,SDK 可执行程序则内嵌这些文件,因此没有载体把技能复制到单独的资源目录。组合可以通过 `assetRoot` 选择其他目录,或用同名文件系统技能覆盖单个技能。
|
||||
|
||||
内置 Python 环境([`tool-workspace-dependencies`](../../../../packages/skill/tool-workspace-dependencies/README.zh.md))只在 Desktop 以及有主运行时的 SDK 中挂载。`skill-office` 显式声明 `pythonEnvironment: 'workspace-dependencies' | 'user'`,由各 bundle 的配置行按部署选择取值。默认值为 `'workspace-dependencies'`,因为旧 SDK patch 会整体替换 `skill-office` 行的 `config`;这样的 patch 不含新键,仍保留它所针对的内置环境说明。
|
||||
|
||||
[`@deepseek-ai/dsh-office-kit`](../../../../packages/document/office-kit/README.zh.md) 负责 Kit 定位和共享转换服务。Kit 定位是提供方的内部实现,不对外导出:提供方传入自己的 `import.meta.resolve` 查找函数,原生引擎路径使用 ASAR 解包映射。定位不读取环境变量。[按需激活的 Office 工具](2026-10-31-native-office-tool.zh.md)在 Host 内执行 Kit 操作,因此任何部署都不发布 Kit CLI 路径或 Office 可执行文件变量。
|
||||
|
||||
Kit 缺失时降级,而不是加载失败。转换服务照常注册,以代码 `unavailable` 拒绝转换,只保留成功的 Kit 导入,并对每种不同的失败只报告一次;`office_render`、`office_convert` 和 `office_recalculate` 工具保持注册并在调用时返回该消息。
|
||||
|
||||
`office-to-pdf` 改为 `office-kit`,使后续的 Kit 消费方(例如原生渲染(PR #5374)和 `tool-office` 包)从同一个包获取 Kit。LibreOffice Kit 依赖在每个清单中声明同一个精确版本,因此转换器和打包的引擎来自同一个 Kit 发行版。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**普通 Web 继续不挂载 Office 技能。** 这样普通 Web 不会出现它可能无法执行的 Office 指导,但 Web 用户也无法使用只需要 Python 库的文档创作。现在没有内置环境时,技能会引导模型使用用户的 Python。
|
||||
|
||||
**`pythonEnvironment` 默认取 `'user'`。** 这与普通 Web 一致,但整体替换该行 `config` 的旧 SDK patch 会让类似 Desktop 的 SDK 部署悄然改用用户的 Python。
|
||||
|
||||
**在定位函数中读取部署环境变量。** 这样可以去掉 `cordis.yml` 表达式,但会把部署配置藏进库中,使源码、Web、Desktop 和 SDK 的行为依赖环境变量。bundle 本来就负责部署专有的表达式。
|
||||
|
||||
**Kit 缺失时让转换配置行失败。** 这样能在加载时报告缺失,符合配置错误应尽早报告的规则;但缺失属于部署环境而非配置,而且会停用不需要 Kit 的预览渲染路径和依赖技能。
|
||||
|
||||
**保留独立的定位包。** 没有 Cordis 依赖的独立包可以让 `skill-office` 与服务隔离,但要为一个紧密耦合的组件重复维护发布、依赖策略和文档配置。提供方以外没有消费方需要定位 Kit,因此定位逻辑留在提供方内部。
|
||||
|
||||
## 影响
|
||||
|
||||
没有内置环境的普通 Web 和 SDK 会话会列出四个 Office 技能,首个上下文因此增加它们的摘要。没有 Kit 的部署看到的是 `unavailable` 预览和报告 Kit 缺失的 `office_*` 工具,而不是启动失败;加载技能时不检查 Kit;之后安装的 Kit 无需重新加载提供方即可用于转换。外部组合按[升级指南](../../../../docs/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.zh.md)从 `office-to-pdf` 配置行、`ctx.officeToPdf` 和 `officeToPdf/*` Remote 方法迁移到 `office-kit` 包。
|
||||
|
||||
从 Desktop 内置终端启动的进程会继承 `DSH_PRIMARY_RUNTIME`,因此 SDK 使用其内置环境。Desktop 壳不发布 Kit 位置;子应用自行解析自己的 Kit。
|
||||
|
||||
验证覆盖从提供方 Kit 入口查找 Kit([kit-lookup.spec.ts](../../../../packages/document/office-kit/tests/kit-lookup.spec.ts))、引擎解析与 ASAR 解包路径映射([engine-package.spec.ts](../../../../packages/document/office-kit/tests/engine-package.spec.ts))、按 Kit 安装独立解析引擎([anchors.spec.ts](../../../../packages/document/office-kit/tests/anchors.spec.ts))、Kit 缺失或无法加载后的转换重试、去重后的警告、Kit 缺失时 `office_*` 工具的 `unavailable` 结果、列出 Office 技能的录制 Web 与 SDK 会话,以及 Office profile 端到端测试。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-26-session-writable-directory-effects.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/architecture/2026-10-26-session-writable-directory-effects.md
|
||||
/agent-note-session-writable-directory-effects:
|
||||
en: b5a98da65d44e212
|
||||
zh: ba6842a9b4a890f7
|
||||
/agent-note-session-writable-directory-effects/problem:
|
||||
en: 2a32db1f27371403
|
||||
zh: 89cf04705ef66769
|
||||
/agent-note-session-writable-directory-effects/decision:
|
||||
en: af67969841bedb67
|
||||
zh: edabdfaa30bcadd1
|
||||
/agent-note-session-writable-directory-effects/alternatives-considered:
|
||||
en: dad77744dea17bde
|
||||
zh: bb3838f9f9099944
|
||||
/agent-note-session-writable-directory-effects/consequences:
|
||||
en: 1c2468df41bfbb46
|
||||
zh: 2118230c0091b20e
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Session writable-directory effects
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-26-session-writable-directory-effects.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Memory plugins need agent writes outside the workspace without granting every Session the same directory. Deployment-wide roots cannot express a plugin enabled for only one Session or a grant that ends when that plugin unloads.
|
||||
|
||||
## Decision
|
||||
|
||||
The sandbox-policy service owns effect-scoped directory registrations keyed by live Session identity. Configured grants remain shared; runtime grants apply only to their named Session. Each resolution captures the active union, and existing request-context logging records the directories advertised to the model. Resume and fork require new registrations rather than restoring authority from historical text.
|
||||
|
||||
Registration disposal affects subsequent operations, not already-resolved calls or running processes. This deliberately permits persistent terminals and PTC environments to retain their launch grants until they stop. The requirement is to update ongoing Sessions' future executions without coordinating process cancellation or losing retained state. This narrows the different-policy reuse restriction in the [retained PTC sandbox decision](2026-10-08-persistent-ptc-sandbox.md) for additional directories only; mode-change invalidation and one-time escalation cleanup remain unchanged.
|
||||
|
||||
Missing, non-directory, and symlinked extra paths stop execution with `SandboxInvalidRootError`, which identifies the path and its problem. Silently skipping a path would hide an invalid grant and let execution proceed with permissions different from those requested. Registration checks path spelling; enforcement validates the physical directory on each call without retaining an earlier resolution. Symlinks remain disallowed, keeping configured and plugin grants under the same validation rules.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Shared mutable extra roots.** Enabling memory in one Session would also authorize unrelated Sessions using the same service.
|
||||
|
||||
**Immediate process revocation.** Existing sandboxes apply grants at launch. Stopping and recreating processes would discard terminal or PTC state and require cleanup coordination beyond the accepted future-operation requirement.
|
||||
|
||||
**Persist runtime grants as Session permissions.** Replaying such grants could authorize directories after the plugin that supplied them has been removed. The active plugin composition owns these grants.
|
||||
|
||||
## Consequences
|
||||
|
||||
Independent registrations for the same directory survive another owner's disposal. Removing a registration cannot revoke overlapping configured or workspace access. A running process may retain a removed grant, or lack a newly added grant; a new process uses the current policy. Windows subprocesses ignore extra roots while file tools enforce them; the [Windows scope decision](2026-10-29-windows-file-tool-directory-grants.md) records this limitation. Focused coverage checks Session isolation, duplicate ownership, plugin unload, filesystem denial and mutation, request context, and retained PTC launch permissions.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Agent Note: 会话可写目录 effect
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-26-session-writable-directory-effects.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
记忆插件需要允许 agent 在工作区之外写入,同时不向所有 Session 授予相同目录。部署级根目录无法表达只为一个 Session 启用的插件,或随插件卸载而结束的授权。
|
||||
|
||||
## 决策
|
||||
|
||||
sandbox-policy 服务拥有以活跃 Session 身份为键、受 effect 生命周期管理的目录注册。配置授权仍然共享;运行时授权只适用于指定 Session。每次解析捕获当前授权的并集,现有请求上下文日志记录向模型展示的目录。恢复与派生需要重新注册,而非从历史文本恢复权限。
|
||||
|
||||
注册清理影响后续操作,不影响已经解析策略的调用或运行中的进程。这明确允许持久终端与 PTC 环境在停止前保留启动时的授权。需求是在不协调进程取消、不丢失保留状态的情况下更新运行中 Session 的后续执行。这仅针对额外目录收窄了[保留 PTC 沙箱决策](2026-10-08-persistent-ptc-sandbox.zh.md)中不同策略禁止复用的限制;模式变化失效与单次提权清理保持不变。
|
||||
|
||||
不存在、不是目录或经过符号链接的额外路径会以 `SandboxInvalidRootError` 阻止执行,错误指出路径及其问题。静默跳过路径会隐藏无效授权,使执行时的权限与请求的权限不同。注册时检查路径写法;执行限制时每次验证物理目录,不保留先前的解析结果。符号链接仍被禁止,使配置授权和插件授权遵循相同的验证规则。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**共享的可变额外根目录。** 在一个 Session 中启用记忆,也会授权使用同一服务的无关 Session。
|
||||
|
||||
**立即撤销进程权限。** 现有沙箱在启动时应用授权。停止并重新创建进程会丢失终端或 PTC 状态,并需要超出已接受的后续操作需求的清理协调。
|
||||
|
||||
**将运行时授权持久化为 Session 权限。** 回放此类授权可能在提供授权的插件已被移除后继续授权目录。当前插件组合拥有这些授权。
|
||||
|
||||
## 影响
|
||||
|
||||
同一目录的独立注册不会随另一个所有者的清理而移除。移除注册无法撤销重叠的配置授权或工作区访问权限。运行中的进程可能保留已移除的授权,或缺少新添加的授权;新进程使用当前策略。Windows 子进程忽略额外根目录,文件工具则执行这些授权;[Windows 范围决策](2026-10-29-windows-file-tool-directory-grants.zh.md)记录此限制。针对性覆盖检查 Session 隔离、重复注册的所有权、插件卸载、文件系统拒绝与修改、请求上下文,以及保留 PTC 环境的启动权限。
|
||||
@@ -18,8 +18,8 @@
|
||||
en: 9d68ffca5c8be681
|
||||
zh: 36bb661f98de0ebb
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/decision/credential-resolution:
|
||||
en: 32f59c35d71a4832
|
||||
zh: e9288313bb18b78d
|
||||
en: 15f6fb26c8605260
|
||||
zh: 93467641a8ff0aad
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/decision/defaults-and-stored-selections:
|
||||
en: 5fcd3319fbceed1d
|
||||
zh: 20771646b9c36fa6
|
||||
@@ -27,11 +27,11 @@
|
||||
en: 2c7c84f8fa1e8745
|
||||
zh: 5d0244e4f17ba12a
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/alternatives-considered:
|
||||
en: 0c227e270894cd71
|
||||
zh: 114484a3e69f5c0a
|
||||
en: 2ec7f019ec3ce309
|
||||
zh: a6a5a8db1f1dd682
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/consequences:
|
||||
en: 50f046bf81a7783d
|
||||
zh: 5c0e27bdcaf9f12e
|
||||
en: ea4d3f05e8be197a
|
||||
zh: 81faeb3edf122869
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/verification:
|
||||
en: 26ade0695094e9bc
|
||||
zh: 7b265c43b1886da5
|
||||
en: 0db673b16e4aeda3
|
||||
zh: 3b086edecebdc5b5
|
||||
@@ -28,11 +28,11 @@ The Search and Fetch APIs read one `web-deepseek-api` namespace, and each resolv
|
||||
|
||||
### Credential resolution
|
||||
|
||||
Search and Fetch follow the DeepSeek credential of the conversation, and the settings page shows no credential control for them. The conversation's route is the `provider` of the initiating Session's latest request context, which account sign-out also reads; `ready()` uses the route of the model request being prepared, so readiness and execution resolve the same plan.
|
||||
Each of Search and Fetch has a billing setting, `web-deepseek-api.searchBilling` or `fetchBilling`, which the settings page edits as a **Billing** menu under the selected API. The default, `auto`, follows the DeepSeek credential of the conversation; `account` sends only the signed-in account's token and `api-key` only the API key, without falling back to the other. The conversation's route is the `provider` of the initiating Session's latest request context, which account sign-out also reads; `ready()` uses the route of the model request being prepared, so readiness and execution resolve the same plan.
|
||||
|
||||
On the `deepseek-account` route, with an active, valid `@deepseek-ai/dsh-llm-deepseek-account` Loader entry at the entry root that the model provider directory lists, the operation sends only the account token that `ctx.deepseekAccount` releases for the dispatch endpoint under that route's origin with `/v1/tools`; an HTTP 401 invalidates that token. Every other operation, including one in an account conversation without a sign-in or a token, sends the key under the `apiKeyEnv` of the active `@deepseek-ai/dsh-llm-deepseek-api-key` entry for `deepseek-official`, resolved with that package's own resolver (including `DEEPSEEK_BASE_URL`), to the origin of that route's base URL with `/v1/tools`; without that entry, `DEEPSEEK_API_KEY` goes to `https://api.deepseek.com/v1/tools`. Sending the API key in a signed-out account conversation keeps web tools available there. A user with only `DEEPSEEK_API_KEY`, including every headless, SDK, and ACP run, therefore searches and fetches by default, and a gateway or internal deployment set through the model provider's base URL serves web access at the same origin. A missing key makes the capability not ready, and an operation fails with `WEB_PROVIDER_CREDENTIAL_MISSING` and guidance naming the Models page, the reference, and the account sign-in. A mounted route entry whose settings its resolver rejects is not treated as absent: an operation that needs that route is not ready and fails with `WEB_PROVIDER_MISCONFIGURED` without reading a credential or sending the request, so a broken gateway setting never redirects requests to the official endpoint.
|
||||
The account token is the one `ctx.deepseekAccount` releases for the dispatch endpoint under the origin of the active `@deepseek-ai/dsh-llm-deepseek-account` Loader entry at the entry root that the model provider directory lists, with `/v1/tools`; without that entry, the origin of that route's default base URL is used. An HTTP 401 invalidates that token. With `auto` billing on the `deepseek-account` route, the operation sends the account token when that entry exists and a token is released. Every other `auto` operation, including one in an account conversation without a sign-in or a token, sends the key under the `apiKeyEnv` of the active `@deepseek-ai/dsh-llm-deepseek-api-key` entry for `deepseek-official`, resolved with that package's own resolver (including `DEEPSEEK_BASE_URL`), to the origin of that route's base URL with `/v1/tools`; without that entry, `DEEPSEEK_API_KEY` goes to `https://api.deepseek.com/v1/tools`. With `auto` billing on a route other than the two DeepSeek routes, or outside a Session, an operation without a configured key sends the account token instead, so a signed-in user without a key keeps web tools in conversations on other model providers; on the `deepseek-official` route it never sends the token, because that conversation bills the API key. Sending the API key in a signed-out account conversation keeps web tools available there. A user with only `DEEPSEEK_API_KEY`, including every headless, SDK, and ACP run, therefore searches and fetches by default, and a gateway or internal deployment set through the model provider's base URL serves web access at the same origin. A missing credential makes the capability not ready, and an operation fails with `WEB_PROVIDER_CREDENTIAL_MISSING` and guidance for its billing that names the account sign-in, the Models page and the reference, and the billing field. A mounted route entry whose settings its resolver rejects is not treated as absent: an operation that needs that route is not ready and fails with `WEB_PROVIDER_MISCONFIGURED` before reading the credential that depends on that route, without trying later sources or sending the request, so a broken gateway setting never redirects requests to the official endpoint.
|
||||
|
||||
A capability whose YAML sets a non-empty `searchBaseURL` or `searchApiKeyEnv` (`fetchBaseURL` or `fetchApiKeyEnv` for Fetch) instead always sends the key under its `*ApiKeyEnv` (`DEEPSEEK_API_KEY` when unset) to its `*BaseURL` (the official base when unset) and never the account token, so an intranet or third-party endpoint receives only the key chosen for it. Each capability has its own fields because a deployment can serve search and fetch from different gateways. These fields are YAML-only, and an empty value counts as unset. Stored values of fields outside the schema, including `baseURL`, `apiKeyEnv`, and `apiKey`, load without schema errors and have no effect.
|
||||
A capability whose YAML sets a non-empty `searchBaseURL` or `searchApiKeyEnv` (`fetchBaseURL` or `fetchApiKeyEnv` for Fetch) instead, whatever its billing, always sends the key under its `*ApiKeyEnv` (`DEEPSEEK_API_KEY` when unset) to its `*BaseURL` (the official base when unset) and never the account token, so an intranet or third-party endpoint receives only the key chosen for it. Each capability has its own fields because a deployment can serve search and fetch from different gateways. These fields are YAML-only, and an empty value counts as unset. Stored values of fields outside the schema, including `baseURL`, `apiKeyEnv`, and `apiKey`, load without schema errors and have no effect.
|
||||
|
||||
The [credential helpers](../../../../packages/llm/llm-deepseek/src/credential.ts) of `@deepseek-ai/dsh-llm-deepseek` read and validate API keys, check key presence, and release account tokens for the DeepSeek model routes, Messages search, and the Search and Fetch APIs, so every DeepSeek request reads the credential store the same way; the account model route, Messages search, and the Search and Fetch APIs also invalidate the token that an HTTP 401 rejects. Shipped profiles need no per-profile credential override, because the API-key path works wherever `DEEPSEEK_API_KEY` or the DeepSeek API-key route has a key.
|
||||
|
||||
@@ -66,7 +66,7 @@ The Messages provider owns and writes `web/deepseek-search-llm-request`. The dec
|
||||
|
||||
**Default fetch to the Fetch API as well.** It adds a per-request charge to every fetch and delegates page retrieval and target access policy to the service, which fetches from its own side rather than from the user's machine.
|
||||
|
||||
**Choose a credential source per capability on the settings page**, from a menu listing the configured DeepSeek model providers plus a custom key and base URL, without fallback between sources. The visible setting would state the whole rule, but the configuration surface is too large for users to understand: each capability adds a credential menu that the user must keep in step with the model provider they chat with, and a signed-in user chatting on the account route gets no web tools until they change it. Following the conversation's credential needs no setting, and the YAML-only own fields cover endpoints that differ from the model provider.
|
||||
**Choose a credential source per capability on the settings page**, from a menu listing the configured DeepSeek model providers plus a custom key and base URL, without fallback between sources. The visible setting would state the whole rule, but the configuration surface is too large for users to understand: each capability adds a credential menu that the user must keep in step with the model provider they chat with, and a signed-in user chatting on the account route gets no web tools until they change it. The default `auto` billing follows the conversation's credential without configuration, the billing menu offers only the two DeepSeek credentials, and the YAML-only own fields cover endpoints that differ from the model provider.
|
||||
|
||||
**Share one custom key and base URL between Search and Fetch.** It cannot express a deployment that serves the two APIs from different gateways.
|
||||
|
||||
@@ -76,10 +76,10 @@ The Messages provider owns and writes `web/deepseek-search-llm-request`. The dec
|
||||
|
||||
## Consequences
|
||||
|
||||
Users without a stored search selection search through the Search API: their queries go to the Search endpoint the conversation's DeepSeek credential selects, cost a Search API request each, and send up to 128 characters of the latest human prompt as `ori_question` unless `sendOriginalQuestion` is off. Deployments whose gateway has no `/v1/tools` routes get search failures until they select Messages search or set `searchBaseURL` to a base that serves them. A stored `deepseek-official` selection uses Messages search. Users without a stored fetch selection fetch through local HTTP fetch. A signed-in user without an API key has DeepSeek web search only in conversations on the account route; the [upgrade guide](../../../../docs/upgrade-guide/v0.2.2-alpha.2/web-search-credential-modes/guide.md) lists each affected setup.
|
||||
Users without a stored search selection search through the Search API: their queries go to the Search endpoint the conversation's DeepSeek credential selects, cost a Search API request each, and send up to 128 characters of the latest human prompt as `ori_question` unless `sendOriginalQuestion` is off. Deployments whose gateway has no `/v1/tools` routes get search failures until they select Messages search or set `searchBaseURL` to a base that serves them. A stored `deepseek-official` selection uses Messages search. Users without a stored fetch selection fetch through local HTTP fetch. A signed-in user without an API key has DeepSeek web search with `auto` billing in every conversation except those on the DeepSeek API-key route; the [upgrade guide](../../../../docs/upgrade-guide/v0.2.2-alpha.2/web-search-credential-modes/guide.md) lists each affected setup.
|
||||
|
||||
A plugin provider, including one written in creator mode, appears in the matching menu as soon as it registers, under its `title`. The menus can offer a provider that cannot run for the current request; the failure appears when a search or fetch runs. Each registry change costs one forwarded event and one list read in each Client that observes it.
|
||||
|
||||
## Verification
|
||||
|
||||
[web.spec.ts](../../../../packages/web/web/tests/web.spec.ts) covers list contents and order, titles, events after registration and disposal, no event for a rejected duplicate, and contained listener failures. [deepseek.spec.ts](../../../../packages/web/web-search-deepseek/tests/deepseek.spec.ts) covers the independent Messages provider, and [credential-resolution.spec.ts](../../../../packages/web/web-deepseek-api/tests/credential-resolution.spec.ts) covers, for both capabilities, the account token on the account route and its invalidation after HTTP 401, the API-key fallback without a sign-in, the route key and endpoint in official, non-DeepSeek, and Session-less conversations, `DEEPSEEK_API_KEY` and the official base without that route, YAML-only own fields that never send the token, independent capabilities, readiness matching each operation, and ignored removed fields; [composition.spec.ts](../../../../packages/web/web-deepseek-api/tests/composition.spec.ts) boots the real model routes through the Loader. [remote-events.host.spec.ts](../../../../packages/api/remotes/tests/remote-events.host.spec.ts) covers event forwarding, and [base.spec.ts](../../../../packages/bundle/base/tests/base.spec.ts) pins the shipped defaults. The settings page's controller and card specs and the [plugin configuration browser scenario](../../../../apps/web/tests/plugin-config.e2e.ts) cover menu contents, independent Search and Fetch selection, the absence of credential controls for the DeepSeek APIs, and the settings each provider keeps.
|
||||
[web.spec.ts](../../../../packages/web/web/tests/web.spec.ts) covers list contents and order, titles, events after registration and disposal, no event for a rejected duplicate, and contained listener failures. [deepseek.spec.ts](../../../../packages/web/web-search-deepseek/tests/deepseek.spec.ts) covers the independent Messages provider, and [credential-resolution.spec.ts](../../../../packages/web/web-deepseek-api/tests/credential-resolution.spec.ts) covers, for both capabilities, the account token on the account route and its invalidation after HTTP 401, the API-key fallback without a sign-in, the route key and endpoint in official, non-DeepSeek, and Session-less conversations, the account fallback without a key outside the DeepSeek routes, account-only and API-key-only billing on every route, own fields over billing, `DEEPSEEK_API_KEY` and the official base without that route, YAML-only own fields that never send the token, independent capabilities, readiness matching each operation, and ignored removed fields; [composition.spec.ts](../../../../packages/web/web-deepseek-api/tests/composition.spec.ts) boots the real model routes through the Loader. [remote-events.host.spec.ts](../../../../packages/api/remotes/tests/remote-events.host.spec.ts) covers event forwarding, and [base.spec.ts](../../../../packages/bundle/base/tests/base.spec.ts) pins the shipped defaults. The settings page's controller and card specs and the [plugin configuration browser scenario](../../../../apps/web/tests/plugin-config.e2e.ts) cover menu contents, independent Search and Fetch selection, the billing menus of the DeepSeek APIs, and the settings each provider keeps.
|
||||
@@ -28,11 +28,11 @@ Search 与 Fetch API 读取同一个 `web-deepseek-api` namespace,并各自解
|
||||
|
||||
### 凭据解析
|
||||
|
||||
Search 与 Fetch 跟随会话的 DeepSeek 凭据,设置页不为它们显示凭据控件。会话的路由是发起会话最近一次请求上下文的 `provider`,账号退出登录读取的也是它;`ready()` 使用正在准备的模型请求的路由,因此就绪判断与执行解析出相同的方案。
|
||||
Search 与 Fetch 各有一个计费方式设置 `web-deepseek-api.searchBilling` 或 `fetchBilling`,设置页在选中的 API 下方以**计费方式**下拉框编辑它。默认值 `auto` 跟随会话的 DeepSeek 凭据;`account` 只发送已登录账号的令牌,`api-key` 只发送 API 密钥,都不回退到另一种凭据。会话的路由是发起会话最近一次请求上下文的 `provider`,账号退出登录读取的也是它;`ready()` 使用正在准备的模型请求的路由,因此就绪判断与执行解析出相同的方案。
|
||||
|
||||
在 `deepseek-account` 路由上,若模型提供方目录列出了入口根部活动且有效的 `@deepseek-ai/dsh-llm-deepseek-account` Loader 条目,操作只发送 `ctx.deepseekAccount` 为该路由 origin 加 `/v1/tools` 下的请求端点给出的账号令牌;HTTP 401 会作废该令牌。其他所有操作,包括账号会话中没有登录或没有令牌的操作,都把 `deepseek-official` 的活动 `@deepseek-ai/dsh-llm-deepseek-api-key` 条目的 `apiKeyEnv` 下的密钥(用该包自己的解析函数解析,包括 `DEEPSEEK_BASE_URL`)发往该路由基础地址的 origin 加 `/v1/tools`;没有该条目时,把 `DEEPSEEK_API_KEY` 发往 `https://api.deepseek.com/v1/tools`。未登录的账号会话发送 API 密钥,网页工具因此在其中仍然可用。因此只有 `DEEPSEEK_API_KEY` 的用户(包括所有 headless、SDK 与 ACP 运行)默认即可搜索和抓取,通过模型提供方基础地址设置的网关或内部部署也在同一 origin 上提供 Web 访问。缺少密钥时,该能力未就绪,操作以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败,指引信息点名模型页面、该引用和账号登录。已挂载、但其解析函数拒绝其设置的路由条目不视为不存在:需要该路由的操作未就绪,并以 `WEB_PROVIDER_MISCONFIGURED` 失败,不读取凭据、不发送请求,因此网关设置出错时请求不会被改发到官方端点。
|
||||
账号令牌是 `ctx.deepseekAccount` 为模型提供方目录列出的入口根部活动 `@deepseek-ai/dsh-llm-deepseek-account` Loader 条目的 origin 加 `/v1/tools` 下的请求端点给出的令牌;没有该条目时使用该路由默认基础地址的 origin。HTTP 401 会作废该令牌。`auto` 计费在 `deepseek-account` 路由上,若存在该条目且取得令牌,操作发送账号令牌。其他所有 `auto` 操作,包括账号会话中没有登录或没有令牌的操作,都把 `deepseek-official` 的活动 `@deepseek-ai/dsh-llm-deepseek-api-key` 条目的 `apiKeyEnv` 下的密钥(用该包自己的解析函数解析,包括 `DEEPSEEK_BASE_URL`)发往该路由基础地址的 origin 加 `/v1/tools`;没有该条目时,把 `DEEPSEEK_API_KEY` 发往 `https://api.deepseek.com/v1/tools`。`auto` 计费在两个 DeepSeek 路由之外的路由上或会话之外,没有配置密钥的操作改为发送账号令牌,因此已登录但没有密钥的用户在其他模型提供方的会话中仍可使用网页工具;在 `deepseek-official` 路由上从不发送令牌,因为该会话按 API 密钥计费。未登录的账号会话发送 API 密钥,网页工具因此在其中仍然可用。因此只有 `DEEPSEEK_API_KEY` 的用户(包括所有 headless、SDK 与 ACP 运行)默认即可搜索和抓取,通过模型提供方基础地址设置的网关或内部部署也在同一 origin 上提供 Web 访问。缺少凭据时,该能力未就绪,操作以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败,指引信息按计费方式点名账号登录、模型页面与该引用,以及计费方式字段。已挂载、但其解析函数拒绝其设置的路由条目不视为不存在:需要该路由的操作未就绪,并在读取依赖该路由的凭据前以 `WEB_PROVIDER_MISCONFIGURED` 失败,不再尝试后续来源,也不发送请求,因此网关设置出错时请求不会被改发到官方端点。
|
||||
|
||||
在 YAML 中设置了非空 `searchBaseURL` 或 `searchApiKeyEnv`(Fetch 为 `fetchBaseURL` 或 `fetchApiKeyEnv`)的能力,则始终把其 `*ApiKeyEnv`(未设置时为 `DEEPSEEK_API_KEY`)下的密钥发往其 `*BaseURL`(未设置时为官方基础地址),从不发送账号令牌,因此内网或第三方端点只收到为它选择的密钥。每个能力有自己的字段,因为一个部署可以由不同网关提供搜索和抓取。这些字段只能在 YAML 中设置,空值视为未设置。schema 之外字段的已保存值,包括 `baseURL`、`apiKeyEnv` 和 `apiKey`,可以加载而不产生 schema 错误,且不生效。
|
||||
在 YAML 中设置了非空 `searchBaseURL` 或 `searchApiKeyEnv`(Fetch 为 `fetchBaseURL` 或 `fetchApiKeyEnv`)的能力,则无论计费方式如何,始终把其 `*ApiKeyEnv`(未设置时为 `DEEPSEEK_API_KEY`)下的密钥发往其 `*BaseURL`(未设置时为官方基础地址),从不发送账号令牌,因此内网或第三方端点只收到为它选择的密钥。每个能力有自己的字段,因为一个部署可以由不同网关提供搜索和抓取。这些字段只能在 YAML 中设置,空值视为未设置。schema 之外字段的已保存值,包括 `baseURL`、`apiKeyEnv` 和 `apiKey`,可以加载而不产生 schema 错误,且不生效。
|
||||
|
||||
`@deepseek-ai/dsh-llm-deepseek` 的[凭据函数](../../../../packages/llm/llm-deepseek/src/credential.ts)为 DeepSeek 模型路由、Messages 搜索以及 Search 与 Fetch API 读取并校验 API 密钥、检查密钥是否存在、取得账号令牌,因此每个 DeepSeek 请求都以同一方式读取凭据库;账号模型路由、Messages 搜索以及 Search 与 Fetch API 还会作废被 HTTP 401 拒绝的令牌。内置配置不需要按 profile 覆盖凭据,因为只要 `DEEPSEEK_API_KEY` 或 DeepSeek API 密钥路由有密钥,API 密钥路径就能工作。
|
||||
|
||||
@@ -66,7 +66,7 @@ Messages 提供方负责声明并写入 `web/deepseek-search-llm-request`。声
|
||||
|
||||
**抓取也默认使用 Fetch API。** 它会给每次抓取增加按次费用,并把页面抓取和目标访问策略交给服务端,由服务端而不是用户的机器发起抓取。
|
||||
|
||||
**在设置页为每个能力选择一种凭据来源**,下拉框列出已配置的 DeepSeek 模型提供方,外加自定义密钥和基础地址,来源之间不回退。这样可见的设置就能说明全部规则,但配置面过大,用户难以理解:每个能力都多出一个凭据下拉框,用户必须让它与自己对话所用的模型提供方保持一致;在账号路由上对话的已登录用户在修改设置前没有网页工具。跟随会话的凭据不需要任何设置,与模型提供方不同的端点由只能在 YAML 中设置的单独字段覆盖。
|
||||
**在设置页为每个能力选择一种凭据来源**,下拉框列出已配置的 DeepSeek 模型提供方,外加自定义密钥和基础地址,来源之间不回退。这样可见的设置就能说明全部规则,但配置面过大,用户难以理解:每个能力都多出一个凭据下拉框,用户必须让它与自己对话所用的模型提供方保持一致;在账号路由上对话的已登录用户在修改设置前没有网页工具。默认的 `auto` 计费无需配置即跟随会话的凭据,计费方式下拉框只提供两种 DeepSeek 凭据,与模型提供方不同的端点由只能在 YAML 中设置的单独字段覆盖。
|
||||
|
||||
**Search 与 Fetch 共用一个自定义密钥和基础地址。** 它无法表达由不同网关分别提供两个 API 的部署。
|
||||
|
||||
@@ -76,10 +76,10 @@ Messages 提供方负责声明并写入 `web/deepseek-search-llm-request`。声
|
||||
|
||||
## 影响
|
||||
|
||||
没有已保存搜索选择的用户通过 Search API 搜索:查询发往会话的 DeepSeek 凭据所选的 Search 端点,每次计为一次 Search API 请求,并且除非关闭 `sendOriginalQuestion`,还会把最近一条用户提示的至多 128 个字符作为 `ori_question` 发送。网关没有 `/v1/tools` 路由的部署会遇到搜索失败,直到选择 Messages 搜索,或把 `searchBaseURL` 设为提供这些路由的基础地址。已保存的 `deepseek-official` 选择使用 Messages 搜索。没有已保存抓取选择的用户通过本地 HTTP 抓取。已登录但没有 API 密钥的用户只在账号路由上的会话中可以使用 DeepSeek 网页搜索;[升级指南](../../../../docs/upgrade-guide/v0.2.2-alpha.2/web-search-credential-modes/guide.zh.md)列出了每种受影响的配置。
|
||||
没有已保存搜索选择的用户通过 Search API 搜索:查询发往会话的 DeepSeek 凭据所选的 Search 端点,每次计为一次 Search API 请求,并且除非关闭 `sendOriginalQuestion`,还会把最近一条用户提示的至多 128 个字符作为 `ori_question` 发送。网关没有 `/v1/tools` 路由的部署会遇到搜索失败,直到选择 Messages 搜索,或把 `searchBaseURL` 设为提供这些路由的基础地址。已保存的 `deepseek-official` 选择使用 Messages 搜索。没有已保存抓取选择的用户通过本地 HTTP 抓取。已登录但没有 API 密钥的用户在 `auto` 计费下,除 DeepSeek API 密钥路由上的会话外,都可以使用 DeepSeek 网页搜索;[升级指南](../../../../docs/upgrade-guide/v0.2.2-alpha.2/web-search-credential-modes/guide.zh.md)列出了每种受影响的配置。
|
||||
|
||||
插件提供方(包括在创作模式下编写的提供方)一经注册,就会以其 `title` 出现在对应的下拉框中。下拉框可能提供在当前请求中无法运行的提供方;失败会在执行搜索或抓取时出现。每次注册表变更会在每个观察它的 Client 中产生一次转发事件和一次列表读取。
|
||||
|
||||
## 验证
|
||||
|
||||
[web.spec.ts](../../../../packages/web/web/tests/web.spec.ts) 覆盖列表内容与顺序、标题、注册与撤销后的事件、重复注册被拒绝时不触发事件,以及监听器失败的隔离。[deepseek.spec.ts](../../../../packages/web/web-search-deepseek/tests/deepseek.spec.ts) 覆盖独立的 Messages 提供方,[credential-resolution.spec.ts](../../../../packages/web/web-deepseek-api/tests/credential-resolution.spec.ts) 针对两个能力覆盖账号路由上的账号令牌及其在 HTTP 401 后被作废、未登录时回退到 API 密钥、官方、非 DeepSeek 和会话之外的操作使用路由密钥与端点、没有该路由时使用 `DEEPSEEK_API_KEY` 与官方基础地址、从不发送令牌的 YAML 单独字段、两个能力相互独立、就绪判断与每次操作一致,以及被忽略的已移除字段;[composition.spec.ts](../../../../packages/web/web-deepseek-api/tests/composition.spec.ts) 通过 Loader 启动真实的模型路由。[remote-events.host.spec.ts](../../../../packages/api/remotes/tests/remote-events.host.spec.ts) 覆盖事件转发,[base.spec.ts](../../../../packages/bundle/base/tests/base.spec.ts) 固定内置默认值。设置页的 controller 与 card 测试以及[插件配置浏览器场景](../../../../apps/web/tests/plugin-config.e2e.ts)覆盖下拉框内容、Search 与 Fetch 的分别选择、DeepSeek API 不显示凭据控件,以及各提供方保留的设置。
|
||||
[web.spec.ts](../../../../packages/web/web/tests/web.spec.ts) 覆盖列表内容与顺序、标题、注册与撤销后的事件、重复注册被拒绝时不触发事件,以及监听器失败的隔离。[deepseek.spec.ts](../../../../packages/web/web-search-deepseek/tests/deepseek.spec.ts) 覆盖独立的 Messages 提供方,[credential-resolution.spec.ts](../../../../packages/web/web-deepseek-api/tests/credential-resolution.spec.ts) 针对两个能力覆盖账号路由上的账号令牌及其在 HTTP 401 后被作废、未登录时回退到 API 密钥、官方、非 DeepSeek 和会话之外的操作使用路由密钥与端点、DeepSeek 路由之外没有密钥时回退到账号、在每个路由上只用账号或只用 API 密钥的计费、单独字段优先于计费方式、没有该路由时使用 `DEEPSEEK_API_KEY` 与官方基础地址、从不发送令牌的 YAML 单独字段、两个能力相互独立、就绪判断与每次操作一致,以及被忽略的已移除字段;[composition.spec.ts](../../../../packages/web/web-deepseek-api/tests/composition.spec.ts) 通过 Loader 启动真实的模型路由。[remote-events.host.spec.ts](../../../../packages/api/remotes/tests/remote-events.host.spec.ts) 覆盖事件转发,[base.spec.ts](../../../../packages/bundle/base/tests/base.spec.ts) 固定内置默认值。设置页的 controller 与 card 测试以及[插件配置浏览器场景](../../../../apps/web/tests/plugin-config.e2e.ts)覆盖下拉框内容、Search 与 Fetch 的分别选择、DeepSeek API 的计费方式下拉框,以及各提供方保留的设置。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-29-windows-file-tool-directory-grants.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/architecture/2026-10-29-windows-file-tool-directory-grants.md
|
||||
/agent-note-windows-additional-directory-grants-stay-in-file-tools:
|
||||
en: aea9858c74dfd36e
|
||||
zh: 4064573838d6000f
|
||||
/agent-note-windows-additional-directory-grants-stay-in-file-tools/problem:
|
||||
en: da14bde7ed3eae49
|
||||
zh: 6aad2181b2756b50
|
||||
/agent-note-windows-additional-directory-grants-stay-in-file-tools/decision:
|
||||
en: acec40cda6036f2c
|
||||
zh: 149c9eecb9863643
|
||||
/agent-note-windows-additional-directory-grants-stay-in-file-tools/alternatives-considered:
|
||||
en: 09d6cceef8004a65
|
||||
zh: e9009eb5bbae1d87
|
||||
/agent-note-windows-additional-directory-grants-stay-in-file-tools/consequences:
|
||||
en: a37925f508bb5644
|
||||
zh: f5a6cb4e099f94c6
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Agent Note: Windows additional directory grants stay in file tools
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-29-windows-file-tool-directory-grants.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Memory plugins need session-specific writes to directories such as `~/.dsh/memory`. Extending Windows process access through ACLs requires managing persistent filesystem grants and token identities for changing directory combinations, increasing reliance on a backend with existing enforcement limitations.
|
||||
|
||||
## Decision
|
||||
|
||||
On Windows, the [session directory effects](2026-10-26-session-writable-directory-effects.md) authorize extra directories only through the harness filesystem checks used by `write` and `edit`. The process provider ignores extra roots and retains its workspace and private-temp permissions. The ACL package, runner arguments, and workspace SID remain unchanged. POSIX process backends continue to honor extra roots.
|
||||
|
||||
Windows process support for extra roots is deferred until a better sandbox backend can enforce it without extending the ACL customization. Model context explicitly distinguishes file-tool grants from Windows process permissions.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**One SID per directory or directory set.** These designs could select process authority for each execution, but require more ACL mutation, identity management, and lifecycle handling. The capability does not justify expanding this mechanism.
|
||||
|
||||
**Reject processes whenever extra grants exist.** This prevents unrelated PowerShell and workspace operations from running when a memory plugin registers a directory, even if the command never accesses it.
|
||||
|
||||
## Consequences
|
||||
|
||||
A Windows memory plugin can use `write` and `edit` for its registered directory while ordinary workspace commands continue to work. A shell command or PTC program cannot use that extra grant. This deliberately accepts different capabilities across file tools and process tools; registration and disposal remain session-scoped, and existing processes retain their launch-time permissions.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Agent Note: Windows 额外目录授权仅用于文件工具
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-29-windows-file-tool-directory-grants.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
记忆插件需要针对会话向 `~/.dsh/memory` 等目录写入。通过 ACL 扩展 Windows 进程访问权限,需要为不断变化的目录组合管理持久文件系统授权与令牌身份,从而加深对已有执行限制的后端的依赖。
|
||||
|
||||
## 决策
|
||||
|
||||
在 Windows 上,[会话目录 effect](2026-10-26-session-writable-directory-effects.zh.md) 仅通过 `write` 与 `edit` 使用的 Harness 文件系统检查授权额外目录。进程提供方忽略额外根目录,保留工作区与私有临时目录权限。ACL 包、runner 参数与工作区 SID 保持不变。POSIX 进程后端继续执行额外根目录授权。
|
||||
|
||||
Windows 进程的额外根目录支持推迟到更好的沙箱后端能够实现该功能、无需扩展 ACL 定制时。模型上下文明示文件工具授权与 Windows 进程权限的区别。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**为每个目录或目录集合分配 SID。** 这些设计可以逐次执行选择进程权限,但需要更多 ACL 修改、身份管理与生命周期处理。该能力不足以证明扩展此机制的必要性。
|
||||
|
||||
**存在额外授权时拒绝进程。** 当记忆插件注册目录时,这会阻止无关的 PowerShell 和工作区操作运行,即使命令从不访问该目录。
|
||||
|
||||
## 影响
|
||||
|
||||
Windows 记忆插件可以通过 `write` 与 `edit` 访问注册的目录,同时普通工作区命令继续运行。shell 命令或 PTC 程序无法使用该额外授权。这明确接受文件工具与进程工具的能力差异;注册和清理仍限定于会话,已有进程保留启动时的权限。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-31-native-office-tool.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/architecture/2026-10-31-native-office-tool.md
|
||||
/agent-note-on-demand-office-tools-for-rendering-conversion-and-recalculation:
|
||||
en: ac1af3d918f27b6f
|
||||
zh: 5822ce52c384fca3
|
||||
/agent-note-on-demand-office-tools-for-rendering-conversion-and-recalculation/problem:
|
||||
en: 5b23962ecc835664
|
||||
zh: c96ea8720ebc9419
|
||||
/agent-note-on-demand-office-tools-for-rendering-conversion-and-recalculation/decision:
|
||||
en: 25ffb4e0d42e9435
|
||||
zh: f9cd42a1807a48ca
|
||||
/agent-note-on-demand-office-tools-for-rendering-conversion-and-recalculation/alternatives-considered:
|
||||
en: f36e8534958e5a46
|
||||
zh: 96a3876a26a9d9ac
|
||||
/agent-note-on-demand-office-tools-for-rendering-conversion-and-recalculation/consequences:
|
||||
en: 8ea5f5c493dfae3d
|
||||
zh: 29d7160bd3f98512
|
||||
@@ -0,0 +1,47 @@
|
||||
# Agent Note: On-demand Office tools for rendering, conversion, and recalculation
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-31-native-office-tool.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Office operation skills ran the LibreOffice Kit CLI through ordinary shell commands, as [progressive Office skills](2026-09-27-progressive-office-skills.md) decided. Kit's native engine starts helper processes, Workers, and font caches that the shell sandbox and PTC permissions restrict, so the same render could succeed in one deployment and fail in another. Each deployment had to publish a standalone Node executable and a CLI path into the skill body, which required Node and CLI configuration, a Python sidecar Node, an Electron check against the application binary, and a runtime-root containment check against the Electron shell's own Kit. Rendering for inspection wrote PNG files into a temporary workspace directory that the model then read with `read_image` and deleted, adding calls and cleanup obligations to every visual check.
|
||||
|
||||
## Decision
|
||||
|
||||
The [`tool-office` package](../../../../packages/document/tool-office/README.md) exposes `office_render`, `office_convert`, and `office_recalculate` as three on-demand Host tools. Each tool states its parameter rules in its own JSON Schema: `office_render` accepts document paths, page or slide `pages`, worksheet `sheet` and `range` selection, and `dpi`; `office_convert` accepts all nine output formats (`pdf`, `docx`, `odt`, and `txt` for Word; `pdf`, `xlsx`, `ods`, and `csv` for Excel; `pdf`, `pptx`, and `odp` for PowerPoint) plus `sheet` for CSV output (required when the workbook has several sheets); and `office_recalculate` accepts `xlsx` and `ods`. Together the three declarations total ~1,813 characters, paid only in Sessions that load an Office skill. They call the [Office kit](../../../../packages/document/office-kit/README.md) service, which loads Kit in the Host process through its `src/locate.ts` resolver and runs every operation through the queue, concurrency limits, byte reservations, and cache that Office previews use. Tool requests use `foreground` priority, the class of an open preview, so background prewarming cannot starve them. The SDK composition mounts `tool-office` beside `office-kit`, and the Web bundle's standard and PTC agent presets mount it while its minimal preset keeps its fixed roster; when Kit is missing the tools still register and every call returns the service's `unavailable` message. Each tool reads the service at each call instead of injecting it, so a composition that omits or disables `office-kit` still creates Sessions with the tools and each call fails as `unavailable`; a service loaded later serves the next call. Standard and PTC modes reach the same registrations; nested PTC calls re-enter the ordinary execution pipeline.
|
||||
|
||||
`office_render` returns PNG images in the tool result; the Web Client's Office row shows only the result's text blocks and does not display the images. The tool commits the images through the attachment store before the result is logged, so the model-visible images are reconstructable from the Session log, resume and fork restore them, and image offload applies to them. The complete result is bounded by image count, per-image pixels and dimensions, and total PNG bytes, each the lower of the tool configuration and the attachment store's limits; an over-limit selection fails instead of being truncated. A route without image input is refused before rendering.
|
||||
|
||||
`office_convert` and `office_recalculate` do not write into the workspace. The filesystem service intentionally provides only binary reads, and binary files in the workspace come only from the shell, whose sandbox and approvals already govern writes. The Office kit saves each output in an owner-only directory under its configurable `outputDirectory` (`$DSH_HOME/office-kit/outputs` by default), grouped by the root Session of the caller's subagent tree, and deletes it after `outputRetentionMs`, when `maxOutputStoreBytes` needs room for newer outputs, when that root Session is disposed, or when the provider unloads. A subagent's output therefore outlives the subagent, so the delegating agent can still copy it. Each tool returns the absolute temporary path, format, size, and the page count the converter reports, with the instruction to copy the file with the shell now, because it may be deleted after a retention period or to free space, and is deleted when the conversation ends. A filesystem backend that cannot read Host paths, such as an SSH workspace, refuses these two tools before converting, because a remote shell cannot read the Host output store. Shell sandboxes allow reads of the Harness home on macOS Seatbelt, Linux bubblewrap, and Linux Landlock, so a confined shell can copy the file into the workspace.
|
||||
|
||||
The three tools share one `when(agent)` predicate instead of staying resident in every request. An `Agent` activates them when `marked.has(agent)` is true or when `agent.session.requestHeader()?.tools` already lists `office_render`, `office_convert`, or `office_recalculate`; `agent/disposed` clears the `WeakSet<Agent>` mark. `tool-office` marks the `Agent` on a non-error `skill` tool result (`tools/result` with `!result.isError`, including a nested `skill` call inside `run_code`) or a direct user skill invocation (`session/event` where `event.type === 'user/message'` and `event.data.source.kind === 'skill-invocation'`) whose skill name is in `activationSkills` (`office`, `office-docx`, `office-xlsx`, and `office-pptx` by default). The declarations appear on the step after the skill loads and stay active for the rest of the Session; calling an `office_*` tool before activation fails as `UNKNOWN_TOOL`. When mid-session activation adds the tools, the agent loop records a `developer/message` (`source: { kind: 'tool-registry' }`) with `tool-addition` items: models that declare `toolUpdate` (`in-history` or `addition-only`) keep their existing request prefix, whereas models without `toolUpdate` start a new request series once. Because standard-mode request headers are already persisted in the Session log, checking `requestHeader()` preserves activation across resume, fork, and compaction without a Session projection. In PTC mode, the recorded request header declares only `run_code`, so a resumed or forked PTC Session (or a process crash between loading a skill and recording the next native request header) does not retain `office_*` bindings until an Office skill loads again; the [Office router and format skills](../../../../packages/skill/skill-office/README.md) instruct the model to load `office` when `office_render`, `office_convert`, or `office_recalculate` is not in the tool list. No skill wraps a single operation, and a standalone render, recalculation, or conversion loads only the `office` router. The skills carry no executable paths, so `skill-office` has no Node, CLI, or Kit location configuration, `office-kit`'s `src/locate.ts` resolves no CLI, Desktop and the bundles publish no Office executable variables, and the Python sidecar's standalone Node remains only for the Python `deepseek_harness.office` module.
|
||||
|
||||
This decision partially supersedes [progressive Office skills](2026-09-27-progressive-office-skills.md): its router and format skills remain current, while its operation skills, ordinary-shell execution, and rejection of a dedicated renderer tool are replaced here.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep shell execution of the Kit CLI.** It reuses shell approvals and process management, but the native engine runs restricted under shell sandboxes and PTC permissions, and every deployment must maintain Node and CLI paths, an Electron Node check, and runtime-root containment.
|
||||
|
||||
**Keep one resident `office` tool.** One resident schema (~1,514 characters) avoids mid-session tool additions, but charges every non-Office Session on every request and forces rendering, conversion, and recalculation parameters into a single flat schema where per-operation parameter combinations must be explained in prose instead of enforced per tool.
|
||||
|
||||
**Add a Session projection for loaded Office skills.** Folding `tools/result` and `user/message` records in a dedicated projection would reconstruct skill activation across PTC resume and fork, but requires a new projection service, fold rules, and cross-package dependencies for state that standard-mode request headers already record and that PTC mode recovers by reloading `office`.
|
||||
|
||||
**Track activation only in `Agent` memory.** Checking only `WeakSet<Agent>` (as `action-cards` does) drops the `office_*` declarations on the first step after a standard-mode resume or fork because the new `Agent` instance starts unmarked; consulting `agent.session.requestHeader()?.tools` preserves the declarations from the persisted header.
|
||||
|
||||
**Keep one skill per operation.** Separate render, recalculation, and PDF export skills add a `skill` call before each standalone operation and repeat what the tool schema and results already state.
|
||||
|
||||
**Write converted files into the workspace from the Host.** This saves the shell copy, but needs binary creation in the filesystem service or a second write path beside it, both of which would duplicate the shell's sandbox and approval decisions for workspace writes.
|
||||
|
||||
**Return converted files through the system temporary directory.** Linux bubblewrap mounts a private `/tmp` for confined shells, so the shell could not read the file there; the Harness home is readable under every local shell sandbox.
|
||||
|
||||
**Return image files instead of image content.** Saving PNGs for a later `read_image` call keeps one image path for all tools, but repeats the temporary-directory workflow this change removes and adds a call per inspection.
|
||||
|
||||
## Consequences
|
||||
|
||||
Visual inspection takes one `office_render` call after loading an Office skill and needs no cleanup, and its images follow the Session's attachment, offload, and replay rules. Sessions that do not load an Office skill pay zero Office tool-declaration tokens; Sessions that load one activate ~1,813 characters of operation-specific schemas, keeping the existing request prefix on models with `toolUpdate` and starting one new request series on models without it. Standard-mode resume, fork, and compaction retain the tools through the recorded request header, while PTC resume or fork and crashes before the next recorded header recover by loading `office` again. Office operations behave the same in standard and PTC modes and across deployments once activated, and the deployment-specific Node, CLI, and containment code is gone.
|
||||
|
||||
Converted files need a shell copy; compositions without a shell tool can render but cannot deliver converted files, and remote workspaces cannot convert at all. Temporary outputs disappear after retention or root Session disposal, so a resumed Session must convert again. Saving page images as files is not offered.
|
||||
|
||||
Verification covers the three tools' schemas, results, argument rules, limits, failures, cancellation, PTC dispatch, and disposal in `tool-office.spec.ts`, and skill-driven activation, custom `activationSkills`, failed skill calls, direct `/office` invocation, `requestHeader()` persistence across resume and fork, and PTC reactivation in `activation.spec.ts`; the output store's retention, eviction, release, and sweeping in Office kit tests; Loader compositions that return the `unavailable` message with Kit absent and with the `office-kit` row absent, then reach the service once the row loads; packaged Desktop smoke checks that render, convert, and recalculate real DOCX, XLSX, and PPTX files through the service; and recorded headless sessions that omit `office_*` before an Office skill loads and declare `office_render`, `office_convert`, and `office_recalculate` on the step after loading one, including a render that `office_render` refuses for a route without image input.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Agent Note: 按需激活的 Office 渲染、转换与重算工具
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-31-native-office-tool.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
按照[渐进式 Office 技能](2026-09-27-progressive-office-skills.zh.md)的决定,Office 操作技能通过普通 shell 命令运行 LibreOffice Kit CLI。Kit 的原生引擎会启动辅助进程、Worker 和字体缓存,而 shell 沙箱和 PTC 权限会限制这些行为,因此同一次渲染可能在一个部署中成功、在另一个部署中失败。每个部署都必须把独立 Node 可执行文件和 CLI 路径发布到技能正文中,这需要 Node 和 CLI 配置、Python sidecar 的 Node、针对应用二进制的 Electron 检查,以及防止选中 Electron 壳自带 Kit 的运行时根目录约束检查。用于检查的渲染把 PNG 文件写入工作区内的临时目录,模型再用 `read_image` 读取并删除它们,使每次视觉检查都多出调用和清理义务。
|
||||
|
||||
## 决策
|
||||
|
||||
[`tool-office` 包](../../../../packages/document/tool-office/README.zh.md)提供三个按需激活的 Host 工具:`office_render`、`office_convert` 和 `office_recalculate`。每个工具在各自的 JSON Schema 中直接声明参数规则:`office_render` 接受文档路径、页码或幻灯片 `pages`、工作表 `sheet` 与 `range` 选择以及 `dpi`;`office_convert` 保留全部九种输出格式(Word 支持 `pdf`、`docx`、`odt`、`txt`,Excel 支持 `pdf`、`xlsx`、`ods`、`csv`,PowerPoint 支持 `pdf`、`pptx`、`odp`)以及仅用于 CSV 输出的 `sheet`(工作簿含多个工作表时必填);`office_recalculate` 接受 `xlsx` 和 `ods`。三个工具声明共约 1,813 个字符,仅在加载了 Office 技能的 Session 中付出。它们调用 [Office 套件](../../../../packages/document/office-kit/README.zh.md)服务;该服务通过 `src/locate.ts` 在 Host 进程中加载 Kit,并让每个操作经过 Office 预览所用的队列、并发上限、字节预留和缓存。工具请求使用 `foreground` 优先级,即打开的预览所属的类别,因此后台预热不会饿死它们。SDK 组合在 `office-kit` 旁挂载 `tool-office`,Web bundle 的标准和 PTC Agent 预设挂载它,minimal 预设保持其固定工具列表;Kit 缺失时三个工具仍然注册,每次调用都返回服务的 `unavailable` 消息。每个工具在每次调用时读取该服务而不是注入它,因此省略或禁用 `office-kit` 的组合仍能带着这些工具创建 Session,每次调用以 `unavailable` 失败;之后加载的服务会处理下一次调用。标准模式与 PTC 模式使用同一组注册;PTC 嵌套调用重新进入普通执行管线。
|
||||
|
||||
`office_render` 在工具结果中返回 PNG 图片;Web Client 的 Office 行只显示结果的文本块,不显示这些图片。工具在结果写入日志之前通过附件存储提交图片,因此模型可见的图片可以从 Session 日志还原,恢复和分叉会还原它们,图片卸载也适用于它们。完整结果受图片数量、单图像素与尺寸以及 PNG 总字节数约束,每项取工具配置与附件存储上限中的较小值;超出上限的选择直接失败,不会被截断。不支持图片输入的路由在渲染前即被拒绝。
|
||||
|
||||
`office_convert` 和 `office_recalculate` 不写入工作区。文件系统服务有意只提供二进制读取,工作区中的二进制文件只由 shell 产生,而 shell 的沙箱和审批已经管理写入。Office 套件把每个输出保存在可配置的 `outputDirectory`(默认 `$DSH_HOME/office-kit/outputs`)下仅属主可访问的目录中,按调用方 subagent 树的根 Session 分组;输出在 `outputRetentionMs` 到期、`maxOutputStoreBytes` 需要为较新的输出腾出空间、该根 Session 卸载或提供方卸载时删除。因此 subagent 的输出在 subagent 结束后仍然保留,委派它的 agent 仍可复制。工具返回临时文件的绝对路径、格式、大小和转换器报告的页数,并指示立即用 shell 复制该文件,因为文件可能在保留期到期或需要腾出空间时被删除,且对话结束后会被删除。无法读取 Host 路径的文件系统后端(例如 SSH 工作区)会在转换前拒绝这两个工具,因为远程 shell 读不到 Host 的输出存储。macOS Seatbelt、Linux bubblewrap 和 Linux Landlock 的 shell 沙箱都允许读取 Harness 主目录,因此受限的 shell 可以把文件复制进工作区。
|
||||
|
||||
三个工具共享同一个 `when(agent)` 谓词,而不是在每次请求中常驻。当 `marked.has(agent)` 为真,或 `agent.session.requestHeader()?.tools` 已包含 `office_render`、`office_convert` 或 `office_recalculate` 时,`Agent` 激活它们;`agent/disposed` 会清除 `WeakSet<Agent>` 标记。当非错误的 `skill` 工具结果(`tools/result` 且 `!result.isError`,包括 `run_code` 内嵌套的 `skill` 调用)或直接的用户技能调用(`session/event` 中 `event.type === 'user/message'` 且 `event.data.source.kind === 'skill-invocation'`)加载了 `activationSkills`(默认为 `office`、`office-docx`、`office-xlsx` 和 `office-pptx`)中的技能时,`tool-office` 标记该 `Agent`。工具声明在加载技能后的下一步出现,并在该 Session 剩余时间内保持激活;激活前调用 `office_*` 工具会以 `UNKNOWN_TOOL` 失败。会话中途激活新增这些工具时,agent loop 会记录一条带 `tool-addition` 项的 `developer/message`(`source: { kind: 'tool-registry' }`):声明了 `toolUpdate`(`in-history` 或 `addition-only`)的模型保留现有请求前缀,未声明 `toolUpdate` 的模型则开启一次新的请求序列。由于标准模式的请求头本就持久化在 Session 日志中,检查 `requestHeader()` 即可在恢复、分叉和压缩后保留激活状态,无需新增 Session projection。PTC 模式下记录的请求头只声明 `run_code`,因此恢复或分叉的 PTC Session(或加载技能后、下一条原生请求头记录前发生进程崩溃)不会保留 `office_*` 绑定,直到再次加载 Office 技能;[Office 路由与格式技能](../../../../packages/skill/skill-office/README.zh.md)指示模型在 `office_render`、`office_convert` 或 `office_recalculate` 不在工具列表中时调用 `skill` 加载 `office`。没有技能只包装单个操作,单独的渲染、重算或转换只需加载 `office` 路由。技能不再携带可执行文件路径,因此 `skill-office` 没有 Node、CLI 或 Kit 位置配置,`office-kit` 的 `src/locate.ts` 没有 CLI 解析器,Desktop 和各 bundle 不发布 Office 可执行文件变量,Python sidecar 的独立 Node 只保留给 Python `deepseek_harness.office` 模块使用。
|
||||
|
||||
此决定部分取代[渐进式 Office 技能](2026-09-27-progressive-office-skills.zh.md):其路由与格式技能仍然有效,而其操作技能、普通 shell 执行方式以及否决专用渲染工具的决定由本文取代。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留通过 shell 执行 Kit CLI。** 这样可复用 shell 的审批和进程管理,但原生引擎在 shell 沙箱和 PTC 权限下运行受限,而且每个部署都要维护 Node 与 CLI 路径、Electron 的 Node 检查以及运行时根目录约束。
|
||||
|
||||
**保留单一常驻 `office` 工具。** 单一常驻 schema(约 1,514 个字符)虽能避免会话中途新增工具,但会让所有不使用 Office 的 Session 在每次请求中都承担这笔开销,并迫使渲染、转换和重算参数挤在同一个扁平 schema 中,各操作的参数组合只能靠自然语言说明而无法按工具分 schema 约束。
|
||||
|
||||
**为已加载的 Office 技能新增 Session projection。** 在专用 projection 中折叠 `tools/result` 和 `user/message` 记录虽可在 PTC 恢复和分叉后还原技能激活状态,但需要新增 projection 服务、折叠规则和跨包依赖,而标准模式已由持久化的请求头记录该状态,PTC 模式只需重新加载 `office` 即可恢复。
|
||||
|
||||
**仅在 `Agent` 内存中记录激活状态。** 若像 `action-cards` 那样只检查 `WeakSet<Agent>`,标准模式恢复或分叉后的第一步会丢失 `office_*` 声明,因为新的 `Agent` 实例初始未标记;同时检查 `agent.session.requestHeader()?.tools` 可从已持久化的请求头保留声明。
|
||||
|
||||
**为每个操作保留一个技能。** 单独的渲染、重算和 PDF 导出技能会在每次单独操作前多一次 `skill` 调用,并重复工具 schema 与结果中已有的说明。
|
||||
|
||||
**由 Host 把转换后的文件写入工作区。** 这样省去 shell 复制,但需要文件系统服务支持创建二进制文件,或在其旁另建一条写入路径,两者都会重复 shell 对工作区写入的沙箱和审批决策。
|
||||
|
||||
**经系统临时目录返回转换后的文件。** Linux bubblewrap 为受限 shell 挂载私有的 `/tmp`,shell 在那里读不到文件;Harness 主目录在所有本地 shell 沙箱下都可读。
|
||||
|
||||
**返回图片文件而不是图片内容。** 先保存 PNG 再调用 `read_image`,能让所有工具共用一种图片路径,但会重现本次变更去除的临时目录流程,每次检查还多一次调用。
|
||||
|
||||
## 影响
|
||||
|
||||
加载 Office 技能后,视觉检查只需一次 `office_render` 调用且无需清理,图片遵循 Session 的附件、卸载和回放规则。未加载 Office 技能的 Session 不消耗任何 Office 工具声明 token;加载了 Office 技能的 Session 激活约 1,813 个字符的专用 schema,支持 `toolUpdate` 的模型保留现有请求前缀,不支持 `toolUpdate` 的模型只开启一次新的请求序列。标准模式的恢复、分叉和压缩通过已记录的请求头保留工具声明,PTC 恢复或分叉以及在记录下一条请求头前崩溃的场景则通过再次加载 `office` 恢复。激活后,Office 操作在标准模式与 PTC 模式之间、在不同部署之间行为一致,各部署专用的 Node、CLI 和约束代码已经删除。
|
||||
|
||||
转换后的文件需要 shell 复制;没有 shell 工具的组合可以渲染,但无法交付转换后的文件,远程工作区则完全不能转换。临时输出在保留期满或根 Session 卸载后消失,恢复的 Session 需要重新转换。不提供把页面图片保存为文件。
|
||||
|
||||
验证包括:`tool-office.spec.ts` 覆盖三个工具的 schema、结果、参数规则、上限、失败、取消、PTC 分发和卸载,`activation.spec.ts` 覆盖技能触发激活、自定义 `activationSkills`、失败的技能调用、直接 `/office` 调用、跨恢复与分叉的 `requestHeader()` 持久化以及 PTC 重新激活;Office 套件测试覆盖输出存储的保留、淘汰、释放和清扫;Loader 组合测试在 Kit 缺失时以及 `office-kit` 条目缺失时返回 `unavailable` 消息,并在该条目加载后调用到服务;Desktop 打包 smoke 通过服务对真实 DOCX、XLSX 和 PPTX 文件执行渲染、转换和重算;录制的 headless Session 在加载 Office 技能前不声明 `office_*`,在加载后的下一步声明 `office_render`、`office_convert` 和 `office_recalculate`,其中包括一次因路由不接受图片输入而被 `office_render` 拒绝的渲染。
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-11-02-subagent-catalog-names.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/architecture/2026-11-02-subagent-catalog-names.md
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog:
|
||||
en: 987d2272bb47fe75
|
||||
zh: 54e701ecbaa60457
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/problem:
|
||||
en: d4798e7bbbd5d49d
|
||||
zh: b388470cf1ff33ac
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/decision:
|
||||
en: 8fafb256536d1bc5
|
||||
zh: f45f9e6d6f9a2407
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/alternatives-considered:
|
||||
en: 14b564fd848248da
|
||||
zh: 5f65c05bbe22d581
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/consequences:
|
||||
en: 4baa2557e5415ee1
|
||||
zh: 8e8d7819c04f9043
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/testing:
|
||||
en: a7963eadbfe52c21
|
||||
zh: 10bfc9fb28f6ae73
|
||||
@@ -0,0 +1,45 @@
|
||||
# Agent Note: Subagent names live in the creator's catalog
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-11-02-subagent-catalog-names.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Models coordinate subagents through `send_message`, `interrupt_agent`, `list_agents`, and Team task owners. Those tools addressed every member by a 36-character Session id. Models copy such ids poorly: they cannot recall which id belongs to which task, they mistype ids, and every listing, receipt, notice, and identity statement spends tokens on them. Descriptions explain responsibilities but repeat and cannot address a member. Models need short, readable references that survive restarts and resolve to exactly one member, without a second execution identity.
|
||||
|
||||
## Decision
|
||||
|
||||
The Session id remains the only execution identity for routing, authorization, persistence, task ownership, and navigation. A name is a model-facing reference to it.
|
||||
|
||||
Each new child has a name that is unique among its creator's children. The name is a field of the `subagent/catalog` entry that the creator already appends when it creates the child, using catalog payload version 3. Local and parent-delivered external creation write it; caller-delivered external runs enter no catalog and take no name. A name is 1–32 lower-case letters, digits, or hyphens starting with a letter, and is never `lead`. A 36-character UUID therefore never parses as a name. Omission selects the first free `agent-N`. An explicit name already held by a sibling fails with `DUPLICATE_AGENT_NAME`, and an invalid name fails with `INVALID_AGENT_NAME`.
|
||||
|
||||
The creator reserves the name synchronously at admission, before startup awaits anything. The reservation lives in process memory beside the creator's committed catalog names and is released when the catalog entry commits or startup fails. Task assignment copies the immutable full name into optional `ownerName` beside `ownerId` in the same task event. Task reads use this saved value and fall back to `ownerId`, so display does not require catalog traversal.
|
||||
|
||||
The root is always `lead`. A descendant's full name is the slash-separated path of names from the root without a `lead/` prefix, such as `reviewer/checker`. Sibling-unique segments make full names unique in the tree. A child whose catalog entry predates names, and every descendant below it, keeps its Session id as its name.
|
||||
|
||||
`ctx.subagents.resolveName()` returns the Session id for `lead` or a full name by reading only the catalogs along the path; any other text is returned unchanged as a Session id. Tools resolve before they call messaging, interruption, or Team assignment, and those operations authorize the resulting Session id. Resolution grants no authority. Runtime-authored text names agents by full name, while durable attribution such as `senderSessionId` and Team `ownerId` keeps Session ids. The [subagent runtime](../../../../packages/subagent/subagent/README.md#agent-names) owns the API contract and the [upgrade guide](../../../../docs/upgrade-guide/v0.2.2-alpha.3/subagent-names/guide.md) owns caller migration.
|
||||
|
||||
This supersedes the rejection of "unique names as sibling-routing opt-in" and the statement that creation accepts no member name in [subagent teammates](2026-10-15-agent-teams-as-subagents.md), and the Session-id identity text in [required runtime-context identity](2026-10-13-team-identity-runtime-context.md). Their single-identity, Team, and runtime-context retention decisions remain active.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Optional naming plugin with a root-Session binding event.** A separate plugin would record a name-to-Session binding in the root Session after the child starts. That adds a second write that can fail independently after startup succeeds, so a started child can lack its binding, and binding after start can report errors for children that are already running. The presentation plugin would then own cancellation of a lifecycle it does not control, would rewrite runtime-authored messages that name agents, and would still change model-visible schemas when it is absent, because the core tools must accept and render either form. Recording the name in the creator's existing catalog fact needs no second write, no rollback protocol, and no optional service.
|
||||
|
||||
**Flat names unique across the tree.** A tree-wide flat namespace needs one registry at the root that every creation must consult and update, including creations by descendants whose root may be cold. Sibling-unique segments joined into paths are unique without a root registry, and each creator checks only its own catalog.
|
||||
|
||||
**Recyclable names.** Releasing a name when its child settles would let a later sibling take it. A model that still remembers the earlier child would then message or interrupt a different agent. A name stays taken for as long as the catalog entry exists.
|
||||
|
||||
**Automatic suffixes on conflict.** Turning a duplicate `reviewer` into `reviewer-2` would let creation succeed, but the model would believe it holds a name that the runtime never assigned. A duplicate fails so that the model chooses a name it knows.
|
||||
|
||||
## Consequences
|
||||
|
||||
Catalog payload version 3 is a same-format addition: `SESSION_FORMAT_VERSION` is unchanged, current readers accept versions 0 through 3, and releases that predate names reject version 3 loudly. The `subagentCatalog` projection state version remains 6 because old checkpoint facts keep their meaning and accept named suffix events; invalidating them would add full catalog folds to cold descendant observations without changing the recovered state. Native admission rejects a repeated sibling name in own catalog facts, so a log that could make name lookup ambiguous fails before any member is resolved. The [persistence acknowledgement](../../../../docs/persistence-changes/2026-11-02-subagent-catalog-names.md) records the type change.
|
||||
|
||||
Tool schemas grow by one optional `name` parameter on each delegation tool, and `send_message` and `interrupt_agent` rename `agent_id` to `agent`. These schema changes invalidate cached request prefixes once after upgrading. Identity runtime context, receipts, settlement notices, agent messages, and listing rows become shorter for shallow trees, while full names lengthen with nesting depth. `list_agents` drops parent ids and depth because a full name encodes both.
|
||||
|
||||
Names cannot be renamed or recycled. Historical children have no names, and no migration assigns them. Name reservations are process-local, as is subagent residency. Tools still accept Session ids, so recorded Session ids and members below unnamed creators remain addressable.
|
||||
|
||||
## Testing
|
||||
|
||||
Runtime tests cover requested and default names, duplicate and invalid names rejected before startup, reservation across concurrent startup and release after failed startup, nested paths and `namesOf`, resolution of names, unknown text, and Session ids without granting authority, cold restoration of a member's path from the catalogs along its creation chain, and Session-id addressing below an unnamed historical creator. Catalog tests restore version-3 names beside unnamed historical entries, and the V4 format reader admits native version-3 catalog facts. Control-tool and Team-tool tests cover name-addressed messaging, listing rows, owner names, and historical owners shown by Session id. Recorded sessions pin the tool schemas, identity runtime context, receipts, listing rows, and Team policy text.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Agent Note: Subagent 名称记录在创建者目录中
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-11-02-subagent-catalog-names.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
模型通过 `send_message`、`interrupt_agent`、`list_agents` 和 Team 任务所有者协调 subagent(子智能体)。这些工具曾以 36 字符的 Session id 寻址每个成员。模型难以准确复制这类 id:记不清哪个 id 对应哪个任务,会输错 id,而且每次列表、回执、通知和身份声明都要为其消耗 token。描述能说明职责,但可以重复,无法寻址成员。模型需要简短、可读、跨重启有效且只解析到唯一成员的引用,同时不引入第二种执行身份。
|
||||
|
||||
## Decision
|
||||
|
||||
Session id 仍是路由、授权、持久化、任务归属和导航使用的唯一执行身份。名称是指向它的面向模型的引用。
|
||||
|
||||
每个新子级都有一个名称,在其创建者的子级之间唯一。名称是创建者在创建子级时本就会追加的 `subagent/catalog` 条目中的一个字段,使用第 3 版目录载荷。本地创建和父级投递的外部创建写入名称;调用方投递的外部执行不进入目录,也没有名称。名称由 1–32 个小写字母、数字或连字符组成,以字母开头,且绝不能是 `lead`。因此 36 字符的 UUID 永远不会被解析为名称。省略时选择第一个空闲的 `agent-N`。显式请求同级已持有的名称会以 `DUPLICATE_AGENT_NAME` 失败,无效名称以 `INVALID_AGENT_NAME` 失败。
|
||||
|
||||
创建者在准入时、启动等待任何操作之前同步预留名称。预留保存在进程内存中,与创建者已提交的目录名称并列,并在目录条目提交或启动失败时释放。任务分配在同一个任务事件中,将不可变的完整名称复制到 `ownerId` 旁的可选 `ownerName`。任务读取使用此保存值,缺失时回退到 `ownerId`,因此展示无需遍历目录。
|
||||
|
||||
根的名称始终为 `lead`。后代的完整名称是从根开始、以斜杠分隔的名称路径,不带 `lead/` 前缀,例如 `reviewer/checker`。同级唯一的各段使完整名称在整棵树中唯一。目录条目早于名称机制的子级及其下所有后代继续以 Session id 作为名称。
|
||||
|
||||
`ctx.subagents.resolveName()` 只读取路径上的目录,为 `lead` 或完整名称返回 Session id;其他文本原样作为 Session id 返回。工具先解析名称,再调用消息、中断或 Team 分配,由这些操作对解析出的 Session id 授权。解析本身不授予权限。运行时撰写的文本以完整名称指称 agent,而 `senderSessionId` 和 Team `ownerId` 等持久归属仍保留 Session id。[subagent 运行时](../../../../packages/subagent/subagent/README.zh.md#agent-names)定义 API 约定,[升级指南](../../../../docs/upgrade-guide/v0.2.2-alpha.3/subagent-names/guide.zh.md)负责调用方迁移。
|
||||
|
||||
本决策取代 [Agent Teams 队友是子代理](2026-10-15-agent-teams-as-subagents.zh.md)中对“以唯一名称选择性启用同级路由”的否决以及“创建不接收成员名称”的表述,并取代[必需运行时身份](2026-10-13-team-identity-runtime-context.zh.md)中以 Session id 表述身份的文本。它们的单一身份、Team 和运行时上下文保留决策仍然有效。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**带根 Session 绑定事件的可选命名插件。** 独立插件会在子级启动后,在根 Session 中记录名称到 Session 的绑定。这会增加第二次写入,且可能在启动成功后单独失败,导致已启动的子级缺少绑定;启动后再绑定还可能为已在运行的子级报告错误。展示用插件因此要负责取消一个不受它控制的生命周期,要改写指称 agent 的运行时消息;而且即使插件不存在,面向模型的 schema 仍会改变,因为核心工具必须接受并渲染两种形式。把名称记录在创建者已有的目录事实中,不需要第二次写入、回滚协议或可选服务。
|
||||
|
||||
**全树唯一的扁平名称。** 全树扁平命名空间需要根上的一个注册表,每次创建都要查询并更新它,包括根可能处于冷状态时由后代发起的创建。同级唯一的各段连接成路径后无需根注册表即可唯一,每个创建者只检查自己的目录。
|
||||
|
||||
**可回收名称。** 子级结算后释放名称会让之后的同级取得该名称。仍记得先前子级的模型会因此向另一个 agent 发消息或发出中断。只要目录条目存在,名称就保持占用。
|
||||
|
||||
**冲突时自动加后缀。** 把重复的 `reviewer` 改为 `reviewer-2` 能让创建成功,但模型会以为自己持有一个运行时从未分配的名称。重复名称直接失败,模型才会选择一个自己知道的名称。
|
||||
|
||||
## Consequences
|
||||
|
||||
第 3 版目录载荷是同格式增量:`SESSION_FORMAT_VERSION` 不变,当前读取器接受第 0 至第 3 版,早于名称机制的发布会明确拒绝第 3 版。`subagentCatalog` 投影状态版本保持为 6,因为旧检查点事实的含义不变,且能继续处理具名后缀事件;使其失效会在冷后代观察时增加完整目录折叠,却不改变恢复结果。原生准入会拒绝自身目录事实中重复的同级名称,因此会让名称查找产生歧义的日志在解析任何成员之前就失败。[持久化确认记录](../../../../docs/persistence-changes/2026-11-02-subagent-catalog-names.zh.md)记录了该类型变更。
|
||||
|
||||
每个委派工具的 schema 增加一个可选 `name` 参数,`send_message` 和 `interrupt_agent` 把 `agent_id` 改名为 `agent`。这些 schema 变更会在升级后使缓存的请求前缀失效一次。对浅层树而言,身份运行时上下文、回执、结算通知、agent 消息和列表行都变短,完整名称则随嵌套深度变长。`list_agents` 不再携带父级 id 和深度,因为完整名称已包含这两项信息。
|
||||
|
||||
名称不能修改或回收。历史子级没有名称,也没有迁移为其分配名称。名称预留与 subagent 驻留一样仅限进程内。工具仍接受 Session id,因此已记录的 Session id 和无名创建者下的成员仍可寻址。
|
||||
|
||||
## Testing
|
||||
|
||||
运行时测试覆盖请求名称与默认名称、启动前拒绝重复和无效名称、并发启动期间的预留和启动失败后的释放、嵌套路径与 `namesOf`、在不授予权限的前提下解析名称、未知文本和 Session id、从创建链上的目录冷恢复成员路径,以及无名历史创建者之下以 Session id 寻址。目录测试在无名历史条目旁恢复第 3 版名称,V4 格式读取器接纳原生第 3 版目录事实。控制工具和 Team 工具测试覆盖按名称发消息、列表行、所有者名称,以及以 Session id 显示的历史所有者。录制会话固定工具 schema、身份运行时上下文、回执、列表行和 Team 策略文本。
|
||||
@@ -12,8 +12,8 @@
|
||||
en: f962826fcf5e3949
|
||||
zh: 8a9932d86332fb47
|
||||
/agent-note-browser-excel-preview-with-saved-formula-results/alternatives-considered:
|
||||
en: 0747bca32af26bfc
|
||||
zh: 1d2739208e7c7d7c
|
||||
en: 91a98713cd1316be
|
||||
zh: 2a27af0f5b8a5e46
|
||||
/agent-note-browser-excel-preview-with-saved-formula-results/consequences:
|
||||
en: 62595b9a9b948422
|
||||
zh: 0618904d9ebfa7fc
|
||||
@@ -36,7 +36,7 @@ The core patch also skips drawing freeze dividers when `allowEdit === false`, re
|
||||
|
||||
**Replace ExcelJS or require users to rewrite their files.** A parser replacement puts retained fonts, borders, merges, and frozen panes at risk. Rewriting a source file makes a viewer defect the user's responsibility. A temporary preview copy preserves both the existing adapter and the user's file.
|
||||
|
||||
**Keep PDF as an Excel fallback or calculate every formula on import.** PDF retains neither spreadsheet interaction nor formula inspection, while recalculation can change saved results. The [Office engine reference](../../../../packages/document/office-to-pdf/README.md) remains applicable to Word/PowerPoint previews and independent conversion consumers; its conversion capability still supports spreadsheets.
|
||||
**Keep PDF as an Excel fallback or calculate every formula on import.** PDF retains neither spreadsheet interaction nor formula inspection, while recalculation can change saved results. The [Office engine reference](../../../../packages/document/office-kit/README.md) remains applicable to Word/PowerPoint previews and independent conversion consumers; its conversion capability still supports spreadsheets.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ core 补丁还在 `allowEdit === false` 时跳过冻结分隔线的绘制,保
|
||||
|
||||
**替换 ExcelJS 或要求用户重写文件。** 更换解析器会使现有字体、边框、合并及冻结窗格的保留面临风险。重写源文件则把查看器缺陷交给用户处理。临时预览副本同时保留现有适配器和用户文件。
|
||||
|
||||
**保留 PDF 作为 Excel 兜底,或在导入时计算所有公式。** PDF 无法保留表格交互与公式查看,而重新计算可能改变已保存结果。[Office 引擎参考](../../../../packages/document/office-to-pdf/README.zh.md)仍适用于 Word/PowerPoint 预览与独立转换使用方;转换能力仍支持表格。
|
||||
**保留 PDF 作为 Excel 兜底,或在导入时计算所有公式。** PDF 无法保留表格交互与公式查看,而重新计算可能改变已保存结果。[Office 引擎参考](../../../../packages/document/office-kit/README.zh.md)仍适用于 Word/PowerPoint 预览与独立转换使用方;转换能力仍支持表格。
|
||||
|
||||
## 影响
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-26-manual-generation-continuation.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/feature/2026-10-26-manual-generation-continuation.md
|
||||
/agent-note-manual-generation-continuation:
|
||||
en: 327aaa1cdc63b1f5
|
||||
zh: 1147b4a23e51772c
|
||||
/agent-note-manual-generation-continuation/problem:
|
||||
en: f118844946afdbdc
|
||||
zh: f1fe37d042f88b24
|
||||
/agent-note-manual-generation-continuation/decision:
|
||||
en: 463db7deb7710e07
|
||||
zh: cfacb69cb58349a9
|
||||
/agent-note-manual-generation-continuation/alternatives-considered:
|
||||
en: 58bb948d210d42ea
|
||||
zh: 5dc920d222d71218
|
||||
/agent-note-manual-generation-continuation/consequences:
|
||||
en: e86aee3a7da7649f
|
||||
zh: d58e8cc94e5008bf
|
||||
/agent-note-manual-generation-continuation/verification:
|
||||
en: e31a5bd5826c689f
|
||||
zh: 02ac700146246664
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: Manual generation continuation
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-26-manual-generation-continuation.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Users need another request after a model failure without inserting an artificial instruction. Browser paging can omit the failed turn's start, and streaming must not repeatedly copy history.
|
||||
|
||||
## Decision
|
||||
|
||||
The play action submits `content: []` through the existing Session prompt API. The API assigns `source.kind: 'retry'`; unchanged AgentLoop admission records an empty `user/message` and opens a normally balanced new turn. Chat presents a compact continuation-request receipt while the log retains the empty input. The original failed turn remains closed.
|
||||
|
||||
The Session API owns the fixed-size `conversationContinuation` projection and shares its value with the browser. Admission requires an eligible failure, true idle ownership, and no pending human input. An appended empty retry message admits the new turn, preserving eligibility if its request also fails.
|
||||
|
||||
The six-code manual allowlist stays independent of provider retry policy. Non-waking injected context accompanies the request. The accepted user message retains request-id deduplication after the inbox input is claimed. Reusing ordinary message admission avoids a Loop branch and a separate durable acceptance event.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Localized or hidden continuation text.** Adds model-visible instructions and changes retained history.
|
||||
|
||||
**A separate resume operation.** The existing prompt and inbox already carry identified control input and own wakeup.
|
||||
|
||||
**Keep the failed turn waiting for a click.** Changes failure settlement, idle status, cancellation, and model-selection semantics.
|
||||
|
||||
## Consequences
|
||||
|
||||
Ordinary empty-draft Enter remains inert. The explicit action reuses submission state and error reporting without an echo or generated draft. Pre-step rejection and cleared batches still prevent model calls.
|
||||
|
||||
## Verification
|
||||
|
||||
Unit and assembled scenarios cover repeated failure, request deduplication, injected context, balanced turns, retained tools, one original user message, and zero Client event-window reads.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: 手动继续生成
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-26-manual-generation-continuation.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
模型请求失败后,用户需要再次请求,但不应插入人为构造的指令。浏览器分页可能缺少失败轮次起点,流式输出也不能反复复制历史。
|
||||
|
||||
## 决策
|
||||
|
||||
播放按钮通过现有 Session prompt API 提交 `content: []`。API 指定 `source.kind: 'retry'`;AgentLoop 沿用普通准入,记录空 `user/message` 并开启正常闭合的新轮次。Chat 显示轻量的继续请求记录,日志保留空输入。原失败轮次保持闭合。
|
||||
|
||||
Session API 拥有固定大小的 `conversationContinuation` 投影,向浏览器发布相同的值。准入要求失败符合条件、持有真正的空闲状态且没有待消费的用户输入。追加的空重试消息使新轮次获得准入,因此请求再次失败后仍保留继续资格。
|
||||
|
||||
六码手动白名单独立于提供方重试策略。非唤醒的注入上下文随请求进入。inbox 输入被领取后,已接受的用户消息仍支持 request-id 去重。复用普通消息准入避免增加 Loop 分支及独立持久接受事件。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**本地化或隐藏的继续文本。** 增加模型可见指令并改变保留历史。
|
||||
|
||||
**单独的 resume 操作。** 现有 prompt 和 inbox 已可承载带身份的控制输入并拥有唤醒流程。
|
||||
|
||||
**保持失败轮次等待点击。** 改变失败结算、空闲状态、取消和模型选择语义。
|
||||
|
||||
## 后果
|
||||
|
||||
普通空草稿回车仍不执行。显式继续沿用提交状态与错误报告,不创建消息回显或生成草稿。pre-step 拒绝和被清空的批次仍阻止模型调用。
|
||||
|
||||
## 验证
|
||||
|
||||
单元和组装场景覆盖重复失败、请求去重、注入上下文、轮次闭合、工具结果保留、仅一条原始用户消息,以及客户端不读取完整事件窗口。
|
||||
@@ -33,8 +33,8 @@
|
||||
en: d83f6cf38529c5b5
|
||||
zh: 4df5659b2154d3b7
|
||||
/agent-note-private-npm-publication-as-three-independent-sequences/decision/release-family-objects:
|
||||
en: 5bc6a83d124b682d
|
||||
zh: 17383ec5c3c7e0e5
|
||||
en: e0cdaa5aaa1f7d25
|
||||
zh: 645f0a0e43a37ddd
|
||||
/agent-note-private-npm-publication-as-three-independent-sequences/decision/workflow-shape-pack-on-pr-push-publish-from-a-manual-dispatch-workflow:
|
||||
en: ea0acc6409da4d5c
|
||||
zh: ac0bd2fe41f4fe67
|
||||
|
||||
@@ -117,7 +117,7 @@ The entity in this domain is a **release family**: a set of packages sharing one
|
||||
| `publish` | the three registry states above |
|
||||
| `process` / `tarball` | the one home for spawning commands and for reading a packed tarball, including the entry guard that keeps every script importable |
|
||||
|
||||
The dsh family applies the repository's publication payload policy, which rejects sources and declaration maps. The vendored family keeps upstream's payload, because those manifests export `./src/*` and dropping `src` would publish an export map pointing at absent files.
|
||||
The dsh family applies the repository's publication payload policy, which publishes `src`, its declaration maps, and the JavaScript maps beside the bundles they describe ([rationale](2026-10-08-published-source-and-docs.md)). The vendored family keeps upstream's payload, because upstream's manifest owns it and those manifests export `./src/*`.
|
||||
|
||||
### Workflow shape: pack on PR/push, publish from a manual dispatch workflow
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ registry 的两个行为决定了「怎么尝试一次发布」。写入之间
|
||||
| `publish` | 上面那三态 |
|
||||
| `process` / `tarball` | 启动命令、读取打包 tarball 的唯一正家,其中的入口守卫让每个脚本都可被 import |
|
||||
|
||||
dsh 族套用仓库的发布 payload 策略(拒绝源码与声明映射)。vendored 族保留上游 payload,因为那些 manifest 导出 `./src/*`,去掉 `src` 会发出一个导出映射指向不存在文件的包。
|
||||
dsh 族套用仓库的发布 payload 策略(发布 `src`、其声明映射,以及紧挨所描述 bundle 的 JavaScript map,[理由](2026-10-08-published-source-and-docs.zh.md))。vendored 族保留上游 payload,因为该 payload 由上游 manifest 拥有,且它们导出 `./src/*`。
|
||||
|
||||
### workflow 形状:PR/push 上 pack,从手动 dispatch 工作流发布
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-08-published-source-and-docs.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/process/2026-10-08-published-source-and-docs.md
|
||||
/agent-note-published-source-maps-and-documentation:
|
||||
en: dbec3958b6bb3a8c
|
||||
zh: f17abeb0533b4191
|
||||
/agent-note-published-source-maps-and-documentation/problem:
|
||||
en: 699d35bd0bb12645
|
||||
zh: c9e362c336c6b440
|
||||
/agent-note-published-source-maps-and-documentation/decision:
|
||||
en: 0181de5ab7314873
|
||||
zh: 6af13b56cf779d9f
|
||||
/agent-note-published-source-maps-and-documentation/alternatives-considered:
|
||||
en: 953acd4b427b6698
|
||||
zh: 43426e6213773943
|
||||
/agent-note-published-source-maps-and-documentation/consequences:
|
||||
en: e57d7a2d3b5cbdac
|
||||
zh: 1f7c314b09643ae6
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note: Published source, maps, and documentation
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-08-published-source-and-docs.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A published `@deepseek-ai/dsh-*` package carried declarations, bundled runtime, and locale resources, but neither the TypeScript the declarations describe nor the maps that resolve into it. Its `exports` map nevertheless declared `./src/*`, so an installed consumer resolved that subpath to files the tarball did not contain. A plugin author who wanted the implementation behind a documented API had to read the declaration, the documentation website, or a checkout at the matching `dsh-v*` tag: the first stops at the declaration, and the other two are not the installed version. The repository's Markdown documentation reached readers only through the website, which tracks the published release.
|
||||
|
||||
## Decision
|
||||
|
||||
Every release member publishes its TypeScript source, the declaration maps that resolve into it, and the JavaScript maps beside the bundles they describe. `@deepseek-ai/dsh` publishes the documentation website's page set plus the upgrade guides, and the build compresses emitted bundles without mangling identifiers.
|
||||
|
||||
[`expectedDshPackageFiles`](../../../../scripts/check-workspace-constraints.ts) lists `lib/types/**/*.d.ts.map`, `lib/**/*.js.map`, and `src`; a package with Typert Remote navigation also lists `lib/typert.remote-client.d.ts.map`, which its generated declaration references through `sourceMappingURL`. No payload member is forbidden, so the static manifest check owns the exact publication list. The `./src/*` export that 315 of the 332 release members declare resolves to those files, which serves editor navigation and reading; plain Node still cannot import them without a TypeScript loader.
|
||||
|
||||
The client preset compresses the browser bundle's text (`minify: { compress: false, mangle: false }`). Identifiers keep their names, so a stack frame and the published map still name the original code. Node-side entries stay uncompressed: the browser worker image lowers `yield*` only as a whole statement and rewrites `for await` loops by their source layout, and compressed input breaks both.
|
||||
|
||||
`@deepseek-ai/dsh` stages that set into `apps/cli/docs` from its `prepack` lifecycle through [`scripts/stage-dsh-docs.ts`](../../../../scripts/stage-dsh-docs.ts), and removes the copy again from `postpack`. The set is the website manifest's Markdown, every `docs/upgrade-guide/` document, and the non-Markdown assets those pages reference. Staging also generates `docs/upgrade-guide/README.md`, an index grouped by the release each `v<T>` directory upgrades from, with each guide's title, its English and Chinese descriptions, and both relative paths; the repository tree keeps no index of its own. The generated `docs/persistence-changes/` records, the `docs/i18n/` guidelines, and the `*.i18n.yaml` consistency records stay out. The vendored family keeps upstream's payload unchanged ([release sequences](2026-08-10-npm-release-sequences.md)).
|
||||
|
||||
Desktop keeps `src/` and the `@deepseek-ai/dsh` `docs/` tree inside `app.asar`, so an agent reads the installed version's documentation and code there; [its runtime file policy](../../../../apps/desktop/scripts/runtime-file-policy.ts) still omits every declaration and map. Ordinary tools cannot open an archive entry, so [`dsh-shell-env`](../../../../packages/shell/shell-env/README.md) gives every model shell call `DSH_INSTALL_DIR`, the `@deepseek-ai/dsh` package directory, and `DSH_NODE_EXECUTABLE`, the executable running the Harness; under Desktop that is Electron, whose Node mode (`ELECTRON_RUN_AS_NODE=1`) reads the archive. The Python single-file executable pays nothing: [`PUBLISHED_PAYLOAD_IGNORES`](../../../../scripts/build-exe-for-python-sdk.ts) excludes the same trees, so its contents do not depend on a staged `docs` left in the checkout.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the payload declarations-only and send readers to the documentation website or the matching `dsh-v*` tag.** Rejected because neither is the installed version, and the website serves the latest published release rather than a prerelease. The declared `./src/*` export also has no meaning without those files.
|
||||
|
||||
**Publish maps without `src`.** Rejected because a map whose `sources` are absent resolves nothing, which is exactly the navigation the [Typert Remote design](../architecture/2026-08-02-typert-remote-method-calls.md) requires.
|
||||
|
||||
**Publish the whole `docs/` tree, or only the website's page set.** Both extremes were rejected. The whole tree publishes maintainer material an installed reader does not need — postmortems, testing guides, agent instructions, and the generated migration records — while the site's page set alone omits the upgrade guides, which are the documents an installed reader consults when a released change breaks something.
|
||||
|
||||
**Mangle the bundles as well.** Rejected because mangled identifiers break the correspondence a published map exists to provide and make an unhandled stack frame unreadable.
|
||||
|
||||
**Compress the node-side entries as well.** Rejected because the browser worker image refuses compressed input: the packer rejects a `yield*` a compressor moved into an expression, and it produces unparseable code for `dsh-agent-instructions`. Both are lowering constraints, not size arguments.
|
||||
|
||||
## Consequences
|
||||
|
||||
`src` adds about 29 MB and declaration maps about 10 MB across the release set; JavaScript maps add about 49 MB, and the staged documentation adds 6.4 MB across 320 files to `@deepseek-ai/dsh`. Compression removes about a third of a browser bundle — `ui-chat`'s `lib/client.js` falls from 591 kB to 420 kB — while node-side entries keep their size until the worker image lowering accepts compressed input. The macOS arm64 `app.asar` grows by about 20 MB of `src` and 5 MB of documentation; the Python executable keeps its previous payload.
|
||||
|
||||
A consumer editor now follows a declaration to the installed implementation, and a plugin author reads that version's documentation without a checkout. The staged copy keeps the repository's relative links, so a link into `../packages/**` or into the omitted `persistence-changes/` records resolves only in a checkout. `docs/` exists in the working tree only between `prepack` and `postpack`, and `pnpm run clean` removes a copy an interrupted pack left behind.
|
||||
|
||||
`pnpm run constraints` rejects a member whose `files` omits `src` or a map, because the canonical list is exact. The release family's tarball check requires `@deepseek-ai/dsh` to carry documentation, the upgrade guide index, and source, so a pack run that skips the staging hook fails instead of publishing without them.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note: 发布源码、映射与文档
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-08-published-source-and-docs.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
已发布的 `@deepseek-ai/dsh-*` 包带有声明文件、打包后的运行时和 locale 资源,却没有声明文件所描述的 TypeScript,也没有能解析到它的映射。它的 `exports` 仍然声明了 `./src/*`,于是已安装的消费方把该子路径解析到 tarball 中并不存在的文件。插件作者若想看到某个已记录 API 背后的实现,只能读声明文件、文档网站,或检出匹配的 `dsh-v*` tag:第一种停在声明处,后两者都不是已安装的版本。仓库的 Markdown 文档也只能通过网站到达读者,而网站跟随的是已发布版本。
|
||||
|
||||
## Decision
|
||||
|
||||
每个发布成员都发布自己的 TypeScript 源码、能解析到该源码的声明映射,以及紧挨所描述 bundle 的 JavaScript map。`@deepseek-ai/dsh` 另发布文档网站的页面集合外加升级指南,构建也会压缩产出的 bundle,但不混淆标识符。
|
||||
|
||||
[`expectedDshPackageFiles`](../../../../scripts/check-workspace-constraints.ts) 列出 `lib/types/**/*.d.ts.map`、`lib/**/*.js.map` 与 `src`;带 Typert Remote 导航的包还会列出 `lib/typert.remote-client.d.ts.map`,其生成的声明通过 `sourceMappingURL` 引用该映射。没有任何载荷成员被禁止,发布清单由静态 manifest 检查精确持有。332 个发布成员中已有 315 个声明过 `./src/*`,该导出现在解析到这些文件,服务于编辑器导航与阅读;纯 Node 仍需 TypeScript loader 才能 import 它们。
|
||||
|
||||
client 预设会压缩浏览器 bundle 的文本(`minify: { compress: false, mangle: false }`)。标识符保持原名,因此栈帧与已发布的 map 仍然指向原始代码。Node 侧入口不压缩:浏览器 worker 镜像只把 `yield*` 当作整条语句下沉,并按源码版式重写 `for await` 循环,压缩后的输入会同时破坏这两者。
|
||||
|
||||
`@deepseek-ai/dsh` 通过 [`scripts/stage-dsh-docs.ts`](../../../../scripts/stage-dsh-docs.ts) 在自己的 `prepack` 生命周期里把这一集合复制到 `apps/cli/docs`,并在 `postpack` 中再次删除这份副本。集合包含:网站清单中的 Markdown、`docs/upgrade-guide/` 下的全部文档,以及这些页面引用的非 Markdown 资产。暂存时还会生成 `docs/upgrade-guide/README.md`:按每个 `v<T>` 目录升级前的版本分组,列出每份指南的标题、中英文描述与两种语言的相对路径;仓库树本身不保留索引。生成的 `docs/persistence-changes/` 记录、`docs/i18n/` 规范与 `*.i18n.yaml` 一致性记录不发布。vendored 族保持上游 payload 不变([发布序列](2026-08-10-npm-release-sequences.zh.md))。
|
||||
|
||||
桌面版在 `app.asar` 内保留 `src/` 与 `@deepseek-ai/dsh` 的 `docs/` 树,agent 可在其中读取已安装版本的文档与代码;其[运行时文件策略](../../../../apps/desktop/scripts/runtime-file-policy.ts)仍剔除全部声明文件与映射。普通工具打不开归档内条目,因此 [`dsh-shell-env`](../../../../packages/shell/shell-env/README.zh.md) 为每次模型 shell 调用提供 `DSH_INSTALL_DIR`(`@deepseek-ai/dsh` 包目录)与 `DSH_NODE_EXECUTABLE`(运行 Harness 的可执行文件);桌面版下它是 Electron,其 Node 模式(`ELECTRON_RUN_AS_NODE=1`)能读取归档。Python 单文件可执行体不付出代价:[`PUBLISHED_PAYLOAD_IGNORES`](../../../../scripts/build-exe-for-python-sdk.ts) 排除同样的树,因此其内容不取决于检出中是否残留暂存的 `docs`。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保持只发声明文件,把读者引向文档网站或匹配的 `dsh-v*` tag。** 否决:两者都不是已安装的版本,而网站提供的是最新已发布版本而非预发布版本。声明的 `./src/*` 导出没有这些文件也毫无意义。
|
||||
|
||||
**只发布映射,不发布 `src`。** 否决:`sources` 不存在的映射解析不到任何东西,而这正是 [Typert Remote 设计](../architecture/2026-08-02-typert-remote-method-calls.zh.md)所要求的导航。
|
||||
|
||||
**整树发布,或只发布网站页面集合。** 两个极端都否决。整树会把已安装读者并不需要的维护者材料发出去——事故复盘、测试指南、Agent 指令与生成的迁移记录;而只发网站页面集合会漏掉升级指南,也就是已发布改动造成破坏时读者真正要查的文档。
|
||||
|
||||
**连标识符一起混淆。** 否决:混淆会破坏已发布 map 本应提供的对应关系,并使未处理栈帧无法阅读。
|
||||
|
||||
**连 Node 侧入口一起压缩。** 否决:浏览器 worker 镜像拒绝压缩后的输入——打包器会拒绝被压缩器挪进表达式的 `yield*`,并且对 `dsh-agent-instructions` 产出无法解析的代码。这两条都是下沉变换的限制,不是体积上的理由。
|
||||
|
||||
## Consequences
|
||||
|
||||
`src` 在整个发布集上增加约 29 MB,声明映射增加约 10 MB,JavaScript map 增加约 49 MB,暂存的文档以 320 个文件给 `@deepseek-ai/dsh` 增加 6.4 MB。压缩让浏览器 bundle 约减少三分之一——`ui-chat` 的 `lib/client.js` 从 591 kB 降到 420 kB——而 Node 侧入口在 worker 镜像接受压缩输入之前保持原体积。macOS arm64 的 `app.asar` 增加约 20 MB `src` 与 5 MB 文档;Python 可执行体保持原有载荷。
|
||||
|
||||
消费方编辑器现在可以从声明跳转到已安装的实现,插件作者无需检出即可读到该版本的文档。暂存副本保留仓库的相对链接,因此指向 `../packages/**` 或指向被排除的 `persistence-changes/` 记录的链接只在检出中可解析。`docs/` 只在 `prepack` 与 `postpack` 之间存在于工作树,`pnpm run clean` 会清掉被中断的打包留下的副本。
|
||||
|
||||
`pnpm run constraints` 会拒绝 `files` 中缺少 `src` 或任一声明映射的发布成员,因为该清单是精确匹配的。发布族的 tarball 校验要求 `@deepseek-ai/dsh` 必须带上文档、升级指南索引与源码,因此跳过暂存钩子的打包会直接失败,而不是发出一个缺少它们的包。
|
||||
@@ -15,8 +15,8 @@
|
||||
en: 403bd786a8411349
|
||||
zh: c44584e8a7e5b74a
|
||||
/agent-note-deferred-desktop-update-extensions/proposal/stable-subscription-and-channel-switching:
|
||||
en: f39b1ed0d941d372
|
||||
zh: 05b9a006c9ec0d3c
|
||||
en: 33663c160d5e280b
|
||||
zh: 96689283c31ed820
|
||||
/agent-note-deferred-desktop-update-extensions/proposal/automatic-installation-setting:
|
||||
en: 758d49f0a7dc6707
|
||||
zh: ddb0bfe616eb93bc
|
||||
|
||||
@@ -29,16 +29,7 @@ Do not use `0.1.3-dsk.1` to repair stable `0.1.3`: SemVer orders it below the st
|
||||
|
||||
### Stable subscription and channel switching
|
||||
|
||||
| User option | Feed | Release contents |
|
||||
|---|---|---|
|
||||
| Stable | `latest.yml` / `latest-mac.yml` | Stable only |
|
||||
| Nightly | `nightly.yml` / `nightly-mac.yml` | Alpha, rc, and stable |
|
||||
|
||||
The [initial publication rule](2026-09-08-desktop-update-policy-and-installation.md) adds the stable feed when shared stable releases exist, without switching fixed-Nightly clients. A user-facing stable subscription and selector remain deferred. If enabled, persist the selected channel; never infer it from the installed version. Preserve `allowDowngrade = false` when assigning the updater channel. Moving from Nightly to stable keeps a higher installed Nightly until a higher stable version exists, without downgrading Desktop or bundled dsh.
|
||||
|
||||
Channel switching may immediately check; download behavior must follow the approved setting. Ignore old-channel responses and reconfirm downloaded artifacts against the new channel before installation. A channel change does not clear mandatory blocking without a valid policy response for the new conditions. Display a persistent Nightly explanation under the selector, with candidate Chinese copy “提前体验新功能,版本可能不稳定。”; hide it after selecting stable, while separately explaining any wait for a higher stable version.
|
||||
|
||||
Design compatibility only for Desktop packages actually distributed. If a future migration has real clients reading `alpha.yml` or `rc.yml`, publish a higher bridge version to the old feed and Nightly until the old entry can be retired. No such bridge is required initially.
|
||||
This part of the proposal is superseded by [Desktop channel settings](../../../../apps/desktop/README.md#local-desktop-settings) and the [packaging channel policy](../../../../apps/desktop/packaging/README.md). The implemented selector persists explicit user choices; publishers select versions independently of channel names, and channel changes do not permit downgrades.
|
||||
|
||||
### Automatic-installation setting
|
||||
|
||||
|
||||
@@ -29,16 +29,7 @@ Status: proposed
|
||||
|
||||
### 稳定版订阅与通道切换
|
||||
|
||||
| 用户选项 | Feed | 发布内容 |
|
||||
|---|---|---|
|
||||
| 稳定版 | `latest.yml` / `latest-mac.yml` | 仅稳定版本 |
|
||||
| Nightly | `nightly.yml` / `nightly-mac.yml` | alpha、rc 和稳定版本 |
|
||||
|
||||
[一期发布规则](2026-09-08-desktop-update-policy-and-installation.zh.md)在共用稳定版本出现时增加稳定 feed,不切换固定 Nightly 客户端。面向用户的稳定版订阅与选择器仍延后。启用后持久化所选通道,不从已安装版本反推。设置 updater 通道时保持 `allowDowngrade = false`。从 Nightly 切到稳定版时,较高的已安装 Nightly 持续运行,直到出现更高稳定版;不得降级 Desktop 或内置 dsh。
|
||||
|
||||
切换通道可以立即检查,下载行为必须遵循获准设置。忽略旧通道响应,安装前按新通道重新确认已下载产物。通道改变不能在缺少新条件有效策略响应时解除强制阻断。选择器下方常驻 Nightly 说明,候选中文为“提前体验新功能,版本可能不稳定。”;选择稳定版后隐藏,必要时另行说明需等待更高稳定版。
|
||||
|
||||
仅为实际分发过的 Desktop 安装包设计兼容。后续迁移若存在读取 `alpha.yml` 或 `rc.yml` 的真实客户端,应向旧 feed 与 Nightly 发布更高桥接版本,直到可淘汰旧入口。一期不需要这类桥接。
|
||||
本提案的这一部分已由[桌面渠道设置](../../../../apps/desktop/README.zh.md#local-desktop-settings)和[打包渠道策略](../../../../apps/desktop/packaging/README.zh.md)取代。已实现的选择器持久化用户的显式选择;发布者独立于渠道名称选择版本,切换渠道不允许降级。
|
||||
|
||||
### 自动安装设置
|
||||
|
||||
|
||||
+4
-4
@@ -6,11 +6,11 @@
|
||||
en: 9966b1c4fdb2585b
|
||||
zh: 4eb24c283facf38d
|
||||
/agent-note-defer-office-background-scheduling-until-a-consumer-needs-it/problem:
|
||||
en: bb725ab25ff39f86
|
||||
zh: d198514119b31a61
|
||||
en: a3e6453fd9d38eb3
|
||||
zh: accb5062ae04673e
|
||||
/agent-note-defer-office-background-scheduling-until-a-consumer-needs-it/proposal:
|
||||
en: e33db390dbbcc86c
|
||||
zh: 890df8891260103a
|
||||
en: b9b3e2dd1f8b4ff5
|
||||
zh: d751ef9cdff21de9
|
||||
/agent-note-defer-office-background-scheduling-until-a-consumer-needs-it/alternatives-considered:
|
||||
en: 597346036314c907
|
||||
zh: 91661d961623c7cd
|
||||
|
||||
+3
-3
@@ -6,15 +6,15 @@ English | [中文](2026-09-17-defer-office-background-scheduling.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The [conversion queue](../../../../packages/document/office-to-pdf/src/queue.ts) implements foreground promotion, demotion after reader cancellation, eviction of queued background work, a reserved foreground reader allowance, and separate background concurrency accounting. These mechanisms protect foreground conversion from speculative prewarming; ordinary conversion still needs the queue's general resource limits.
|
||||
The [conversion queue](../../../../packages/document/office-kit/src/queue.ts) implements foreground promotion, demotion after reader cancellation, eviction of queued background work, a reserved foreground reader allowance, and separate background concurrency accounting. These mechanisms protect foreground conversion from speculative prewarming; ordinary conversion still needs the queue's general resource limits.
|
||||
|
||||
The [Office preview](../../../../packages/client/ui-sidebar-documentpreview/src/client/office/index.ts) calls its cache without a priority argument, so the [Client cache](../../../../packages/client/ui-sidebar-documentpreview/src/client/office/cache.ts) forwards the default foreground priority to the Host. Repository searches for `officeToPdf`, `OfficeToPdfRequest`, `OfficeToPdfPriority`, and `maxBackgroundConversions` find no production background request. The [conversion smoke](../../../../packages/bundle/web-app/tests/document-conversion.e2e.ts) also submits foreground requests. Background requests appear in queue, Remote, and Client-cache tests. The exposed API and Client priority plumbing alone do not establish which scheduling policy prewarming requires. The open delivery prewarming PR #4064 already provides a concrete prewarming consumer, but its implementation calls the older `documentRender` API without priority. Removing scheduling must first be reconciled with that consumer; absence from master is insufficient evidence to proceed.
|
||||
The [Office preview](../../../../packages/client/ui-sidebar-documentpreview/src/client/office/index.ts) calls its cache without a priority argument, so the [Client cache](../../../../packages/client/ui-sidebar-documentpreview/src/client/office/cache.ts) forwards the default foreground priority to the Host. Repository searches for `officeKit`, `OfficeKitRequest`, `OfficeKitPriority`, and `maxBackgroundConversions` find no production background request. The [conversion smoke](../../../../packages/bundle/web-app/tests/document-conversion.e2e.ts) also submits foreground requests. Background requests appear in queue, Remote, and Client-cache tests. The exposed API and Client priority plumbing alone do not establish which scheduling policy prewarming requires. The open delivery prewarming PR #4064 already provides a concrete prewarming consumer, but its implementation calls the older `documentRender` API without priority. Removing scheduling must first be reconciled with that consumer; absence from master is insufficient evidence to proceed.
|
||||
|
||||
The [bounded conversion decision](../../implemented/architecture/2026-09-15-bounded-office-conversion.md) deliberately reserves capacity for foreground consumers. That remains the correct rule if speculation exists, but the current caller inventory does not justify maintaining the speculative request class and its lifecycle interactions.
|
||||
|
||||
## Proposal
|
||||
|
||||
Remove `OfficeToPdfPriority`, request `priority`, and configuration `maxBackgroundConversions`. Remove reader/job priority fields, promotion and demotion, speculative eviction, foreground reader reservation, and the queue's background counter. Remove Client-cache priority plumbing and update the Remote signature together. Admit the oldest queued job whose source reservation fits whenever conversion capacity is available; reject a new distinct job when the metadata queue is full. Continue joining an existing source job subject to the shared reader limit.
|
||||
Remove `OfficeKitPriority`, request `priority`, and configuration `maxBackgroundConversions`. Remove reader/job priority fields, promotion and demotion, speculative eviction, foreground reader reservation, and the queue's background counter. Remove Client-cache priority plumbing and update the Remote signature together. Admit the oldest queued job whose source reservation fits whenever conversion capacity is available; reject a new distinct job when the metadata queue is full. Continue joining an existing source job subject to the shared reader limit.
|
||||
|
||||
Keep the provider, reusable converters, deferred authorized reads, all remaining resource limits, source-version checks, content sharing, bounded result cache, independent reader cancellation, and teardown that waits for actual work to settle. This proposal changes admission policy, not conversion ownership or engine selection.
|
||||
|
||||
|
||||
+3
-3
@@ -6,15 +6,15 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
[转换队列](../../../../packages/document/office-to-pdf/src/queue.ts) 实现了前台优先级提升、读取方取消后的降级、排队后台任务的驱逐、预留前台读取名额,以及独立的后台并发计数。这些机制保护前台转换不受推测性预热影响;普通转换仍需要队列的通用资源限制。
|
||||
[转换队列](../../../../packages/document/office-kit/src/queue.ts) 实现了前台优先级提升、读取方取消后的降级、排队后台任务的驱逐、预留前台读取名额,以及独立的后台并发计数。这些机制保护前台转换不受推测性预热影响;普通转换仍需要队列的通用资源限制。
|
||||
|
||||
[Office 预览](../../../../packages/client/ui-sidebar-documentpreview/src/client/office/index.ts) 调用缓存时未传优先级,因此[Client 缓存](../../../../packages/client/ui-sidebar-documentpreview/src/client/office/cache.ts) 向 Host 转发默认前台优先级。在仓库中搜索 `officeToPdf`、`OfficeToPdfRequest`、`OfficeToPdfPriority` 和 `maxBackgroundConversions`,未发现生产后台请求。[转换冒烟测试](../../../../packages/bundle/web-app/tests/document-conversion.e2e.ts) 也提交前台请求。后台请求出现在队列、Remote 和 Client 缓存测试中。仅凭暴露 API 和 Client 优先级传递逻辑,无法确定预热需要哪种调度策略。开放的交付文件预热 PR #4064已经提供具体预热消费者,但其实现仍调用不含优先级的旧 `documentRender` API。删除调度前必须先与该消费者协调;尚未进入 master 不足以支持直接实施。
|
||||
[Office 预览](../../../../packages/client/ui-sidebar-documentpreview/src/client/office/index.ts) 调用缓存时未传优先级,因此[Client 缓存](../../../../packages/client/ui-sidebar-documentpreview/src/client/office/cache.ts) 向 Host 转发默认前台优先级。在仓库中搜索 `officeKit`、`OfficeKitRequest`、`OfficeKitPriority` 和 `maxBackgroundConversions`,未发现生产后台请求。[转换冒烟测试](../../../../packages/bundle/web-app/tests/document-conversion.e2e.ts) 也提交前台请求。后台请求出现在队列、Remote 和 Client 缓存测试中。仅凭暴露 API 和 Client 优先级传递逻辑,无法确定预热需要哪种调度策略。开放的交付文件预热 PR #4064已经提供具体预热消费者,但其实现仍调用不含优先级的旧 `documentRender` API。删除调度前必须先与该消费者协调;尚未进入 master 不足以支持直接实施。
|
||||
|
||||
[有界转换决策](../../implemented/architecture/2026-09-15-bounded-office-conversion.zh.md) 有意为前台消费者预留容量。存在推测性任务时,这条规则仍然正确,但当前调用方清单不足以支持维护推测性请求类别及其生命周期交互。
|
||||
|
||||
## 提案
|
||||
|
||||
移除 `OfficeToPdfPriority`、请求中的 `priority` 和配置 `maxBackgroundConversions`。删除读取方及任务的优先级字段、升降级、推测性任务驱逐、前台读取名额预留,以及队列的后台计数器。同时删除 Client 缓存的优先级传递逻辑并更新 Remote 签名。在转换容量可用时,接纳源预留容量足够的最早排队任务;元数据队列满时拒绝新的独立任务。仍允许在共享读取方数量限制内加入已有源任务。
|
||||
移除 `OfficeKitPriority`、请求中的 `priority` 和配置 `maxBackgroundConversions`。删除读取方及任务的优先级字段、升降级、推测性任务驱逐、前台读取名额预留,以及队列的后台计数器。同时删除 Client 缓存的优先级传递逻辑并更新 Remote 签名。在转换容量可用时,接纳源预留容量足够的最早排队任务;元数据队列满时拒绝新的独立任务。仍允许在共享读取方数量限制内加入已有源任务。
|
||||
|
||||
保留提供者、可复用转换器、延迟授权读取、其余所有资源限制、源版本校验、内容共享、有界结果缓存、读取方独立取消,以及等待实际工作结束的清理。这项提案改变接纳策略,不改变转换所有权或引擎选择。
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ description: Use when reviewing a pull request in the deepseek-harness repo —
|
||||
- [dsh-prose-standard](../dsh-prose-standard/SKILL.md): required coverage and editorial judgment for comments, docs, prompts, and visible strings.
|
||||
- [dsh-ci-test-reliability](../dsh-ci-test-reliability/SKILL.md): isolation and regression-proof rules for resource-owning, asynchronous, or flaky tests and fixtures.
|
||||
- [dsh-client-ui-ux](../dsh-client-ui-ux/SKILL.md): feedback-surface choice, overlay safety, platform window adaptation, and visual token discipline for product-user-visible GUI changes.
|
||||
- [dsh-error-handling](../dsh-error-handling/SKILL.md): failure classification, handling ownership, containment, state guarantees, and actionable reports.
|
||||
- [dsh-error-handling](../dsh-error-handling/SKILL.md): failure classification, error classes and codes, tool failures, handling ownership, containment, state guarantees, and actionable reports.
|
||||
- [dsh-session-projection](../dsh-session-projection/SKILL.md): projection ownership, state size, historical reads, and migrations.
|
||||
- [docs/testing.md](../../../docs/testing.md) and the [quality-gates reference](../../../docs/testing.md): required test tiers and gates.
|
||||
- [Agent Notes](../../notes/README.md): design rationale. Treat disagreement with an Agent Note as a design discussion, not an automatic veto.
|
||||
@@ -41,7 +41,7 @@ description: Use when reviewing a pull request in the deepseek-harness repo —
|
||||
|
||||
- **UI review coverage:** apply the [UI change workflow](../dsh-client-ui-ux/SKILL.md#required-review-loop) to the cumulative visible diff. Verify its [minimum checks](../dsh-client-ui-ux/SKILL.md#minimum-checks-for-affected-ui) for the changed properties and consumers. Missing required inspection remains a gap; missing optional reports does not. Do not demand full-audit records for ordinary changes. Recheck only new interactions or invalidated evidence. Existing tests, GUI GIFs and designer/product approval remain required under their original triggers.
|
||||
- **Intent and interface contracts:** trace both sides of every changed interface. Confirm the implementation matches the PR and any Agent Note, including errors, cancellation, ownership, and disposal.
|
||||
- **Error handling:** apply [dsh-error-handling](../dsh-error-handling/SKILL.md) to changed failure paths, including their state guarantees and final user-visible reports. Reject catch-and-continue unless the state owner establishes that subsequent work's preconditions hold, including after recovery.
|
||||
- **Error handling:** apply [dsh-error-handling](../dsh-error-handling/SKILL.md) to changed failure paths, including their state guarantees and final user-visible reports. Flag an expected tool failure that would reach `tool/result` without `error.name`/`error.code`. Reject catch-and-continue unless the state owner establishes that subsequent work's preconditions hold, including after recovery.
|
||||
- **Lifecycle and concurrency:** for async setup, callbacks, processes, or teardown, apply [defensive-patterns/general.md](../../../docs/defensive-patterns/general.md). Check races before publication, cancellation during awaits, independent error reporting, callback containment, ownership before reentry, complete detach cleanup, and quiescent disposal.
|
||||
- **Scope, ownership, and necessity:** first identify the required behavior behind each new distinction, field, adapter, state machine, option, defensive copy, and compatibility path. A requirement, check, or Agent Note introduced by the PR is a design choice to assess, not independent evidence of necessity. Compare existing source fields, discriminants, and their consumers before accepting another representation; distinguish deleting a concept from relocating its declaration. Moving an unnecessary concept closer to its owner is not a simplification. Challenge unrelated features and speculative generality against [the root rules](../../../AGENTS.md#conventions).
|
||||
- **Capability and consumer fit:** trace every current consumer, then flag consumer-specific behavior leaking into the interface under [the package rules](../../../packages/AGENTS.md). For required behavior, prefer an existing extension point near its producer or consumer when the local implementation adds little complexity, following [the ownership rule](../../../docs/defensive-patterns/general.md#a-business-capability-owns-itself); compare producer-owned declaration merging with changes to distant core or shared type declarations. Flag the inverse too: a new public method on a generic service (registry, session, agent) whose only caller is one internal consumer is an unnecessary API expansion — require a private capability closure handed to that consumer at construction instead.
|
||||
|
||||
@@ -29,7 +29,7 @@ Pre-stable internal APIs whose consumers are all updated in the same change need
|
||||
|
||||
## Maintenance
|
||||
|
||||
While the root version is unchanged, the current version directory describes the delta from vT to current `master`. When a later change touches a surface a guide there covers, update that guide in the same change; delete the guide when the break is reverted. After the version bump, guides in older version directories are frozen; a new break goes into the new version directory. Never write a guide spanning more than one release.
|
||||
While the root version is unchanged, the current version directory describes the delta from vT to current `master`. When a later change touches a surface a guide there covers, update that guide in the same change; delete the guide when the break is reverted. After the version bump, guides in older version directories are frozen; a new break goes into the new version directory. When a release lands while your pull request is open, move its guides to the new version directory: `Policy / Pre-Merge Check` runs `verify-upgrade-guides --base` on the merged tree and rejects a guide added outside the root version's directory unless the `release/dsh-<x.y.z>` branch of that guide's version line already contains it. Never write a guide spanning more than one release.
|
||||
|
||||
## Format
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: dsh-error-handling
|
||||
description: Apply error-handling principles when designing or reviewing failures, recovery, retries, background tasks, and user-visible error reports.
|
||||
description: Apply error-handling principles when designing or reviewing failures, error classes and codes, tool failures, recovery, retries, background tasks, and user-visible error reports.
|
||||
---
|
||||
|
||||
# DeepSeek Harness Error Handling
|
||||
@@ -11,6 +11,23 @@ Define failures, their owners, and what remains safe after failure.
|
||||
|
||||
Expected failures need declared types, stable discriminants, and handling-relevant details. Reuse package error conventions, preserve causes, and separate machine identity from display text. Never route handling by parsing messages. Unexpected defects such as `assertNever` may remain plain `Error`; containment must never turn them into success.
|
||||
|
||||
An error class that carries a machine code extends `HarnessError` from `dsh-llm`, directly or through its package's error class, never bare `Error`. Prefer one error class per owning package or domain over a class per failure; a separate class is justified when callers catch it by type or it carries its own structured fields, such as `ToolArgsError.violations`. `name` is the class name, except for cancellation (below), and `code` values are stable `UPPER_SNAKE_CASE` strings, usually prefixed by the domain and declared as a union or constants beside the class. A code names the failure class its consumers act on, not the call site; add a code rather than reuse one whose meaning differs. The [error class and code guide](references/error-codes.md) defines class choice, naming, the standard conditions, declaration, foreign-error conversion, and compatibility.
|
||||
|
||||
Convert foreign failures where the dependency is owned. Node errno, SDK, protocol, native, worker, and cross-realm errors become the owning package's error with the original as `cause`; a foreign numeric or vendor code belongs in the message, not in `code`. Preserve structure across layers: never re-wrap a `HarnessError` in a plain `Error`, store only its message for a later rethrow, or drop its `name` and `code` while redacting text. A process or worker boundary transports `name` and `code` and reconstructs the error.
|
||||
|
||||
## Tool failures
|
||||
|
||||
The tool registry keeps `error.name` and `error.code` on `tool/result` when the producer supplies them: a `HarnessError` thrown by the tool body, a pre-execute `deny` with `info`, the registry's own cancellation result (`AbortError` with `ABORTED` or `ABORTED_BEFORE_DISPATCH`), or a `tools/execute` wrapper result such as the timeout policy's `TOOL_TIMEOUT`. Any other thrown value, a guard denial, an approval rejection, and a post-execute block persist only text that retry policy, UI, and analytics cannot route on. Every expected failure reachable from a tool body therefore throws a `HarnessError`, normally the owning domain's subclass:
|
||||
|
||||
- Hand-checked argument constraints throw `ToolArgsError` (`INVALID_ARGS`) from `dsh-tools`.
|
||||
- Missing composition or caller context, such as an absent Agent, service, or open turn, throws the owning package's error with its own code.
|
||||
- Caller cancellation throws a `HarnessError` whose `name` is `AbortError` and whose code is the `dsh-tools` `TOOL_ABORTED` constant (`ABORTED`), matching the registry's cancellation result; never a bare `AbortError` or `signal.reason`.
|
||||
- Dependency failures follow the conversion rule above; filesystem errno maps to `FsError` codes such as `FS_NOT_FOUND`, `FS_PERMISSION_DENIED`, and `FS_IO_ERROR`.
|
||||
|
||||
A policy that denies a call in `tools/pre-execute` supplies `info` with its own `name` and `code`. The registry's built-in approval rejections and guard denials currently carry only a reason, so they remain unroutable until the registry gives them codes. A failure the model must not treat as success is an `isError` result; a successful domain outcome in a non-ideal state, such as a non-zero exit or no matches, stays a success value, and a success-shaped failure value is allowed only when the output schema documents it. Tests assert `error.info.name` and `error.info.code` for each expected failure path, not only the message.
|
||||
|
||||
Existing `name` and `code` values persist in Session logs and feed analytics. Renaming or reclassifying one is an externally perceptible change that needs an [upgrade guide](../dsh-create-upgrade-guide/SKILL.md).
|
||||
|
||||
## Catch, recover, or stop
|
||||
|
||||
For each failure, define handling and reporting owners, containment, and guarantees in the owning API documentation: valid invariants, absent/partial/committed/unknown effects, and cleanup responsibility. Background work retains these obligations.
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Error class and code guide
|
||||
|
||||
Use this guide when adding or reviewing an error class, an error code, or a failure path whose `name` and `code` reach a durable record such as `tool/result` `error`.
|
||||
|
||||
## Where `name` and `code` go
|
||||
|
||||
`HarnessError` carries `name` and `code` beside `message` and `cause`. The tool registry copies `name` and `code` of a thrown `HarnessError` into `ToolErrorInfo`, which the Session log persists as `tool/result` `error`; the model sees only the rendered `message`. Retry policy, UI cards, replay, and failure analytics route on `name` and `code`, so both are compatibility promises from the first release that records them.
|
||||
|
||||
| Field | Audience | Content |
|
||||
|---|---|---|
|
||||
| `name` | Code and analytics | The error class; groups every failure one domain owns |
|
||||
| `code` | Code and analytics | The failure class a consumer acts on |
|
||||
| `message` | Model and logs | English, actionable, names the operation and the safe next step |
|
||||
| `reason` (`ToolErrorInfo`) | User-facing projections | Optional raw detail kept out of model-facing content; only a policy decision that supplies `info` can set it |
|
||||
| `cause` | Debugging | The original foreign or lower-level error |
|
||||
|
||||
## Choose the class
|
||||
|
||||
1. Reuse the owning domain's existing class when the failure belongs to that domain: `FsError` for filesystem targets, `SearchError` for `glob`/`grep`, `LspError`, `WebError`, `SubagentError`, `TeamError`, `GoalError`, `WorkflowError`, `UserQuestionError`, `SessionQueryError`, the `Sandbox*Error` classes, and `CodeRunFailedError`.
|
||||
2. Use `ToolArgsError` (`INVALID_ARGS`) for argument constraints the parameter schema cannot express, such as an empty string, a range, or a cross-field rule.
|
||||
3. Declare a new class only for a new owning domain. Prefer one class with a code union over one class per failure; a separate subclass is justified when consumers must catch it by type or it carries its own structured fields, such as `ToolArgsError.violations`, and it then extends the domain class when one exists so the domain still matches with `instanceof`.
|
||||
4. Never declare a coded class that extends bare `Error`. An existing one is migrated by changing its base class and keeping every `name` and `code`.
|
||||
|
||||
## Name the class
|
||||
|
||||
- `name` is the PascalCase class name ending in `Error`, such as `JobError` or `SandboxEscalationError`.
|
||||
- Assign `this.name` a string literal in the constructor. `HarnessError` defaults `name` to `new.target.name`, but a bundler may rename a colliding class binding, and a renamed class would change persisted data.
|
||||
- Do not reuse another domain's class name, and do not encode a code in the name.
|
||||
- Cancellation is the one exception: a tool throws a `HarnessError` whose `name` is `AbortError` and whose code is the `dsh-tools` `TOOL_ABORTED` constant (`ABORTED`), so tool-thrown and registry-generated cancellations share one `name` and `code`.
|
||||
|
||||
## Format the code
|
||||
|
||||
- A code matches `^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$`: ASCII `UPPER_SNAKE_CASE`.
|
||||
- New codes start with the domain prefix shared by the class, followed by the condition: `<DOMAIN>_<CONDITION>`, such as `FS_NOT_FOUND`, `TEAM_TASK_STALE_REVISION`, or `MCP_CONNECTION_CLOSED`. A subject between the prefix and the condition is allowed when the domain has several subjects, as in `TEAM_TASK_*` and `TEAM_MEMBER_*`.
|
||||
- Existing unprefixed codes, such as `SubagentError`'s `NOT_RESUMABLE` or `LlmError`'s `TIMEOUT`, stay as they are; renaming them is a breaking change. New codes in those classes follow the class's existing style.
|
||||
- A code is a literal. Never build one from a template string, a status number, a path, an id, a tool name, or a foreign code.
|
||||
|
||||
## Name the condition
|
||||
|
||||
Pick the condition from what the consumer does next, not from where the failure happened. Reuse these standard conditions before inventing one:
|
||||
|
||||
| Condition | Meaning | Typical consumer action |
|
||||
|---|---|---|
|
||||
| `INVALID_<FIELD>` or `INVALID_ARGUMENT` | The caller's input violates a documented constraint | The model corrects the input |
|
||||
| `NOT_FOUND` | The named target does not exist | The model picks another target |
|
||||
| `ALREADY_EXISTS` | Creation conflicts with an existing target | The model reuses or renames |
|
||||
| `PERMISSION_DENIED` | The host or the platform refused access | Report; do not retry unchanged |
|
||||
| `SANDBOX_DENIED` | Sandbox policy refused the operation | Request escalation or stop |
|
||||
| `REJECTED` | A person or a review policy declined the request | Stop and explain; do not work around it |
|
||||
| `CANCELLED` / `ABORTED` | The user or the caller cancelled the operation | Stop without retrying |
|
||||
| `TIMEOUT` | A deadline elapsed and the outcome is known to be incomplete | Retry only when the operation is idempotent |
|
||||
| `UNAVAILABLE` | A required service, provider, or backend is not present or not reachable | Report the missing composition or retry later |
|
||||
| `UNSUPPORTED_<FEATURE>` | The request is valid but this implementation cannot serve it | Choose another option |
|
||||
| `LIMIT_REACHED` / `TOO_LARGE` | A configured count, size, or rate bound was hit | Reduce the request |
|
||||
| `STALE_<STATE>` | The caller acted on an outdated version | Re-read and retry |
|
||||
| `CONFLICT` | Concurrent or duplicate work holds the target | Wait or pick another target |
|
||||
| `CORRUPT` | Persisted state failed validation | Report; do not trust the state |
|
||||
| `<OPERATION>_FAILED` | A dependency failed in a way the owner cannot classify further | Report with the cause |
|
||||
|
||||
Use `<OPERATION>_FAILED` only after the specific conditions above do not fit. One code covers one consumer action: split a code when consumers must act differently, and merge candidates that every consumer handles alike.
|
||||
|
||||
## Declare the codes
|
||||
|
||||
A domain that owns all of its codes declares a closed list beside the class and types the constructor with it, as in this example for a job domain:
|
||||
|
||||
```ts
|
||||
/** Every machine-routable job failure. */
|
||||
export const JOB_ERROR_CODES = [
|
||||
'JOB_NOT_FOUND',
|
||||
'JOB_FOREIGN_SESSION',
|
||||
'JOB_LIMIT_REACHED',
|
||||
] as const
|
||||
|
||||
/** One job failure code. */
|
||||
export type JobErrorCode = typeof JOB_ERROR_CODES[number]
|
||||
|
||||
/** Typed job failure; `code` is one of {@link JOB_ERROR_CODES}. */
|
||||
export class JobError extends HarnessError {
|
||||
override readonly code: JobErrorCode
|
||||
|
||||
constructor(message: string, code: JobErrorCode, options?: ErrorOptions) {
|
||||
super(message, code, options)
|
||||
this.name = 'JobError'
|
||||
this.code = code
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A Service Definition whose providers may add codes, such as `WebError` or `LlmError`, keeps `code: string`, documents the shared codes with exported constants, and requires consumers to tolerate unknown codes. Document each code in the class JSDoc or the owning subsystem page with its meaning and the consumer action.
|
||||
|
||||
## Convert foreign failures
|
||||
|
||||
Convert at the package that owns the dependency, keep the original as `cause`, and put any foreign numeric or vendor code in `message`, never in `code`.
|
||||
|
||||
| Foreign failure | Converted error |
|
||||
|---|---|
|
||||
| `ENOENT`, `ENOTDIR` | `FsError` `FS_NOT_FOUND` |
|
||||
| `EACCES`, `EPERM` | `FsError` `FS_PERMISSION_DENIED` |
|
||||
| `EISDIR` where a file is required | `FsError` `FS_NOT_REGULAR_FILE` |
|
||||
| Other filesystem errno | `FsError` `FS_IO_ERROR` |
|
||||
| Process spawn `ENOENT` | The spawning domain's `*_NOT_FOUND`, such as a missing executable |
|
||||
| JSON-RPC, HTTP, or SDK status | The domain's condition for that status, with the status in `message` |
|
||||
| Native or Win32 API failure | The calling domain's `<OPERATION>_FAILED`, with the API name and code in `message` |
|
||||
| Worker or process boundary | Transport `name` and `code`, then reconstruct the same class |
|
||||
|
||||
When an error passes through several layers, the lowest layer that understands the failure classifies it and the layers above preserve it. Wrap a `HarnessError` only to add context, and keep its `name` and `code` on the wrapper.
|
||||
|
||||
## Change existing codes
|
||||
|
||||
Adding a code or a class is compatible. Renaming, splitting, merging, or reclassifying an existing `name` or `code` changes persisted data and analytics: record it in an [upgrade guide](../../dsh-create-upgrade-guide/SKILL.md) and list the old and new values. Do not reuse a retired code for a different meaning.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Every expected failure reachable from the changed path throws a `HarnessError` subclass.
|
||||
- [ ] The class extends `HarnessError` or the domain class and assigns a literal `name`.
|
||||
- [ ] Each code is a literal from the declared list and names a consumer action.
|
||||
- [ ] Foreign errors are converted at their owner with `cause`, and no layer drops `name` or `code`.
|
||||
- [ ] `message` is actionable for the model and carries no sensitive internal detail.
|
||||
- [ ] Tests assert `name` and `code` for each expected failure.
|
||||
- [ ] A changed existing `name` or `code` has an upgrade guide.
|
||||
@@ -17,7 +17,7 @@ Keep recoverable session facts in Projection and expose them through their ownin
|
||||
|
||||
3. State recovery through `init` and `apply` is synchronous and deterministic. Do not perform I/O or trigger Cordis hooks, models, or tools. Runtime hooks own those effects. Replay must not repeat them. Return the same state reference for unrelated events. Keep views synchronous and preserve their references when visible values do not change.
|
||||
|
||||
4. Persist JSON state that its schema accepts. Follow the README's state-version and checkpoint-watermark rules. Restoring a checkpoint and replaying its suffix must match full replay. Change the version when state fields or recovery semantics change.
|
||||
4. Persist JSON state that its schema accepts. Follow the README's state-version and checkpoint-watermark rules. Restoring a checkpoint and replaying its suffix must match full replay. Change the version when state fields or recovery semantics change, except for optional additions that preserve historical meaning when absent and have tested old-checkpoint suffix equivalence with full replay. Schema acceptance alone is insufficient.
|
||||
|
||||
5. Prefer fixed-size or sublinear state. Growth is acceptable only when slow enough to remain negligible. Necessary linear state must have a current use and explicit memory, replay, and per-event costs. Do not retain history-sized indexes, including event sequence collections, by habit. Do not discard required data or change behavior to reduce size. Start with a complete implementation; optimize for a concrete need.
|
||||
|
||||
|
||||
+3
-1
@@ -4,6 +4,8 @@ Run jobs on Windows runners (`windows-*` labels) under native `pwsh`. Native Win
|
||||
|
||||
The manual [Windows packaging workflow](workflows/windows-package.yml) uses the dedicated self-hosted Windows x64 label `dsh-win-package-trial` under native `pwsh`; preserve this routing separately from the automatic CI lanes.
|
||||
|
||||
The merge checks run from three workflows named `Policy`, which run on `pull_request_target` and cover pull requests into master or a `release/dsh-x.y.z` branch, so their definitions come from the base branch, and call the `deepseek-harness-check` action directly: `pr-check-on-head-branch.yml` on pull-request updates gives `Policy / Master Change` and `Policy / Pre-Merge Check`, `pr-check-after-approve.yml` on pull-request updates and submitted or dismissed reviews gives `Policy / Approve Check`, which also publishes the review verdict as the check run `Policy / Reviewer Check`, and `pr-check-on-base-branch.yml` on pushes to master or a release branch re-runs the head-branch checks as `Policy / Recheck`. Branch rules require those check names, so keep the job names and the workflow name. Each job reads its mode from a repository variable: `MERGE_POLICY_MASTER_DIFF`, `MERGE_POLICY_PRE_MERGE`, and `MERGE_POLICY_HEAD_DIFF`, each `off` (skip), `report` (run without failing the job), or `block` (fail the job); a missing value or an unknown one counts as `report`. `MERGE_POLICY_HEAD_DIFF` decides whether `Policy / Approve Check` runs and fails; while it runs, `Policy / Reviewer Check` is published whatever that mode says, with `success` when every file that needs review has one and `action_required` with the reviewers to ask otherwise. `Policy / Pre-Merge Check` merges the commit `Policy / Master Change` reports, so it runs only after that job and stays off while `MERGE_POLICY_MASTER_DIFF` is `off`; a merge it cannot do and a build that fails both follow its own mode, so `report` warns and skips the build while `block` fails the job. `Policy / Pre-Merge Check` runs `scripts/pre-merge-check.ts` under plain Node and knows nothing else about the merged tree: that script checks the released manifests against the root version, installs, and builds the Host and Client contracts, so change it in one pull request together with the build it orchestrates.
|
||||
The merge checks run from three workflows named `Policy`, which run on `pull_request_target` and cover pull requests into master or a `release/dsh-x.y.z` branch, so their definitions come from the base branch, and call the `deepseek-harness-check` action directly: `pr-check-on-head-branch.yml` on pull-request updates gives `Policy / Master Change` and `Policy / Pre-Merge Check`, `pr-check-after-approve.yml` on pull-request updates and submitted or dismissed reviews gives `Policy / Approve Check`, which also publishes the review verdict as the check run `Policy / Reviewer Check`, and `pr-check-on-base-branch.yml` on pushes to master or a release branch re-runs the head-branch checks as `Policy / Recheck`. Branch rules require those check names, so keep the job names and the workflow name. Each job reads its mode from a repository variable: `MERGE_POLICY_MASTER_DIFF`, `MERGE_POLICY_PRE_MERGE`, and `MERGE_POLICY_HEAD_DIFF`, each `off` (skip), `report` (run without failing the job), or `block` (fail the job); a missing value or an unknown one counts as `report`. `MERGE_POLICY_HEAD_DIFF` decides whether `Policy / Approve Check` runs and fails; while it runs, `Policy / Reviewer Check` is published whatever that mode says, with `success` when every file that needs review has one and `action_required` with the reviewers to ask otherwise. `Policy / Pre-Merge Check` merges the commit `Policy / Master Change` reports, so it runs only after that job and stays off while `MERGE_POLICY_MASTER_DIFF` is `off`; a merge it cannot do and a build that fails both follow its own mode, so `report` warns and skips the build while `block` fails the job. `Policy / Pre-Merge Check` runs `scripts/pre-merge-check.ts` under plain Node with the merged base commit in `BASE_SHA` and knows nothing else about the merged tree: that script checks the released manifests against the root version, installs, rejects upgrade guides added outside the root version's directory, and builds the Host and Client contracts, so change it in one pull request together with the build it orchestrates.
|
||||
|
||||
The manual Desktop publication workflow selects the original successful `package` job attempt independently of `upload-artifact` archival status, then routes each retained build through `DESKTOP_PUBLISH_RUNNERS` to its original machine's unique label. Keep Windows run steps on native PowerShell and Mac run steps on the default POSIX shell; shared entrypoints are Node scripts. Test and production jobs use separate `desktop-test` and `desktop-production` approval Environments after a credential-free preparation job.
|
||||
|
||||
`Publish Desktop · Rollout & Website` reuses the same original-machine routing and approval Environments. Its preparation reads retained metadata and initial publication evidence without revalidating package binaries; it does not create release tags. Cache-only recovery uses the existing website approval and upload journal.
|
||||
@@ -9,6 +9,11 @@ on:
|
||||
description: '打包运行 ID:填写成功的 Package Desktop run ID;发布环境和 release ID 沿用原包'
|
||||
type: string
|
||||
required: true
|
||||
staging_percentage:
|
||||
description: '更新灰度比例:0–100 整数,默认 100;留空沿用包内值,作用于原包所属渠道'
|
||||
type: string
|
||||
required: false
|
||||
default: '100'
|
||||
upload_website:
|
||||
description: '同时上传官网安装包并刷新缓存(仅 production);取消后仅执行版本与自动更新发布'
|
||||
type: boolean
|
||||
@@ -66,6 +71,7 @@ jobs:
|
||||
website-destination: ${{ steps.check.outputs.website-destination }}
|
||||
label: ${{ steps.check.outputs.label }}
|
||||
env:
|
||||
DESKTOP_STAGING_PERCENTAGE: ${{ inputs.staging_percentage }}
|
||||
DESKTOP_PACKAGE_SELECTION: ${{ needs.route.outputs.selection }}
|
||||
DESKTOP_UPLOAD_WEBSITE: ${{ inputs.upload_website }}
|
||||
DOWNLOAD_PROD_EO_ZONE_ID: ${{ vars.DOWNLOAD_PROD_EO_ZONE_ID }}
|
||||
@@ -111,6 +117,7 @@ jobs:
|
||||
cancel-in-progress: false
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
DESKTOP_STAGING_PERCENTAGE: ${{ inputs.staging_percentage }}
|
||||
DESKTOP_APPROVED_PACKAGE: ${{ needs.prepare.outputs.approval }}
|
||||
DESKTOP_PUBLISH_ROUTE: ${{ needs.prepare.outputs.route }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
name: Publish Desktop · Rollout & Website
|
||||
|
||||
run-name: Rollout Desktop package ${{ inputs.package_run_id }} / attempt ${{ inputs.package_run_attempt }} · ${{ inputs.rollout_percentage && format('{0}%', inputs.rollout_percentage) || 'website only' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package_run_id:
|
||||
description: '已经完成首次 COS 发布的 Package Desktop run ID'
|
||||
type: string
|
||||
required: true
|
||||
rollout_percentage:
|
||||
description: '目标放量比例:0–100 整数,只允许保持或增加;留空只发布官网'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
upload_website:
|
||||
description: '上传已发布版本的官网安装包并刷新缓存(仅 production,不要求放量达到 100%)'
|
||||
type: boolean
|
||||
default: false
|
||||
required: true
|
||||
package_run_attempt:
|
||||
description: '已发布的原打包 attempt;默认 1,请核对原 run'
|
||||
type: string
|
||||
required: true
|
||||
default: '1'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
route:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
selection: ${{ steps.route.outputs.selection }}
|
||||
runner: ${{ steps.route.outputs.runner }}
|
||||
steps:
|
||||
- name: Require master for publication
|
||||
run: node -e "if (process.env.GITHUB_REF !== 'refs/heads/master') throw new Error('Desktop publication must be dispatched from master')"
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Reject debugging packages before dependency installation
|
||||
id: preflight
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: node apps/desktop/scripts/preflight-desktop-publish.mjs
|
||||
- uses: pnpm/[email protected]
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '24'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- name: Resolve original package and machine
|
||||
id: route
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
DESKTOP_PUBLISH_RUNNERS: ${{ vars.DESKTOP_PUBLISH_RUNNERS }}
|
||||
DESKTOP_PACKAGE_ATTEMPT: ${{ steps.preflight.outputs.attempt }}
|
||||
run: node --import tsx apps/desktop/scripts/resolve-desktop-publish-ci.ts
|
||||
|
||||
prepare:
|
||||
needs: route
|
||||
runs-on: [self-hosted, '${{ needs.route.outputs.runner }}']
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
route: ${{ steps.local.outputs.route }}
|
||||
preset: ${{ steps.local.outputs.preset }}
|
||||
approval: ${{ steps.check.outputs.approval }}
|
||||
website-approval: ${{ steps.check.outputs.website-approval }}
|
||||
website-destination: ${{ steps.check.outputs.website-destination }}
|
||||
label: ${{ steps.check.outputs.label }}
|
||||
env:
|
||||
DESKTOP_ROLLOUT_PERCENTAGE: ${{ inputs.rollout_percentage }}
|
||||
DESKTOP_PACKAGE_SELECTION: ${{ needs.route.outputs.selection }}
|
||||
DESKTOP_UPLOAD_WEBSITE: ${{ inputs.upload_website }}
|
||||
DOWNLOAD_PROD_EO_ZONE_ID: ${{ vars.DOWNLOAD_PROD_EO_ZONE_ID }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
persist-credentials: false
|
||||
clean: false
|
||||
path: desktop-publisher
|
||||
- uses: pnpm/[email protected]
|
||||
with:
|
||||
package_json_file: desktop-publisher/package.json
|
||||
dest: ${{ runner.temp }}/publish-pnpm-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Check local publication configuration before dependency installation
|
||||
working-directory: desktop-publisher
|
||||
run: node apps/desktop/scripts/preflight-desktop-publish-local.mjs
|
||||
- run: pnpm install --frozen-lockfile
|
||||
working-directory: desktop-publisher
|
||||
- name: Resolve retained package and approval environment
|
||||
id: local
|
||||
working-directory: desktop-publisher
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: node --import tsx apps/desktop/scripts/publish-desktop-rollout-ci.ts route
|
||||
- name: Verify initial publication and metadata without upload credentials
|
||||
id: check
|
||||
working-directory: desktop-publisher
|
||||
env:
|
||||
DESKTOP_PUBLISH_ROUTE: ${{ steps.local.outputs.route }}
|
||||
run: node --import tsx apps/desktop/scripts/publish-desktop-rollout-ci.ts check
|
||||
|
||||
publish:
|
||||
name: Approve ${{ needs.prepare.outputs.label }}
|
||||
needs: [route, prepare]
|
||||
runs-on: [self-hosted, '${{ needs.route.outputs.runner }}']
|
||||
environment: ${{ needs.prepare.outputs.preset == 'production' && 'desktop-production' || 'desktop-test' }}
|
||||
concurrency:
|
||||
group: desktop-publish-${{ needs.route.outputs.runner }}-${{ needs.prepare.outputs.preset }}
|
||||
cancel-in-progress: false
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
DESKTOP_ROLLOUT_PERCENTAGE: ${{ inputs.rollout_percentage }}
|
||||
DESKTOP_ROLLOUT_APPROVAL: ${{ needs.prepare.outputs.approval }}
|
||||
DESKTOP_UPLOAD_WEBSITE: ${{ inputs.upload_website }}
|
||||
DOWNLOAD_PROD_EO_ZONE_ID: ${{ vars.DOWNLOAD_PROD_EO_ZONE_ID }}
|
||||
DESKTOP_PUBLISH_ROUTE: ${{ needs.prepare.outputs.route }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
persist-credentials: false
|
||||
clean: false
|
||||
path: desktop-publisher
|
||||
- uses: pnpm/[email protected]
|
||||
with:
|
||||
package_json_file: desktop-publisher/package.json
|
||||
dest: ${{ runner.temp }}/publish-pnpm-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Check local publication configuration before dependency installation
|
||||
working-directory: desktop-publisher
|
||||
run: node apps/desktop/scripts/preflight-desktop-publish-local.mjs
|
||||
- run: pnpm install --frozen-lockfile
|
||||
working-directory: desktop-publisher
|
||||
- name: Verify initial publication and metadata without upload credentials
|
||||
id: check
|
||||
working-directory: desktop-publisher
|
||||
run: node --import tsx apps/desktop/scripts/publish-desktop-rollout-ci.ts check
|
||||
- name: Apply rollout to the published feed
|
||||
id: publish
|
||||
working-directory: desktop-publisher
|
||||
run: node --import tsx apps/desktop/scripts/publish-desktop-rollout-ci.ts apply
|
||||
- name: Retain publication audit
|
||||
continue-on-error: true
|
||||
if: always() && steps.publish.outputs.record != ''
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: desktop-publication-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.publish.outputs.record }}/
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
upload-website-installer:
|
||||
needs: [route, prepare, publish]
|
||||
if: inputs.upload_website && needs.prepare.outputs.preset != 'test'
|
||||
runs-on: [self-hosted, '${{ needs.route.outputs.runner }}']
|
||||
environment: desktop-production
|
||||
timeout-minutes: 60
|
||||
concurrency:
|
||||
group: desktop-website-upload-${{ needs.prepare.outputs.website-destination }}
|
||||
cancel-in-progress: false
|
||||
env:
|
||||
DESKTOP_PUBLISH_ROUTE: ${{ needs.prepare.outputs.route }}
|
||||
DESKTOP_WEBSITE_APPROVAL: ${{ needs.prepare.outputs.website-approval }}
|
||||
DESKTOP_ROLLOUT_APPROVAL: ${{ needs.prepare.outputs.approval }}
|
||||
DESKTOP_ROLLOUT_PERCENTAGE: ${{ inputs.rollout_percentage }}
|
||||
DESKTOP_UPLOAD_WEBSITE: ${{ inputs.upload_website }}
|
||||
DOWNLOAD_PROD_EO_ZONE_ID: ${{ vars.DOWNLOAD_PROD_EO_ZONE_ID }}
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
persist-credentials: false
|
||||
clean: false
|
||||
path: website-publisher
|
||||
- uses: pnpm/[email protected]
|
||||
with:
|
||||
package_json_file: website-publisher/package.json
|
||||
dest: ${{ runner.temp }}/website-pnpm-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Check local publication configuration
|
||||
working-directory: website-publisher
|
||||
run: node apps/desktop/scripts/preflight-desktop-publish-local.mjs
|
||||
- run: pnpm install --frozen-lockfile
|
||||
working-directory: website-publisher
|
||||
- name: Upload website installer
|
||||
id: website
|
||||
working-directory: website-publisher
|
||||
run: node --import tsx apps/desktop/scripts/publish-desktop-rollout-ci.ts website-upload
|
||||
- name: Archive website feed verification
|
||||
if: always() && steps.website.outputs.rollout-record != ''
|
||||
continue-on-error: true
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: website-feed-verification-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.website.outputs.rollout-record }}/
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
- name: Archive website publication diagnostics
|
||||
if: always() && steps.website.outputs.record != ''
|
||||
continue-on-error: true
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: website-upload-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.website.outputs.record }}/
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
|
||||
purge-website-installer:
|
||||
needs: [route, prepare, upload-website-installer]
|
||||
if: inputs.upload_website && needs.prepare.outputs.preset != 'test'
|
||||
runs-on: [self-hosted, '${{ needs.route.outputs.runner }}']
|
||||
environment: desktop-production
|
||||
timeout-minutes: 60
|
||||
concurrency:
|
||||
group: desktop-website-purge-${{ needs.prepare.outputs.website-destination }}
|
||||
cancel-in-progress: false
|
||||
env:
|
||||
DESKTOP_PUBLISH_ROUTE: ${{ needs.prepare.outputs.route }}
|
||||
DESKTOP_WEBSITE_APPROVAL: ${{ needs.prepare.outputs.website-approval }}
|
||||
DESKTOP_UPLOAD_WEBSITE: ${{ inputs.upload_website }}
|
||||
DOWNLOAD_PROD_EO_ZONE_ID: ${{ vars.DOWNLOAD_PROD_EO_ZONE_ID }}
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
persist-credentials: false
|
||||
clean: false
|
||||
path: website-publisher
|
||||
- uses: pnpm/[email protected]
|
||||
with:
|
||||
package_json_file: website-publisher/package.json
|
||||
dest: ${{ runner.temp }}/website-pnpm-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Check local publication configuration
|
||||
working-directory: website-publisher
|
||||
run: node apps/desktop/scripts/preflight-desktop-publish-local.mjs
|
||||
- run: pnpm install --frozen-lockfile
|
||||
working-directory: website-publisher
|
||||
- name: Recover and observe website cache purge
|
||||
id: website
|
||||
working-directory: website-publisher
|
||||
run: node --import tsx apps/desktop/scripts/publish-desktop-rollout-ci.ts website-purge
|
||||
- name: Archive website publication diagnostics
|
||||
if: always() && steps.website.outputs.record != ''
|
||||
continue-on-error: true
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: website-purge-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.website.outputs.record }}/
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
@@ -15,15 +15,15 @@ on:
|
||||
default: test
|
||||
required: true
|
||||
update_channel:
|
||||
description: '升级通道:nightly 或 stable(须为稳定版本);默认通道由此打包输入确定'
|
||||
description: '升级通道:人工选择 nightly 或 stable,与版本后缀无关;默认通道由此打包输入确定'
|
||||
type: choice
|
||||
options: [nightly, stable]
|
||||
default: nightly
|
||||
required: true
|
||||
version_mode:
|
||||
description: '版本模式:default 为 test 自动编号、production 跟随产品;product 保留原版本,auto 自动编号,manual 手填'
|
||||
description: '版本模式:default 按环境自动选择(test 取 build-number,production 取 follow-dsh);follow-dsh 跟随 dsh 包版本号;build-number 自动追加构建编号;manual 手填'
|
||||
type: choice
|
||||
options: [default, product, auto, manual]
|
||||
options: [default, follow-dsh, build-number, manual]
|
||||
default: default
|
||||
required: true
|
||||
build_version:
|
||||
@@ -75,7 +75,8 @@ jobs:
|
||||
PACKAGE_MACOS_ZIP_COMPRESSION_LEVEL: ${{ inputs.compression_level }}
|
||||
PACKAGE_SKIP_NOTARIZATION: ${{ inputs.skip_notarization }}
|
||||
PACKAGE_SOURCE_REF: ${{ inputs.source_ref }}
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode }}
|
||||
# TODO: Remove this legacy source_ref mode mapping on 2027-01-04 with the parser aliases.
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode == 'follow-dsh' && 'product' || inputs.version_mode == 'build-number' && 'auto' || inputs.version_mode }}
|
||||
DSH_DESKTOP_UPDATE_CHANNEL: ${{ inputs.update_channel }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
DSH_PACKAGING_PRESET: ${{ inputs.preset }}
|
||||
@@ -187,6 +188,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
DESKTOP_RETAINED_DIRECTORY: ${{ needs.package.outputs.directory }}
|
||||
DESKTOP_ARCHIVE_NAME: macos-x64-${{ needs.package.outputs.mode }}-${{ needs.package.outputs.version }}-${{ github.run_id }}-${{ needs.package.outputs.attempt }}.zip
|
||||
DESKTOP_PACKAGE_ATTEMPT: ${{ needs.package.outputs.attempt }}
|
||||
DESKTOP_PACKAGE_RUNNER: ${{ needs.package.outputs.runner-name }}
|
||||
steps:
|
||||
@@ -209,17 +211,17 @@ jobs:
|
||||
echo "PNPM_CONFIG_STORE_DIR=$DSH_DESKTOP_PACKAGE_CACHE_DIR/workspace-pnpm-store" >> "$GITHUB_ENV"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
working-directory: artifact-archiver
|
||||
- name: Verify original retained build before GitHub archival
|
||||
- name: Archive verified retained build
|
||||
id: archive
|
||||
working-directory: artifact-archiver
|
||||
run: node --import tsx apps/desktop/scripts/archive-desktop-ci.ts
|
||||
- name: Retain installers for the selected mode
|
||||
if: success()
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: macos-x64-${{ needs.package.outputs.mode }}-${{ needs.package.outputs.version }}-${{ github.run_id }}-${{ needs.package.outputs.attempt }}
|
||||
include-hidden-files: true
|
||||
path: ${{ needs.package.outputs.directory }}/*
|
||||
compression-level: 0
|
||||
name: ${{ env.DESKTOP_ARCHIVE_NAME }}
|
||||
path: ${{ steps.archive.outputs.path }}
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
overwrite: true
|
||||
@@ -15,15 +15,15 @@ on:
|
||||
default: test
|
||||
required: true
|
||||
update_channel:
|
||||
description: '升级通道:nightly 或 stable(须为稳定版本);默认通道由此打包输入确定'
|
||||
description: '升级通道:人工选择 nightly 或 stable,与版本后缀无关;默认通道由此打包输入确定'
|
||||
type: choice
|
||||
options: [nightly, stable]
|
||||
default: nightly
|
||||
required: true
|
||||
version_mode:
|
||||
description: '版本模式:default 为 test 自动编号、production 跟随产品;product 保留原版本,auto 自动编号,manual 手填'
|
||||
description: '版本模式:default 按环境自动选择(test 取 build-number,production 取 follow-dsh);follow-dsh 跟随 dsh 包版本号;build-number 自动追加构建编号;manual 手填'
|
||||
type: choice
|
||||
options: [default, product, auto, manual]
|
||||
options: [default, follow-dsh, build-number, manual]
|
||||
default: default
|
||||
required: true
|
||||
build_version:
|
||||
@@ -75,7 +75,8 @@ jobs:
|
||||
PACKAGE_MACOS_ZIP_COMPRESSION_LEVEL: ${{ inputs.compression_level }}
|
||||
PACKAGE_SKIP_NOTARIZATION: ${{ inputs.skip_notarization }}
|
||||
PACKAGE_SOURCE_REF: ${{ inputs.source_ref }}
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode }}
|
||||
# TODO: Remove this legacy source_ref mode mapping on 2027-01-04 with the parser aliases.
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode == 'follow-dsh' && 'product' || inputs.version_mode == 'build-number' && 'auto' || inputs.version_mode }}
|
||||
DSH_DESKTOP_UPDATE_CHANNEL: ${{ inputs.update_channel }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
DSH_PACKAGING_PRESET: ${{ inputs.preset }}
|
||||
@@ -184,6 +185,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
DESKTOP_RETAINED_DIRECTORY: ${{ needs.package.outputs.directory }}
|
||||
DESKTOP_ARCHIVE_NAME: macos-arm64-${{ needs.package.outputs.mode }}-${{ needs.package.outputs.version }}-${{ github.run_id }}-${{ needs.package.outputs.attempt }}.zip
|
||||
DESKTOP_PACKAGE_ATTEMPT: ${{ needs.package.outputs.attempt }}
|
||||
DESKTOP_PACKAGE_RUNNER: ${{ needs.package.outputs.runner-name }}
|
||||
steps:
|
||||
@@ -206,17 +208,17 @@ jobs:
|
||||
echo "PNPM_CONFIG_STORE_DIR=$DSH_DESKTOP_PACKAGE_CACHE_DIR/workspace-pnpm-store" >> "$GITHUB_ENV"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
working-directory: artifact-archiver
|
||||
- name: Verify original retained build before GitHub archival
|
||||
- name: Archive verified retained build
|
||||
id: archive
|
||||
working-directory: artifact-archiver
|
||||
run: node --import tsx apps/desktop/scripts/archive-desktop-ci.ts
|
||||
- name: Retain installers for the selected mode
|
||||
if: success()
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: macos-arm64-${{ needs.package.outputs.mode }}-${{ needs.package.outputs.version }}-${{ github.run_id }}-${{ needs.package.outputs.attempt }}
|
||||
include-hidden-files: true
|
||||
path: ${{ needs.package.outputs.directory }}/*
|
||||
compression-level: 0
|
||||
name: ${{ env.DESKTOP_ARCHIVE_NAME }}
|
||||
path: ${{ steps.archive.outputs.path }}
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
overwrite: true
|
||||
@@ -108,6 +108,7 @@ jobs:
|
||||
if: steps.merge.outputs.merged == 'true'
|
||||
env:
|
||||
CHECK_MODE: ${{ vars.MERGE_POLICY_PRE_MERGE }}
|
||||
BASE_SHA: ${{ needs.master.outputs.base_sha }}
|
||||
run: |
|
||||
# The check names one file and knows nothing else about how the merged tree builds itself.
|
||||
if [ ! -f scripts/pre-merge-check.ts ]; then
|
||||
|
||||
@@ -15,15 +15,15 @@ on:
|
||||
default: test
|
||||
required: true
|
||||
update_channel:
|
||||
description: '升级通道:nightly 或 stable(须为稳定版本);默认通道由此打包输入确定'
|
||||
description: '升级通道:人工选择 nightly 或 stable,与版本后缀无关;默认通道由此打包输入确定'
|
||||
type: choice
|
||||
options: [nightly, stable]
|
||||
default: nightly
|
||||
required: true
|
||||
version_mode:
|
||||
description: '版本模式:default 为 test 自动编号、production 跟随产品;product 保留原版本,auto 自动编号,manual 手填'
|
||||
description: '版本模式:default 按环境自动选择(test 取 build-number,production 取 follow-dsh);follow-dsh 跟随 dsh 包版本号;build-number 自动追加构建编号;manual 手填'
|
||||
type: choice
|
||||
options: [default, product, auto, manual]
|
||||
options: [default, follow-dsh, build-number, manual]
|
||||
default: default
|
||||
required: true
|
||||
build_version:
|
||||
@@ -77,7 +77,8 @@ jobs:
|
||||
shell: pwsh
|
||||
env:
|
||||
PACKAGE_SOURCE_REF: ${{ inputs.source_ref }}
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode }}
|
||||
# TODO: Remove this legacy source_ref mode mapping on 2027-01-04 with the parser aliases.
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode == 'follow-dsh' && 'product' || inputs.version_mode == 'build-number' && 'auto' || inputs.version_mode }}
|
||||
DSH_DESKTOP_UPDATE_CHANNEL: ${{ inputs.update_channel }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
DSH_PACKAGING_PRESET: ${{ inputs.preset }}
|
||||
@@ -220,6 +221,7 @@ jobs:
|
||||
env:
|
||||
PACKAGE_CACHE_NAMESPACE: ${{ inputs.cache_namespace }}
|
||||
DESKTOP_RETAINED_DIRECTORY: ${{ needs.package.outputs.directory }}
|
||||
DESKTOP_ARCHIVE_NAME: windows-x64-${{ inputs.signing }}-${{ needs.package.outputs.version }}-${{ github.run_id }}-${{ needs.package.outputs.attempt }}.zip
|
||||
DESKTOP_PACKAGE_ATTEMPT: ${{ needs.package.outputs.attempt }}
|
||||
DESKTOP_PACKAGE_RUNNER: ${{ needs.package.outputs.runner-name }}
|
||||
steps:
|
||||
@@ -251,28 +253,17 @@ jobs:
|
||||
"PNPM_CONFIG_STORE_DIR=$storeRoot" >> $env:GITHUB_ENV
|
||||
- run: pnpm install --frozen-lockfile
|
||||
working-directory: artifact-archiver
|
||||
- name: Verify original retained build before GitHub archival
|
||||
- name: Archive verified retained build
|
||||
id: archive
|
||||
working-directory: artifact-archiver
|
||||
run: node --import tsx apps/desktop/scripts/archive-desktop-ci.ts
|
||||
- name: Retain verified unsigned installer
|
||||
if: success() && inputs.signing == 'unsigned'
|
||||
- name: Retain installers for the selected mode
|
||||
if: success()
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: windows-x64-unsigned-${{ needs.package.outputs.version }}-${{ github.run_id }}-${{ needs.package.outputs.attempt }}
|
||||
compression-level: 0
|
||||
include-hidden-files: true
|
||||
path: ${{ needs.package.outputs.directory }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
overwrite: true
|
||||
- name: Retain verified signed installer
|
||||
if: success() && inputs.signing == 'signed'
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: windows-x64-signed-${{ needs.package.outputs.version }}-${{ github.run_id }}-${{ needs.package.outputs.attempt }}
|
||||
compression-level: 0
|
||||
include-hidden-files: true
|
||||
path: ${{ needs.package.outputs.directory }}/*
|
||||
name: ${{ env.DESKTOP_ARCHIVE_NAME }}
|
||||
path: ${{ steps.archive.outputs.path }}
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
overwrite: true
|
||||
@@ -37,6 +37,7 @@ python/**/__pycache__/
|
||||
python/**/.pytest_cache/
|
||||
packages/**/__pycache__/
|
||||
apps/web/dist/
|
||||
apps/cli/docs/
|
||||
.artifacts/
|
||||
.dsh-build/
|
||||
.dsh-report/
|
||||
|
||||
@@ -148,7 +148,7 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and
|
||||
- **No new assertions to `unknown`** (`as unknown` or `<unknown>`). Preserve or reduce the exact legacy baseline; use typed values or validation for replacements ([rule](.agents/notes/implemented/process/2026-09-19-no-unknown-casts.md)).
|
||||
- **Source plane vs artifact plane, never mixed.** Static gates and tests resolve workspace imports through tsconfig `paths` to `src` and pass on a clean tree; gates consuming built `lib/` declare that dependency ([layout](docs/development.md#typescript-project-layout)).
|
||||
- **Keep compiler faces explicit.** A package with both Host and Client programs exposes face-specific leaf configs and a solution-only root; repo-wide programs seed a face config, never the root solution ([layout](docs/development.md#typescript-project-layout)).
|
||||
- **Error handling:** use typed, structured failures and localized, actionable reports. Define handling ownership, containment, and failure guarantees; stop dependent work and visibly report untrusted state or unknown outcomes. Follow [dsh-error-handling](.agents/skills/dsh-error-handling/SKILL.md).
|
||||
- **Error handling:** use typed, structured failures and localized, actionable reports. Coded errors and expected tool failures use `HarnessError`. Define handling ownership, containment, and failure guarantees; stop dependent work and visibly report untrusted state or unknown outcomes. Follow [dsh-error-handling](.agents/skills/dsh-error-handling/SKILL.md).
|
||||
- **An empty `catch` names the error** and why; keep its `try` to one statement.
|
||||
- **Keep comments local.** Do not restate code, expand unrelated comments, or explain distant behavior without local need ([rationale](.agents/skills/dsh-prose-standard/SKILL.md)).
|
||||
- **Ban `prove` + `nance`** ([rule](.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md)).
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
/deepseek-ai-dsh/optional-overlays:
|
||||
en: 3a40ddc44e55f79c
|
||||
zh: 8d3949132e1ef93c
|
||||
/deepseek-ai-dsh/installed-payload:
|
||||
en: c19f3d56f611f173
|
||||
zh: 5002a7a318414960
|
||||
/deepseek-ai-dsh/development:
|
||||
en: 0a289c1235869927
|
||||
zh: a1f473f70036ad37
|
||||
@@ -53,6 +53,12 @@ The [CLI behavior reference](reference/README.md) owns exact layer precedence, f
|
||||
|
||||
`config/examples/` ships opt-in overlays for GitHub review webhooks, memory MCP servers, and runtime Cordis tools. They are never part of a default profile; the [user guides](../../docs/user/guide/index.md) and [developer practice guides](../../docs/user/develop/practice/index.md) own setup and safety instructions.
|
||||
|
||||
## Installed payload
|
||||
|
||||
The published tarball carries the bundled runtime under `lib/`, the TypeScript under `src/`, the declaration and JavaScript maps beside the files they describe, and the documentation under `docs/`. Only `prepack` stages `docs/`, and `postpack` removes that copy again, so a reader of the working tree sees whatever the checkout holds.
|
||||
|
||||
`docs/` holds the documentation website's pages plus every upgrade guide, with the assets those pages reference. `docs/upgrade-guide/README.md` is generated during staging: it groups the guides by the release each `v<T>` directory upgrades from and lists each guide's title, English and Chinese descriptions, and relative paths. The Desktop application carries the same `docs/` and `src/` inside its `app.asar`. The generated `persistence-changes/` records, the `i18n/` guidelines, and the `*.i18n.yaml` consistency records are not published. Its pages keep the repository's relative links, so a link into `../packages/**` or into the omitted `persistence-changes/` records resolves only in a checkout.
|
||||
|
||||
## Development
|
||||
|
||||
Production runs require built package and frontend artifacts. From the repository root, run `pnpm run build` separately, then use `pnpm dsh <args...>` to run the TypeScript entry and forward every argument; the [source-execution reference](reference/README.md#source-execution) owns the module-resolution contract.
|
||||
|
||||
@@ -53,6 +53,12 @@ profile 目录包含一个 `package.json`,其中记录树外插件依赖,以
|
||||
|
||||
`config/examples/` 交付 GitHub 评审 webhook、记忆 MCP 服务器与运行时 Cordis 工具的可选覆盖层。它们绝不属于默认 profile;设置与安全说明由[用户指南](../../docs/user/guide/index.zh.md)和[开发实战指南](../../docs/user/develop/practice/index.zh.md)负责。
|
||||
|
||||
## 安装包内容
|
||||
|
||||
发布 tarball 在 `lib/` 下携带打包后的运行时,在 `src/` 下携带 TypeScript 源码,在各自描述的文件旁携带声明映射与 JavaScript map,并在 `docs/` 下携带文档。只有 `prepack` 会暂存 `docs/`,`postpack` 又会删除这份副本,因此工作树里的读者看到的始终是检出当前的内容。
|
||||
|
||||
`docs/` 包含文档网站的页面与全部升级指南,以及这些页面引用的资产。`docs/upgrade-guide/README.md` 在暂存时生成:它按每个 `v<T>` 目录升级前的版本对指南分组,列出每份指南的标题、中英文描述与相对路径。桌面应用在其 `app.asar` 内携带同样的 `docs/` 与 `src/`。生成的 `persistence-changes/` 记录、`i18n/` 规范与 `*.i18n.yaml` 一致性记录不发布。其中的页面保留仓库的相对链接,因此指向 `../packages/**` 或指向被排除的 `persistence-changes/` 记录的链接只在检出中可解析。
|
||||
|
||||
## 开发
|
||||
|
||||
生产运行需要已构建的包与前端产物。请在仓库根目录单独运行 `pnpm run build`,然后使用 `pnpm dsh <args...>` 运行 TypeScript 入口并转发所有参数;模块解析约定以[源码执行参考](reference/README.zh.md#source-execution)为准。
|
||||
|
||||
+10
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh",
|
||||
"description": "dsh CLI: profile launch, plugin management, and configuration inspection",
|
||||
"version": "0.2.2-alpha.3",
|
||||
"version": "0.2.2-rc.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -16,8 +16,16 @@
|
||||
},
|
||||
"files": [
|
||||
"lib/*.js",
|
||||
"lib/types/*.d.ts"
|
||||
"lib/*.js.map",
|
||||
"lib/types/*.d.ts",
|
||||
"lib/types/*.d.ts.map",
|
||||
"docs",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"prepack": "tsx ../../scripts/stage-dsh-docs.ts",
|
||||
"postpack": "tsx ../../scripts/stage-dsh-docs.ts --clean"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
|
||||
@@ -102,11 +102,11 @@ describe('dsh run with Agent Teams enabled', () => {
|
||||
expect(eventTypes).not.toContain('team/member')
|
||||
const catalog = root!.filter(record => record.type === 'subagent/catalog').map(record => record.data)
|
||||
expect(catalog).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ version: 0, childId: workflowChild![0]?.id }),
|
||||
expect.objectContaining({ version: 0, label: 'Own deterministic implementation.' }),
|
||||
expect.objectContaining({ version: 0, label: 'Own deterministic research.' }),
|
||||
expect.objectContaining({ version: 3, childId: workflowChild![0]?.id, name: 'agent-1' }),
|
||||
expect.objectContaining({ version: 3, label: 'Own deterministic implementation.', name: 'agent-2' }),
|
||||
expect.objectContaining({ version: 3, label: 'Own deterministic research.', name: 'agent-3' }),
|
||||
]))
|
||||
for (const entry of catalog) expect(entry).not.toHaveProperty('name')
|
||||
for (const entry of catalog) expect(entry).toHaveProperty('name')
|
||||
expect(eventTypes).not.toContain('subagent-name/bound')
|
||||
expect(eventTypes).not.toContain('team/message/queued')
|
||||
expect(eventTypes).not.toContain('team/message/delivered')
|
||||
|
||||
@@ -13,19 +13,13 @@ it.each([false, true])('settles startup after parent IPC disconnect (boot failur
|
||||
const modules = join(root, 'node_modules', '@deepseek-ai')
|
||||
const hostDirectory = fileURLToPath(new URL('../../desktop-host/', import.meta.url))
|
||||
const manifest = JSON.parse(readFileSync(join(hostDirectory, 'package.json'), 'utf8')) as { dependencies: Record<string, string> }
|
||||
const stubbed = new Set(['@deepseek-ai/dsh-app-boot', '@deepseek-ai/dsh', '@deepseek-ai/dsh-home-paths'])
|
||||
const stubbed = new Set(['@deepseek-ai/dsh-app-boot', '@deepseek-ai/dsh'])
|
||||
for (const name of Object.keys(manifest.dependencies)) {
|
||||
const destination = join(root, 'node_modules', name)
|
||||
mkdirSync(dirname(destination), { recursive: true })
|
||||
if (stubbed.has(name)) mkdirSync(destination)
|
||||
else symlinkSync(realpathSync(join(hostDirectory, 'node_modules', name)), destination, 'junction')
|
||||
}
|
||||
for (const [name, source] of [
|
||||
['dsh-home-paths', `export const resolveDshHome = () => ${JSON.stringify(root)}`],
|
||||
] as const) {
|
||||
writeFileSync(join(modules, name, 'package.json'), '{"type":"module","exports":"./index.js"}')
|
||||
writeFileSync(join(modules, name, 'index.js'), source)
|
||||
}
|
||||
writeFileSync(join(root, 'package.json'), '{"type":"module"}')
|
||||
writeFileSync(join(modules, 'dsh-app-boot', 'package.json'), '{"type":"module","exports":"./index.js"}')
|
||||
writeFileSync(join(modules, 'dsh-app-boot', 'index.js'), `
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
{"type":"step/start","data":{"turn":1,"step":1}}
|
||||
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Create a durable two-round goal for the ACP snapshot, inspect it, then report readiness."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"<system-reminder>\nThe following information describes your current session:\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are the root agent. Your agent id is \"{{sessionId}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids.\n</system-reminder>"}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:identity","text":"You are the root agent. Your agent id is \"{{sessionId}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"<system-reminder>\nThe following information describes your current session:\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"<agent-name>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names.\n</system-reminder>"}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:identity","text":"You are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"<agent-name>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"session/title","data":{"title":"Create a durable two-round goal","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{"type":"step/start","data":{"turn":1,"step":1}}
|
||||
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Create a durable goal for the wrap-up snapshot, then report readiness."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"<system-reminder>\nThe following information describes your current session:\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are the root agent. Your agent id is \"{{sessionId}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids.\n</system-reminder>"}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:identity","text":"You are the root agent. Your agent id is \"{{sessionId}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"<system-reminder>\nThe following information describes your current session:\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"<agent-name>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names.\n</system-reminder>"}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:identity","text":"You are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"<agent-name>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"session/title","data":{"title":"Create a durable goal for","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
|
||||
@@ -140,13 +140,13 @@ it('pins Messages Files offload and inline fallback in assembled requests', asyn
|
||||
const fileImage = { type: 'image', source: { type: 'file', file_id: 'file-api-snapshot-1' } }
|
||||
const offloadedImage = `[image omitted to fit request image limits; ${attachmentId}.${normalizedAccess}]`
|
||||
const imageHandle = `Image ${attachmentId}; request preview 1x1px.${normalizedAccess}`
|
||||
const runtimeContext = (cwd: string, sessionId: string): string => '<system-reminder>\nThe following information describes your current session:\n\n'
|
||||
const runtimeContext = (cwd: string): string => '<system-reminder>\nThe following information describes your current session:\n\n'
|
||||
+ `Current working directory: ${JSON.stringify(cwd)}.\n\n`
|
||||
+ 'Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\n'
|
||||
+ 'Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\n'
|
||||
+ `You are the root agent. Your agent id is ${JSON.stringify(sessionId)}.\n\n`
|
||||
+ 'To message other agents, use send_message({ agent_id: "<agent-id>", message: "..." }).\n'
|
||||
+ 'Use list_agents({}) to find your collaborator agents and their ids.\n</system-reminder>'
|
||||
+ 'You are the root agent. Your name is "lead".\n\n'
|
||||
+ 'To message other agents, use send_message({ agent: "<agent-name>", message: "..." }).\n'
|
||||
+ 'Use list_agents({}) to find your collaborator agents and their names.\n</system-reminder>'
|
||||
const messages = requests[0]?.messages as { content?: unknown }[] | undefined
|
||||
const offloaded = messages?.find(message => JSON.stringify(message.content).includes('[image omitted'))
|
||||
expect(offloaded?.content).toEqual([
|
||||
@@ -156,7 +156,7 @@ it('pins Messages Files offload and inline fallback in assembled requests', asyn
|
||||
{ type: 'text', text: imageHandle },
|
||||
fileImage,
|
||||
{ type: 'text', text: ', then use read_image on red.png and reply with DONE.' },
|
||||
{ type: 'text', text: runtimeContext(result.cwd, result.sessionId) },
|
||||
{ type: 'text', text: runtimeContext(result.cwd) },
|
||||
])
|
||||
|
||||
const followup = requests[1]?.messages as Array<{ role: string; content: Array<Record<string, unknown>> }>
|
||||
@@ -208,7 +208,7 @@ it('pins Messages Files offload and inline fallback in assembled requests', asyn
|
||||
{ type: 'text', text: `Image ${attachmentId}; request preview 1x1px.${fallbackAccess}` },
|
||||
{ type: 'image', source: { type: 'base64', media_type: 'image/png', data: image } },
|
||||
{ type: 'text', text: ', then use read_image on red.png and reply with DONE.' },
|
||||
{ type: 'text', text: runtimeContext(fallback.cwd, fallback.sessionId) },
|
||||
{ type: 'text', text: runtimeContext(fallback.cwd) },
|
||||
])
|
||||
} finally {
|
||||
await new Promise<void>(resolve => server.close(() => { resolve() }))
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following information describes your current session:\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nYou are the root agent. Your agent id is \"{{sessionId}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids.\n</system-reminder>"}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"subagent:identity","text":"You are the root agent. Your agent id is \"{{sessionId}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following information describes your current session:\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nYou are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"<agent-name>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names.\n</system-reminder>"}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"subagent:identity","text":"You are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"<agent-name>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Probe strict-schema fillers against miss","messageSeqs":[8],"source":{"kind":"fallback"}}}}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
{"type":"step/start","data":{"turn":1,"step":1}}
|
||||
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Prove the product headless profile path with one real tool round trip."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"<system-reminder>\nThe following information describes your current session:\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are the root agent. Your agent id is \"{{sessionId}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids.\n</system-reminder>"}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:identity","text":"You are the root agent. Your agent id is \"{{sessionId}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"<system-reminder>\nThe following information describes your current session:\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"<agent-name>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names.\n</system-reminder>"}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:identity","text":"You are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"<agent-name>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"cli-mock","model":"cli-mock","reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"cli-mock","model":"cli-mock"}}
|
||||
{"type":"session/title","data":{"title":"Prove the product headless profile","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
|
||||
Loaded 100 of 1948 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user