mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-11-09 20:07:43 +08:00
Merge remote-tracking branch 'github/master' into fix/ptc-run-code-call-limit
# Conflicts: # docs/tool-catalog.i18n.yaml
This commit is contained in:
@@ -9,11 +9,11 @@
|
||||
en: 97fe58a13135b4a1
|
||||
zh: 7490373a8d941da5
|
||||
/agent-note-active-tool-headers-and-stateful-request-projection/decision:
|
||||
en: 0c40a9283650c643
|
||||
zh: 91a18d752d09a9ce
|
||||
en: 24138152d511bf12
|
||||
zh: 95516d7566b6db7c
|
||||
/agent-note-active-tool-headers-and-stateful-request-projection/alternatives-considered:
|
||||
en: b78785f2a603eba8
|
||||
zh: b8c268228da7e2e7
|
||||
en: 311db1f781e386e7
|
||||
zh: 7e3312544154a158
|
||||
/agent-note-active-tool-headers-and-stateful-request-projection/consequences:
|
||||
en: 60d7bf28fa5a8fd1
|
||||
zh: a17b222e7aca01e2
|
||||
en: 8a64dba42c01f647
|
||||
zh: 9e7ba7a5bd855031
|
||||
|
||||
@@ -14,9 +14,9 @@ Plugin and MCP tools change during conversations. Providers differ in whether th
|
||||
|
||||
`Session.toolHistory()` caches a capability-independent fold of committed headers and developer messages. First access restores inherited events; later reads consume only unseen events. Immutable snapshots carry initial declarations and ordered update identities with additions resolved through their historical headers. Explicit series starts and changed retained definitions rebuild declarations. Restoring a retained name with an unchanged definition continues the series: the projection keeps the declaration unchanged and lets the recorded addition re-offer the tool after its earlier removal, so a removal never disables a declared tool and a loading mode never rewrites an earlier declaration.
|
||||
|
||||
Loop and compaction requests carry this snapshot as `GenerateOptions.toolHistory`. At adapter dispatch, `projectToolUpdates` uses the prepared route's `toolUpdate`. Unsupported routes receive active definitions without `deferLoading` or developer messages. Supported routes defer historical additions; `in-history` retains removed definitions and removal blocks, while `addition-only` omits both. Only current-series update identities survive. Missing history or a request prefix omitting recorded updates uses current declarations without developer updates. Explicit deferred loading remains available on capable routes.
|
||||
Loop and compaction requests carry this snapshot as `GenerateOptions.toolHistory`. At adapter dispatch, `projectToolUpdates` uses the prepared route's `toolUpdate`. Unsupported routes receive active definitions without `deferLoading` or developer messages. Supported routes defer historical additions; `in-history` retains removed definitions and removal blocks, while `addition-only` omits both. On addition-only routes, removing any tool rebuilds the route's baseline after that update: the request declarations change even when the removed tool was activated later, so retaining superseded activation blocks cannot preserve the full conversation prefix. Adding tools when no declarations exist also rebuilds the baseline. Deferred declarations count as existing tools. The first native tool changes the empty declaration prefix; deferring it provides no cache advantage over declaring it immediately. Previously activated tools become immediate; explicitly deferred declarations with no activation remain deferred. Subsequent additions remain deferred until their recorded activation. Superseded tool blocks are omitted without discarding other content in those messages. The Session fold and durable events remain capability-independent. Missing history or a request prefix omitting recorded updates uses current declarations without developer updates.
|
||||
|
||||
DeepSeek translates projected updates into system-role `tool_addition` and `tool_removal` blocks and `defer_loading` declarations, sending the tool-changes beta header when update blocks occur. The default `deepseek-flash` entry declares `addition-only`; pi-ai declares no mode. Chat and Trajectory display developer messages as context nodes, using localized tool-change notices: single changes show the tool name; multiple changes show counts and expandable name lists.
|
||||
DeepSeek translates projected updates into system-role `tool_addition` and `tool_removal` blocks and `defer_loading` declarations, sending the tool-changes beta header when update blocks occur. The default `deepseek-flash` entry declares `addition-only`; pi-ai selects `in-history` for compatible Anthropic routes and `addition-only` for compatible Chat Completions and Responses routes. Chat and Trajectory display developer messages as context nodes, using localized tool-change notices: single changes show the tool name; multiple changes show counts and expandable name lists.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -26,6 +26,8 @@ DeepSeek translates projected updates into system-role `tool_addition` and `tool
|
||||
|
||||
**Scan the complete log on every request.** The cached fold consumes new events and supplies stable snapshots to ordinary and auxiliary calls without adding arbitrary synchronous history readers.
|
||||
|
||||
**Rebuild the baseline on every removal.** Deferred tools also have request declarations; deleting a tool changes those declarations regardless of its activation position. Addition-only routes cannot express that deletion in history. The rebuild therefore also covers removal-only updates and removals of later-activated tools. A past reset cannot force every future addition to become immediate: each reset establishes a new baseline from which deferred additions resume.
|
||||
|
||||
**Reject same-name schema changes during tool registration.** This restriction is not enforced. Immediate failure for a plugin registering an incompatible tool requires comparing the new registration with historical definitions of tools called on each affected current surface, while excluding calls removed by compaction. That registration-time historical and scope-aware check is deferred because of its implementation complexity. Same-name schema changes remain allowed; they rebuild declarations and invalidate prefix reuse even on `in-history` routes.
|
||||
|
||||
## Consequences
|
||||
@@ -34,4 +36,6 @@ Tool lists and logical update events are capability-independent; only outgoing d
|
||||
|
||||
The authored [headless prompt/tool scenario](../../../../snapshots/session/dynamic-tool-prompt-updates/snapshot.yml) pins an appended prompt beside a deferred tool addition and verifies that the first request's messages stay unchanged. It shares the stable-prompt scenario's tool lifecycle and schema expectations.
|
||||
|
||||
The [DeepSeek replacement scenario](../../../../snapshots/session/deepseek-deferred-tools/snapshot.yml) checks the first additions to an empty set, a rebuilt baseline, a subsequent deferred addition, and removal of that later-activated tool through Messages serialization. Explicit deferred-only requests remain unchanged under the [DeepSeek failure policy](../simplification/2026-11-02-deepseek-deferred-tool-failures.md). These checks establish request construction, not measured cache savings from rebuilding.
|
||||
|
||||
The [real DeepSeek SDK test](../../../../apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts) measures the first request after a native tool addition with both unchanged and simultaneously updated instructions. Unique user history prevents a shared system-prompt cache hit from satisfying the preceding-input assertion. Removal checks tool availability without requiring cache retention; each case disables retries.
|
||||
|
||||
@@ -14,9 +14,9 @@ Status: implemented
|
||||
|
||||
`Session.toolHistory()` 缓存对已提交请求头和 developer 消息的归约,不依赖模型能力。首次访问恢复继承事件;后续读取仅处理新增事件。不可变快照携带初始声明和有序更新标识,添加定义通过历史请求头解析。显式新序列和保留定义变化会重建声明。以未变化的定义恢复保留名称时序列继续:投影保持声明不变,由记录的添加在先前移除之后重新提供该工具,因此移除不会禁用已声明的工具,加载模式也不会改写先前声明。
|
||||
|
||||
循环和压缩请求通过 `GenerateOptions.toolHistory` 携带此快照。适配器分发时,`projectToolUpdates` 使用已准备路由的 `toolUpdate`。不支持的路由接收有效定义,不携带 `deferLoading` 或 developer 消息。支持的路由延迟历史添加;`in-history` 保留已移除定义和移除块,`addition-only` 则省略两者。仅保留当前序列的更新标识。缺少历史或请求前缀遗漏已记录更新时,使用当前声明且不发送 developer 更新。显式延迟加载在支持的路由上仍然可用。
|
||||
循环和压缩请求通过 `GenerateOptions.toolHistory` 携带此快照。适配器分发时,`projectToolUpdates` 使用已准备路由的 `toolUpdate`。不支持的路由接收有效定义,不携带 `deferLoading` 或 developer 消息。支持的路由延迟历史添加;`in-history` 保留已移除定义和移除块,`addition-only` 则省略两者。在 addition-only 路由移除任何工具时,使用该次更新后的状态重建路由基线:即使被移除工具是后来激活的,请求声明也已发生变化,保留已被取代的激活块无法保留完整对话前缀。不存在任何声明时添加工具,也会重建基线。延迟声明也计入已有工具。首个原生工具会改变空声明前缀;将其延迟加载并不比直接声明具有缓存优势。此前已激活工具变为立即加载;没有激活记录的显式延迟声明仍保持延迟。后续添加在记录的激活位置前保持延迟加载。已被取代的工具块会被省略,但不会丢弃这些消息中的其他内容。Session 归约和持久事件仍不依赖模型能力。缺少历史或请求前缀遗漏已记录更新时,使用当前声明且不发送 developer 更新。
|
||||
|
||||
DeepSeek 把投影后的更新转换为 system 角色的 `tool_addition` 和 `tool_removal` 块以及 `defer_loading` 声明,在存在更新块时发送工具变更 beta 请求头。默认 `deepseek-flash` 条目声明 `addition-only`;pi-ai 不声明模式。Chat 和 Trajectory 将 developer 消息显示为上下文节点,工具变更使用本地化通知:单项变更显示工具名,多项变更显示数量及可展开的名称列表。
|
||||
DeepSeek 把投影后的更新转换为 system 角色的 `tool_addition` 和 `tool_removal` 块以及 `defer_loading` 声明,在存在更新块时发送工具变更 beta 请求头。默认 `deepseek-flash` 条目声明 `addition-only`;pi-ai 为兼容的 Anthropic 路由选择 `in-history`,为兼容的 Chat Completions 和 Responses 路由选择 `addition-only`。Chat 和 Trajectory 将 developer 消息显示为上下文节点,工具变更使用本地化通知:单项变更显示工具名,多项变更显示数量及可展开的名称列表。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -26,6 +26,8 @@ DeepSeek 把投影后的更新转换为 system 角色的 `tool_addition` 和 `to
|
||||
|
||||
**每次请求扫描完整日志。** 缓存归约处理新事件,为普通和辅助调用提供稳定快照,不增加任意同步历史读取接口。
|
||||
|
||||
**每次移除都重建基线。** 延迟工具也具有请求声明;删除工具会改变这些声明,与其激活位置无关。addition-only 路由无法通过历史表达删除。因此,仅移除的更新和移除后来激活的工具也会重建基线。一次历史重置不能让所有后续添加都变为立即加载:每次重置确立新基线,后续添加继续采用延迟加载。
|
||||
|
||||
**在工具注册时拒绝同名 schema 变化。** 当前不执行此限制。要让注册不兼容工具的插件立即收到异常,需要将新注册与各受影响当前 surface 中已调用工具的历史定义比较,同时排除已被压缩移除的调用。由于实现复杂,注册时结合历史和作用域的检查留待后续实现。同名 schema 变化仍被允许;它们会重建声明,即使在 `in-history` 路由上也会使前缀复用失效。
|
||||
|
||||
## Consequences
|
||||
@@ -34,4 +36,6 @@ DeepSeek 把投影后的更新转换为 system 角色的 `tool_addition` 和 `to
|
||||
|
||||
手写的[无界面提示词/工具场景](../../../../snapshots/session/dynamic-tool-prompt-updates/snapshot.yml)钉住追加提示词与延迟工具添加同时发生的行为,并验证首次请求的消息保持不变。它复用稳定提示词场景的工具生命周期和 schema 期望值。
|
||||
|
||||
[DeepSeek 替换场景](../../../../snapshots/session/deepseek-deferred-tools/snapshot.yml)通过 Messages 序列化验证向空工具集首次添加工具、重建后的基线、后续延迟添加,以及移除该后来激活的工具。根据 [DeepSeek 失败策略](../simplification/2026-11-02-deepseek-deferred-tool-failures.zh.md),显式全延迟请求保持原样。这些检查验证请求构造,不代表已经测量重建带来的缓存收益。
|
||||
|
||||
[真实 DeepSeek SDK 测试](../../../../apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts) 分别在指令不变和指令同时更新时测量原生工具新增后的首次请求。独有的用户历史确保仅命中共享系统提示词缓存无法满足前序输入断言。移除场景检查工具可用性,不要求保留缓存;每个用例均禁用重试。
|
||||
|
||||
+6
-6
@@ -9,11 +9,11 @@
|
||||
en: 47ba548b869be190
|
||||
zh: c2e5038a56fe741c
|
||||
/agent-note-asynchronous-projection-registration-and-retained-readers/decision:
|
||||
en: be0d17bfbd1b59d4
|
||||
zh: 7a23bccdf95c21ff
|
||||
en: a6f9ae8a53af5e4b
|
||||
zh: 0aece791574ef060
|
||||
/agent-note-asynchronous-projection-registration-and-retained-readers/alternatives-considered:
|
||||
en: e7c66403fab991d2
|
||||
zh: 1580ec29116c7d42
|
||||
en: 12e0f5fc9638acc6
|
||||
zh: 24213550da27b70b
|
||||
/agent-note-asynchronous-projection-registration-and-retained-readers/consequences:
|
||||
en: 31ce6f02dd3bf39d
|
||||
zh: bcd29b2146f689b5
|
||||
en: f5c294608d87844f
|
||||
zh: 24488e439a04d81a
|
||||
|
||||
+6
-2
@@ -12,7 +12,7 @@ Host readers coupled to registry key lookup lose access when a plugin unloads, a
|
||||
|
||||
`SessionProjectionRegistry.register` returns an awaitable registration for a typed handle. Domain owners await it during activation before exposing dependent operations. Function plugins retain their namespace exports and use asynchronous `apply`; services use their initialization hook. Each domain service keeps its definitions and handles internal and exposes methods returning business data. Consumers depend on that service rather than registering its definitions or receiving projection readers. This supersedes the optional first-access acquisition and disposer-return details in [the mandatory-reader decision](2026-08-19-session-projection-mandatory-seam.md), while retaining its mandatory-state and cropped-client-view rules.
|
||||
|
||||
A handle owns one reference to a shared definition and its per-Session cells. Its disposer releases global participation exactly once; the final release removes the unit from live drives, snapshots, and checkpoints. Its reader remains usable after release, folding unseen events with the original definition. A later registration at a different state version does not replace an older handle's definition. Registration checks owner liveness after its asynchronous handoff and rejects if the owner unloaded before receiving the handle.
|
||||
A handle owns one reference to a shared definition and its per-Session cells. Its disposer releases global participation exactly once; the final release removes the unit from live drives and snapshots. Checkpoints keep each Session's last cell of the released unit until its key registers again: the registry no longer advances that cell, while a retained handle read still advances it forward, so a writer that runs after the owner unloads still stores the state already folded. Under the Cordis rc.11 teardown order, Web-layer plugins release their units before the Sessions they describe detach, and the final projection-cache write would otherwise drop those rows. Stored rows can therefore lag the log; the cache promises only possibly stale rows. Its reader remains usable after release, folding unseen events with the original definition. A later registration at a different state version does not replace an older handle's definition. Registration checks owner liveness after its asynchronous handoff and rejects if the owner unloaded before receiving the handle.
|
||||
|
||||
AgentRegistry owns turn and step state and exposes `turnBoundaryOf(session)`. AgentLoop acquires its inbox handle once before publishing its factory, reads the last turn before creating an Agent scope, and passes an inbox callback plus that turn number into synchronous Agent construction. The callback retains its handle through cleanup after the loop fiber removes global registrations. This preserves the existing creation/setup sequence without per-Agent registration or an extra initialization state.
|
||||
|
||||
@@ -20,6 +20,10 @@ Plugins compiled against the synchronous API keep running during migration. The
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Detach owned Sessions before unregistering.** Other Web-layer plugins unload in earlier fibers under the rc.11 teardown order, and plugin reload or disable reaches the same timing, so ordering one owner cannot keep every row.
|
||||
|
||||
**Checkpoint a released unit only while its cell is at the Session cursor.** A Session with a running turn at shutdown appends teardown events after its units are released, so its rows would be dropped again.
|
||||
|
||||
**Keep registry-level lookup and delay unregistering.** This couples unrelated plugin disposal order to every reader's final access. Retained readers make cleanup independent of that order.
|
||||
|
||||
**Return a synchronous handle.** This preserves callers' immediate reads but commits the acquisition API to synchronous initialization. Awaited acquisition leaves that timing explicit while state reads remain synchronous after readiness.
|
||||
@@ -36,4 +40,4 @@ Plugins compiled against the synchronous API keep running during migration. The
|
||||
|
||||
## Consequences
|
||||
|
||||
Projection owners must migrate acquisition and retain handles; other consumers use the owning services. Released handles retain their definitions and weakly keyed cells for as long as the caller keeps them; they do not restore global wire participation. Registration-reader tests cover post-release folding, version replacement, unrelated-unit isolation, repeated disposal, concurrent owner disposal, and owner unload during acquisition. Agent lifecycle tests cover cleanup failures, scope-minting races, and background child shutdown. Compatibility tests and a real Loader subprocess verify the deprecated calls.
|
||||
Projection owners must migrate acquisition and retain handles; other consumers use the owning services. Released handles retain their definitions and weakly keyed cells for as long as the caller keeps them; they do not restore global wire participation. The registry also retains a released predecessor, with its definition closures and weakly keyed cells, until the same key registers again, so a key that a reloaded plugin renames or stops registering keeps the old definition referenced for the process lifetime. Registration-reader tests cover post-release folding, version replacement, unrelated-unit isolation, repeated disposal, concurrent owner disposal, and owner unload during acquisition. Agent lifecycle tests cover cleanup failures, scope-minting races, and background child shutdown. Compatibility tests and a real Loader subprocess verify the deprecated calls.
|
||||
|
||||
+6
-2
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
`SessionProjectionRegistry.register` 返回可等待的注册结果,用于获取类型化句柄。领域所有者在激活期间等待它,再提供依赖操作。函数插件保留命名空间导出并使用异步 `apply`;服务使用初始化钩子。领域服务在内部持有定义和句柄,通过方法返回业务数据。消费方依赖该服务,无需注册其定义或接收投影读取句柄。这取代了[必需读取方决策](2026-08-19-session-projection-mandatory-seam.zh.md)中的可选首次访问获取方式与返回释放函数的细节,同时保留必需状态和裁剪 Client 视图的规则。
|
||||
|
||||
句柄拥有共享定义及其逐 Session 单元的一次引用。其释放函数恰好一次地解除全局参与;最后一次释放会从实时推进、快照和检查点中移除单元。读取句柄在释放后仍可使用原定义折叠未读取事件。后续注册的其他状态版本不会替换旧句柄的定义。注册在异步交接后检查 owner 是否存活;如果 owner 在收到句柄前卸载,注册拒绝。
|
||||
句柄拥有共享定义及其逐 Session 单元的一次引用。其释放函数恰好一次地解除全局参与;最后一次释放会从实时推进和快照中移除单元。检查点仍保留每个 Session 中已释放单元的最后一个 cell,直到它的 key 再次注册:注册表不再推进这个 cell,但已保留的 handle 读取时仍会将其向前推进,因此 owner 卸载后才运行的写入方仍会存下已经折叠出的状态。按 Cordis rc.11 的卸载顺序,Web 层插件会在它们所描述的 Session detach 之前释放单元,否则 projection cache 的最终写入会丢掉这些行。因此存储的行可能落后于日志;cache 只承诺行可能过时。读取句柄在释放后仍可使用原定义折叠未读取事件。后续注册的其他状态版本不会替换旧句柄的定义。注册在异步交接后检查 owner 是否存活;如果 owner 在收到句柄前卸载,注册拒绝。
|
||||
|
||||
AgentRegistry 持有轮次和步骤状态,并提供 `turnBoundaryOf(session)`。AgentLoop 在发布工厂前只获取一次 inbox 句柄,在创建 Agent 作用域前读取最后轮次,并将 inbox 回调与该轮次编号传入同步 Agent 构造过程。loop fiber 移除全局注册后,回调仍在清理期间保留其句柄。这保留既有创建与 setup 顺序,无需逐 Agent 注册或增加初始化状态。
|
||||
|
||||
@@ -20,6 +20,10 @@ AgentRegistry 持有轮次和步骤状态,并提供 `turnBoundaryOf(session)`
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**卸载前先 detach 自己拥有的 Session。** 按 rc.11 的卸载顺序,其他 Web 层插件在更早卸载的 fiber 里,插件重载或禁用也会碰上同样的时机,调整单个 owner 的顺序保不住所有行。
|
||||
|
||||
**只在已释放单元的 cell 位于 Session 游标时才写入检查点。** 关停时正在运行一轮的会话会在单元释放后追加 teardown 事件,因此它的这些行会再次被丢掉。
|
||||
|
||||
**保留注册表级查询并延迟注销。** 这使无关插件的释放顺序依赖每个读取方的最后一次访问。保留读取句柄使清理不再依赖该顺序。
|
||||
|
||||
**返回同步句柄。** 这保留消费者的立即读取,却将获取 API 固定为同步初始化。等待获取过程使时序显式,同时就绪后的状态读取仍同步。
|
||||
@@ -36,4 +40,4 @@ AgentRegistry 持有轮次和步骤状态,并提供 `turnBoundaryOf(session)`
|
||||
|
||||
## Consequences
|
||||
|
||||
投影所有者必须迁移获取方式并保留句柄;其他消费方使用所属服务。只要调用方持有已释放句柄,其定义和弱键单元就仍存在;句柄不会恢复全局 wire 参与。注册读取测试覆盖释放后折叠、版本替换、无关单元隔离、重复释放、并发 owner 释放以及获取期间 owner 卸载。Agent 生命周期测试覆盖清理失败、作用域创建竞态与后台子 Agent 停止。兼容测试与真实 Loader 子进程验证这些已弃用调用。
|
||||
投影所有者必须迁移获取方式并保留句柄;其他消费方使用所属服务。只要调用方持有已释放句柄,其定义和弱键单元就仍存在;句柄不会恢复全局 wire 参与。注册表自身也会保留已释放的前任及其定义闭包和弱键单元,直到同一 key 再次注册;因此重载后改名或不再注册的 key 会在进程生命周期内一直引用旧定义。注册读取测试覆盖释放后折叠、版本替换、无关单元隔离、重复释放、并发 owner 释放以及获取期间 owner 卸载。Agent 生命周期测试覆盖清理失败、作用域创建竞态与后台子 Agent 停止。兼容测试与真实 Loader 子进程验证这些已弃用调用。
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 9f84c9bd85b6aace
|
||||
zh: 8ebb10962c219624
|
||||
/agent-note-serve-session-list-rows-from-a-summary-key-set/decision:
|
||||
en: 6e8f43d7a53c35fd
|
||||
zh: a6e2bccc20474515
|
||||
en: aada3ca481120bff
|
||||
zh: 1fa45a276a47e921
|
||||
/agent-note-serve-session-list-rows-from-a-summary-key-set/alternatives-considered:
|
||||
en: ce8320f1032018ed
|
||||
zh: 4c5fad5e605bf524
|
||||
|
||||
+2
-2
@@ -13,10 +13,10 @@ A Session-list row carries every registered client view. Four of those views are
|
||||
The Session summary is every registered client view except the keys whose `SessionProjectionMap` entry carries the summary-exclusion mark.
|
||||
|
||||
- [`packages/session/session-projection/src/types.ts`](../../../../packages/session/session-projection/src/types.ts) declares the type-only mark `SessionProjectionSummaryExcluded<T>` over a unique symbol that is never exported and never present at runtime. `SessionProjectionViewValue<T>` strips it distributively, so a nullable entry keeps its `null`; `SessionProjectionSummaryExcludedKey` tests the non-nullish part; `SessionProjectionSummaryValues` is every view value minus those keys.
|
||||
- Each owning package wraps only its own map entry: `turnOutline` (session-turn-outline), `sessionStats` (session-stats), `todos` (tool-todo), and `imageLimits` (session-controller's boot-constant attachment limits, which every Session shares). The todo entry is `SessionProjectionSummaryExcluded<TodoItem[]> | null`, because an intersection with `null` would absorb the null member.
|
||||
- Each owning package wraps only its own map entry: `turnOutline` (session-turn-outline), `sessionStats` (session-stats), `todos` (tool-todo), `imageLimits` (session-controller's boot-constant attachment limits, which every Session shares), and `contextPressure` and `contextBreakdown` (token-meter). The todo entry is `SessionProjectionSummaryExcluded<TodoItem[]> | null`, because an intersection with `null` would absorb the null member.
|
||||
- Registration carries the same decision as a plain boolean: `wire.summary` says whether a Session summary includes the unit, and omitting it means `true`. A key whose map entry carries the mark declares `false` so the runtime key set and the client view type agree. The mark itself has no runtime representation — `wire.view` and `viewSchema` are typed with `SessionProjectionViewValue`, which strips it, so the erase in `register` only widens the state type.
|
||||
- [`packages/session/session-projection/src/index.ts`](../../../../packages/session/session-projection/src/index.ts) exposes `summaryKeys(): ReadonlySet<key>`, derived from the live registrations and rebuilt only when a new key registers or a key's last registrant disposes. `cachedSnapshot`, `viewCheckpoint`, and `snapshot` accept `ProjectionKeySelection` (`readonly Key[] | ReadonlySet<Key>`) and use a passed set as-is.
|
||||
- [`packages/session/session-projection-cache/src/index.ts`](../../../../packages/session/session-projection-cache/src/index.ts) forwards that selection unchanged, and [`packages/api/session-controller/src/list.ts`](../../../../packages/api/session-controller/src/list.ts) passes `summaryKeys()` to the live and cold cached faces. `cachedPredecessorTitle` keeps its title-only read.
|
||||
- [`packages/session/session-projection-cache/src/index.ts`](../../../../packages/session/session-projection-cache/src/index.ts) forwards that selection unchanged, and [`packages/api/session-controller/src/list.ts`](../../../../packages/api/session-controller/src/list.ts) passes `summaryKeys()` to the live registry's `snapshot` and to the cold cache read. `cachedPredecessorTitle` keeps its title-only read.
|
||||
- Client types only: `SessionSummary.projectionValues` narrows to `Partial<SessionProjectionSummaryValues>`, while `SessionProjectionSnapshot.values` keeps every view value. No wire field, function, or UI name changes, and an opened Session still receives every key through `session/follow`, the control baseline, and `session.projections`.
|
||||
|
||||
Estimated effect: with logs whose outlines dominate the projection payload, the four keys are estimated at roughly two thirds of a Session-list response for a few hundred Sessions — the outline alone near 57%, the figures and todo list near 5% together, and the shared image limits near 5% — and at roughly 85% of the same response after transport compression, where the outline's unique text barely compresses while the remaining keys do.
|
||||
|
||||
+2
-2
@@ -13,10 +13,10 @@ Session 列表行携带每个已注册的客户端视图。其中四个视图从
|
||||
Session 摘要就是每个已注册客户端视图,减去 `SessionProjectionMap` 条目带摘要排除标记的键。
|
||||
|
||||
- [`packages/session/session-projection/src/types.ts`](../../../../packages/session/session-projection/src/types.ts) 声明类型层标记 `SessionProjectionSummaryExcluded<T>`,它基于一个从不导出、运行时从不存在的 unique symbol。`SessionProjectionViewValue<T>` 以分发方式去掉标记,可空条目因此保留 `null`;`SessionProjectionSummaryExcludedKey` 取非空部分判断;`SessionProjectionSummaryValues` 是全部视图值减去这些键。
|
||||
- 每个 owning package 只包装自己的 map 条目:`turnOutline`(session-turn-outline)、`sessionStats`(session-stats)、`todos`(tool-todo),以及 `imageLimits`(session-controller 的启动常量附件上限,每个 Session 共享)。todo 条目写作 `SessionProjectionSummaryExcluded<TodoItem[]> | null`,因为与 `null` 求交会吞掉 null 成员。
|
||||
- 每个 owning package 只包装自己的 map 条目:`turnOutline`(session-turn-outline)、`sessionStats`(session-stats)、`todos`(tool-todo)、`imageLimits`(session-controller 的启动常量附件上限,每个 Session 共享),以及 `contextPressure` 和 `contextBreakdown`(token-meter)。todo 条目写作 `SessionProjectionSummaryExcluded<TodoItem[]> | null`,因为与 `null` 求交会吞掉 null 成员。
|
||||
- 注册用普通布尔值承载同一决定:`wire.summary` 表示 Session 摘要是否包含该单元,省略即 `true`。map 条目带标记的 key 声明 `false`,让运行时键集合与客户端视图类型一致。标记本身没有运行时表示 —— `wire.view` 与 `viewSchema` 的签名已用 `SessionProjectionViewValue` 去标记,因此 `register` 里的擦除只是放宽状态类型。
|
||||
- [`packages/session/session-projection/src/index.ts`](../../../../packages/session/session-projection/src/index.ts) 提供 `summaryKeys(): ReadonlySet<key>`,从实时注册派生,只有新键注册或某键最后一个注册者注销时才重建。`cachedSnapshot`、`viewCheckpoint`、`snapshot` 接受 `ProjectionKeySelection`(`readonly Key[] | ReadonlySet<Key>`),传入 Set 时直接使用。
|
||||
- [`packages/session/session-projection-cache/src/index.ts`](../../../../packages/session/session-projection-cache/src/index.ts) 原样转发该选择,[`packages/api/session-controller/src/list.ts`](../../../../packages/api/session-controller/src/list.ts) 把 `summaryKeys()` 传给 live 与 cold 两个缓存读取面。`cachedPredecessorTitle` 保持只读 title。
|
||||
- [`packages/session/session-projection-cache/src/index.ts`](../../../../packages/session/session-projection-cache/src/index.ts) 原样转发该选择,[`packages/api/session-controller/src/list.ts`](../../../../packages/api/session-controller/src/list.ts) 把 `summaryKeys()` 传给实时注册表的 `snapshot` 与冷会话的缓存读取。`cachedPredecessorTitle` 保持只读 title。
|
||||
- 客户端只有类型变化:`SessionSummary.projectionValues` 收窄为 `Partial<SessionProjectionSummaryValues>`,而 `SessionProjectionSnapshot.values` 保留全部视图值。wire 字段、函数名与 UI 名称均不变;打开的 Session 仍通过 `session/follow`、控制基线与 `session.projections` 收到每个键。
|
||||
|
||||
估算效果:当 outline 在投影负载中占主导时,四个键估计占几百个 Session 时列表响应的约三分之二 —— outline 单项约 57%,统计与 todo 合计约 5%,共享的图片上限约 5% —— 占传输压缩后同一响应的约 85%,因为 outline 的唯一文本几乎无法压缩,而其余键可以。
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 344bcc7e9825bbc8
|
||||
zh: c1d0f4b54f5b3786
|
||||
/agent-note-change-request-panel-and-worktree-sessions/decision:
|
||||
en: 6a0dc836205863ba
|
||||
zh: 3d4feb0385953dda
|
||||
en: 66bde24e7c601eb6
|
||||
zh: 5091591f3dc1ac85
|
||||
/agent-note-change-request-panel-and-worktree-sessions/deferred:
|
||||
en: 32ef8af85f4d2f66
|
||||
zh: b82b67f94346399e
|
||||
@@ -18,5 +18,5 @@
|
||||
en: e471e16b6b6450ca
|
||||
zh: 5fbc9242edc8d112
|
||||
/agent-note-change-request-panel-and-worktree-sessions/consequences:
|
||||
en: 81790c448f0a7d3b
|
||||
zh: ec63ccb2ad315ff8
|
||||
en: 0b785a2204963c4f
|
||||
zh: be4bd8db40b801a1
|
||||
|
||||
@@ -10,7 +10,9 @@ English | [中文](2026-10-09-forge-panel.zh.md)
|
||||
|
||||
## Decision
|
||||
|
||||
[`dsh-experimental-client-ui-forge`](../../../../packages/experimental/client-ui-forge/README.md) is a two-face package. Its Host half, the `forgePanel` Remote, assembles everything the panel shows for one Session into one streamed state; the forge registry and the binding service keep only their domain operations. Its Client half is a right-sidebar tab, a Session header button that shows or hides it, a New Session option, a creation card above the composer, a toast, and the [Session list icons and hover card section](2026-10-20-forge-session-list.md). Every surface calls a bound item a change request, the forge registry's term, whatever its code host calls it. The [forge bundle](../../../../packages/experimental/forge-bundle/README.md) ships the panel, the forge registry, the GitHub provider, the bindings, and an isolated worktree service as one default-disabled Plugins page entry.
|
||||
[`dsh-experimental-client-ui-forge`](../../../../packages/experimental/client-ui-forge/README.md) is a two-face package. Its Host half, the `forgePanel` Remote, assembles everything the panel shows for one Session into one streamed state; the forge registry and the binding service keep only their domain operations. Its Client half is a right-sidebar tab, a Session header button that shows or hides it, a New Session option, a creation card above the composer, a toast, and the [Session list icons and hover card section](2026-10-20-forge-session-list.md). Every surface calls a bound item a change request, the forge registry's term, whatever its code host calls it. The [forge bundle](../../../../packages/experimental/forge-bundle/README.md) ships the panel, the forge registry, the GitHub provider, the bindings, the worktree model tool, and an isolated worktree service for the panel as one default-disabled Plugins page entry.
|
||||
|
||||
Review and worktree controls serve one code-change workflow. One Plugins entry lets users enable that workflow without having to distinguish the underlying package split. Packages and the two worktree services remain independent so each owns its before-Session or in-conversation creation flow.
|
||||
|
||||
The Remote inspects the checkout through the Session's subprocess provider under its [resolved permissions and query restrictions](../bug-fix/2026-10-28-forge-git-session-permissions.md), so it reads what the Session's commands see. Inspection reads the top-level, Git, and common Git directories, the branch or the abbreviated HEAD commit, the upstream, the remotes, and the size of the branch's changes against its fork point. It runs when a Session is followed, after each `working-directory/change`, `tool/result`, and `sandbox/mode` event, and on refresh, one at a time with one trailing rerun.
|
||||
|
||||
@@ -89,4 +91,4 @@ The changes view offers no scope for one commit or for the last turn yet; both w
|
||||
|
||||
## Consequences
|
||||
|
||||
The panel shows current status only while it is open; checks that finish while nothing is mounted produce no notification. Changes made outside the Session appear after Refresh or the next tool result. Every inspection spawns up to twelve short Git processes in the Session's execution world with one remote, and two more for each further remote it tries for a fork point. A worktree exists only for a Session that requested it before its first step, and a failed or cancelled attempt can leave its branch or checkout behind. A message that enters while the Agent runs a turn or other maintenance gets no hold, so its worktree is created within the first turn and a removed worktree is not checked. While a message waits, the Agent stays idle in maintenance: the composer offers no stop, and callers waiting for the Agent to become idle wait as well. Restoring loses changes that were never committed. A base branch that requires a merge queue cannot merge from the panel. With Git Worktrees also enabled, two independent worktree services exist; the model's tool never sees the panel's service. A local or change request diff over `maxDiffBytes`, or one the host refuses, shows no files. Expanding unchanged lines of a change request reads each file from the code host once.
|
||||
The panel shows current status only while it is open; checks that finish while nothing is mounted produce no notification. Changes made outside the Session appear after Refresh or the next tool result. Every inspection spawns up to twelve short Git processes in the Session's execution world with one remote, and two more for each further remote it tries for a fork point. A worktree exists only for a Session that requested it before its first step, and a failed or cancelled attempt can leave its branch or checkout behind. A message that enters while the Agent runs a turn or other maintenance gets no hold, so its worktree is created within the first turn and a removed worktree is not checked. While a message waits, the Agent stays idle in maintenance: the composer offers no stop, and callers waiting for the Agent to become idle wait as well. Restoring loses changes that were never committed. A base branch that requires a merge queue cannot merge from the panel. The bundle mounts two independent worktree services; the model's tool never sees the panel's service. A local or change request diff over `maxDiffBytes`, or one the host refuses, shows no files. Expanding unchanged lines of a change request reads each file from the code host once.
|
||||
|
||||
@@ -10,7 +10,9 @@ Status: implemented
|
||||
|
||||
## Decision
|
||||
|
||||
[`dsh-experimental-client-ui-forge`](../../../../packages/experimental/client-ui-forge/README.zh.md) 是一个双端包。宿主端的 `forgePanel` Remote 把面板为一个 Session 展示的全部内容组装成一条流式状态;forge 注册表与绑定服务只保留各自的领域操作。客户端包括一个右侧边栏标签页、一个显示或隐藏它的 Session 标题栏按钮、一个新会话选项、一张位于输入框上方的创建卡片、一个 toast,以及[会话列表中的图标与悬浮卡片部分](2026-10-20-forge-session-list.zh.md)。所有界面都把已绑定的条目称为变更请求(forge 注册表的术语),不随代码托管平台的叫法变化。[forge 套件](../../../../packages/experimental/forge-bundle/README.zh.md)把面板、forge 注册表、GitHub 提供方、绑定服务与一个隔离的 worktree 服务作为插件页中默认禁用的一个条目发布。
|
||||
[`dsh-experimental-client-ui-forge`](../../../../packages/experimental/client-ui-forge/README.zh.md) 是一个双端包。宿主端的 `forgePanel` Remote 把面板为一个 Session 展示的全部内容组装成一条流式状态;forge 注册表与绑定服务只保留各自的领域操作。客户端包括一个右侧边栏标签页、一个显示或隐藏它的 Session 标题栏按钮、一个新会话选项、一张位于输入框上方的创建卡片、一个 toast,以及[会话列表中的图标与悬浮卡片部分](2026-10-20-forge-session-list.zh.md)。所有界面都把已绑定的条目称为变更请求(forge 注册表的术语),不随代码托管平台的叫法变化。[forge 套件](../../../../packages/experimental/forge-bundle/README.zh.md)把面板、forge 注册表、GitHub 提供方、绑定服务、工作树模型工具与面板使用的一个隔离 worktree 服务作为插件页中默认禁用的一个条目发布。
|
||||
|
||||
评审与工作树控件服务于同一个代码修改流程。统一插件入口让用户可以一次启用这些能力,无需根据底层包的拆分判断应该开启哪张卡片。包与两套工作树服务仍保持独立,分别负责会话开始前和对话中创建工作树的流程。
|
||||
|
||||
Remote 通过 Session 的子进程提供方,按其[解析出的权限与查询限制](../bug-fix/2026-10-28-forge-git-session-permissions.zh.md)检查检出,因此它读到的与 Session 命令看到的相同。检查读取顶层目录、Git 目录与公共 Git 目录、分支或缩写的 HEAD 提交、上游与远端,以及分支相对于其分叉点的变更规模。它在 Session 被关注时、每个 `working-directory/change`、`tool/result` 与 `sandbox/mode` 事件之后以及刷新时运行,一次只运行一个,最多再追加一次。
|
||||
|
||||
@@ -89,4 +91,4 @@ Session 所在的 worktree 可能在 Session 之外被删除。每次检查都
|
||||
|
||||
## Consequences
|
||||
|
||||
面板只在打开时显示当前状态;没有任何界面挂载时检查结束不会有通知。Session 之外的改动要等到刷新或下一个工具结果才会出现。只有一个远端时,每次检查最多在 Session 的执行环境中启动十二个短暂的 Git 进程;为寻找分叉点每多尝试一个远端,就多启动两个。只有在第一步之前提出请求的 Session 才会有 worktree,失败或被取消的尝试可能留下它的分支或检出。Agent 正在运行一轮或其他维护任务时进入的消息不会被暂留,因此它的 worktree 在第一轮之内创建,已删除的 worktree 也不会被检查。消息等待期间,Agent 以空闲状态处于维护任务中:输入框不提供停止,等待 Agent 空闲的调用方也会一并等待。恢复会丢失从未提交的修改。要求合并队列的基准分支无法从面板合并。同时启用 Git Worktrees 时会存在两个互不影响的 worktree 服务;模型的工具看不到面板的服务。本地或变更请求的 diff 超过 `maxDiffBytes`,或被托管平台拒绝时,不显示任何文件。展开变更请求中未变更的行时,每个文件只从托管平台读取一次。
|
||||
面板只在打开时显示当前状态;没有任何界面挂载时检查结束不会有通知。Session 之外的改动要等到刷新或下一个工具结果才会出现。只有一个远端时,每次检查最多在 Session 的执行环境中启动十二个短暂的 Git 进程;为寻找分叉点每多尝试一个远端,就多启动两个。只有在第一步之前提出请求的 Session 才会有 worktree,失败或被取消的尝试可能留下它的分支或检出。Agent 正在运行一轮或其他维护任务时进入的消息不会被暂留,因此它的 worktree 在第一轮之内创建,已删除的 worktree 也不会被检查。消息等待期间,Agent 以空闲状态处于维护任务中:输入框不提供停止,等待 Agent 空闲的调用方也会一并等待。恢复会丢失从未提交的修改。要求合并队列的基准分支无法从面板合并。组合包挂载两个互不影响的 worktree 服务;模型的工具看不到面板的服务。本地或变更请求的 diff 超过 `maxDiffBytes`,或被托管平台拒绝时,不显示任何文件。展开变更请求中未变更的行时,每个文件只从托管平台读取一次。
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
en: 641cbfefc76f2f95
|
||||
zh: e3e718473eace85f
|
||||
/agent-note-direct-deepseek-search-and-fetch-apis/consequences:
|
||||
en: a55da02f4d3c06c0
|
||||
zh: 3c1a624f03f34f28
|
||||
en: 124a35cad810c2fc
|
||||
zh: 504da2a59c3873be
|
||||
/agent-note-direct-deepseek-search-and-fetch-apis/verification-and-remaining-api-adoption:
|
||||
en: 4c84e7bd035b52a7
|
||||
zh: 4e5687bda0a47706
|
||||
|
||||
@@ -70,7 +70,7 @@ The retained Messages path explicitly sends `thinking: { type: 'disabled' }` and
|
||||
|
||||
Retrieval no longer requires auxiliary model tokens in direct mode, but its results still consume the main model's context. One Messages web search through a gateway on 2026-10-16 consumed 8,154 input and 828 output tokens for a single server-side search. The guide prices Search and Fetch independently at CNY 5 per 1,000 successful requests with results; result count does not change a request's price. Multiple queries remain multiple requests. The stated Claude Code and Codex promotion does not establish a Harness exemption. Search API mode also sends up to 128 characters of the user's prompt to the search service with each query unless `sendOriginalQuestion` is off.
|
||||
|
||||
Search and Fetch each have an account-wide 5 QPS limit, independent of model concurrency and shared across that account's API keys. Fetch also has a target-domain limit. The current tool can issue four queries concurrently and fails the batch if one query fails. The provider has no shared rate scheduler, automatic retries, or fallback; other Sessions or processes can exhaust the same account allowance.
|
||||
Search and Fetch each have an account-wide 5 QPS limit, independent of model concurrency and shared across that account's API keys. Fetch also has a target-domain limit. The tool accepts up to `searchMaxQueries` queries per call, 10 by default, and fails the batch if one query fails. The Search provider sends at most `searchRequestsPerSecond` requests, 5 by default, in any one-second window of one plugin instance, so the second five queries of a full batch start one second after the first. The pacing is not shared across processes, and the provider has no automatic retries or fallback; other processes or clients can exhaust the same account allowance.
|
||||
|
||||
## Verification and remaining API adoption
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ HTTP 提供方继续负责自身的 URL 校验和资源限制。选择 DeepSeek
|
||||
|
||||
直接模式中的检索不再需要辅助模型 token,但结果仍消耗主模型上下文。2026-10-16 通过网关进行的一次 Messages 网页搜索,为一次服务端搜索消耗了 8,154 个输入 token 和 828 个输出 token。指南对 Search 和 Fetch 分别按每 1,000 次成功且有结果的请求收取 5 元;结果条数不影响单次价格。多个查询仍对应多次请求。文档中的 Claude Code 与 Codex 优惠不能作为 Harness 免收费用的依据。除非关闭 `sendOriginalQuestion`,Search API 模式还会随每个查询把用户提示的至多 128 个字符发送给搜索服务。
|
||||
|
||||
Search 和 Fetch 各有账号级 5 QPS 限制,独立于模型并发,同一账号的 API 密钥共享额度。Fetch 还有目标域名限制。当前工具可并发发送四个查询,任一查询失败会使整批失败。提供方没有共享限速调度器、自动重试或回退;其他会话或进程也会消耗同一账号的额度。
|
||||
Search 和 Fetch 各有账号级 5 QPS 限制,独立于模型并发,同一账号的 API 密钥共享额度。Fetch 还有目标域名限制。工具每次调用最多接受 `searchMaxQueries` 个查询,默认 10 个;任一查询失败会使整批失败。Search 提供方在单个插件实例的任意一秒窗口内最多发送 `searchRequestsPerSecond` 个请求,默认 5 个,因此满批次的后五个查询比前五个晚一秒开始。该限速不跨进程共享,提供方也没有自动重试或回退;其他进程或客户端也会消耗同一账号的额度。
|
||||
|
||||
## 验证与尚未接入的 API 能力
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 3e229ecf646af866
|
||||
zh: d358507b1e1d381f
|
||||
/agent-note-wine-and-native-windows-ci/decision:
|
||||
en: 1125b44a170f70fb
|
||||
zh: 37838b9da2003651
|
||||
en: 9038773113cf3553
|
||||
zh: 67875d393093c105
|
||||
/agent-note-wine-and-native-windows-ci/alternatives-considered:
|
||||
en: 0e9a322cfc38d313
|
||||
zh: f7bdeb86ba4fea1a
|
||||
|
||||
@@ -30,7 +30,7 @@ Native watchers use `canonicalizeWatchPath()` to realpath the deepest existing a
|
||||
|
||||
Windows durable JSONL paths keep drive roots in native spelling and apply the extended-length namespace only to descendants and staging paths. The ACP teardown ladder uses real Node children, proves graceful and forced tiers with host-appropriate outcomes, and avoids claiming POSIX signal delivery on Windows. Executable fixtures provide `.cmd` shims and `PATHEXT` where the product accepts a bare command. Repository-cache helpers live inside the selected Git subpath so their declared `file:` dependencies expose command shims identically on Windows. The bundled installer exports pnpm's own workspace-ignore configuration, retains `PNPM_HOME` for pnpm data while removing that directory from lifecycle `PATH`, and prioritizes `.CMD` in `PATHEXT`; nested Git-package installation therefore cannot rejoin the enclosing workspace or let an inherited Windows pnpm executable outrank the transaction-owned wrapper.
|
||||
|
||||
Post-boot profile watcher setup proceeds only while the root fiber and Loader are both live. A concurrent setup error is contained only when the same invocation's recorded signal already owns shutdown; unrelated HMR failures remain loud. The [process-shutdown controller](../../../../apps/cli/src/process-shutdown.ts) lets a successful one-shot completion drain Node's remaining handles after root disposal, while teardown failure, deadline, and signal escalation retain forced exit. The vendored Include serializes debounced writes, retries only transient access or busy failures with bounded backoff, and observes every timer rejection. A terminal persistence failure remains on the queue and is rethrown to the teardown owner, while successful teardown drains the latest write.
|
||||
Post-boot profile watcher setup proceeds only while the root fiber and Loader are both live. A concurrent setup error is contained only when the same invocation's recorded signal already owns shutdown; unrelated HMR failures remain loud. The [process-shutdown controller](../../../../apps/cli/src/process-shutdown.ts) lets a successful one-shot completion drain Node's remaining handles after root disposal, while teardown failure, deadline, and signal escalation retain forced exit. The vendored Include journals runtime changes and writes them through an atomic rename, retrying only transient access or busy failures with bounded backoff. `flush()` and `stop()` reject while changes remain unsaved, and a later successful flush clears the previous error.
|
||||
|
||||
Shiki disables lazy TextMate-regex compilation and warms each boot grammar before user content enters the unchanged per-line tokenization budget, so scheduler contention cannot publish a partial highlighted stream. The Codex real-product fixture is pinned to stable 0.153.4 schemas and selects an actually advertised command tool and argument shape, preserving the provider-owned protocol while proving unattended rejection and whole-tree exit on each host.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Wine 在 Linux 内核与区分大小写的 ext4 之上采用 hoisted 依赖布
|
||||
|
||||
Windows 的持久 JSONL 路径会保留驱动器根目录的原生写法,并仅对后代路径与暂存路径应用扩展长度命名空间。ACP(Agent Client Protocol)拆卸阶梯使用真实 Node 子进程,以符合宿主语义的结果证明优雅终止与强制终止两个层级,并避免声称 Windows 会交付 POSIX 信号。产品接受裸命令时,可执行 fixture 会提供 `.cmd` 包装脚本与 `PATHEXT`。repository-cache 辅助包位于所选 Git 子路径内,因此它们声明的 `file:` 依赖会在 Windows 上以相同方式暴露命令包装脚本。随附的安装器会导出 pnpm 自有的 workspace-ignore 配置,保留 `PNPM_HOME` 作为 pnpm 数据配置,同时从生命周期 `PATH` 中移除该目录,并在 `PATHEXT` 中优先选择 `.CMD`;因此,嵌套 Git 包安装既不会重新加入外层 workspace,也不会让继承的 Windows pnpm 可执行文件抢在事务持有的 wrapper 之前。
|
||||
|
||||
启动后,只有根 fiber 与 Loader 均处于活跃状态时,系统才会继续设置 profile watcher。只有当同一次调用所记录的信号已取得关闭流程所有权时,系统才会隔离并发设置错误;无关 HMR 故障仍会响亮失败。[进程关闭控制器](../../../../apps/cli/src/process-shutdown.ts)会在根级 dispose 成功后让单次任务的正常完成流程排空 Node 剩余句柄,同时让拆卸失败、截止时间到期和信号升级继续强制退出。vendored Include 会串行化防抖写入,只对瞬时访问或忙碌故障执行有界退避重试,并确保每个由计时器触发的拒绝都得到观察。持久化最终失败后,该故障会保留在队列中,并重新抛给拆卸责任方;成功拆卸则会排空最新写入。
|
||||
启动后,只有根 fiber 与 Loader 均处于活跃状态时,系统才会继续设置 profile watcher。只有当同一次调用所记录的信号已取得关闭流程所有权时,系统才会隔离并发设置错误;无关 HMR 故障仍会响亮失败。[进程关闭控制器](../../../../apps/cli/src/process-shutdown.ts)会在根级 dispose 成功后让单次任务的正常完成流程排空 Node 剩余句柄,同时让拆卸失败、截止时间到期和信号升级继续强制退出。vendored Include 通过 journal 记录运行时变更,并以原子 rename 写入,只对瞬时访问或忙碌故障执行有界退避重试。仍有未保存变更时,`flush()` 和 `stop()` 会拒绝;之后一次成功的 flush 会清除先前的错误。
|
||||
|
||||
Shiki 会禁用 TextMate 正则的延迟编译,并在用户内容进入保持不变的逐行 tokenization(词元化)预算前预热每种启动语法,从而避免调度器争用发布不完整的高亮流。Codex 真实产品 fixture 固定使用稳定版 0.153.4 schema,并选择实际提供的命令工具与对应参数形态;这样既保留由提供方负责的协议,也能在每种宿主上证明无人值守拒绝和整棵进程树退出。
|
||||
|
||||
|
||||
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: d4ab65a39cf4fd7c
|
||||
zh: 62144be9b4fd7956
|
||||
/agent-note-keep-loader-mutations-non-transactional/decision:
|
||||
en: 9b661159de63d421
|
||||
zh: 15697f468a90edb9
|
||||
en: 83268169e9436127
|
||||
zh: ce740df03e9fe24a
|
||||
/agent-note-keep-loader-mutations-non-transactional/alternatives-considered:
|
||||
en: 72b871064c7ae315
|
||||
zh: a09e5168f8124c4c
|
||||
en: 645db43a48b9cfc1
|
||||
zh: d03fabd1e82dadbd
|
||||
/agent-note-keep-loader-mutations-non-transactional/consequences:
|
||||
en: 100f687b9d498308
|
||||
zh: 1c4bc2d989e43ec7
|
||||
|
||||
@@ -12,19 +12,19 @@ Transactional config reload preserves an old plugin generation after a failed ed
|
||||
|
||||
Revert the five commits in #932, resolving package moves and retaining independent later behavior. The reported merge commit belongs to the larger #936 dependency chain; reverting its first-parent diff would remove unrelated repository-plugin support. The [vendor ledger](../../../../vendor/README.md#local-modifications) records every retained source change against the unchanged pins.
|
||||
|
||||
Loader changes entry options eagerly. EntryGroup starts siblings concurrently and logs application failures; EntryTree waits for outstanding work without rejecting failed fibers. Neither restores a previous plugin or configuration. Include retains parse validation and patch reapplication, but plugin failures can leave a partially applied tree.
|
||||
Loader changes entry options eagerly. EntryGroup starts siblings concurrently and logs application failures; EntryTree waits for outstanding work without rejecting failed fibers. Neither restores a previous plugin or configuration. Include keeps the running tree after an invalid file, but plugin failures can leave a partially applied tree.
|
||||
|
||||
Application consumers own their completion checks. The CLI waits for its fallback HMR service before installing live patch watchers. The directory chooser checks the entries it mounts. The chooser and browser package runner capture the first fiber-disposal result before removing the entry, then await it before reporting teardown complete. Preset mounting waits for its subtree and reports import, activation, and missing-service failures. [App boot](../../../../packages/boot/app-boot/README.md) owns exact patch-file watching, activation audits, and partial-context cleanup. Web startup audits activation before printing a URL or opening the browser. These adaptations preserve existing consumer behavior after the reverse patch.
|
||||
|
||||
Fiber, Entry, and isolate keep their upstream update return behavior. App boot observes discarded restart promises through the existing `internal/update` waterfall and waits for fibers before auditing a patch reload. The detached import-completion observer handles both fiber outcomes; the fiber still retains its failure for an explicit audit. Durable Include writes drain before and after child removal so a later teardown write cannot erase an earlier terminal write failure.
|
||||
Fiber, Entry, and isolate keep their upstream update return behavior. App boot observes discarded restart promises through the existing `internal/update` waterfall and waits for fibers before auditing a patch reload. The detached import-completion observer handles both fiber outcomes; the fiber still retains its failure for an explicit audit. Include persistence follows the journal adaptation in the vendor ledger: `flush()` and `stop()` reject while changes remain unsaved.
|
||||
|
||||
Two #932-specific vendor changes remain: awaited initial-file creation and forced rereading in Include, and Schemastery conditional exports. Restoring the pre-#932 debounced write/read sequence reproduces `ENOENT` in the missing-file initialization test. Keeping these two lines preserves the existing `initial` option without an application-side file writer or a second YAML serializer. Removing Schemastery exports reproduces `ERR_REQUIRE_ESM_RACE_CONDITION` while the Web preset suite boots: Node falls back to the CJS entry during concurrent ESM imports. The HMR injection decorators, conditional patch cloning, and update return values use the pre-#932 behavior. Explicit `workspace:` dependencies make the #932 workspace-link switch and dedicated lockfile check unnecessary.
|
||||
One #932-specific vendor change remains: Schemastery conditional exports. Include 1.1.1 writes the `initial` file before reading it, so missing-file initialization no longer needs a local change. Removing Schemastery exports reproduces `ERR_REQUIRE_ESM_RACE_CONDITION` while the Web preset suite boots: Node falls back to the CJS entry during concurrent ESM imports. The HMR injection decorators and update return values use the pre-#932 behavior; patch cloning follows upstream Include. Explicit `workspace:` dependencies make the #932 workspace-link switch and dedicated lockfile check unnecessary.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep transactional Loader updates.** They provide automatic recovery from a rejected plugin candidate, but retain the vendored lifecycle machinery being removed. Parse failures can be contained without plugin rollback.
|
||||
|
||||
**Restore every vendored file verbatim.** This would also remove lazy injected config evaluation, conditional disabled entries, lifecycle disposal fixes, durable writes, and module-loader compatibility. Those changes have independent consumers and remain recorded in the vendor ledger.
|
||||
**Restore every vendored file verbatim.** This would also remove lazy injected config evaluation, conditional disabled entries, lifecycle disposal fixes, and module-loader compatibility. Those changes have independent consumers and remain recorded in the vendor ledger.
|
||||
|
||||
**Move generic rollback into app boot.** This would retain the same candidate-generation and restoration obligations under another owner. Applications instead report failures and allow a later valid edit to recover.
|
||||
|
||||
|
||||
@@ -12,19 +12,19 @@ Status: implemented
|
||||
|
||||
撤销 #932 中的五个提交,解决包移动冲突并保留后续独立行为。记录的合并提交属于更大的 #936 依赖链;撤销其第一父提交差异还会删除无关的仓库插件支持。[Vendor 修改记录](../../../../vendor/README.md#local-modifications) 按不变的固定来源记录每项保留的源码更改。
|
||||
|
||||
Loader 立即更改条目选项。EntryGroup 并发启动同级条目并记录应用失败;EntryTree 等待未完成的工作,但不因失败的 fiber 而拒绝。两者均不恢复旧插件或配置。Include 保留解析校验和 patch 重应用,但插件失败可能留下部分应用的配置树。
|
||||
Loader 立即更改条目选项。EntryGroup 并发启动同级条目并记录应用失败;EntryTree 等待未完成的工作,但不因失败的 fiber 而拒绝。两者均不恢复旧插件或配置。Include 在文件无效时保留正在运行的配置树,但插件失败可能留下部分应用的配置树。
|
||||
|
||||
应用消费者负责完成检查。CLI 在安装实时 patch 监视器前等待其回退 HMR 服务。目录选择器检查其挂载的条目。选择器和浏览器包运行器在移除条目前取得首次 fiber 释放的结果,并等待该结果后才报告拆卸完成。预设挂载等待其子树,并报告导入、激活和缺少服务的失败。[应用启动](../../../../packages/boot/app-boot/README.zh.md) 负责精确 patch 文件监视、激活检查和部分上下文清理。Web 启动在打印 URL 或打开浏览器前检查激活状态。这些适配维持应用反向补丁后已有的消费者行为。
|
||||
|
||||
Fiber、Entry 和 isolate 保持上游的更新返回行为。App boot 通过现有的 `internal/update` waterfall 观察被丢弃的重启 promise,并在检查 patch 重载前等待 fiber。游离的导入完成观察器处理 fiber 的两种结果;fiber 仍保留失败信息供显式检查。Include 的持久写入在删除子条目前后均排空,防止后续拆卸写入掩盖更早的终止性写入失败。
|
||||
Fiber、Entry 和 isolate 保持上游的更新返回行为。App boot 通过现有的 `internal/update` waterfall 观察被丢弃的重启 promise,并在检查 patch 重载前等待 fiber。游离的导入完成观察器处理 fiber 的两种结果;fiber 仍保留失败信息供显式检查。Include 的持久化遵循 vendor 修改记录中的 journal 适配:仍有未保存变更时,`flush()` 和 `stop()` 会拒绝。
|
||||
|
||||
保留两项 #932 专属 vendor 改动:Include 中等待初始文件创建并强制重新读取,以及 Schemastery 条件导出。恢复 #932 前的防抖写入和读取顺序,会在缺失文件初始化测试中复现 `ENOENT`。保留这两行可以维持已有 `initial` 选项,而无需在应用侧增加文件写入器或另一份 YAML 序列化逻辑。移除 Schemastery exports 后,Web preset 测试在启动时复现 `ERR_REQUIRE_ESM_RACE_CONDITION`:并发 ESM 导入使 Node 回退到 CJS 入口。HMR 注入装饰器、条件 patch 克隆及更新返回值采用 #932 前的行为。显式 `workspace:` 依赖使 #932 的 workspace 链接开关与专用锁文件检查不再必要。
|
||||
保留一项 #932 专属 vendor 改动:Schemastery 条件导出。Include 1.1.1 会先写入 `initial` 文件再读取,因此缺失文件的初始化不再需要本地改动。移除 Schemastery exports 后,Web preset 测试在启动时复现 `ERR_REQUIRE_ESM_RACE_CONDITION`:并发 ESM 导入使 Node 回退到 CJS 入口。HMR 注入装饰器及更新返回值采用 #932 前的行为;patch 克隆遵循上游 Include。显式 `workspace:` 依赖使 #932 的 workspace 链接开关与专用锁文件检查不再必要。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留事务化 Loader 更新。** 它们能从被拒绝的插件候选自动恢复,但会保留本次删除的 vendor 生命周期机制。解析失败可以独立于插件回滚进行处理。
|
||||
|
||||
**逐字恢复所有 vendor 文件。** 这还会删除延迟注入配置求值、条件禁用条目、生命周期释放修复、持久写入和模块加载器兼容性。这些更改具有独立消费者,并继续记录在 vendor 修改记录中。
|
||||
**逐字恢复所有 vendor 文件。** 这还会删除延迟注入配置求值、条件禁用条目、生命周期释放修复和模块加载器兼容性。这些更改具有独立消费者,并继续记录在 vendor 修改记录中。
|
||||
|
||||
**将通用回滚移入应用启动。** 这会在另一归属下保留相同的候选代次与恢复义务。应用改为报告失败,并允许后续有效编辑恢复。
|
||||
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-11-02-deepseek-deferred-tool-failures.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/simplification/2026-11-02-deepseek-deferred-tool-failures.md
|
||||
/agent-note-expose-deepseek-deferred-only-request-failures:
|
||||
en: 1c4af8b4e2ec0e46
|
||||
zh: fcb806d33973025e
|
||||
/agent-note-expose-deepseek-deferred-only-request-failures/problem:
|
||||
en: c974406f21854541
|
||||
zh: 701cd64cabdfee79
|
||||
/agent-note-expose-deepseek-deferred-only-request-failures/decision:
|
||||
en: 6878caaaebd91a41
|
||||
zh: 2ebd5da04dfb1ac6
|
||||
/agent-note-expose-deepseek-deferred-only-request-failures/alternatives-considered:
|
||||
en: d10efff97379cfbb
|
||||
zh: 572b124ddbb2d86d
|
||||
/agent-note-expose-deepseek-deferred-only-request-failures/consequences:
|
||||
en: d60323c595b076f4
|
||||
zh: 1bf1068424777b7e
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Agent Note: Expose DeepSeek deferred-only request failures
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-11-02-deepseek-deferred-tool-failures.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A synthetic immediate tool can make an all-deferred DeepSeek request acceptable while hiding an invalid tool-update projection. It also changes the request prefix and advertises a tool with no executor.
|
||||
|
||||
## Decision
|
||||
|
||||
The DeepSeek adapter sends deferred declarations unchanged and propagates provider rejection. This replaces the placeholder accommodation described by the [tool-update decision](../architecture/2026-09-20-dynamic-tool-updates.md), whose shared history projection remains authoritative.
|
||||
|
||||
Ordinary addition-only histories start with native tools or an empty set. Removals and first additions to an empty set rebuild native declarations; later deferred additions retain a native baseline. Explicit deferred-only registrations, direct adapter calls, and custom in-history routes can still produce all-deferred requests. Those configurations receive no adapter workaround.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep a synthetic immediate tool.** It conceals unexpected deferred-only requests and cannot preserve a prefix that lacked that tool. Automatically activating a real tool instead changes its declared availability.
|
||||
|
||||
**Reject locally.** Provider validation owns acceptance; another local restriction would duplicate it. Reconsider accommodation only for a supported workflow that requires deferred-only requests and cannot retain a native declaration.
|
||||
|
||||
## Consequences
|
||||
|
||||
Deferred-only compatibility is deliberately given up so request errors remain visible. Serializer and HTTP tests verify exact declarations and propagation of a provider rejection. The all-deferred keyless snapshot verifies request construction only; replay success does not establish provider acceptance. Replacement and empty-set scenarios verify the supported addition-only path.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Agent Note: 显露 DeepSeek 全延迟请求失败
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-11-02-deepseek-deferred-tool-failures.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
合成的立即加载工具可使 DeepSeek 接受全延迟请求,却掩盖无效的工具更新投影。它还会改变请求前缀,并声明一个没有执行器的工具。
|
||||
|
||||
## Decision
|
||||
|
||||
DeepSeek 适配器原样发送延迟声明,并传递提供方的拒绝错误。这取代了[工具更新决策](../architecture/2026-09-20-dynamic-tool-updates.zh.md)中描述的占位工具兼容机制;其中的共享历史投影仍是权威依据。
|
||||
|
||||
普通 addition-only 历史以原生工具或空集开始。移除工具或首次向空集添加工具会重建原生声明;后续延迟添加保留原生基线。显式全延迟注册、直接调用适配器和自定义 in-history 路由仍可产生全延迟请求。适配器不为这些配置提供变通处理。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留合成的立即加载工具。** 它会掩盖意外的全延迟请求,也无法保留原本没有该工具的前缀。改为自动激活真实工具则会改变其声明的可用性。
|
||||
|
||||
**在本地拒绝。** 是否接受请求由提供方验证决定;额外的本地限制会重复该验证。只有受支持工作流确实需要全延迟请求,且无法保留原生声明时,才重新考虑兼容处理。
|
||||
|
||||
## Consequences
|
||||
|
||||
有意放弃全延迟兼容性,使请求错误保持可见。序列化和 HTTP 测试验证精确声明以及提供方拒绝错误的传递。全延迟无密钥快照仅验证请求构造;回放成功不代表提供方接受。替换和空集场景验证受支持的 addition-only 路径。
|
||||
@@ -40,3 +40,8 @@
|
||||
- id: session-log-deepseek
|
||||
config:
|
||||
enabled: false
|
||||
|
||||
# The recorded tool catalog includes the optional patch tool.
|
||||
- id: tool-fs
|
||||
config:
|
||||
applyPatch: true
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('Python SDK dsh profile keyless smoke', () => {
|
||||
{ label: 'reports max-token turns with mapping enabled through env', envValue: 'true', editorEnabled: false },
|
||||
{ label: 'reports max-token turns with mapping disabled through env', envValue: 'false', editorEnabled: false },
|
||||
{ label: 'allows an explicit patch to enable str_replace_editor', envValue: undefined, editorEnabled: true },
|
||||
{ label: 'allows an explicit patch to enable apply_patch', envValue: undefined, editorEnabled: false, patchEnabled: true },
|
||||
{ label: 'uses the PTC executor overlay despite the retired environment selector', envValue: undefined, editorEnabled: false, ptc: true },
|
||||
// The shipped sdk profile mounts no observation policy: an edit of a file the model never read lands on disk.
|
||||
{ label: 'edits a file the model never read through the shipped profile', envValue: undefined, editorEnabled: false, unreadEdit: true },
|
||||
@@ -64,11 +65,14 @@ describe('Python SDK dsh profile keyless smoke', () => {
|
||||
const { envValue, editorEnabled } = scenario
|
||||
const ptc = 'ptc' in scenario && scenario.ptc
|
||||
const unreadEdit = 'unreadEdit' in scenario && scenario.unreadEdit
|
||||
const patchEnabled = 'patchEnabled' in scenario && scenario.patchEnabled
|
||||
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-python-sdk-runtime-smoke-'))
|
||||
const unreadFile = join(root, 'notes.txt')
|
||||
if (unreadEdit) await writeFile(unreadFile, 'alpha\n')
|
||||
const editorPatch = join(root, 'editor.patch.yml')
|
||||
const fsPatch = join(root, 'fs.patch.yml')
|
||||
if (patchEnabled) await writeFile(fsPatch, '- {id: tool-fs, config: {applyPatch: true}}\n')
|
||||
if (editorEnabled) await writeFile(editorPatch, [
|
||||
'- insert:',
|
||||
' - id: tool-str-replace-editor',
|
||||
@@ -103,6 +107,7 @@ describe('Python SDK dsh profile keyless smoke', () => {
|
||||
'--profile',
|
||||
'sdk',
|
||||
...(editorEnabled ? ['--patch', editorPatch] : []),
|
||||
...(patchEnabled ? ['--patch', fsPatch] : []),
|
||||
...(ptc ? ['--patch', join(repoRoot, 'apps/cli/config/examples/ptc.yml')] : []),
|
||||
], {
|
||||
cwd: repoRoot,
|
||||
@@ -189,16 +194,16 @@ describe('Python SDK dsh profile keyless smoke', () => {
|
||||
const toolNames = tools.map(tool => tool.name)
|
||||
expect(modelRequests[0]?.output_config).toEqual({ effort: 'max' })
|
||||
expect(modelRequests[0]?.max_tokens).toBe(1234)
|
||||
const expectedTools = ['read', 'write', 'edit', 'apply_patch', 'web_fetch', 'web_search', 'subagent', 'subagent_fork',
|
||||
const expectedTools = ['read', 'write', 'edit', 'web_fetch', 'web_search', 'subagent', 'subagent_fork',
|
||||
'present', 'ask_user_question', 'schedule_create', 'schedule_edit', 'schedule_list', 'schedule_view', 'plugin_manager']
|
||||
const requestText = JSON.stringify(modelRequests[0])
|
||||
if (ptc) {
|
||||
expect(toolNames).toEqual(['run_code'])
|
||||
// The PTC overlay keeps the pre-apply_patch tool set, as the Web ptc preset does.
|
||||
for (const tool of expectedTools.filter(tool => tool !== 'apply_patch')) expect(requestText.includes(`${tool}:`), tool).toBe(true)
|
||||
for (const tool of expectedTools) expect(requestText.includes(`${tool}:`), tool).toBe(true)
|
||||
expect(requestText.includes('apply_patch:')).toBe(false)
|
||||
} else {
|
||||
expect(toolNames).toEqual(expect.arrayContaining(expectedTools))
|
||||
expect(toolNames.includes('apply_patch')).toBe(patchEnabled)
|
||||
expect(toolNames.includes('str_replace_editor')).toBe(editorEnabled)
|
||||
expect(toolNames).not.toContain('list_subagent_models')
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ describe('the shipped Web composition', () => {
|
||||
// excluded for the reason the TUI composition e2e excludes them — they
|
||||
// depend on ripgrep being present on the machine.
|
||||
expect(toolNames(ctx, handle.agent).filter(name => name !== 'glob' && name !== 'grep')).toEqual([
|
||||
'apply_patch', 'ask_user_question', 'bash', 'create_goal', 'edit', 'exit_plan_mode',
|
||||
'ask_user_question', 'bash', 'create_goal', 'edit', 'exit_plan_mode',
|
||||
'get_goal', 'interrupt_agent', 'job_kill', 'job_list', 'job_output', 'list_agents', 'list_subagent_models', 'plugin_manager', 'present', 'read', 'read_image',
|
||||
'schedule_create', 'schedule_edit', 'schedule_list', 'schedule_view',
|
||||
'send_message', 'skill',
|
||||
|
||||
@@ -59,3 +59,37 @@ it.each(languages)('selects %s from the browser, localizes metadata and persists
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
})
|
||||
|
||||
const forgePreviews = [
|
||||
['de', 'de-DE', 'Plugins', 'Eine Unterhaltung mit verknüpftem GitHub-Pull-Request, Prüfungen, Reviews, Codeunterschieden und einem separaten Arbeitsbaum'],
|
||||
['es', 'es-MX', 'Complementos', 'Una conversación vinculada a un pull request de GitHub, con comprobaciones, revisiones, diferencias de código y un árbol de trabajo aislado'],
|
||||
['fr', 'fr-CA', 'Plugins', 'Une conversation liée à une pull request GitHub, avec vérifications, revues, différences de code et un arbre de travail isolé'],
|
||||
['ja', 'ja-JP', 'プラグイン', 'GitHub のプルリクエストに関連付けられた会話、チェックとレビューの状況、コードの差分、独立したワークツリー'],
|
||||
['pt', 'pt-BR', 'Plug-ins', 'Uma conversa vinculada a um pull request do GitHub, com verificações, revisões, diferenças de código e uma árvore de trabalho isolada'],
|
||||
['ru', 'ru-RU', 'Плагины', 'Беседа, связанная с pull request GitHub: проверки, рецензии, различия в коде и отдельное рабочее дерево'],
|
||||
] as const
|
||||
|
||||
it.each(forgePreviews)('reads the Forge preview in %s before activation', async (id, tag, plugins, alt) => {
|
||||
const resources: { scaffold?: WebScaffold; browser?: Browser } = {}
|
||||
onTestFinished(async () => {
|
||||
try { await resources.browser?.close() } finally { await resources.scaffold?.close() }
|
||||
})
|
||||
const scaffold = await launchWebScaffold()
|
||||
resources.scaffold = scaffold
|
||||
const browser = await chromium.launch()
|
||||
resources.browser = browser
|
||||
const page = await browser.newPage({ locale: tag, viewport: { width: 1440, height: 960 } })
|
||||
const tripwire = watchConsole(page)
|
||||
onTestFailed(() => saveFailureShot(page, `forge-preview-${id}`))
|
||||
await page.goto(scaffold.authenticatedUrl)
|
||||
await expect.poll(() => page.locator('html').getAttribute('lang')).toBe(id)
|
||||
await page.getByRole('button', { name: plugins, exact: true }).click()
|
||||
const card = page.locator('[data-plugin-package="@deepseek-ai/dsh-experimental-forge-bundle"]')
|
||||
expect(await card.getByRole('switch').getAttribute('aria-checked')).toBe('false')
|
||||
await card.getByRole('button').click()
|
||||
const image = page.locator('[data-detail-document]').getByRole('img', { name: alt, exact: true })
|
||||
await image.waitFor()
|
||||
await image.evaluate(async (node: HTMLImageElement) => { await node.decode() })
|
||||
expect(await image.evaluate((node: HTMLImageElement) => [node.naturalWidth, node.naturalHeight])).toEqual([640, 300])
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
})
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
/** Saved credentials and endpoints reach the assembled editor and provider requests. */
|
||||
import { once } from 'node:events'
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { createServer } from 'node:http'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium } from 'playwright'
|
||||
import { expect, it, onTestFinished } from 'vitest'
|
||||
import { BlockAssembler } from '@deepseek-ai/dsh-llm'
|
||||
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
||||
import { createLaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
|
||||
import { captureStableAria, compareOrRefreshGolden, launchWebScaffold, webSnapshotMode } from './scaffold.ts'
|
||||
import { openSettings, ZH_BROWSER_LOCALE } from './support.ts'
|
||||
|
||||
it.each([false, true])('edits saved DeepSeek keys and restores environment detection (environment present: %s)', async (hasEnvironment) => {
|
||||
const scaffold = await launchWebScaffold({
|
||||
deepSeekMissingCredential: true,
|
||||
extraOverlayPath: fileURLToPath(new URL('./fixtures/credential-editor.patch.yml', import.meta.url)),
|
||||
openInAppEnvironment: createLaunchEnvironmentSnapshot([{
|
||||
source: 'process', values: hasEnvironment ? { DEEPSEEK_API_KEY: 'fixture-environment-key' } : {},
|
||||
}]),
|
||||
})
|
||||
try {
|
||||
const browser = await chromium.launch()
|
||||
try {
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 960 }, locale: ZH_BROWSER_LOCALE })
|
||||
const ref = credentialRef('DEEPSEEK_API_KEY')
|
||||
await scaffold.ctx.credentials.set(ref, 'fixture-saved-deepseek-key')
|
||||
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
|
||||
await openSettings(page, 'zh')
|
||||
const dialog = page.getByRole('dialog', { name: '设置' })
|
||||
await dialog.getByRole('button', { name: '模型', exact: true }).click()
|
||||
const edit = dialog.getByRole('button', { name: '编辑 DeepSeek (deepseek-official)', exact: true })
|
||||
await edit.click()
|
||||
const key = dialog.getByLabel('API 密钥', { exact: true })
|
||||
await expect.poll(() => key.inputValue()).toBe('fixture-saved-deepseek-key')
|
||||
expect(await key.getAttribute('type')).toBe('password')
|
||||
await compareOrRefreshGolden(
|
||||
fileURLToPath(new URL('./expected/credential-editor/saved-key.expected.md', import.meta.url)),
|
||||
await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd), webSnapshotMode(),
|
||||
)
|
||||
await dialog.getByRole('button', { name: '显示密钥', exact: true }).click()
|
||||
expect(await key.getAttribute('type')).toBe('text')
|
||||
await key.fill('fixture-cancelled-key')
|
||||
await dialog.getByRole('button', { name: '取消', exact: true }).click()
|
||||
expect(await scaffold.ctx.credentials.readStored(ref)).toBe('fixture-saved-deepseek-key')
|
||||
await edit.click()
|
||||
await expect.poll(() => key.inputValue()).toBe('fixture-saved-deepseek-key')
|
||||
expect(await key.getAttribute('type')).toBe('password')
|
||||
await key.fill('fixture-replacement-key')
|
||||
await dialog.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await dialog.getByText('已保存 DeepSeek (deepseek-official)。', { exact: true }).waitFor()
|
||||
expect(await scaffold.ctx.credentials.readStored(ref)).toBe('fixture-replacement-key')
|
||||
await page.reload({ waitUntil: 'load' })
|
||||
await openSettings(page, 'zh')
|
||||
await dialog.getByRole('button', { name: '模型', exact: true }).click()
|
||||
await edit.click()
|
||||
await expect.poll(() => key.inputValue()).toBe('fixture-replacement-key')
|
||||
await key.fill('')
|
||||
expect(await key.getAttribute('placeholder')).toBe('留空使用环境认证(DEEPSEEK_API_KEY)')
|
||||
await dialog.getByText('环境凭据状态尚未确认', { exact: true }).waitFor()
|
||||
expect(await scaffold.ctx.credentials.readStored(ref)).toBe('fixture-replacement-key')
|
||||
await dialog.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await dialog.getByText('已保存 DeepSeek (deepseek-official)。', { exact: true }).waitFor()
|
||||
expect(await scaffold.ctx.credentials.readStored(ref)).toBeUndefined()
|
||||
// A missing credential opens the first-run settings card automatically;
|
||||
// an environment credential keeps an ordinary row whose editor is closed.
|
||||
if (hasEnvironment) await edit.click()
|
||||
await expect.poll(() => key.inputValue()).toBe('')
|
||||
expect(await key.getAttribute('placeholder')).toBe('留空使用环境认证(DEEPSEEK_API_KEY)')
|
||||
const hint = hasEnvironment
|
||||
? '已检测到环境变量(DEEPSEEK_API_KEY)'
|
||||
: '未检测到环境变量。请填写 API 密钥,或设置环境变量 DEEPSEEK_API_KEY。'
|
||||
const detection = dialog.getByText(hint, { exact: true })
|
||||
await detection.waitFor()
|
||||
const colors = await detection.evaluate((element, present) => {
|
||||
const label = element as HTMLElement
|
||||
const actual = getComputedStyle(label).color
|
||||
const previous = label.style.color
|
||||
label.style.color = present ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-warn-label)'
|
||||
const expected = getComputedStyle(label).color
|
||||
label.style.color = previous
|
||||
return { actual, expected }
|
||||
}, hasEnvironment)
|
||||
expect(colors.actual).toBe(colors.expected)
|
||||
expect(await dialog.getByRole('button', { name: '显示密钥', exact: true }).count()).toBe(0)
|
||||
expect((await scaffold.ctx.credentials.resolve(ref))?.source).toBe(hasEnvironment ? 'env' : undefined)
|
||||
await compareOrRefreshGolden(
|
||||
fileURLToPath(new URL(`./expected/credential-editor/${hasEnvironment ? 'detected' : 'missing'}-environment.expected.md`, import.meta.url)),
|
||||
await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd), webSnapshotMode(),
|
||||
)
|
||||
} finally { await browser.close() }
|
||||
} finally { await scaffold.close() }
|
||||
})
|
||||
|
||||
/** Local provider endpoint recording the destination path and authentication actually received. */
|
||||
async function connectionProbe(messages: boolean) {
|
||||
const calls: { path: string; key: string | undefined }[] = []
|
||||
const server = createServer((request, response) => {
|
||||
request.on('end', () => {
|
||||
const key = messages ? request.headers['x-api-key'] : request.headers.authorization
|
||||
calls.push({ path: request.url ?? '', key: typeof key === 'string' ? key : undefined })
|
||||
const events = messages ? [
|
||||
{ type: 'message_start', message: { id: 'probe', type: 'message', role: 'assistant', model: 'credential-editor-model', content: [], usage: { input_tokens: 1, output_tokens: 0 } } },
|
||||
{ type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } },
|
||||
{ type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: 'connection-ok' } },
|
||||
{ type: 'content_block_stop', index: 0 },
|
||||
{ type: 'message_delta', delta: { stop_reason: 'end_turn' }, usage: { output_tokens: 1 } },
|
||||
{ type: 'message_stop' },
|
||||
] : [
|
||||
{ choices: [{ delta: { role: 'assistant', content: 'connection-ok' }, index: 0, finish_reason: null }] },
|
||||
{ choices: [{ delta: {}, index: 0, finish_reason: 'stop' }], usage: { prompt_tokens: 1, completion_tokens: 1 } },
|
||||
]
|
||||
response.writeHead(200, { 'content-type': 'text/event-stream' })
|
||||
response.end(events.map(event => `${'type' in event ? `event: ${event.type}\n` : ''}data: ${JSON.stringify(event)}\n\n`).join('')
|
||||
+ (messages ? '' : 'data: [DONE]\n\n'))
|
||||
})
|
||||
request.resume()
|
||||
})
|
||||
onTestFinished(async () => {
|
||||
await new Promise<void>((resolve) => { server.close(() => { resolve() }); server.closeAllConnections() })
|
||||
})
|
||||
server.listen(0, '127.0.0.1')
|
||||
await once(server, 'listening')
|
||||
const address = server.address()
|
||||
if (address === null || typeof address === 'string') throw new Error('connection probe did not bind TCP')
|
||||
return { url: `http://127.0.0.1:${String(address.port)}`, calls }
|
||||
}
|
||||
|
||||
it.each([
|
||||
{ provider: 'deepseek-official', ref: 'DEEPSEEK_API_KEY', messages: true },
|
||||
{ provider: 'openai', ref: 'OPENAI_API_KEY', messages: false },
|
||||
{ provider: 'anthropic', ref: 'ANTHROPIC_API_KEY', messages: true },
|
||||
{ provider: 'credential-fixture', ref: 'CREDENTIAL_FIXTURE_API_KEY', messages: false },
|
||||
])('uses the saved endpoint and key on the wire for $provider, then restores fallbacks', async ({ provider, ref, messages }) => {
|
||||
const ambient = await connectionProbe(messages)
|
||||
const saved = await connectionProbe(messages)
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-credential-routing-'))
|
||||
onTestFinished(() => rm(root, { recursive: true, force: true }))
|
||||
const overlay = join(root, 'connection.patch.yml')
|
||||
const models = [{ id: 'credential-editor-model' }]
|
||||
const baseSuffix = provider === 'anthropic' ? '' : '/v1'
|
||||
const operation = provider === 'anthropic' ? '/v1/messages?beta=true' : messages ? '/messages' : '/chat/completions'
|
||||
const ambientURL = `${ambient.url}/ambient${baseSuffix}`
|
||||
await writeFile(overlay, JSON.stringify([
|
||||
{ id: 'llm-deepseek', config: { models } },
|
||||
...provider === 'deepseek-official' ? [] : [{
|
||||
id: 'llm-pi-ai', config: { providers: { [provider]: {
|
||||
api: messages ? 'anthropic-messages' : 'openai-completions',
|
||||
baseURL: ambientURL, models,
|
||||
...provider === 'credential-fixture' ? {} : { apiKeyEnv: ref },
|
||||
} } },
|
||||
}],
|
||||
]))
|
||||
const scaffold = await launchWebScaffold({
|
||||
deepSeekMissingCredential: true,
|
||||
extraOverlayPath: overlay,
|
||||
openInAppEnvironment: createLaunchEnvironmentSnapshot([{
|
||||
source: 'process', values: {
|
||||
[ref]: 'fixture-environment-key',
|
||||
DEEPSEEK_BASE_URL: ambientURL,
|
||||
OPENAI_BASE_URL: ambientURL,
|
||||
ANTHROPIC_BASE_URL: ambientURL,
|
||||
},
|
||||
}]),
|
||||
})
|
||||
try {
|
||||
const browser = await chromium.launch()
|
||||
try {
|
||||
const page = await browser.newPage({ locale: ZH_BROWSER_LOCALE })
|
||||
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
|
||||
await openSettings(page, 'zh')
|
||||
const dialog = page.getByRole('dialog', { name: '设置' })
|
||||
await dialog.getByRole('button', { name: '模型', exact: true }).click()
|
||||
const edit = dialog.getByRole('button', {
|
||||
name: `编辑 ${provider === 'deepseek-official' ? 'DeepSeek (deepseek-official)' : provider}`, exact: true,
|
||||
})
|
||||
const key = dialog.getByLabel('API 密钥', { exact: true })
|
||||
const endpoint = dialog.getByLabel('API 地址', { exact: true })
|
||||
const savedURL = `${saved.url}/configured${baseSuffix}`
|
||||
const generate = async (): Promise<void> => {
|
||||
const assembler = new BlockAssembler()
|
||||
for await (const chunk of scaffold.ctx.llm.stream({ provider, model: 'credential-editor-model', messages: [] })) assembler.push(chunk)
|
||||
expect(assembler.message({ provider, model: 'credential-editor-model' }).content).toContainEqual({ type: 'text', text: 'connection-ok' })
|
||||
}
|
||||
await edit.click()
|
||||
await key.fill('fixture-saved-key')
|
||||
await dialog.getByText('自定义设置', { exact: true }).click()
|
||||
await endpoint.fill(savedURL)
|
||||
await dialog.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await expect.poll(() => key.count()).toBe(0)
|
||||
await generate()
|
||||
expect(saved.calls).toEqual([{ path: `/configured${baseSuffix}${operation}`, key: messages ? 'fixture-saved-key' : 'Bearer fixture-saved-key' }])
|
||||
expect(ambient.calls).toEqual([])
|
||||
await page.reload({ waitUntil: 'load' })
|
||||
await openSettings(page, 'zh')
|
||||
await dialog.getByRole('button', { name: '模型', exact: true }).click()
|
||||
await edit.click()
|
||||
expect(await key.inputValue()).toBe('fixture-saved-key')
|
||||
expect(await key.getAttribute('type')).toBe('password')
|
||||
await dialog.getByText('自定义设置', { exact: true }).click()
|
||||
expect(await endpoint.inputValue()).toBe(savedURL)
|
||||
await generate()
|
||||
expect(saved.calls).toHaveLength(2)
|
||||
expect(saved.calls[1]).toEqual(saved.calls[0])
|
||||
expect(ambient.calls).toEqual([])
|
||||
await key.fill('')
|
||||
await dialog.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await expect.poll(() => key.count()).toBe(0)
|
||||
await generate()
|
||||
expect(saved.calls).toHaveLength(3)
|
||||
expect(saved.calls[2]).toEqual({ path: `/configured${baseSuffix}${operation}`, key: messages ? 'fixture-environment-key' : 'Bearer fixture-environment-key' })
|
||||
expect(ambient.calls).toEqual([])
|
||||
await edit.click()
|
||||
await key.fill('fixture-saved-key')
|
||||
await dialog.getByText('自定义设置', { exact: true }).click()
|
||||
await endpoint.fill('')
|
||||
await dialog.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await expect.poll(() => key.count()).toBe(0)
|
||||
await generate()
|
||||
expect(saved.calls).toHaveLength(3)
|
||||
expect(ambient.calls).toEqual([{ path: `/ambient${baseSuffix}${operation}`, key: messages ? 'fixture-saved-key' : 'Bearer fixture-saved-key' }])
|
||||
await edit.click()
|
||||
await key.fill('')
|
||||
await dialog.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await expect.poll(() => key.count()).toBe(0)
|
||||
await generate()
|
||||
expect(saved.calls).toHaveLength(3)
|
||||
expect(ambient.calls[1]).toEqual({ path: `/ambient${baseSuffix}${operation}`, key: messages ? 'fixture-environment-key' : 'Bearer fixture-environment-key' })
|
||||
expect(ambient.calls).toHaveLength(2)
|
||||
expect(await scaffold.ctx.credentials.readStored(credentialRef(ref))).toBeUndefined()
|
||||
} finally { await browser.close() }
|
||||
} finally { await scaffold.close() }
|
||||
})
|
||||
@@ -1,25 +0,0 @@
|
||||
- dialog "设置":
|
||||
- text: 设置
|
||||
- navigation:
|
||||
- button "通用设置"
|
||||
- button "模型"
|
||||
- button "内置插件"
|
||||
- button "Agent 预设"
|
||||
- button "打开配置文件"
|
||||
- button "关闭"
|
||||
- heading "模型" [level=2]
|
||||
- paragraph: 填入各提供商的 API 密钥即可使用其模型。
|
||||
- list:
|
||||
- listitem:
|
||||
- text: DeepSeek
|
||||
- img "API 密钥已配置"
|
||||
- button "编辑 DeepSeek (deepseek-official)": 编辑
|
||||
- text: DeepSeek deepseek-official API 密钥
|
||||
- button "认证说明": "?"
|
||||
- textbox "API 密钥":
|
||||
- /placeholder: 留空使用环境认证(DEEPSEEK_API_KEY)
|
||||
- paragraph: 已检测到环境变量(DEEPSEEK_API_KEY)
|
||||
- group: 自定义设置
|
||||
- button "取消"
|
||||
- button "保存"
|
||||
- button "添加模型提供商"
|
||||
@@ -1,23 +0,0 @@
|
||||
- dialog "设置":
|
||||
- text: 设置
|
||||
- navigation:
|
||||
- button "通用设置"
|
||||
- button "模型"
|
||||
- button "内置插件"
|
||||
- button "Agent 预设"
|
||||
- button "打开配置文件"
|
||||
- button "关闭"
|
||||
- heading "模型" [level=2]
|
||||
- paragraph: 填入各提供商的 API 密钥即可使用其模型。
|
||||
- status: 已保存 DeepSeek (deepseek-official)。
|
||||
- list:
|
||||
- listitem:
|
||||
- text: DeepSeek deepseek-official API 密钥
|
||||
- button "认证说明": "?"
|
||||
- textbox "API 密钥":
|
||||
- /placeholder: 留空使用环境认证(DEEPSEEK_API_KEY)
|
||||
- paragraph: 未检测到环境变量。请填写 API 密钥,或设置环境变量 DEEPSEEK_API_KEY。
|
||||
- group: 自定义设置
|
||||
- button "取消"
|
||||
- button "保存"
|
||||
- button "添加模型提供商"
|
||||
@@ -1,26 +0,0 @@
|
||||
- dialog "设置":
|
||||
- text: 设置
|
||||
- navigation:
|
||||
- button "通用设置"
|
||||
- button "模型"
|
||||
- button "内置插件"
|
||||
- button "Agent 预设"
|
||||
- button "打开配置文件"
|
||||
- button "关闭"
|
||||
- heading "模型" [level=2]
|
||||
- paragraph: 填入各提供商的 API 密钥即可使用其模型。
|
||||
- list:
|
||||
- listitem:
|
||||
- text: DeepSeek
|
||||
- img "API 密钥已配置"
|
||||
- button "编辑 DeepSeek (deepseek-official)": 编辑
|
||||
- text: DeepSeek deepseek-official API 密钥
|
||||
- button "认证说明": "?"
|
||||
- textbox "API 密钥":
|
||||
- /placeholder: 留空使用环境认证(DEEPSEEK_API_KEY)
|
||||
- text: fixture-saved-deepseek-key
|
||||
- button "显示密钥"
|
||||
- group: 自定义设置
|
||||
- button "取消"
|
||||
- button "保存"
|
||||
- button "添加模型提供商"
|
||||
@@ -17,9 +17,7 @@
|
||||
- text: DeepSeek deepseek-official API 密钥
|
||||
- button "认证说明": "?"
|
||||
- textbox "API 密钥":
|
||||
- /placeholder: 留空使用环境认证(DEEPSEEK_API_KEY)
|
||||
- text: sk-messages-onboarding
|
||||
- button "显示密钥"
|
||||
- /placeholder: 留空使用已保存凭据
|
||||
- group:
|
||||
- text: 自定义设置 API 地址
|
||||
- textbox "API 地址":
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
- text: DeepSeek deepseek-official API 密钥
|
||||
- button "认证说明": "?"
|
||||
- textbox "API 密钥":
|
||||
- /placeholder: 留空使用环境认证(DEEPSEEK_API_KEY)
|
||||
- text: dsh_onboarding_fixture_key
|
||||
- button "显示密钥"
|
||||
- /placeholder: 留空使用已保存凭据
|
||||
- group:
|
||||
- text: 自定义设置 API 地址
|
||||
- textbox "API 地址":
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
- text: DeepSeek deepseek-official API 密钥
|
||||
- button "认证说明": "?"
|
||||
- textbox "API 密钥":
|
||||
- /placeholder: 留空使用环境认证(DEEPSEEK_API_KEY)
|
||||
- text: dsh_onboarding_fixture_key
|
||||
- button "显示密钥"
|
||||
- /placeholder: 留空使用已保存凭据
|
||||
- group:
|
||||
- text: 自定义设置 API 地址
|
||||
- textbox "API 地址":
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
- switch "启用 自动授权审查"
|
||||
- listitem:
|
||||
- button "查看 变更请求与工作树": 变更请求与工作树
|
||||
- text: 实验性 跟踪每个会话的变更请求及其检查与评审,新会话也可以在 Git 工作树中开始;GitHub 通过 gh 命令行访问。
|
||||
- text: 实验性 在会话侧栏集中跟踪关联的 GitHub PR:查看检查状态、评审意见与代码差异,并把修改建议交给智能体继续处理。也可查看本地改动,用独立工作树隔离任务。GitHub 功能需要本机安装 gh 并登录。
|
||||
- status
|
||||
- switch "启用 变更请求与工作树"
|
||||
- heading "已安装插件" [level=3]
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
- button "返回插件": 插件
|
||||
- status
|
||||
- switch "启用 变更请求与工作树"
|
||||
- heading "变更请求与工作树" [level=3]
|
||||
- text: v{{version}} 实验性
|
||||
- paragraph:
|
||||
- code: "@deepseek-ai/dsh-experimental-forge-bundle"
|
||||
- paragraph: 在会话侧栏集中跟踪关联的 GitHub PR:查看检查状态、评审意见与代码差异,并把修改建议交给智能体继续处理。也可查看本地改动,用独立工作树隔离任务。GitHub 功能需要本机安装 gh 并登录。
|
||||
- region "详细介绍":
|
||||
- paragraph:
|
||||
- img "会话关联 GitHub PR,跟踪检查、评审与代码差异,并用工作树隔离任务改动"
|
||||
- heading "包含的组件" [level=4]
|
||||
- text: 共 7 个 · 7 已停用
|
||||
- list:
|
||||
- listitem:
|
||||
- text: 工作树服务 创建命名的 Git 工作树,并切换当前会话的工作目录。
|
||||
- code: worktree
|
||||
- code: "@deepseek-ai/dsh-experimental-worktree"
|
||||
- text: 已关闭
|
||||
- listitem:
|
||||
- text: Git 工作树 要求在新分支上工作时,智能体会创建单独的 Git 工作树并在其中继续,你当前的工作树保持原样。
|
||||
- code: tool-worktree
|
||||
- code: "@deepseek-ai/dsh-experimental-tool-worktree"
|
||||
- text: 已关闭
|
||||
- listitem:
|
||||
- text: 变更请求 通过一个与提供方无关的服务注册代码托管提供方,并轮询变更请求状态。
|
||||
- code: forge
|
||||
- code: "@deepseek-ai/dsh-experimental-forge"
|
||||
- text: 已关闭
|
||||
- listitem:
|
||||
- text: GitHub 变更请求 通过宿主上的 GitHub CLI 为 forge 注册表读取 GitHub 变更请求。
|
||||
- code: forge-github
|
||||
- code: "@deepseek-ai/dsh-experimental-forge-github"
|
||||
- text: 已关闭
|
||||
- listitem:
|
||||
- text: 会话变更请求 把变更请求绑定到创建、推送或跟踪它们的会话,并监听其状态。
|
||||
- code: forge-session
|
||||
- code: "@deepseek-ai/dsh-experimental-forge-session"
|
||||
- text: 已关闭
|
||||
- listitem:
|
||||
- text: 工作树服务 创建命名的 Git 工作树,并切换当前会话的工作目录。
|
||||
- code: forge-worktree
|
||||
- code: "@deepseek-ai/dsh-experimental-worktree"
|
||||
- text: 已关闭
|
||||
- listitem:
|
||||
- text: 变更请求面板 在侧栏标签页中显示会话的变更请求,并让新会话可以在工作树中开始。
|
||||
- code: forge-panel
|
||||
- code: "@deepseek-ai/dsh-experimental-client-ui-forge"
|
||||
- text: 已关闭
|
||||
- heading "来源信息" [level=4]
|
||||
- term: 代码来源
|
||||
- definition: 内置
|
||||
- term: 当前版本
|
||||
- definition: {{version}}
|
||||
@@ -39,7 +39,7 @@
|
||||
- switch "启用 自动授权审查"
|
||||
- listitem:
|
||||
- button "查看 变更请求与工作树": 变更请求与工作树
|
||||
- text: 实验性 跟踪每个会话的变更请求及其检查与评审,新会话也可以在 Git 工作树中开始;GitHub 通过 gh 命令行访问。
|
||||
- text: 实验性 在会话侧栏集中跟踪关联的 GitHub PR:查看检查状态、评审意见与代码差异,并把修改建议交给智能体继续处理。也可查看本地改动,用独立工作树隔离任务。GitHub 功能需要本机安装 gh 并登录。
|
||||
- status
|
||||
- switch "启用 变更请求与工作树"
|
||||
- heading "已安装插件" [level=3]
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
- switch "启用 自动授权审查"
|
||||
- listitem:
|
||||
- button "查看 变更请求与工作树": 变更请求与工作树
|
||||
- text: 实验性 跟踪每个会话的变更请求及其检查与评审,新会话也可以在 Git 工作树中开始;GitHub 通过 gh 命令行访问。
|
||||
- text: 实验性 在会话侧栏集中跟踪关联的 GitHub PR:查看检查状态、评审意见与代码差异,并把修改建议交给智能体继续处理。也可查看本地改动,用独立工作树隔离任务。GitHub 功能需要本机安装 gh 并登录。
|
||||
- status
|
||||
- switch "启用 变更请求与工作树"
|
||||
- heading "已安装插件" [level=3]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
- id: llm-deepseek
|
||||
config:
|
||||
baseURL: http://127.0.0.1
|
||||
models:
|
||||
- id: credential-editor-model
|
||||
@@ -447,6 +447,8 @@ it.skipIf(MODE === 'record')('automatically shows a numbered PR when gh JSON omi
|
||||
const { settled } = await sendPrompt(page, scaffold)
|
||||
const sessionId = await settled
|
||||
const events = await readPersistedEvents(scaffold, sessionId)
|
||||
const request = events.find(event => event.type === 'request/header')
|
||||
expect(request?.data.header.tools?.filter(tool => tool.name === 'create_worktree')).toHaveLength(1)
|
||||
const toolOutput = events.flatMap(event => event.type === 'tool/result'
|
||||
? event.data.message.content.flatMap(block => block.type === 'text' ? [block.text] : []) : []).join('\n')
|
||||
expect(toolOutput).toContain('Add the pull request panel')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// wire — the one add card offers the dormant pi-ai catalog on its third-party
|
||||
// mode and the create form on its custom-API mode, a blank key saves a
|
||||
// reference-free profile for provider-native auth, and typing an API key later
|
||||
// stores it under the derived reference (`MINIMAX_CN_API_KEY`)
|
||||
// stores it write-only under the derived reference (`MINIMAX_CN_API_KEY`)
|
||||
// while the settings document records only that reference. Each saved row
|
||||
// appears after route topology invalidation without presenting liveness as
|
||||
// provider status. The customized-settings fold writes its curated fields —
|
||||
@@ -154,7 +154,8 @@ describe('web e2e: Models settings page configures a dormant provider', () => {
|
||||
await dialog.getByText('该 API 密钥格式错误,请检查。').waitFor({ timeout: 10_000 })
|
||||
await expect.poll(async () => save.isEnabled(), { timeout: 10_000 }).toBe(false)
|
||||
|
||||
// An empty field leaves this new profile on native authentication.
|
||||
// Clearing it restores submit: an empty field means "keep what is stored",
|
||||
// never a refusal, or editing any other setting would demand the key.
|
||||
await key.fill('')
|
||||
await expect.poll(async () => save.isEnabled(), { timeout: 10_000 }).toBe(true)
|
||||
expect(await dialog.getByText('该 API 密钥格式错误,请检查。').count()).toBe(0)
|
||||
@@ -220,9 +221,7 @@ describe('web e2e: Models settings page configures a dormant provider', () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-models-customized'))
|
||||
const dialog = page.getByRole('dialog', { name: '设置' })
|
||||
await dialog.getByRole('button', { name: '编辑 minimax-cn' }).click()
|
||||
const savedKey = dialog.getByLabel('API 密钥', { exact: true })
|
||||
await expect.poll(() => savedKey.inputValue(), { timeout: 10_000 }).toBe('sk-e2e-minimax')
|
||||
expect(await savedKey.getAttribute('type')).toBe('password')
|
||||
await dialog.getByPlaceholder('留空使用已保存凭据', { exact: true }).waitFor({ timeout: 10_000 })
|
||||
expect(await dialog.textContent()).not.toContain('API 密钥来源:')
|
||||
expect(await dialog.textContent()).not.toMatch(/oauth/i)
|
||||
await dialog.getByText('自定义设置').click()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// credential is absent, both ordered steps share the shipped modal chrome,
|
||||
// and the inline key write lands in an isolated harness home without a reload
|
||||
// or model call.
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { assertModelInputLayout } from './model-input-layout.ts'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -32,7 +33,6 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
|
||||
let page: Page
|
||||
let tripwire: ReturnType<typeof watchConsole>
|
||||
const browserConsole: string[] = []
|
||||
const secret = 'dsh_onboarding_fixture_key'
|
||||
|
||||
beforeAll(async () => {
|
||||
scaffold = await launchWebScaffold({ deepSeekMissingCredential: true, welcomeNoticePending: true })
|
||||
@@ -50,7 +50,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
|
||||
await scaffold?.close()
|
||||
})
|
||||
|
||||
it('stores a key and loads it masked only in an explicitly opened editor', async () => {
|
||||
it('stores a key write-only and observes configured state without restarting', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-onboarding-deepseek-config'))
|
||||
const welcome = page.getByRole('dialog', { name: WELCOME_NOTICE_COPY.zh.title })
|
||||
await welcome.waitFor({ timeout: 15_000 })
|
||||
@@ -86,6 +86,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
|
||||
const initial = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(MISSING_EXPECTED, initial, MODE)
|
||||
|
||||
const secret = `dsh_onboarding_${randomBytes(12).toString('hex')}`
|
||||
await keyInput.fill(secret)
|
||||
await credentialStep.getByRole('button', { name: '保存并继续' }).click()
|
||||
await credentialStep.waitFor({ state: 'detached', timeout: 15_000 })
|
||||
@@ -100,7 +101,8 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
|
||||
const acknowledgedSettings = await readFile(join(scaffold.harnessHome, 'profiles', 'scaffold', 'cordis.patch.yml'), 'utf8')
|
||||
expect(acknowledgedSettings).toContain(`${WELCOME_NOTICE_ACK_FIELD}: ${WELCOME_NOTICE_VERSION}`)
|
||||
|
||||
// Opening the ordinary Models editor loads the saved key masked.
|
||||
// The ordinary Models surface reuses the refreshed join and exposes the
|
||||
// configured write-only placeholder without a reload.
|
||||
await openSettings(page, 'zh')
|
||||
const settings = page.getByRole('dialog', { name: '设置' })
|
||||
await settings.waitFor({ timeout: 10_000 })
|
||||
@@ -111,10 +113,9 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
|
||||
const configuredInput = settings.getByLabel('API 密钥', { exact: true })
|
||||
await configuredInput.waitFor({ timeout: 10_000 })
|
||||
await expect.poll(
|
||||
() => configuredInput.inputValue(),
|
||||
() => configuredInput.getAttribute('placeholder'),
|
||||
{ timeout: 10_000 },
|
||||
).toBe(secret)
|
||||
expect(await configuredInput.getAttribute('type')).toBe('password')
|
||||
).toBe('留空使用已保存凭据')
|
||||
|
||||
const secondReloadWarnings = tripwire.warnings.length
|
||||
await page.reload({ waitUntil: 'load' })
|
||||
@@ -254,9 +255,9 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
|
||||
await settings.getByRole('group', { name: '输入类型 1' }).getByRole('checkbox', { name: '图片' }).check()
|
||||
|
||||
await expect.poll(
|
||||
() => settings.getByLabel('API 密钥', { exact: true }).inputValue(),
|
||||
() => settings.getByLabel('API 密钥', { exact: true }).getAttribute('placeholder'),
|
||||
{ timeout: 10_000 },
|
||||
).toBe(secret)
|
||||
).toBe('留空使用已保存凭据')
|
||||
const modelEditor = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(MODELS_EXPECTED, modelEditor, MODE)
|
||||
await settings.getByRole('button', { name: '保存', exact: true }).click()
|
||||
|
||||
@@ -9,6 +9,7 @@ import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { Browser, Locator, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { HIDDEN_BUNDLES } from '@deepseek-ai/dsh-official-bundles'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import { join } from 'node:path'
|
||||
import {
|
||||
@@ -115,17 +116,14 @@ describe('web e2e: plugin configuration pages', () => {
|
||||
// The More list omits the shipped bundles whose own rows do not run (the visibility rule lives in
|
||||
// ui-plugin-manager), so it holds fewer cards than the installed optional-bundle list.
|
||||
const bundles = await scaffold.ctx.pluginManager.listBundles()
|
||||
const hiddenBundles = new Set([
|
||||
'@deepseek-ai/dsh-experimental-terminal-bundle',
|
||||
'@deepseek-ai/dsh-experimental-liveshare',
|
||||
])
|
||||
expect(await panel.locator('[data-plugin-group="more"] [data-plugin-package]').count())
|
||||
.toBe(bundles.filter(bundle => !hiddenBundles.has(bundle.name)
|
||||
.toBe(bundles.filter(bundle => !HIDDEN_BUNDLES.has(bundle.name)
|
||||
&& bundle.name !== '@deepseek-ai/dsh-experimental-liveshare'
|
||||
&& bundle.official && (bundle.listedByDefault !== false || bundle.enabled || bundle.installed)).length)
|
||||
expect(await basic.count()).toBe(0)
|
||||
expect(await extensions.count()).toBe(0)
|
||||
expect(await panel.locator('[data-plugin-group="bundles"]').count()).toBe(0)
|
||||
await panel.getByRole('button', { name: '查看 Git 工作树', exact: true }).click()
|
||||
await panel.getByRole('button', { name: '查看 开发者工具', exact: true }).click()
|
||||
await panel.locator('[data-plugin-detail]').waitFor()
|
||||
await panel.getByRole('button', { name: '返回 实验性插件', exact: true }).click()
|
||||
await panel.getByRole('heading', { name: '实验性插件', exact: true, level: 1 }).waitFor()
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { Browser, Locator, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { FiberState } from '@deepseek-ai/cordis'
|
||||
import { readProfileManifest } from '@deepseek-ai/dsh-app-boot'
|
||||
import { HIDDEN_BUNDLES } from '@deepseek-ai/dsh-official-bundles'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed, onTestFinished, vi } from 'vitest'
|
||||
import { join } from 'node:path'
|
||||
import {
|
||||
@@ -852,7 +853,7 @@ describe('web e2e: plugin manager', () => {
|
||||
// the eligible installed optional-bundle list.
|
||||
expect(await panel.locator('[data-plugin-group="more"] [data-plugin-package="@deepseek-ai/dsh-experimental-liveshare"]').count()).toBe(0)
|
||||
expect(await panel.locator('[data-plugin-group="more"] [data-plugin-package]').count())
|
||||
.toBe(bundles.filter(bundle => bundle.name !== '@deepseek-ai/dsh-experimental-terminal-bundle'
|
||||
.toBe(bundles.filter(bundle => !HIDDEN_BUNDLES.has(bundle.name)
|
||||
&& bundle.name !== '@deepseek-ai/dsh-experimental-liveshare'
|
||||
&& bundle.official && (bundle.listedByDefault !== false || bundle.enabled || bundle.installed)).length)
|
||||
// Computer Use ships with the installation and is listed only on the macOS Hosts it supports.
|
||||
@@ -878,7 +879,6 @@ describe('web e2e: plugin manager', () => {
|
||||
['@deepseek-ai/dsh-personalized-recommendations', 1],
|
||||
['@deepseek-ai/dsh-experimental-inspector-profile', 1],
|
||||
...cuaSupported ? [['@deepseek-ai/dsh-cua-bundle', 1]] : [],
|
||||
['@deepseek-ai/dsh-experimental-tool-worktree', 1],
|
||||
['@deepseek-ai/dsh-experimental-cot-translation-bundle', 1],
|
||||
['@deepseek-ai/dsh-experimental-session-titles-bundle', 1],
|
||||
['@deepseek-ai/dsh-experimental-badge-skill-bundle', 1],
|
||||
@@ -1065,6 +1065,51 @@ describe('web e2e: plugin manager', () => {
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
})
|
||||
|
||||
it('introduces code review and worktrees with localized artwork before activation', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-forge-preview'))
|
||||
const panel = await openPluginsPanel()
|
||||
await panel.getByRole('button', { name: '查看 变更请求与工作树', exact: true }).click()
|
||||
const detail = panel.locator('[data-plugin-detail]')
|
||||
expect(await detail.getByRole('switch').getAttribute('aria-checked')).toBe('false')
|
||||
const sources: (string | null)[] = []
|
||||
let english = false
|
||||
try {
|
||||
for (const [language, alt] of [
|
||||
['zh', '会话关联 GitHub PR,跟踪检查、评审与代码差异,并用工作树隔离任务改动'],
|
||||
['en', 'A conversation linked to a GitHub pull request, with checks, reviews, code diffs, and an isolated worktree'],
|
||||
] as const) {
|
||||
if (language === 'en') {
|
||||
await setLanguage(language)
|
||||
english = true
|
||||
}
|
||||
const document = detail.locator('[data-detail-document]')
|
||||
const image = document.getByRole('img', { name: alt, exact: true })
|
||||
await image.waitFor()
|
||||
await image.evaluate(async (node: HTMLImageElement) => { await node.decode() })
|
||||
expect(await image.evaluate((node: HTMLImageElement) => [node.naturalWidth, node.naturalHeight])).toEqual([640, 300])
|
||||
sources.push(await image.getAttribute('src'))
|
||||
for (const width of [1680, 390]) {
|
||||
await page.setViewportSize({ width, height: 1000 })
|
||||
expect(await image.evaluate((node: HTMLImageElement) => {
|
||||
const bounds = node.getBoundingClientRect()
|
||||
return bounds.width > 0 && bounds.left >= 0 && bounds.right <= window.innerWidth
|
||||
&& window.document.documentElement.scrollWidth <= window.innerWidth
|
||||
})).toBe(true)
|
||||
}
|
||||
await page.setViewportSize({ width: 1680, height: 1000 })
|
||||
}
|
||||
expect(sources[0]).not.toBe(sources[1])
|
||||
} finally {
|
||||
await page.setViewportSize({ width: 1680, height: 1000 })
|
||||
if (english) await setLanguage('zh')
|
||||
}
|
||||
await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'forge-preview.expected.md'),
|
||||
await captureStableAria(page, '[data-plugin-panel]', scaffold.workspaceCwd, {
|
||||
replacements: [[badgeVersion, '{{version}}']],
|
||||
}), MODE)
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
})
|
||||
|
||||
it('localizes independent exports and falls back per field without activating the plugins', async () => {
|
||||
onTestFinished(closeSettings)
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-plugin-manager-exports'))
|
||||
@@ -1494,7 +1539,7 @@ describe('web e2e: plugin manager', () => {
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, [
|
||||
'manager.expected.md', 'live-enabled.expected.md', 'missing-bundle.expected.md', 'exports.expected.md', 'exports-en.expected.md', 'icons.expected.md', 'loading.expected.md', 'refresh.expected.md',
|
||||
'ime-enter.expected.md', 'header-responsive.expected.md', 'header-natural-motion.expected.md', 'preview.expected.md',
|
||||
'touch-tooltips.expected.md',
|
||||
'touch-tooltips.expected.md', 'forge-preview.expected.md',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
type NormalizeContext,
|
||||
} from '@deepseek-ai/dsh-session-snapshot'
|
||||
import type { Profile, ProfileContext } from '@deepseek-ai/dsh-app-boot'
|
||||
import type { PresetDefinition } from '@deepseek-ai/dsh-agent-preset-registry'
|
||||
import { dshHomePath } from '@deepseek-ai/dsh-home-paths'
|
||||
import { LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import type {
|
||||
@@ -713,6 +714,20 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
|
||||
{ id: 'llm-deepseek', disabled: mode !== 'record' && !maskDeepSeekCredential },
|
||||
]
|
||||
|
||||
// Recorded standard sessions pin the optional patch tool in their request headers.
|
||||
if (options.replayFixture !== undefined || options.compareReplaySession === true) {
|
||||
const config = composedRows.find(row => row.id === 'preset-standard')!.config as PresetDefinition
|
||||
overlayPatches.push({
|
||||
id: 'preset-standard',
|
||||
config: {
|
||||
...config,
|
||||
plugins: config.plugins.map(plugin => plugin.id === 'tool-fs'
|
||||
? { ...plugin, config: { ...plugin.config, applyPatch: true } }
|
||||
: plugin),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Live fields use a shared deployment layer; process-specific ports and roots stay in CLI overlays.
|
||||
const formEntries = new Set(['agent-default-model', 'agent-preset-registry', 'llm-deepseek', 'llm-pi-ai',
|
||||
'web', 'web-search-deepseek', 'web-deepseek-api', 'agent-loop', 'subagent', 'bash-sandbox', 'pwsh-sandbox',
|
||||
|
||||
@@ -1,7 +1,51 @@
|
||||
/** Manual task creation and editing through the shipped Web composition. */
|
||||
/** Schedule creation, prompt prefill, and editing through the shipped Web composition. */
|
||||
import { chromium } from 'playwright'
|
||||
import { expect, it } from 'vitest'
|
||||
import { launchWebScaffold, watchConsole } from './scaffold.ts'
|
||||
import { writeComposerDraft } from './support.ts'
|
||||
|
||||
it.each([
|
||||
{ locale: 'en', panel: 'Automation tasks', create: 'New', menu: 'Choose a creation method', chat: 'Create in a session',
|
||||
prompt: 'Please create an automation task based on the requirements.' },
|
||||
{ locale: 'zh', panel: '自动化任务', create: '新建', menu: '选择创建方式', chat: '在会话中创建',
|
||||
prompt: '请根据需求创建一个自动化任务。' },
|
||||
])('prefills an unsent Schedule creation draft in $locale through both conversation entries', async (copy) => {
|
||||
const scaffold = await launchWebScaffold({ firstUse: true })
|
||||
let browser: Awaited<ReturnType<typeof chromium.launch>> | undefined
|
||||
try {
|
||||
browser = await chromium.launch()
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 960 }, locale: copy.locale })
|
||||
await page.addInitScript((locale) => { localStorage.setItem('dsh.locale', locale) }, copy.locale)
|
||||
const tripwire = watchConsole(page)
|
||||
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
|
||||
const input = page.locator('[data-composer-input][contenteditable="true"]').first()
|
||||
for (const { entry, draft } of [
|
||||
{ entry: 'primary', draft: '' },
|
||||
{ entry: 'primary', draft: 'Existing unsent draft' },
|
||||
{ entry: 'menu', draft: '' },
|
||||
{ entry: 'menu', draft: 'Existing unsent draft' },
|
||||
]) {
|
||||
await writeComposerDraft(page, input, draft)
|
||||
await page.getByRole('button', { name: copy.panel, exact: true }).click()
|
||||
const manager = page.getByTestId('task-manager-page')
|
||||
if (entry === 'primary') await manager.getByRole('button', { name: copy.create, exact: true }).click()
|
||||
else {
|
||||
await manager.getByRole('button', { name: copy.menu, exact: true }).click()
|
||||
await page.getByRole('menuitem', { name: copy.chat, exact: true }).click()
|
||||
}
|
||||
await manager.waitFor({ state: 'detached' })
|
||||
await input.getByText(copy.prompt, { exact: true }).waitFor()
|
||||
expect(await input.evaluate(element => [...element.children].map(paragraph => paragraph.textContent).join('\n')))
|
||||
.toBe(draft === '' ? copy.prompt : `${draft}\n${copy.prompt}`)
|
||||
const events = scaffold.ctx.agents.list().flatMap(agent => agent.session.snapshotEvents())
|
||||
expect(events.filter(event => event.type === 'user/message' || event.type === 'turn/start')).toEqual([])
|
||||
expect(await scaffold.ctx.schedule.catalog()).toEqual([])
|
||||
}
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
} finally {
|
||||
try { await browser?.close() } finally { await scaffold.close() }
|
||||
}
|
||||
})
|
||||
|
||||
it('creates from a paused empty state, selects the task, and saves an inline edit without starting an Agent', async () => {
|
||||
// First use registers the default Workspace, which a new task's directory starts with.
|
||||
|
||||
@@ -84,6 +84,24 @@ class RestartableServer {
|
||||
logs(): string { return this.output.replace(/([?&]token=)[A-Za-z0-9_-]+/gu, '$1<redacted>') }
|
||||
}
|
||||
|
||||
type HostRpc = <T>(endpoint: string, args: object) => Promise<T>
|
||||
|
||||
/** Authenticate against one server process and return its Host RPC caller. */
|
||||
async function hostRpc(url: string): Promise<HostRpc> {
|
||||
const authenticated = await fetch(url, { redirect: 'manual' })
|
||||
const cookie = authenticated.headers.get('set-cookie')?.split(';', 1)[0]
|
||||
if (cookie === undefined) throw new Error('Server did not issue an authentication cookie')
|
||||
return async <T>(endpoint: string, args: object): Promise<T> => {
|
||||
const response = await fetch(new URL('/api/' + endpoint, url), {
|
||||
method: 'POST', headers: { 'content-type': 'application/json', cookie },
|
||||
body: JSON.stringify({ type: 'client-request', rpcId: randomUUID(), method: endpoint, payload: { args } }),
|
||||
})
|
||||
const result = await response.json() as { result: { ok: boolean; value: T; error?: { message: string } } }
|
||||
if (!result.result.ok) throw new Error(`${endpoint}: ${String(result.result.error?.message)}`)
|
||||
return result.result.value
|
||||
}
|
||||
}
|
||||
|
||||
it.each([false, true])('keeps the same revision, Session and page across a server restart (delayed startup: %s)', async (holdStartup) => {
|
||||
const world = await mkdtemp(join(tmpdir(), 'dsh-server-restart-'))
|
||||
onTestFinished(() => rm(world, { recursive: true, force: true }))
|
||||
@@ -118,18 +136,7 @@ it.each([false, true])('keeps the same revision, Session and page across a serve
|
||||
|
||||
const url = await server.start(0)
|
||||
const port = Number(new URL(url).port)
|
||||
const authenticated = await fetch(url, { redirect: 'manual' })
|
||||
const cookie = authenticated.headers.get('set-cookie')?.split(';', 1)[0]
|
||||
if (cookie === undefined) throw new Error('Server did not issue an authentication cookie')
|
||||
const rpc = async <T>(endpoint: string, args: object): Promise<T> => {
|
||||
const response = await fetch(new URL('/api/' + endpoint, url), {
|
||||
method: 'POST', headers: { 'content-type': 'application/json', cookie },
|
||||
body: JSON.stringify({ type: 'client-request', rpcId: randomUUID(), method: endpoint, payload: { args } }),
|
||||
})
|
||||
const result = await response.json() as { result: { ok: boolean; value: T; error?: { message: string } } }
|
||||
if (!result.result.ok) throw new Error(`${endpoint}: ${String(result.result.error?.message)}`)
|
||||
return result.result.value
|
||||
}
|
||||
const rpc = await hostRpc(url)
|
||||
await rpc('workspace/create', { request: { path: workspacePath } })
|
||||
const scenarioId = randomUUID()
|
||||
|
||||
@@ -234,3 +241,30 @@ it.each([false, true])('keeps the same revision, Session and page across a serve
|
||||
expect(streamErrors).toEqual([])
|
||||
expect(navigations).toBe(0)
|
||||
})
|
||||
|
||||
it('keeps a live blank Session blank across a graceful server restart', async () => {
|
||||
const world = await mkdtemp(join(tmpdir(), 'dsh-server-restart-'))
|
||||
onTestFinished(() => rm(world, { recursive: true, force: true }))
|
||||
// The scenario runs no model turn, so the model endpoint is never contacted.
|
||||
const server = new RestartableServer(world, 'http://127.0.0.1:9')
|
||||
onTestFinished(() => server.stop())
|
||||
const workspacePath = join(world, 'workspace')
|
||||
await mkdir(workspacePath)
|
||||
onTestFailed(async () => {
|
||||
await writeFile(join(REPO_ROOT, '.artifacts', 'server-restart-blank-' + randomUUID() + '.json'), JSON.stringify({ server: server.logs() }, null, 2))
|
||||
})
|
||||
|
||||
const before = await hostRpc(await server.start(0))
|
||||
const { workspace } = await before<{ workspace: { id: string } }>('workspace/create', { request: { path: workspacePath } })
|
||||
const { sessionId } = await before<{ sessionId: string }>('session/create', { request: { workspaceId: workspace.id } })
|
||||
const listed = async (rpc: HostRpc) => (await rpc<{
|
||||
items: { sessionId: string; blank: boolean; agentAvailable: boolean }[]
|
||||
}>('session/list', { _request: {} })).items.find(item => item.sessionId === sessionId)
|
||||
expect(await listed(before)).toMatchObject({ blank: true, agentAvailable: true })
|
||||
|
||||
// SIGTERM unloads the Web layer, including the list projection, before its live Session detaches;
|
||||
// stopping right after creation also precedes the throttled checkpoint of the Session's setup events.
|
||||
await server.stop()
|
||||
const after = await hostRpc(await server.start(0))
|
||||
expect(await listed(after)).toMatchObject({ blank: true, agentAvailable: false })
|
||||
})
|
||||
|
||||
@@ -473,7 +473,6 @@ function assertLeanChildRecord(agent: Agent): void {
|
||||
* Agent Note owns the rationale and its sources.
|
||||
*/
|
||||
const EXPECTED_TOOLS = [
|
||||
'apply_patch',
|
||||
'ask_user_question',
|
||||
'bash',
|
||||
'create_goal',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Git Worktrees opt-in through the shipped plugin manager, profile, and Loader. */
|
||||
/** Unified review and worktree opt-in through the shipped plugin manager, profile, and Loader. */
|
||||
|
||||
import { execFile } from 'node:child_process'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
@@ -6,16 +6,19 @@ import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { promisify } from 'node:util'
|
||||
import { FiberState } from '@deepseek-ai/cordis'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type {} from '@deepseek-ai/dsh-experimental-worktree'
|
||||
import type {} from '@deepseek-ai/dsh-experimental-client-ui-forge'
|
||||
import { chromium } from 'playwright'
|
||||
import { expect, it, onTestFailed, onTestFinished } from 'vitest'
|
||||
import { launchWebScaffold, watchConsole } from './scaffold.ts'
|
||||
import { connectFreshWorkspace, newEnglishPage, PLUGIN_TOGGLE_SETTLE_MS, saveFailureShot } from './support.ts'
|
||||
|
||||
const BUNDLE = '@deepseek-ai/dsh-experimental-tool-worktree'
|
||||
const BUNDLE = '@deepseek-ai/dsh-experimental-forge-bundle'
|
||||
const STANDALONE = '@deepseek-ai/dsh-experimental-tool-worktree'
|
||||
const exec = promisify(execFile)
|
||||
|
||||
it('persists the Git Worktrees switch and removes its capability while retaining checkouts', async () => {
|
||||
it('persists the unified review and worktree switch and retains checkouts after disabling', async () => {
|
||||
const scaffold = await launchWebScaffold({
|
||||
extraOverlayPath: fileURLToPath(new URL('./pin-browse-picker.overlay.yml', import.meta.url)),
|
||||
})
|
||||
@@ -70,13 +73,15 @@ it('persists the Git Worktrees switch and removes its capability while retaining
|
||||
}
|
||||
let panel = await openPlugins()
|
||||
let card = panel.locator(`[data-plugin-group="more"] [data-plugin-package="${BUNDLE}"]`)
|
||||
await card.getByRole('button', { name: 'View Git worktrees', exact: true }).waitFor()
|
||||
await card.getByRole('button', { name: 'View Change requests & worktrees', exact: true }).waitFor()
|
||||
expect(await panel.locator(`[data-plugin-package="${STANDALONE}"]`).count()).toBe(0)
|
||||
expect(await card.getByText('Experimental', { exact: true }).count()).toBe(1)
|
||||
let toggle = card.getByRole('switch', { name: 'Enable Git worktrees', exact: true })
|
||||
let toggle = card.getByRole('switch', { name: 'Enable Change requests & worktrees', exact: true })
|
||||
expect(await toggle.getAttribute('aria-checked')).toBe('false')
|
||||
expect(await selectedBundles()).not.toContain(BUNDLE)
|
||||
expect(rows()).toEqual([])
|
||||
expect(scaffold.ctx.get('worktrees')).toBeUndefined()
|
||||
expect(scaffold.ctx.get('forgePanel')).toBeUndefined()
|
||||
expect(tools()).toContain('working_directory')
|
||||
expect(tools()).not.toContain('create_worktree')
|
||||
|
||||
@@ -85,14 +90,21 @@ it('persists the Git Worktrees switch and removes its capability while retaining
|
||||
await expect.poll(() => toggle.getAttribute('aria-checked'), { timeout: PLUGIN_TOGGLE_SETTLE_MS }).toBe('true')
|
||||
expect(await selectedBundles()).toContain(BUNDLE)
|
||||
expect(tools()).toContain('create_worktree')
|
||||
const created = await scaffold.ctx.worktrees.create(agent, { name: 'gui-opt-in' })
|
||||
expect(scaffold.ctx.get('forgePanel')).toBeDefined()
|
||||
const created = { path: join(repository, '.agents/worktrees/gui-opt-in'), baseCommit: (await git('rev-parse', 'HEAD')).stdout.trim() }
|
||||
const result = await scaffold.ctx.tools.execute({
|
||||
agent, signal: new AbortController().signal,
|
||||
calls: [{ type: 'tool-call', id: ToolCallId('gui-opt-in'), name: 'create_worktree', arguments: JSON.stringify({ name: 'gui-opt-in' }) }],
|
||||
})
|
||||
expect(result.results[0]?.result.isError).not.toBe(true)
|
||||
expect(result.results[0]?.result.value).toEqual({ ...created, branch: 'gui-opt-in', repositoryRoot: repository })
|
||||
expect(await readFile(join(created.path, 'retained.txt'), 'utf8')).toBe('retained checkout\n')
|
||||
expect(scaffold.ctx.workingDirectory.get(agent.session)).toBe(created.path)
|
||||
|
||||
await page.reload({ waitUntil: 'load' })
|
||||
panel = await openPlugins()
|
||||
card = panel.locator(`[data-plugin-group="more"] [data-plugin-package="${BUNDLE}"]`)
|
||||
toggle = card.getByRole('switch', { name: 'Enable Git worktrees', exact: true })
|
||||
toggle = card.getByRole('switch', { name: 'Enable Change requests & worktrees', exact: true })
|
||||
await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('true')
|
||||
expect(tools()).toContain('create_worktree')
|
||||
await toggle.click()
|
||||
@@ -100,6 +112,7 @@ it('persists the Git Worktrees switch and removes its capability while retaining
|
||||
await expect.poll(() => toggle.getAttribute('aria-checked'), { timeout: PLUGIN_TOGGLE_SETTLE_MS }).toBe('false')
|
||||
expect(await selectedBundles()).not.toContain(BUNDLE)
|
||||
expect(scaffold.ctx.get('worktrees')).toBeUndefined()
|
||||
expect(scaffold.ctx.get('forgePanel')).toBeUndefined()
|
||||
expect(tools()).not.toContain('create_worktree')
|
||||
expect(tools()).toContain('working_directory')
|
||||
expect(await readFile(join(created.path, 'retained.txt'), 'utf8')).toBe('retained checkout\n')
|
||||
@@ -108,7 +121,7 @@ it('persists the Git Worktrees switch and removes its capability while retaining
|
||||
|
||||
await page.reload({ waitUntil: 'load' })
|
||||
panel = await openPlugins()
|
||||
await expect.poll(() => panel.getByRole('switch', { name: 'Enable Git worktrees', exact: true })
|
||||
await expect.poll(() => panel.getByRole('switch', { name: 'Enable Change requests & worktrees', exact: true })
|
||||
.getAttribute('aria-checked')).toBe('false')
|
||||
expect(tools()).not.toContain('create_worktree')
|
||||
expect(await selectedBundles()).not.toContain(BUNDLE)
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
// this client-registered project must not also hold them (one program
|
||||
// cannot see both sides of the cordis Context merges).
|
||||
"exclude": [
|
||||
"tests/credential-editor.e2e.ts",
|
||||
"tests/sidebar-drawer.e2e.ts",
|
||||
"tests/creation-recommendations.e2e.ts",
|
||||
"tests/plugin-recommendations.e2e.ts",
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
en: 41d772192075e133
|
||||
zh: 5ef3da69b4de1d8a
|
||||
/capability-seams-and-core-services:
|
||||
en: 6215d15e0e273fa4
|
||||
zh: dec5c7779e0a42f5
|
||||
en: 3c972fccec17a890
|
||||
zh: 94c82847a3c8b83c
|
||||
|
||||
@@ -698,7 +698,7 @@ flowchart LR
|
||||
| `ctx.configEditor` | `core` | [`config-editor`](../packages/boot/config-editor) | - | [`settings`](../packages/settings/settings), [`agent-default-model`](../packages/core/agent-default-model) | - | Persists profile config patches under the application file lock and HMR queue, then reconciles Loader entries. |
|
||||
| `ctx.settings` | `core` | [`settings`](../packages/settings/settings) | - | [`api-settings-controller`](../packages/api/settings-controller) | - | Forms project volatile Config fields from active profile entries and delegate validated edits to config-editor. Plugins consume their own Config references. |
|
||||
| `ctx.subagentModelSelection` | `core` | [`tool-subagent`](../packages/subagent/tool-subagent) | - | [`tool-subagent`](../packages/subagent/tool-subagent) | - | Owns the default-off settings namespace that Agent-scoped delegation tools sample when composing a new top-level Session. |
|
||||
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`api-settings-controller`](../packages/api/settings-controller), [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request; the settings controller exposes value-free metadata, writes, and owner-only reads of stored keys. |
|
||||
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`api-settings-controller`](../packages/api/settings-controller), [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request; the settings controller exposes value-free views and write-only storage. |
|
||||
| `ctx.deepseekAccount` | `seam` | [`deepseek-account`](../packages/credentials/deepseek-account) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) | [`api-account-controller`](../packages/api/account-controller), [`llm-deepseek`](../packages/llm/llm-deepseek) | - | The Host owns browser authorization and local credentials; UI consumers receive state without tokens. |
|
||||
| `ctx.authorization` | `seam` | [`authorization`](../packages/credentials/authorization) | - | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | Flows are registered by the plugin that knows how to obtain one credential and keyed by the record they write; the seam owns the conversation and the one-attempt-per-key lifecycle, never the protocol. |
|
||||
| `ctx.productAnalytics` | `service` | [`client-product-analytics`](../packages/client/product-analytics) | - | - | - | Accepts selected Desktop events, enriches available login identity, and observes live compaction under the live Host collection policy. |
|
||||
|
||||
@@ -700,7 +700,7 @@ flowchart LR
|
||||
| `ctx.configEditor` | `core` | [`config-editor`](../packages/boot/config-editor) | - | [`settings`](../packages/settings/settings), [`agent-default-model`](../packages/core/agent-default-model) | - | Persists profile config patches under the application file lock and HMR queue, then reconciles Loader entries. |
|
||||
| `ctx.settings` | `core` | [`settings`](../packages/settings/settings) | - | [`api-settings-controller`](../packages/api/settings-controller) | - | Forms project volatile Config fields from active profile entries and delegate validated edits to config-editor. Plugins consume their own Config references. |
|
||||
| `ctx.subagentModelSelection` | `core` | [`tool-subagent`](../packages/subagent/tool-subagent) | - | [`tool-subagent`](../packages/subagent/tool-subagent) | - | 拥有默认关闭的设置命名空间;Agent 作用域的委派工具会在组合新顶层 Session 时读取它。 |
|
||||
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`api-settings-controller`](../packages/api/settings-controller), [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | 配置携带对机密信息的引用;提供方拥有实际值。消费方按操作解析,因此轮换后的凭据会在紧接着的下一次请求中生效;settings controller 提供不含实际值的元数据和写入操作,并仅向所有者读取已保存密钥。 |
|
||||
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`api-settings-controller`](../packages/api/settings-controller), [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | 配置携带对机密信息的引用;提供方拥有实际值。消费方按操作解析,因此轮换后的凭据会在紧接着的下一次请求中生效;settings controller 提供不含实际值的视图和只写存储。 |
|
||||
| `ctx.deepseekAccount` | `seam` | [`deepseek-account`](../packages/credentials/deepseek-account) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) | [`api-account-controller`](../packages/api/account-controller), [`llm-deepseek`](../packages/llm/llm-deepseek) | - | Host 负责浏览器授权和本地凭证;UI 使用方只接收不含 token 的状态。 |
|
||||
| `ctx.authorization` | `seam` | [`authorization`](../packages/credentials/authorization) | - | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | flow 由知道如何取得某份凭据的插件注册,并以其写入的记录为键;seam 拥有这段对话与"每个键同时只跑一次尝试"的生命周期,而非协议本身。 |
|
||||
| `ctx.productAnalytics` | `service` | [`client-product-analytics`](../packages/client/product-analytics) | - | - | - | 根据 Host 动态采集策略接收指定桌面事件、补充可用登录身份并监听实时压缩。 |
|
||||
|
||||
@@ -4450,9 +4450,9 @@ export interface Config {
|
||||
search?: boolean
|
||||
/** Register `web_fetch`. Defaults to true. */
|
||||
fetch?: boolean
|
||||
/** Upper bound on sources returned by one `web_search` call. */
|
||||
/** Upper bound on sources returned by one `web_search` call. Defaults to 10. */
|
||||
searchMaxResults?: number
|
||||
/** Upper bound on queries accepted by one `web_search` call. */
|
||||
/** Upper bound on queries accepted by one `web_search` call. Defaults to 10. */
|
||||
searchMaxQueries?: number
|
||||
/** Cap on each source summary in `web_search` output, in characters. Defaults to 800. */
|
||||
searchSummaryMaxChars?: number
|
||||
@@ -4632,7 +4632,7 @@ export interface Config {
|
||||
|
||||
- `inject`: `web`
|
||||
- `refs`: `Volatile` (`@deepseek-ai/cordis`)
|
||||
- `source`: [`packages/web/web-deepseek-api/src/index.ts:36`](../packages/web/web-deepseek-api/src/index.ts)
|
||||
- `source`: [`packages/web/web-deepseek-api/src/index.ts:38`](../packages/web/web-deepseek-api/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Settings read by the next search or fetch without re-registering either provider. */
|
||||
@@ -4645,6 +4645,12 @@ export interface Config {
|
||||
baseURL: Volatile<string>
|
||||
/** Search API results per search, from 1 through 50; a smaller request bound wins. Defaults to 10. */
|
||||
count: Volatile<number>
|
||||
/**
|
||||
* Most Search requests this plugin instance sends in any one-second window; later searches wait,
|
||||
* and the wait counts against the caller's timeout. Other processes and clients on the same account
|
||||
* share the account limit without coordination. Defaults to 5.
|
||||
*/
|
||||
searchRequestsPerSecond: Volatile<number>
|
||||
/** Send the triggering user question, bounded to 128 characters, as `ori_question`. Defaults to true. */
|
||||
sendOriginalQuestion: Volatile<boolean>
|
||||
}
|
||||
|
||||
@@ -4452,9 +4452,9 @@ export interface Config {
|
||||
search?: boolean
|
||||
/** Register `web_fetch`. Defaults to true. */
|
||||
fetch?: boolean
|
||||
/** Upper bound on sources returned by one `web_search` call. */
|
||||
/** Upper bound on sources returned by one `web_search` call. Defaults to 10. */
|
||||
searchMaxResults?: number
|
||||
/** Upper bound on queries accepted by one `web_search` call. */
|
||||
/** Upper bound on queries accepted by one `web_search` call. Defaults to 10. */
|
||||
searchMaxQueries?: number
|
||||
/** Cap on each source summary in `web_search` output, in characters. Defaults to 800. */
|
||||
searchSummaryMaxChars?: number
|
||||
@@ -4634,7 +4634,7 @@ export interface Config {
|
||||
|
||||
- `inject`: `web`
|
||||
- `refs`: `Volatile` (`@deepseek-ai/cordis`)
|
||||
- `source`: [`packages/web/web-deepseek-api/src/index.ts:36`](../packages/web/web-deepseek-api/src/index.ts)
|
||||
- `source`: [`packages/web/web-deepseek-api/src/index.ts:38`](../packages/web/web-deepseek-api/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Settings read by the next search or fetch without re-registering either provider. */
|
||||
@@ -4647,6 +4647,12 @@ export interface Config {
|
||||
baseURL: Volatile<string>
|
||||
/** Search API results per search, from 1 through 50; a smaller request bound wins. Defaults to 10. */
|
||||
count: Volatile<number>
|
||||
/**
|
||||
* Most Search requests this plugin instance sends in any one-second window; later searches wait,
|
||||
* and the wait counts against the caller's timeout. Other processes and clients on the same account
|
||||
* share the account limit without coordination. Defaults to 5.
|
||||
*/
|
||||
searchRequestsPerSecond: Volatile<number>
|
||||
/** Send the triggering user question, bounded to 128 characters, as `ori_question`. Defaults to true. */
|
||||
sendOriginalQuestion: Volatile<boolean>
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ Application listeners are outside this package matrix. Desktop Host subscribes t
|
||||
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
|
||||
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
|
||||
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
|
||||
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:700`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:680`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:707`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:686`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:693`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:701`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:681`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:708`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:687`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:694`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes`, `server` |
|
||||
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
|
||||
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
|
||||
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
|
||||
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:700`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:680`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:707`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:686`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:693`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:701`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:681`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:708`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:687`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:694`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes`, `server` |
|
||||
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
|
||||
|
||||
@@ -1362,8 +1362,8 @@
|
||||
en: 4147b170f52814af
|
||||
zh: e78991ac1c41ebec
|
||||
/session-persistence-event-catalog/resolved-persistence-types/string:
|
||||
en: c178efefdf01cade
|
||||
zh: 200b1ab00ed8bfd3
|
||||
en: caa52cd38cec3aee
|
||||
zh: 3ff0f1fceaac2eb1
|
||||
/session-persistence-event-catalog/resolved-persistence-types/string~2:
|
||||
en: 0089620bf4638263
|
||||
zh: 1922bef1713094bb
|
||||
@@ -1884,8 +1884,8 @@
|
||||
en: 4e102391d685eff5
|
||||
zh: 9762243a13014f3d
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-user:
|
||||
en: 984cac7296874653
|
||||
zh: 13b4ee707a8ce305
|
||||
en: eaecebdbcd5cc4e4
|
||||
zh: f519d7ade666f48a
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-user-approval:
|
||||
en: a52d2dfbaf844127
|
||||
zh: 4c5306ca40f20873
|
||||
|
||||
@@ -5639,7 +5639,7 @@ Array of `number`.
|
||||
|
||||
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
|
||||
|
||||
Sources: [`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/team/agent-team/src/types.ts:20`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:32`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:8`](../packages/team/agent-team/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
Sources: [`packages/api/session-controller/src/types.ts:389`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/team/agent-team/src/types.ts:20`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:32`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:8`](../packages/team/agent-team/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
|
||||
`string`
|
||||
|
||||
@@ -7759,7 +7759,7 @@ Sources: [`packages/core/tools/src/index.ts:28`](../packages/core/tools/src/inde
|
||||
|
||||
SHA-256: `a4afec59b75aad5d13ef82d6931f35cc2d98918503916cf069355b77c5e42302`
|
||||
|
||||
Sources: [`packages/api/session-controller/src/types.ts:397`](../packages/api/session-controller/src/types.ts)
|
||||
Sources: [`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
|
||||
@@ -5641,7 +5641,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
|
||||
|
||||
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
|
||||
|
||||
来源:[`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/team/agent-team/src/types.ts:20`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:32`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:8`](../packages/team/agent-team/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
来源:[`packages/api/session-controller/src/types.ts:389`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/team/agent-team/src/types.ts:20`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:32`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:8`](../packages/team/agent-team/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
|
||||
`string`
|
||||
|
||||
@@ -7761,7 +7761,7 @@ SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543`
|
||||
|
||||
SHA-256: `a4afec59b75aad5d13ef82d6931f35cc2d98918503916cf069355b77c5e42302`
|
||||
|
||||
来源:[`packages/api/session-controller/src/types.ts:397`](../packages/api/session-controller/src/types.ts)
|
||||
来源:[`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
|
||||
Generated
+2
-2
@@ -253,7 +253,7 @@
|
||||
{"digest":"4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731","names":["packages/llm/llm/src/message.ts#ToolResultMessage"],"sources":["packages/llm/llm/src/message.ts:173"]},
|
||||
{"digest":"4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea","names":[],"sources":[]},
|
||||
{"digest":"4bb451330ff699f36b45539ceb0eb2606ead72084e2c5bb637c36cc80e2252db","names":[],"sources":["packages/core/session/src/types.ts:198"]},
|
||||
{"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/team/agent-team/src/types.ts#TeamId","packages/team/agent-team/src/types.ts#TeamMessageId","packages/team/agent-team/src/types.ts#TeamTaskId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:388","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/team/agent-team/src/types.ts:20","packages/team/agent-team/src/types.ts:32","packages/team/agent-team/src/types.ts:8","packages/workflow/workflow/src/types.ts:13"]},
|
||||
{"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/team/agent-team/src/types.ts#TeamId","packages/team/agent-team/src/types.ts#TeamMessageId","packages/team/agent-team/src/types.ts#TeamTaskId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:389","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/team/agent-team/src/types.ts:20","packages/team/agent-team/src/types.ts:32","packages/team/agent-team/src/types.ts:8","packages/workflow/workflow/src/types.ts:13"]},
|
||||
{"digest":"4d60dddaa65d3da8d4cd014d85ce3d2c2875664dd0433e331d8e9d395289686b","names":["packages/experimental/liveshare/src/host/viewer-principal.ts#ShareViewerPrincipal"],"sources":["packages/experimental/liveshare/src/host/viewer-principal.ts:24"]},
|
||||
{"digest":"4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159","names":[],"sources":[]},
|
||||
{"digest":"4e44c9118152f6829634bfd508e47669ab0497caffa380c94b4be91f9390d35d","names":[],"sources":[]},
|
||||
@@ -474,7 +474,7 @@
|
||||
{"digest":"a3c6f1ea38e9a61d7a9706057ba65aabe02dfe6d9dd90d4736aa62325d874531","names":[],"sources":[]},
|
||||
{"digest":"a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa","names":[],"sources":[]},
|
||||
{"digest":"a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c","names":[],"sources":["packages/team/agent-team/src/types.ts:175"]},
|
||||
{"digest":"a4afec59b75aad5d13ef82d6931f35cc2d98918503916cf069355b77c5e42302","names":[],"sources":["packages/api/session-controller/src/types.ts:397"]},
|
||||
{"digest":"a4afec59b75aad5d13ef82d6931f35cc2d98918503916cf069355b77c5e42302","names":[],"sources":["packages/api/session-controller/src/types.ts:398"]},
|
||||
{"digest":"a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc","names":[],"sources":[]},
|
||||
{"digest":"a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb","names":["packages/experimental/hook-protocol/src/types.ts#HookDialect"],"sources":["packages/experimental/hook-protocol/src/types.ts:48"]},
|
||||
{"digest":"a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c","names":["packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData"],"sources":["packages/workflow/tool-workflow/src/types.ts:14"]},
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
en: c8aa4d3384ea1306
|
||||
zh: 5aa030587d5b634d
|
||||
/vendored-package-rescope/name-mapping:
|
||||
en: 239fa7993825ac62
|
||||
zh: df35fa9f5ef42a05
|
||||
en: 50a68b9b3724eff8
|
||||
zh: 9facad930deeb014
|
||||
/vendored-package-rescope/what-the-rename-does-not-touch:
|
||||
en: 9bb7d09f8cccf695
|
||||
zh: 2a8f3f60ae5116f3
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ The Cordis framework and its foundation libraries are vendored under [`vendor/`]
|
||||
| `vendor/cordis/` | `cordis` | `@deepseek-ai/cordis` | 4.0.0-rc.11 | Framework core: `Context`, `Service`, `Fiber`, events |
|
||||
| `vendor/cosmokit/` | `cosmokit` | `@deepseek-ai/cosmokit` | 1.8.1 | Shared utilities the framework and Schemastery build on |
|
||||
| `vendor/schemastery/` | `schemastery` | `@deepseek-ai/schemastery` | 3.18.0 | Config schemas (`Schema`) behind every plugin's `Config` |
|
||||
| `vendor/loader/` | `@cordisjs/plugin-loader` | `@deepseek-ai/cordis-plugin-loader` | 1.0.0-rc.5 | `cordis.yml` loading, plugin resolution, repository cache |
|
||||
| `vendor/include/` | `@cordisjs/plugin-include` | `@deepseek-ai/cordis-plugin-include` | 1.0.4 | Config includes and patch overlays |
|
||||
| `vendor/loader/` | `@cordisjs/plugin-loader` | `@deepseek-ai/cordis-plugin-loader` | 1.0.0-rc.8 | `cordis.yml` loading, plugin resolution, repository cache |
|
||||
| `vendor/include/` | `@cordisjs/plugin-include` | `@deepseek-ai/cordis-plugin-include` | 1.1.1 | Config includes and patch overlays |
|
||||
| `vendor/group/` | `@cordisjs/plugin-group` | `@deepseek-ai/cordis-plugin-group` | 1.0.0 | Nested plugin groups |
|
||||
| `vendor/timer/` | `@cordisjs/plugin-timer` | `@deepseek-ai/cordis-plugin-timer` | 1.1.4 | Disposal-aware timers on `ctx` |
|
||||
| `vendor/hmr/` | `@cordisjs/plugin-hmr` | `@deepseek-ai/cordis-plugin-hmr` | 1.0.15 | Hot module replacement for plugins and config |
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ Cordis 框架及其基础库以源码形式 vendored 在 [`vendor/`](../vendor/R
|
||||
| `vendor/cordis/` | `cordis` | `@deepseek-ai/cordis` | 4.0.0-rc.11 | 框架核心:`Context`、`Service`、`Fiber`、事件 |
|
||||
| `vendor/cosmokit/` | `cosmokit` | `@deepseek-ai/cosmokit` | 1.8.1 | 框架与 Schemastery 共用的基础工具 |
|
||||
| `vendor/schemastery/` | `schemastery` | `@deepseek-ai/schemastery` | 3.18.0 | 配置 schema(`Schema`),每个插件的 `Config` 都基于它 |
|
||||
| `vendor/loader/` | `@cordisjs/plugin-loader` | `@deepseek-ai/cordis-plugin-loader` | 1.0.0-rc.5 | `cordis.yml` 装载、插件解析、repository 缓存 |
|
||||
| `vendor/include/` | `@cordisjs/plugin-include` | `@deepseek-ai/cordis-plugin-include` | 1.0.4 | 配置包含与 patch 叠加 |
|
||||
| `vendor/loader/` | `@cordisjs/plugin-loader` | `@deepseek-ai/cordis-plugin-loader` | 1.0.0-rc.8 | `cordis.yml` 装载、插件解析、repository 缓存 |
|
||||
| `vendor/include/` | `@cordisjs/plugin-include` | `@deepseek-ai/cordis-plugin-include` | 1.1.1 | 配置包含与 patch 叠加 |
|
||||
| `vendor/group/` | `@cordisjs/plugin-group` | `@deepseek-ai/cordis-plugin-group` | 1.0.0 | 嵌套插件分组 |
|
||||
| `vendor/timer/` | `@cordisjs/plugin-timer` | `@deepseek-ai/cordis-plugin-timer` | 1.1.4 | `ctx` 上随 disposal 回收的定时器 |
|
||||
| `vendor/hmr/` | `@cordisjs/plugin-hmr` | `@deepseek-ai/cordis-plugin-hmr` | 1.0.15 | 插件与配置的热替换 |
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 0083032b9d19467d
|
||||
zh: 15d7a7520bac094e
|
||||
/user-credentials/resolution:
|
||||
en: 0583aeab49d80a4b
|
||||
zh: 1ffd60d7e79f67cf
|
||||
en: baaab4bca76bea6b
|
||||
zh: 54ccdc03ffd44f63
|
||||
/user-credentials/description:
|
||||
en: f7b09588c0f34952
|
||||
zh: 1b93d673c5bf6a80
|
||||
|
||||
@@ -20,8 +20,6 @@ type CredentialRef = Branded<'CredentialRef'>
|
||||
|
||||
`resolve(ref)` returns the value with the provider-defined source layer that supplied it, or `undefined` while unconfigured. Consumers re-resolve at each operation and never cache across operations — that per-operation read is the hot-update mechanism.
|
||||
|
||||
`readStored(ref)` returns only the provider-managed value for editing, or `undefined` when none is stored. It never reads environment fallbacks. The Remote editor operation restricts this read to the owner.
|
||||
|
||||
```ts type-equiv
|
||||
/** One resolved credential value and the source layer that supplied it. */
|
||||
interface ResolvedCredential {
|
||||
@@ -154,13 +152,6 @@ A CredentialKey answers "what credential does this plugin hold for this id". Not
|
||||
*/
|
||||
abstract resolve(ref: CredentialRef): Promise<ResolvedCredential | undefined>
|
||||
|
||||
/**
|
||||
* Read the provider-managed value without consulting environment fallbacks.
|
||||
* @param ref - the reference to read.
|
||||
* @returns the stored secret, or undefined when no non-empty value is stored.
|
||||
*/
|
||||
abstract readStored(ref: CredentialRef): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Describe one reference for configuration surfaces without exposing the
|
||||
* value.
|
||||
@@ -237,7 +228,7 @@ Source: [`packages/credentials/credentials/src/index.ts`](../../packages/credent
|
||||
|
||||
### `ctx.credentialsController` — `CredentialsController`
|
||||
|
||||
Host service backing the generated `ctx.remote.credentials` namespace. It carries every wire obligation the credential seam itself does not: the batch fan-out bound, the field-by-field view projection, the reference-grammar guard, and the refusal mapping. Metadata reads exclude secrets; readStored returns only a provider-managed value for an explicitly opened editor.
|
||||
Host service backing the generated `ctx.remote.credentials` namespace. It carries every wire obligation the credential seam itself does not: the batch fan-out bound, the field-by-field view projection, the reference-grammar guard, and the refusal mapping. Secret values cross in one direction only — no method here returns one.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
@@ -252,15 +243,8 @@ Host service backing the generated `ctx.remote.credentials` namespace. It carrie
|
||||
@Remote async describe(refs: string[]): Promise<Record<string, CredentialInfo>>
|
||||
|
||||
/**
|
||||
* Read one stored value for a credential editor, excluding environment fallbacks.
|
||||
* @param ref - reference name to read.
|
||||
* @returns the stored secret, or undefined when the provider stores none.
|
||||
* @throws RemoteError for a non-owner Remote caller, an invalid reference, or an absent provider.
|
||||
*/
|
||||
@Remote async readStored(ref: string): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Store one value from a configuration surface.
|
||||
* Store one value from a configuration surface. The value crosses the wire in
|
||||
* this direction only: no read path returns it.
|
||||
* @param ref - reference name to store under.
|
||||
* @param value - the non-empty secret value.
|
||||
* @throws RemoteError when the request is invalid, no provider is mounted, or the provider refuses the write.
|
||||
|
||||
@@ -20,8 +20,6 @@ type CredentialRef = Branded<'CredentialRef'>
|
||||
|
||||
`resolve(ref)` 返回值及提供该值的来源层(由提供方定义);未配置期间返回 `undefined`。消费方在每个操作中重新解析,绝不跨操作缓存——这种按操作进行的读取正是热更新机制。
|
||||
|
||||
`readStored(ref)` 仅返回用于编辑的提供方托管值;未存储时返回 `undefined`。它不读取环境后备值。编辑器的 Remote 操作仅允许所有者执行此读取。
|
||||
|
||||
```ts type-equiv
|
||||
/** One resolved credential value and the source layer that supplied it. */
|
||||
interface ResolvedCredential {
|
||||
@@ -154,13 +152,6 @@ A CredentialKey answers "what credential does this plugin hold for this id". Not
|
||||
*/
|
||||
abstract resolve(ref: CredentialRef): Promise<ResolvedCredential | undefined>
|
||||
|
||||
/**
|
||||
* Read the provider-managed value without consulting environment fallbacks.
|
||||
* @param ref - the reference to read.
|
||||
* @returns the stored secret, or undefined when no non-empty value is stored.
|
||||
*/
|
||||
abstract readStored(ref: CredentialRef): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Describe one reference for configuration surfaces without exposing the
|
||||
* value.
|
||||
@@ -237,7 +228,7 @@ Source: [`packages/credentials/credentials/src/index.ts`](../../packages/credent
|
||||
|
||||
### `ctx.credentialsController` — `CredentialsController`
|
||||
|
||||
Host service backing the generated `ctx.remote.credentials` namespace. It carries every wire obligation the credential seam itself does not: the batch fan-out bound, the field-by-field view projection, the reference-grammar guard, and the refusal mapping. Metadata reads exclude secrets; readStored returns only a provider-managed value for an explicitly opened editor.
|
||||
Host service backing the generated `ctx.remote.credentials` namespace. It carries every wire obligation the credential seam itself does not: the batch fan-out bound, the field-by-field view projection, the reference-grammar guard, and the refusal mapping. Secret values cross in one direction only — no method here returns one.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
@@ -252,15 +243,8 @@ Host service backing the generated `ctx.remote.credentials` namespace. It carrie
|
||||
@Remote async describe(refs: string[]): Promise<Record<string, CredentialInfo>>
|
||||
|
||||
/**
|
||||
* Read one stored value for a credential editor, excluding environment fallbacks.
|
||||
* @param ref - reference name to read.
|
||||
* @returns the stored secret, or undefined when the provider stores none.
|
||||
* @throws RemoteError for a non-owner Remote caller, an invalid reference, or an absent provider.
|
||||
*/
|
||||
@Remote async readStored(ref: string): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Store one value from a configuration surface.
|
||||
* Store one value from a configuration surface. The value crosses the wire in
|
||||
* this direction only: no read path returns it.
|
||||
* @param ref - reference name to store under.
|
||||
* @param value - the non-empty secret value.
|
||||
* @throws RemoteError when the request is invalid, no provider is mounted, or the provider refuses the write.
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/forge.md
|
||||
/forges:
|
||||
en: 1b51f9d385dafe76
|
||||
zh: 4bb73627aa69d652
|
||||
en: 3ee521b77b4601e2
|
||||
zh: 4170c33e49248f8f
|
||||
/forges/providers-and-change-requests:
|
||||
en: 4ec7d1892b4067db
|
||||
zh: 6bb70d21ae3d7a89
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](forge.zh.md)
|
||||
|
||||
The experimental forge capability seam reads change requests, meaning pull requests and merge requests, from code hosts. [`dsh-experimental-forge`](../../packages/experimental/forge/README.md) is the Service Definition and provides `ctx.forge`; [`dsh-experimental-forge-github`](../../packages/experimental/forge-github/README.md) is the Service Provider for GitHub hosts; [`dsh-experimental-forge-session`](../../packages/experimental/forge-session/README.md) is the Consumer that binds change requests to Sessions and provides `ctx.forgeSession`; [`dsh-experimental-client-ui-forge`](../../packages/experimental/client-ui-forge/README.md) is the GUI Consumer that provides `ctx.forgePanel`. The default-disabled [forge bundle](../../packages/experimental/forge-bundle/README.md) composes all four, with an isolated [worktree service](../../packages/experimental/worktree/README.md) for the panel, from the Plugins page; otherwise they load only through explicit composition.
|
||||
The experimental forge capability seam reads change requests, meaning pull requests and merge requests, from code hosts. [`dsh-experimental-forge`](../../packages/experimental/forge/README.md) is the Service Definition and provides `ctx.forge`; [`dsh-experimental-forge-github`](../../packages/experimental/forge-github/README.md) is the Service Provider for GitHub hosts; [`dsh-experimental-forge-session`](../../packages/experimental/forge-session/README.md) is the Consumer that binds change requests to Sessions and provides `ctx.forgeSession`; [`dsh-experimental-client-ui-forge`](../../packages/experimental/client-ui-forge/README.md) is the GUI Consumer that provides `ctx.forgePanel`. The default-disabled [forge bundle](../../packages/experimental/forge-bundle/README.md) composes all four, with the [worktree model tool](../../packages/experimental/tool-worktree/README.md) and an isolated [worktree service](../../packages/experimental/worktree/README.md) for the panel, from the Plugins page; otherwise they load only through explicit composition.
|
||||
|
||||
## Providers and change requests
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](forge.md) | 中文
|
||||
|
||||
实验性的 forge 能力 seam 从代码托管平台读取变更请求(change request,即 pull request 与 merge request)。[`dsh-experimental-forge`](../../packages/experimental/forge/README.zh.md) 是 Service Definition,提供 `ctx.forge`;[`dsh-experimental-forge-github`](../../packages/experimental/forge-github/README.zh.md) 是服务 GitHub 主机的 Service Provider;[`dsh-experimental-forge-session`](../../packages/experimental/forge-session/README.zh.md) 是把变更请求绑定到 Session 的 Consumer,提供 `ctx.forgeSession`;[`dsh-experimental-client-ui-forge`](../../packages/experimental/client-ui-forge/README.zh.md) 是提供 `ctx.forgePanel` 的 GUI Consumer。默认禁用的 [forge Bundle](../../packages/experimental/forge-bundle/README.zh.md) 可从插件页面组合这四个包,并为面板附带一个隔离的 [worktree 服务](../../packages/experimental/worktree/README.zh.md);除此之外,它们只能通过显式组合加载。
|
||||
实验性的 forge 能力 seam 从代码托管平台读取变更请求(change request,即 pull request 与 merge request)。[`dsh-experimental-forge`](../../packages/experimental/forge/README.zh.md) 是 Service Definition,提供 `ctx.forge`;[`dsh-experimental-forge-github`](../../packages/experimental/forge-github/README.zh.md) 是服务 GitHub 主机的 Service Provider;[`dsh-experimental-forge-session`](../../packages/experimental/forge-session/README.zh.md) 是把变更请求绑定到 Session 的 Consumer,提供 `ctx.forgeSession`;[`dsh-experimental-client-ui-forge`](../../packages/experimental/client-ui-forge/README.zh.md) 是提供 `ctx.forgePanel` 的 GUI Consumer。默认禁用的 [forge Bundle](../../packages/experimental/forge-bundle/README.zh.md) 可从插件页面组合这四个包,同时加载[工作树模型工具](../../packages/experimental/tool-worktree/README.zh.md),并为面板附带一个隔离的 [worktree 服务](../../packages/experimental/worktree/README.zh.md);除此之外,它们只能通过显式组合加载。
|
||||
|
||||
## 提供方与变更请求
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/session-projection.md
|
||||
/session-projections:
|
||||
en: b2789f5257a935c5
|
||||
zh: 12d430a9cd454592
|
||||
en: 1804bc8fc5f226e4
|
||||
zh: 2f31d0f014508d29
|
||||
/session-projections/the-unit:
|
||||
en: d48814d3ab0e997f
|
||||
zh: d0337d8eba097a63
|
||||
@@ -12,11 +12,11 @@
|
||||
en: bc9d438b90fc2baa
|
||||
zh: df7cbea2dad3786b
|
||||
/session-projections/summary-reads:
|
||||
en: d2bc9bcc775acfb7
|
||||
zh: b1d2ba74496305b8
|
||||
en: fcc9d07e5c41abb7
|
||||
zh: 798004ad1c66787b
|
||||
/session-projections/summary-reads/summary-vocabulary:
|
||||
en: 6a93a4e1682fd6e6
|
||||
zh: 28729129c3d8a3b1
|
||||
/session-projections/the-registry-ctx-sessionprojections:
|
||||
en: 67fe0f49dee90e25
|
||||
zh: c23de3a2803040f0
|
||||
en: 33e4aa5276d4c0fa
|
||||
zh: 18019b4990734b82
|
||||
|
||||
@@ -6,7 +6,7 @@ The session-projection seam — a [capability seam](../capability-seams.md) thro
|
||||
|
||||
Source: [`packages/session/session-projection/src/index.ts`](../../packages/session/session-projection/src/index.ts)
|
||||
|
||||
A `ProjectionHandle` retains its synchronous reader after unregistration. `stateOf(session)` folds newly committed events with the original unit; it does not rejoin global snapshots, checkpoints, or notifications. Registration must still finish before the handle can be used. See the [registration lifecycle](../../packages/session/session-projection/README.md).
|
||||
A `ProjectionHandle` retains its synchronous reader after unregistration. `stateOf(session)` folds newly committed events with the original unit; it does not rejoin global snapshots or notifications. Registration must still finish before the handle can be used. See the [registration lifecycle](../../packages/session/session-projection/README.md).
|
||||
|
||||
```ts type-equiv
|
||||
/** An effect-owned registration with a reader retained independently of registration. */
|
||||
@@ -14,6 +14,8 @@ interface ProjectionHandle<S> {
|
||||
/**
|
||||
* Read the current state, including after unregistering, by folding new
|
||||
* Session events with this handle's original unit. Do not mutate the live value.
|
||||
* After the final release, a read also advances the released cell that
|
||||
* `checkpoint(session)` serves, until the key registers again.
|
||||
* @param session - Session whose committed events are folded.
|
||||
* @returns the projection state at the Session's current cursor.
|
||||
*/
|
||||
@@ -156,9 +158,9 @@ type ProjectionEventChangeListener = (
|
||||
|
||||
A Session summary carries every registered client view except the keys whose `SessionProjectionMap` entry carries the `SessionProjectionSummaryExcluded<T>` mark. The mark has no runtime representation: it decides the client view type, while `wire.summary` decides the runtime key set, with omission meaning `true`. A key whose entry carries the mark declares `wire.summary: false` so the two agree; the field is a plain boolean, so the type does not enforce it.
|
||||
|
||||
`summaryKeys()` derives the key set from the live registrations and keeps returning the same instance until a registration or its disposal changes the key table. A summary consumer passes it to the existing key-taking faces — `cachedSnapshot(session, keys)` for cached cells and `viewCheckpoint(rows, keys)` for checkpoint rows, both keeping their watermark rules and both using a set without copying — so there is no summary-specific read face and no second definition of the set.
|
||||
`summaryKeys()` derives the key set from the live registrations and keeps returning the same instance until a registration or its disposal changes the key table. A summary consumer passes it to the existing key-taking faces — `snapshot(session, keys)` for live Sessions and `viewCheckpoint(rows, keys)` for checkpoint rows, both using a set without copying — so there is no summary-specific read face and no second definition of the set.
|
||||
|
||||
A list row carries that summary projection, never the full projection: it promises the summary key set and nothing more. `snapshot`, `cachedSnapshot`, and `viewCheckpoint` remain complete when called without a key subset, which is how an opened Session, its follow baseline, and the control baseline keep every projected value; the `@` reference reads only its label keys through the same key-taking faces.
|
||||
A list row carries that summary projection, never the full projection: it promises the summary key set and nothing more. `snapshot(session, keys)` computes only the units it returns. Without a key subset, `snapshot` computes every client-visible unit and no Host-only unit, and `viewCheckpoint` serves every wire key, so an opened Session, its follow baseline, and the control baseline carry every projected value; the `@` reference reads only its label keys through the same key-taking faces. The registry's `cachedSnapshot(session, keys)` is deprecated: it returns only values another read already computed and may omit keys.
|
||||
|
||||
### Summary vocabulary
|
||||
|
||||
@@ -230,7 +232,7 @@ type ProjectionKeySelection =
|
||||
|
||||
## The registry: `ctx.sessionProjections`
|
||||
|
||||
`SessionProjectionRegistry` ([signatures](#ctxsessionprojections--sessionprojectionregistry)) owns the drive: one `session/event` subscription, eager `apply` over every registered unit, and per-session per-unit watermark cells. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Domains await `register(definition)` during activation and retain its handle. Registration belongs to the calling fiber: its last release removes the key from global drives and snapshots, while retained handles remain readable. Same-key, same-version registrants share one unit; incompatible versions reject registration.
|
||||
`SessionProjectionRegistry` ([signatures](#ctxsessionprojections--sessionprojectionregistry)) owns the drive: one `session/event` subscription, eager `apply` over every registered unit, and per-session per-unit watermark cells. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Domains await `register(definition)` during activation and retain its handle. Registration belongs to the calling fiber: its last release removes the key from global drives and snapshots, checkpoints keep each Session's last state of it until the key registers again, and retained handles remain readable. Same-key, same-version registrants share one unit; incompatible versions reject registration.
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
@@ -331,7 +333,7 @@ Source: [`packages/session/session-projection-cache/src/index.ts`](../../package
|
||||
|
||||
### `ctx.sessionProjections` — `SessionProjectionRegistry`
|
||||
|
||||
`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive). A changed state reference computes the next client view; the change feed is notified only when its raw result changes by `Object.is`. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Consumers declare `sessionProjections` as a required injection, await registration during activation, and retain its ready handle. Registrants sharing a key share one unit and are counted. The last release removes the unit from live drives, checkpoints, and client snapshots; retained handles still fold new events on demand using the original unit.
|
||||
`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive). A changed state reference computes the next client view; the change feed is notified only when its raw result changes by `Object.is`. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Consumers declare `sessionProjections` as a required injection, await registration during activation, and retain its ready handle. Registrants sharing a key share one unit and are counted. The last release removes the unit from live drives, restores, and client snapshots, while checkpoints keep each Session's last cell of it until the key registers again and the new unit refolds the log; retained handles still fold new events on demand using the original unit.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
@@ -376,30 +378,22 @@ onEventChanged(listener: ProjectionEventChangeListener): () => void
|
||||
/**
|
||||
* One consistent cut over every registered client-visible unit for one session, read from
|
||||
* the watermark cache (missing cells fold lazily over the in-memory log).
|
||||
* Only the returned units are computed: other units, including Host-only
|
||||
* units without `wire`, are neither folded nor advanced by this read.
|
||||
* Fully synchronous — every value and `asOfSeq` reflect the same log
|
||||
* position. Each value passes its unit's `viewSchema` before leaving.
|
||||
* @param session - the session whose projection values are read.
|
||||
* @param keys - optional client-visible outputs; state materialization remains complete.
|
||||
* @param keys - optional client-visible outputs; only these units are computed.
|
||||
* @returns the snapshot; `values` is empty when no selected client-visible unit is registered.
|
||||
*/
|
||||
snapshot( session: Session, keys?: ProjectionKeySelection, ): ProjectionSnapshot
|
||||
|
||||
/**
|
||||
* Read only already-materialized client-visible cells without folding history.
|
||||
* Values may trail the live Session and are therefore hints, not a complete
|
||||
* baseline. Missing cells are omitted.
|
||||
* @param session - attached Session whose cached cells are inspected.
|
||||
* @param keys - optional wire keys to view.
|
||||
* @returns the lowest common cached cut, or `undefined` when no wire cell exists.
|
||||
*/
|
||||
cachedSnapshot( session: Session, keys?: ProjectionKeySelection, ): ProjectionSnapshot | undefined
|
||||
|
||||
/**
|
||||
* The keys a Session summary carries: every registered client-visible key
|
||||
* whose unit does not declare `summary: false`. The returned set is stable —
|
||||
* the same instance on every call — until a registration or its disposal
|
||||
* changes the key table, so a consumer may hold it across rows. Callers pass
|
||||
* it to a key-taking read face such as `cachedSnapshot(session, summaryKeys())`
|
||||
* it to a key-taking read face such as `snapshot(session, summaryKeys())`
|
||||
* or `viewCheckpoint(rows, summaryKeys())`; those faces use the set as-is.
|
||||
* @returns the current summary key set; do not mutate it.
|
||||
*/
|
||||
@@ -416,8 +410,13 @@ summaryKeys(): ReadonlySet<Extract<keyof SessionProjectionMap, string>>
|
||||
* mutable state, and a caller reaching the live reference could corrupt
|
||||
* every subsequent snapshot and frame through it (plain JSON by the unit
|
||||
* contract, so the clone is total).
|
||||
* A released unit contributes the Session's last cell. The registry no
|
||||
* longer advances that cell, while a retained handle read still advances it
|
||||
* forward, so a writer running after a plugin unloads keeps that unit's
|
||||
* latest state; a Session that has no cell of it gets no row. {@link restore}
|
||||
* folds only live units, so its refreshed rows omit released units.
|
||||
* @param session - the session whose unit states are checkpointed.
|
||||
* @returns one row per registered key.
|
||||
* @returns one row per registered key, plus one per released key with a cell for this Session.
|
||||
*/
|
||||
checkpoint(session: Session): ProjectionCheckpoint
|
||||
|
||||
@@ -434,7 +433,7 @@ checkpoint(session: Session): ProjectionCheckpoint
|
||||
* re-read.
|
||||
* @param checkpoint - persisted rows for one session (possibly stale or empty).
|
||||
* @returns the offset for the stored-log suffix read (`SessionHandle.read`),
|
||||
* or `undefined` when no unit is registered (no read needed —
|
||||
* or `undefined` when no live unit is registered (released predecessors do not count; no read needed —
|
||||
* {@link restore} would serve empty values regardless).
|
||||
*/
|
||||
restoreFloor(checkpoint: ProjectionCheckpoint): SessionLogOffset | undefined
|
||||
@@ -475,6 +474,8 @@ viewCheckpoint( checkpoint: ProjectionCheckpoint, keys?: ProjectionKeySelection,
|
||||
* @returns the snapshot cut at the supplied log end (`asOfSeq` is the last
|
||||
* supplied event's seq, `baseSeq - 1` for an empty tail) plus the
|
||||
* refreshed checkpoint rows at that cut, ready for a durable write-back.
|
||||
* The rows cover live units only: a released unit has no cell for a log
|
||||
* folded without a live `Session`, so a write-back of them omits it.
|
||||
*/
|
||||
restore( checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: SessionLogOffset, header: SessionHeader, inheritedEventCount: SessionLogOffset, ): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
源码:[`packages/session/session-projection/src/index.ts`](../../packages/session/session-projection/src/index.ts)
|
||||
|
||||
`ProjectionHandle` 在注销后仍保留同步 reader。`stateOf(session)` 用原有单元折叠新提交的事件,不会重新加入全局快照、检查点或通知。必须等待注册完成才能使用 handle。参见[注册生命周期](../../packages/session/session-projection/README.zh.md)。
|
||||
`ProjectionHandle` 在注销后仍保留同步 reader。`stateOf(session)` 用原有单元折叠新提交的事件,不会重新加入全局快照或通知。必须等待注册完成才能使用 handle。参见[注册生命周期](../../packages/session/session-projection/README.zh.md)。
|
||||
|
||||
```ts type-equiv
|
||||
/** An effect-owned registration with a reader retained independently of registration. */
|
||||
@@ -14,6 +14,8 @@ interface ProjectionHandle<S> {
|
||||
/**
|
||||
* Read the current state, including after unregistering, by folding new
|
||||
* Session events with this handle's original unit. Do not mutate the live value.
|
||||
* After the final release, a read also advances the released cell that
|
||||
* `checkpoint(session)` serves, until the key registers again.
|
||||
* @param session - Session whose committed events are folded.
|
||||
* @returns the projection state at the Session's current cursor.
|
||||
*/
|
||||
@@ -156,9 +158,9 @@ type ProjectionEventChangeListener = (
|
||||
|
||||
Session 摘要包含每个已注册客户端视图,但排除 `SessionProjectionMap` 条目带 `SessionProjectionSummaryExcluded<T>` 标记的 key。标记没有运行时表示:它决定客户端视图类型,`wire.summary` 决定运行时键集合,省略即 `true`。map 条目带标记的 key 应声明 `wire.summary: false` 以保持一致;该字段是普通布尔值,类型层不做强制。
|
||||
|
||||
`summaryKeys()` 从实时注册派生该集合,并在注册或注销改变 key 表之前持续返回同一实例。摘要消费方把它传给既有的带 key 读取面 —— 缓存 cell 用 `cachedSnapshot(session, keys)`,checkpoint 行用 `viewCheckpoint(rows, keys)`,两者保持各自的水位规则且直接使用 Set、不做复制 —— 因此不存在摘要专用读取面,集合也只有一处定义。
|
||||
`summaryKeys()` 从实时注册派生该集合,并在注册或注销改变 key 表之前持续返回同一实例。摘要消费方把它传给既有的带 key 读取面 —— 实时 Session 用 `snapshot(session, keys)`,checkpoint 行用 `viewCheckpoint(rows, keys)`,两者都直接使用 Set、不做复制 —— 因此不存在摘要专用读取面,集合也只有一处定义。
|
||||
|
||||
列表行携带的是这个摘要投影,绝不是全量投影:它只承诺摘要键集合,不承诺更多。`snapshot`、`cachedSnapshot` 与 `viewCheckpoint` 在不传 key 子集时仍是完整的,打开的 Session、其 follow 基线与控制基线因此保留每个投影值;`@` 引用只通过同一组带 key 的读取面读取其标签 key。
|
||||
列表行携带的是这个摘要投影,绝不是全量投影:它只承诺摘要键集合,不承诺更多。`snapshot(session, keys)` 只计算它返回的单元。不传 key 子集时,`snapshot` 计算每个客户端可见单元、不计算 Host 专用单元,`viewCheckpoint` 返回每个 wire key,因此打开的 Session、其 follow 基线与控制基线携带每个投影值;`@` 引用只通过同一组带 key 的读取面读取其标签 key。注册表的 `cachedSnapshot(session, keys)` 已废弃:它只返回其他读取已经算好的值,可能缺少 key。
|
||||
|
||||
### 摘要词汇
|
||||
|
||||
@@ -230,7 +232,7 @@ type ProjectionKeySelection =
|
||||
|
||||
## 注册表:`ctx.sessionProjections`
|
||||
|
||||
`SessionProjectionRegistry`([签名](#ctxsessionprojections--sessionprojectionregistry))拥有驱动逻辑:一个 `session/event` 订阅、对所有已注册单元立即执行的 `apply`,以及按会话、按单元保存的水位线缓存。缓存单元惰性构建:若单元在事件产生后才注册,或会话早于注册表存在,第一次访问(事件或读取)会从 `init` 开始折叠内存日志。领域在激活期间等待 `register(definition)`,并保留返回的 handle(句柄)。注册归调用 fiber 所有:最后一次释放会从全局驱动和快照中移除该 key,已有句柄仍可读取。同 key、同版本的注册者共享一个单元;版本不兼容时注册失败。
|
||||
`SessionProjectionRegistry`([签名](#ctxsessionprojections--sessionprojectionregistry))拥有驱动逻辑:一个 `session/event` 订阅、对所有已注册单元立即执行的 `apply`,以及按会话、按单元保存的水位线缓存。缓存单元惰性构建:若单元在事件产生后才注册,或会话早于注册表存在,第一次访问(事件或读取)会从 `init` 开始折叠内存日志。领域在激活期间等待 `register(definition)`,并保留返回的 handle(句柄)。注册归调用 fiber 所有:最后一次释放会从全局驱动和快照中移除该 key,检查点仍保留每个会话中它的最后状态,直到该 key 再次注册,已有句柄仍可读取。同 key、同版本的注册者共享一个单元;版本不兼容时注册失败。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
@@ -331,7 +333,7 @@ Source: [`packages/session/session-projection-cache/src/index.ts`](../../package
|
||||
|
||||
### `ctx.sessionProjections` — `SessionProjectionRegistry`
|
||||
|
||||
`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive). A changed state reference computes the next client view; the change feed is notified only when its raw result changes by `Object.is`. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Consumers declare `sessionProjections` as a required injection, await registration during activation, and retain its ready handle. Registrants sharing a key share one unit and are counted. The last release removes the unit from live drives, checkpoints, and client snapshots; retained handles still fold new events on demand using the original unit.
|
||||
`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive). A changed state reference computes the next client view; the change feed is notified only when its raw result changes by `Object.is`. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Consumers declare `sessionProjections` as a required injection, await registration during activation, and retain its ready handle. Registrants sharing a key share one unit and are counted. The last release removes the unit from live drives, restores, and client snapshots, while checkpoints keep each Session's last cell of it until the key registers again and the new unit refolds the log; retained handles still fold new events on demand using the original unit.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
@@ -376,30 +378,22 @@ onEventChanged(listener: ProjectionEventChangeListener): () => void
|
||||
/**
|
||||
* One consistent cut over every registered client-visible unit for one session, read from
|
||||
* the watermark cache (missing cells fold lazily over the in-memory log).
|
||||
* Only the returned units are computed: other units, including Host-only
|
||||
* units without `wire`, are neither folded nor advanced by this read.
|
||||
* Fully synchronous — every value and `asOfSeq` reflect the same log
|
||||
* position. Each value passes its unit's `viewSchema` before leaving.
|
||||
* @param session - the session whose projection values are read.
|
||||
* @param keys - optional client-visible outputs; state materialization remains complete.
|
||||
* @param keys - optional client-visible outputs; only these units are computed.
|
||||
* @returns the snapshot; `values` is empty when no selected client-visible unit is registered.
|
||||
*/
|
||||
snapshot( session: Session, keys?: ProjectionKeySelection, ): ProjectionSnapshot
|
||||
|
||||
/**
|
||||
* Read only already-materialized client-visible cells without folding history.
|
||||
* Values may trail the live Session and are therefore hints, not a complete
|
||||
* baseline. Missing cells are omitted.
|
||||
* @param session - attached Session whose cached cells are inspected.
|
||||
* @param keys - optional wire keys to view.
|
||||
* @returns the lowest common cached cut, or `undefined` when no wire cell exists.
|
||||
*/
|
||||
cachedSnapshot( session: Session, keys?: ProjectionKeySelection, ): ProjectionSnapshot | undefined
|
||||
|
||||
/**
|
||||
* The keys a Session summary carries: every registered client-visible key
|
||||
* whose unit does not declare `summary: false`. The returned set is stable —
|
||||
* the same instance on every call — until a registration or its disposal
|
||||
* changes the key table, so a consumer may hold it across rows. Callers pass
|
||||
* it to a key-taking read face such as `cachedSnapshot(session, summaryKeys())`
|
||||
* it to a key-taking read face such as `snapshot(session, summaryKeys())`
|
||||
* or `viewCheckpoint(rows, summaryKeys())`; those faces use the set as-is.
|
||||
* @returns the current summary key set; do not mutate it.
|
||||
*/
|
||||
@@ -416,8 +410,13 @@ summaryKeys(): ReadonlySet<Extract<keyof SessionProjectionMap, string>>
|
||||
* mutable state, and a caller reaching the live reference could corrupt
|
||||
* every subsequent snapshot and frame through it (plain JSON by the unit
|
||||
* contract, so the clone is total).
|
||||
* A released unit contributes the Session's last cell. The registry no
|
||||
* longer advances that cell, while a retained handle read still advances it
|
||||
* forward, so a writer running after a plugin unloads keeps that unit's
|
||||
* latest state; a Session that has no cell of it gets no row. {@link restore}
|
||||
* folds only live units, so its refreshed rows omit released units.
|
||||
* @param session - the session whose unit states are checkpointed.
|
||||
* @returns one row per registered key.
|
||||
* @returns one row per registered key, plus one per released key with a cell for this Session.
|
||||
*/
|
||||
checkpoint(session: Session): ProjectionCheckpoint
|
||||
|
||||
@@ -434,7 +433,7 @@ checkpoint(session: Session): ProjectionCheckpoint
|
||||
* re-read.
|
||||
* @param checkpoint - persisted rows for one session (possibly stale or empty).
|
||||
* @returns the offset for the stored-log suffix read (`SessionHandle.read`),
|
||||
* or `undefined` when no unit is registered (no read needed —
|
||||
* or `undefined` when no live unit is registered (released predecessors do not count; no read needed —
|
||||
* {@link restore} would serve empty values regardless).
|
||||
*/
|
||||
restoreFloor(checkpoint: ProjectionCheckpoint): SessionLogOffset | undefined
|
||||
@@ -475,6 +474,8 @@ viewCheckpoint( checkpoint: ProjectionCheckpoint, keys?: ProjectionKeySelection,
|
||||
* @returns the snapshot cut at the supplied log end (`asOfSeq` is the last
|
||||
* supplied event's seq, `baseSeq - 1` for an empty tail) plus the
|
||||
* refreshed checkpoint rows at that cut, ready for a durable write-back.
|
||||
* The rows cover live units only: a released unit has no cell for a log
|
||||
* folded without a live `Session`, so a write-back of them omits it.
|
||||
*/
|
||||
restore( checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: SessionLogOffset, header: SessionHeader, inheritedEventCount: SessionLogOffset, ): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint }
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 380da3cd0aabd52b
|
||||
zh: 1317c26089c3ab07
|
||||
/web-access/search-request-and-result:
|
||||
en: 1ad94deb713024e2
|
||||
zh: 9d3df730fa99ad89
|
||||
en: c6eee490ae914ae0
|
||||
zh: d576eaeb48480c81
|
||||
/web-access/fetch-request-and-result:
|
||||
en: b96bcdf42ced9eab
|
||||
zh: aa927e2450238557
|
||||
|
||||
@@ -12,7 +12,7 @@ Search and fetch share no request schema and no business logic, but they are del
|
||||
|
||||
## Search request and result
|
||||
|
||||
Each seam request carries exactly one `query`. The `dsh-tool-web` consumer accepts a required `queries` array and fans it out into separate seam requests; a one-item array performs one search. `maxResults` is a consumer-owned bound (`dsh-tool-web`'s `searchMaxResults` config, default `8`) passed through the seam and enforced on the way back — if a provider over-returns, the seam truncates `sources[]` and sets `truncated`.
|
||||
Each seam request carries exactly one `query`. The `dsh-tool-web` consumer accepts a required `queries` array and fans it out into separate seam requests; a one-item array performs one search. `maxResults` is a consumer-owned bound (`dsh-tool-web`'s `searchMaxResults` config, default `10`) passed through the seam and enforced on the way back — if a provider over-returns, the seam truncates `sources[]` and sets `truncated`.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@ Web 访问 seam 是一个[能力 seam](../../.agents/notes/implemented/architect
|
||||
|
||||
## 搜索请求与结果
|
||||
|
||||
每个 seam 请求只携带一个 `query`。消费方 `dsh-tool-web` 接受必填的 `queries` 数组,并把它扇出为多个独立 seam 请求;单元素数组执行一次搜索。`maxResults` 是消费方自有的上限(`dsh-tool-web` 的 `searchMaxResults` 配置,默认 `8`),通过 seam 传递并在返回时强制执行——如果提供方返回超量,seam 截断 `sources[]` 并设置 `truncated`。
|
||||
每个 seam 请求只携带一个 `query`。消费方 `dsh-tool-web` 接受必填的 `queries` 数组,并把它扇出为多个独立 seam 请求;单元素数组执行一次搜索。`maxResults` 是消费方自有的上限(`dsh-tool-web` 的 `searchMaxResults` 配置,默认 `10`),通过 seam 传递并在返回时强制执行——如果提供方返回超量,seam 截断 `sources[]` 并设置 `truncated`。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/worktrees.md
|
||||
/git-worktrees:
|
||||
en: 21ec4a3b825d7e81
|
||||
zh: c841c5ca4b4e8959
|
||||
en: 3a3d69d2b12a78f9
|
||||
zh: a7a7d12487968bbc
|
||||
/git-worktrees/creation-and-retention:
|
||||
en: 53f643c5d30e00b2
|
||||
zh: ba9e52efc62c7656
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](worktrees.zh.md)
|
||||
|
||||
The experimental [`dsh-experimental-worktree`](../../packages/experimental/worktree/README.md) service provides `ctx.worktrees`. Its [`create_worktree`](../../packages/experimental/tool-worktree/README.md) consumer creates a new Git branch and checkout, then selects it through the [working-directory service](working-directory.md). The [Git Worktrees optional bundle](../../packages/experimental/tool-worktree/README.md#use-this-package) loads both packages through the GUI plugin manager. It ships switched off; explicit compositions can also mount the two packages.
|
||||
The experimental [`dsh-experimental-worktree`](../../packages/experimental/worktree/README.md) service provides `ctx.worktrees`. Its [`create_worktree`](../../packages/experimental/tool-worktree/README.md) consumer creates a new Git branch and checkout, then selects it through the [working-directory service](working-directory.md). The [Change requests & worktrees bundle](../../packages/experimental/forge-bundle/README.md#use-this-package) loads both packages through the GUI plugin manager and ships switched off. Other profiles can select the [standalone worktree bundle](../../packages/experimental/tool-worktree/README.md#use-this-package) or mount the two packages explicitly.
|
||||
|
||||
## Creation and retention
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](worktrees.md) | 中文
|
||||
|
||||
实验性的 [`dsh-experimental-worktree`](../../packages/experimental/worktree/README.zh.md) 服务提供 `ctx.worktrees`。其消费方 [`create_worktree`](../../packages/experimental/tool-worktree/README.zh.md) 创建新的 Git 分支与检出目录,再通过[工作目录服务](working-directory.zh.md)选中它。[Git 工作树可选 bundle](../../packages/experimental/tool-worktree/README.zh.md#use-this-package)通过 GUI 插件管理器一起加载这两个包。它随安装提供,但默认关闭;显式组合也可以挂载这两个包。
|
||||
实验性的 [`dsh-experimental-worktree`](../../packages/experimental/worktree/README.zh.md) 服务提供 `ctx.worktrees`。其消费方 [`create_worktree`](../../packages/experimental/tool-worktree/README.zh.md) 创建新的 Git 分支与检出目录,再通过[工作目录服务](working-directory.zh.md)选中它。[变更请求与工作树组合包](../../packages/experimental/forge-bundle/README.zh.md#use-this-package)通过 GUI 插件管理器一起加载这两个包,随安装提供但默认关闭。其他 profile 可以选择[独立工作树组合包](../../packages/experimental/tool-worktree/README.zh.md#use-this-package),或显式挂载这两个包。
|
||||
|
||||
## 创建与保留
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 6cc2cd9ced70bb09
|
||||
zh: 1c0e6cb4ba2ed637
|
||||
/tool-schema-catalog/tool-package-map:
|
||||
en: 50b6f8b820d129ca
|
||||
zh: 2d0310f934f8e271
|
||||
en: 7e4516e824b1500e
|
||||
zh: b0ea6f61792d98a0
|
||||
/tool-schema-catalog/deepseek-ai-dsh-plugin-manager:
|
||||
en: e0143916bb634762
|
||||
zh: e0143916bb634762
|
||||
@@ -309,8 +309,8 @@
|
||||
en: d5f5679abe86e316
|
||||
zh: d5f5679abe86e316
|
||||
/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-worktree/create-worktree:
|
||||
en: d8fe3c566ba2147c
|
||||
zh: baf372cb9242670c
|
||||
en: 41950f76db71e7f7
|
||||
zh: 654d2e5505ff3d3c
|
||||
/tool-schema-catalog/deepseek-ai-dsh-tool-workflow:
|
||||
en: bb38a9d29f46563c
|
||||
zh: bb38a9d29f46563c
|
||||
|
||||
@@ -46,7 +46,7 @@ This table connects model-visible tool names to the plugin package and service s
|
||||
| `@deepseek-ai/dsh-tool-agent-team` | `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `ctx.subagents`, `an exact live Team member Agent` | `tool/call`, `team/task`, `tool/result` | - | Four task-board tools share the subagent tree’s root Session. Enabling Team mode on the root Session adds task-board guidance alongside standard subagent delegation and control tools. Standard subagent controls provide required identity independently of Team mode. |
|
||||
| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist. `allowParallelInProgress` is required with no default, so the catalog states its choice: `true`, whose description invites several `in_progress` items. A deployment choosing `false` receives the same tool with a description asking for exactly one active task. |
|
||||
| `@deepseek-ai/dsh-tool-working-directory` | `working_directory` | `ctx.tools`, `ctx.workingDirectory` | `tool/call`, `working-directory/change`, `user context for directory changes`, `tool/result` | - | Reads or changes the calling Session directory. Existing shells and processes retain their own directories; origin metadata and permission roots stay fixed. |
|
||||
| `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` | `ctx.tools`, `ctx.worktrees`, `a calling Agent` | `tool/call`, `Git branch and checkout`, `working-directory/change`, `tool/result` | - | Explicit experimental composition only. Creates a new branch and checkout from a pinned local commit under existing write permissions, then changes the Session directory. Leaving retains the branch and checkout. |
|
||||
| `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` | `ctx.tools`, `ctx.worktrees`, `a calling Agent` | `tool/call`, `Git branch and checkout`, `working-directory/change`, `tool/result` | - | Enabled through the Change requests & worktrees bundle on Web and Desktop; other profiles can select the standalone bundle. Creates a new branch and checkout from a pinned local commit under existing write permissions, then changes the Session directory. Leaving retains the branch and checkout. |
|
||||
| `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflowEngine`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-workspace-dependencies` | `load_workspace_dependencies` | `ctx.tools` | `tool/call`, `tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-web` | `web_fetch`, `web_search` | `ctx.tools`, `ctx.web`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. |
|
||||
@@ -2521,7 +2521,7 @@ Create a new Git branch and worktree from a local commit, branch, or tag, then c
|
||||
|
||||
Source: [`packages/experimental/tool-worktree/src/index.ts`](../packages/experimental/tool-worktree/src/index.ts)
|
||||
|
||||
Explicit experimental composition only. Creates a new branch and checkout from a pinned local commit under existing write permissions, then changes the Session directory. Leaving retains the branch and checkout.
|
||||
Enabled through the Change requests & worktrees bundle on Web and Desktop; other profiles can select the standalone bundle. Creates a new branch and checkout from a pinned local commit under existing write permissions, then changes the Session directory. Leaving retains the branch and checkout.
|
||||
|
||||
<a id="deepseek-aidsh-tool-workflow"></a>
|
||||
|
||||
@@ -2670,7 +2670,7 @@ Search the web for current information. Returns an optional summary answer and a
|
||||
"properties": {
|
||||
"queries": {
|
||||
"type": "array",
|
||||
"description": "1–4 search queries; their results are merged.",
|
||||
"description": "1–10 search queries; their results are merged.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
| `@deepseek-ai/dsh-tool-agent-team` | `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `ctx.subagents`, `an exact live Team member Agent` | `tool/call`, `team/task`, `tool/result` | - | 四个任务板工具共享 subagent 树的根 Session。在根 Session 开启 Team 模式,会在标准 subagent 委派和控制工具之外增加任务板指导。标准 subagent 控制工具提供必需身份,不受 Team 模式影响。 |
|
||||
| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`、`owning Agent session` | `tool/call`、`todo/write`、`tool/result` | - | todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为检查清单。`allowParallelInProgress` 是没有默认值的必填项,因此本目录明确选择 `true`,对应描述允许同时存在多个 `in_progress` 项。选择 `false` 的部署会获得同一工具,但描述会要求只能有 1 个活动任务。 |
|
||||
| `@deepseek-ai/dsh-tool-working-directory` | `working_directory` | `ctx.tools`, `ctx.workingDirectory` | `tool/call`, `working-directory/change`, `user context for directory changes`, `tool/result` | - | 读取或改变调用方 Session 的目录。已有 shell 和进程保留自己的目录;原始元数据和权限根目录保持不变。 |
|
||||
| `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` | `ctx.tools`, `ctx.worktrees`, `a calling Agent` | `tool/call`, `Git branch and checkout`, `working-directory/change`, `tool/result` | - | 仅通过显式实验组合加载。在现有写权限下从固定的本地提交创建新分支与检出目录,再改变 Session 工作目录。离开时保留分支与检出目录。 |
|
||||
| `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` | `ctx.tools`, `ctx.worktrees`, `a calling Agent` | `tool/call`, `Git branch and checkout`, `working-directory/change`, `tool/result` | - | 在 Web 和 Desktop 上通过变更请求与工作树组合包启用;其他 profile 可以选择独立组合包。在现有写权限下从固定的本地提交创建新分支与检出目录,再改变 Session 工作目录。离开时保留分支与检出目录。 |
|
||||
| `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`、`ctx.workflowEngine`、`ctx.systemPrompt`、`a calling Agent (exec.agent parents the script children)` | `tool/call`、`tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-workspace-dependencies` | `load_workspace_dependencies` | `ctx.tools` | `tool/call`, `tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-web` | `web_fetch`、`web_search` | `ctx.tools`、`ctx.web`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | web_search 和 web_fetch 将提供方选择置于 ctx.web 之后,使模型可见 schema 在更换后端时保持稳定。 |
|
||||
@@ -2527,7 +2527,7 @@ todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为
|
||||
|
||||
来源:[`packages/experimental/tool-worktree/src/index.ts`](../packages/experimental/tool-worktree/src/index.ts)
|
||||
|
||||
仅通过显式实验组合加载。在现有写权限下从固定的本地提交创建新分支与检出目录,再改变 Session 工作目录。离开时保留分支与检出目录。
|
||||
在 Web 和 Desktop 上通过变更请求与工作树组合包启用;其他 profile 可以选择独立组合包。在现有写权限下从固定的本地提交创建新分支与检出目录,再改变 Session 工作目录。离开时保留分支与检出目录。
|
||||
|
||||
<a id="deepseek-aidsh-tool-workflow"></a>
|
||||
|
||||
@@ -2677,7 +2677,7 @@ todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为
|
||||
"properties": {
|
||||
"queries": {
|
||||
"type": "array",
|
||||
"description": "1–4 search queries; their results are merged.",
|
||||
"description": "1–10 search queries; their results are merged.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.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 docs/upgrade-guide/v0.2.1-rc.3/web-search-defaults/guide.md
|
||||
/:
|
||||
en: 0bac687ed0a7a944
|
||||
zh: 0dade41ad394e9ed
|
||||
/web-search-defaults:
|
||||
en: a8cdc03730cb149c
|
||||
zh: 9ddab73784ab6241
|
||||
/web-search-defaults/change:
|
||||
en: bb2364852099c2f0
|
||||
zh: 2ff6e8911af96569
|
||||
/web-search-defaults/migration:
|
||||
en: dd501144b0f64744
|
||||
zh: 6940e5842fc140db
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "One web_search call accepts 10 queries and returns 10 sources by default, and the DeepSeek Search API provider paces its requests."
|
||||
---
|
||||
|
||||
# Web search defaults
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
In `dsh-tool-web`, the `searchMaxQueries` default rises from 4 to 10 and the `searchMaxResults` default rises from 8 to 10. The `web_search` schema now advertises `1–10 search queries`, so a call with 5–10 queries runs instead of failing validation, and each call can return up to 10 sources. A call can therefore start more provider searches and add more source text to the context. With the shipped Messages search provider, each query is one auxiliary model request. `dsh-web-deepseek-api` adds `searchRequestsPerSecond`, default 5: its Search provider sends at most that many requests in any one-second window, so the second five queries of a 10-query call start one second after the first instead of exceeding the account's 5 QPS limit. Values that a deployment already sets are kept.
|
||||
|
||||
## Migration
|
||||
|
||||
1. To keep the previous limits, set `searchMaxQueries: 4` and `searchMaxResults: 8` in the `tool-web` entry's `config` in `cordis.patch.yml`.
|
||||
2. If several processes share one DeepSeek account through the Search API, lower `searchRequestsPerSecond` in the `web-deepseek-api` entry so that their combined rate stays at or below 5.
|
||||
3. Confirm the result by running a task that sends 10 queries in one `web_search` call: it returns results instead of `Error: queries must contain at most 4 queries`. See the [tool-web configuration](../../../../packages/web/tool-web/README.md).
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "一次 web_search 调用默认接受 10 个查询并返回 10 个来源,DeepSeek Search API 提供方会为请求限速。"
|
||||
---
|
||||
|
||||
# Web 搜索默认值
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
在 `dsh-tool-web` 中,`searchMaxQueries` 默认值从 4 提高到 10,`searchMaxResults` 默认值从 8 提高到 10。`web_search` 的 schema 现在公布 `1–10 search queries`,因此含 5–10 个查询的调用会执行,而不是校验失败;每次调用最多返回 10 个来源。因此一次调用可能发起更多提供方搜索,并向上下文加入更多来源文本。使用内置的 Messages 搜索提供方时,每个查询都是一次辅助模型请求。`dsh-web-deepseek-api` 新增 `searchRequestsPerSecond`,默认 5:其 Search 提供方在任意一秒窗口内最多发送这么多请求,因此 10 个查询的调用中后五个查询比前五个晚一秒开始,而不会超过账号的 5 QPS 限制。部署已设置的值保持不变。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 如需保留原上限,在 `cordis.patch.yml` 的 `tool-web` 条目 `config` 中设置 `searchMaxQueries: 4` 和 `searchMaxResults: 8`。
|
||||
2. 如果多个进程通过 Search API 共享同一个 DeepSeek 账号,在 `web-deepseek-api` 条目中调低 `searchRequestsPerSecond`,使它们的合计速率不超过 5。
|
||||
3. 运行一个在一次 `web_search` 调用中发送 10 个查询的任务来确认:调用返回结果,而不是 `Error: queries must contain at most 4 queries`。参见 [tool-web 配置](../../../../packages/web/tool-web/README.zh.md)。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.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 docs/upgrade-guide/v0.2.2-alpha.2/cordis-loader-entry-trees/guide.md
|
||||
/:
|
||||
en: 02dfa517303f3206
|
||||
zh: abf9a22603299cc7
|
||||
/loader-entry-tree-reports-and-include-persistence:
|
||||
en: dc27815908c90cd9
|
||||
zh: e9cc077d4d3fdbec
|
||||
/loader-entry-tree-reports-and-include-persistence/change:
|
||||
en: 6bbe6a3a14e182d1
|
||||
zh: 4256bd997293f8f2
|
||||
/loader-entry-tree-reports-and-include-persistence/migration:
|
||||
en: 574f30ad53da1e66
|
||||
zh: 1b9914c4ead412f7
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Loader entry trees report mutations through commit(change), and Include persists them through a journal."
|
||||
---
|
||||
|
||||
# Loader entry tree reports and Include persistence
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
`@deepseek-ai/cordis-plugin-loader` follows Loader 1.0.0-rc.8, and `@deepseek-ai/cordis-plugin-include` follows Include 1.1.1. This affects `cordis.yml` and patch files, code that subclasses `EntryTree`, and code that calls `applyEntryPatches` or relies on Include writes.
|
||||
|
||||
- `EntryTree.write()` is removed. Subclasses implement `commit(change: EntryChange)`, which receives each create, update, move, removal and explicit Fiber save synchronously. In-memory trees implement a no-op.
|
||||
- `applyEntryPatches(data, patches, warn)` always returns cloned entries, including without patches, and clones inserted rows. Callers can no longer mutate the input through the result.
|
||||
- Include persists runtime changes through a journal reconciled with the latest file. File edits win conflicting fields and are logged as conflicts. Changes to patch-owned fields and inserted rows update the parent Include's patches. `flush()` waits for pending writes and rejects while changes remain unsaved; `refresh()` and `stop()` return the same result.
|
||||
- Include writes generated ids back into file rows that had none. Runtime changes to rows inserted by patches, including bundle rows, update the parent's patches instead of the included file. The parent of the `dsh` profile's root Include is the in-memory Loader, so such changes, for example a bundle row's self-disposal `disabled: true`, no longer survive a restart; previously the whole patched tree was written into `cordis.yml`.
|
||||
|
||||
## Migration
|
||||
|
||||
1. In `EntryTree` subclasses, replace `write()` with `commit(change)`. Persist from `change.options` and `change.legacy`: a missing `legacy` is a creation and a missing `options` is a removal.
|
||||
2. Treat the `applyEntryPatches` result as the only copy to modify.
|
||||
3. Where code needs a runtime change on disk, await `include.flush()`; for patch-owned changes, flush the child Include and then its parent.
|
||||
4. Start the application and confirm that runtime edits appear in the configuration file after `flush()`.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Loader 条目树通过 commit(change) 报告变更,Include 通过 journal 持久化这些变更。"
|
||||
---
|
||||
|
||||
# Loader 条目树变更报告与 Include 持久化
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
`@deepseek-ai/cordis-plugin-loader` 跟随 Loader 1.0.0-rc.8,`@deepseek-ai/cordis-plugin-include` 跟随 Include 1.1.1。受影响的是 `cordis.yml` 与 patch 文件、继承 `EntryTree` 的代码,以及调用 `applyEntryPatches` 或依赖 Include 写盘的代码。
|
||||
|
||||
- 移除了 `EntryTree.write()`。子类改为实现 `commit(change: EntryChange)`,它会同步收到每一次创建、更新、移动、删除和显式 Fiber 保存。内存树实现为空操作。
|
||||
- `applyEntryPatches(data, patches, warn)` 总是返回克隆后的条目,没有 patch 时也一样,插入行同样会被克隆。调用方不能再通过结果修改输入。
|
||||
- Include 通过与最新文件协调的 journal 持久化运行时变更。文件编辑在字段冲突时优先,并记录为冲突。对 patch 所拥有字段和插入行的修改会写入父 Include 的 patch。`flush()` 等待待处理的写入,并在仍有未保存变更时拒绝;`refresh()` 和 `stop()` 返回相同的结果。
|
||||
- Include 会把生成的 id 写回原本没有 id 的文件行。对 patch 插入行(包括 bundle 行)的运行时修改会写入父级的 patch,而不是被 include 的文件。`dsh` profile 根 Include 的父级是内存中的 Loader,因此这类修改(例如 bundle 行自行释放时写入的 `disabled: true`)重启后不再保留;此前整棵已应用 patch 的树会被写入 `cordis.yml`。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 在 `EntryTree` 子类中把 `write()` 换成 `commit(change)`。根据 `change.options` 和 `change.legacy` 持久化:缺少 `legacy` 表示创建,缺少 `options` 表示删除。
|
||||
2. 只修改 `applyEntryPatches` 返回的结果。
|
||||
3. 需要运行时变更落盘时,await `include.flush()`;对于 patch 所拥有的变更,先 flush 子 Include,再 flush 其父 Include。
|
||||
4. 启动应用,确认 `flush()` 之后运行时编辑已出现在配置文件中。
|
||||
@@ -1,16 +0,0 @@
|
||||
# Bilingual-pair consistency record for guide.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 docs/upgrade-guide/v0.2.2-alpha.2/credential-editor/guide.md
|
||||
/:
|
||||
en: 2f1907963c732ea8
|
||||
zh: e25494c07150f6b9
|
||||
/saved-api-keys-in-provider-editors:
|
||||
en: 63376a595a0e6dae
|
||||
zh: 2709df13410087e2
|
||||
/saved-api-keys-in-provider-editors/change:
|
||||
en: 463718dbe83f900d
|
||||
zh: d2e55dfebf8a4b9e
|
||||
/saved-api-keys-in-provider-editors/migration:
|
||||
en: 19c94ae9626773b1
|
||||
zh: 1098744f47162c43
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Saved API keys appear in masked editor fields, and clearing a saved key removes its override."
|
||||
---
|
||||
# Saved API keys in provider editors
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
Provider editors show stored API keys as masked values instead of empty fields. Applying an empty field removes a previously loaded key and restores environment authentication. Cancel preserves the stored key. Environment values are never loaded into the editor.
|
||||
|
||||
Credential providers implement `readStored(ref): Promise<string | undefined>` to return only the managed value, without environment fallbacks. The owner-only `credentials.readStored` Remote method supplies this value to an opened editor; general metadata reads remain redacted.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Keep the loaded value to retain a saved key, replace it to rotate the key, or clear it and apply to use environment authentication.
|
||||
2. Implement `readStored` in custom `CredentialProvider` subclasses; return `undefined` when no non-empty value is stored.
|
||||
3. Reopen the DeepSeek editor to confirm the saved key appears masked. After clearing and applying, confirm the field is empty and offers environment authentication.
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "已保存的 API 密钥显示为编辑器中的掩码值,清空已保存密钥会删除其覆盖。"
|
||||
---
|
||||
# 提供商编辑器中的已保存 API 密钥
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
提供商编辑器以掩码值显示已保存的 API 密钥,不再显示空字段。应用空字段会删除之前加载的密钥并恢复环境认证。「取消」保留已保存密钥。环境变量值从不加载到编辑器。
|
||||
|
||||
凭据提供方实现 `readStored(ref): Promise<string | undefined>`,仅返回托管值,不使用环境后备。仅允许所有者调用的 `credentials.readStored` Remote 方法向已打开的编辑器提供此值;普通元数据读取仍保持脱敏。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 保留加载的值即可保留已保存密钥,替换它可轮换密钥,清空并应用则使用环境认证。
|
||||
2. 为自定义 `CredentialProvider` 子类实现 `readStored`;未存储非空值时返回 `undefined`。
|
||||
3. 重新打开 DeepSeek 编辑器,确认已保存密钥显示为掩码。清空并应用后,确认字段为空且提示使用环境认证。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.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 docs/upgrade-guide/v0.2.2-alpha.2/forge-worktree-entry/guide.md
|
||||
/:
|
||||
en: e25e3adac59e3584
|
||||
zh: 1a16ac4c16ab3a0d
|
||||
/one-plugin-entry-for-change-requests-and-worktrees:
|
||||
en: 2eb2f0805d09f0c4
|
||||
zh: a7b02646515b39df
|
||||
/one-plugin-entry-for-change-requests-and-worktrees/change:
|
||||
en: b814d167dea20941
|
||||
zh: a8e3c416acd4fb65
|
||||
/one-plugin-entry-for-change-requests-and-worktrees/migration:
|
||||
en: 08b0e3bae9e9697e
|
||||
zh: 62f66d48328f1037
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "The Plugins page replaces the standalone Git worktrees card with Change requests & worktrees."
|
||||
---
|
||||
|
||||
# One plugin entry for change requests and worktrees
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
Web and Desktop use **Change requests & worktrees** to enable the review panel, the new-Session worktree option, and the model's `create_worktree` tool together. The separate **Git worktrees** card is hidden. Profiles that already enabled the Forge bundle also gain the model tool.
|
||||
|
||||
Existing profiles are not migrated automatically. A selected `@deepseek-ai/dsh-experimental-tool-worktree` bundle stays active even though its card is hidden, so turning off Forge does not disable that standalone tool. Selecting both it and `@deepseek-ai/dsh-experimental-forge-bundle` inserts duplicate `worktree` and `tool-worktree` ids, making those component controls read-only. Remove the standalone selection before using the unified switch.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Stop Web or quit Desktop. Back up `$DSH_HOME/profiles/<name>/package.json` and its sibling `cordis.patch.yml`. Use `~/.dsh` when `DSH_HOME` is unset; the standard profile names are `web` and `desktop`. For a custom profile, use its name.
|
||||
2. Edit `dsh.profile.bundles` in that `package.json`. Remove every `@deepseek-ai/dsh-experimental-tool-worktree` entry. To keep worktree support through the unified switch, add `@deepseek-ai/dsh-experimental-forge-bundle` once; if it is already selected, keep that entry. To disable both features, remove both bundle names. Preserve all other entries and fields.
|
||||
3. Keep existing `cordis.patch.yml` overrides: the `worktree`, `tool-worktree`, and `optional-forge-panel` ids are unchanged. To use the Forge panel without exposing the model tool, add or update this entry in the existing patch list:
|
||||
|
||||
```yaml
|
||||
- id: tool-worktree
|
||||
disabled: true
|
||||
```
|
||||
|
||||
4. Restart the application. In Plugins, confirm that **Change requests & worktrees** reflects your selection. With no disabling row override, enabling it provides the panel and `create_worktree`; turning it off removes both. Confirm that the profile lists no standalone worktree bundle. Existing worktrees, branches, and Session records are retained.
|
||||
|
||||
Headless profiles and custom compositions that need only the standalone tool can keep it; do not also select the Forge bundle. In Web or Desktop, that standalone selection remains hidden and must be managed through the profile file.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "插件页将独立的 Git 工作树卡片合入变更请求与工作树。"
|
||||
---
|
||||
|
||||
# 变更请求与工作树使用同一个插件入口
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
Web 和 Desktop 通过**变更请求与工作树**同时启用评审面板、新会话的工作树选项和模型的 `create_worktree` 工具。独立的 **Git 工作树**卡片被隐藏。已经启用 Forge 组合包的 profile 也会新增模型工具。
|
||||
|
||||
已有 profile 不会自动迁移。选中的 `@deepseek-ai/dsh-experimental-tool-worktree` 组合包仍会运行,即使其卡片已隐藏,因此关闭 Forge 不会停用这个独立工具。同时选择它和 `@deepseek-ai/dsh-experimental-forge-bundle` 会插入重复的 `worktree` 与 `tool-worktree` id,使对应组件控件变为只读。使用统一开关前,请移除独立组合包的选择。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 停止 Web 或退出 Desktop。备份 `$DSH_HOME/profiles/<name>/package.json` 及同目录下的 `cordis.patch.yml`。未设置 `DSH_HOME` 时使用 `~/.dsh`;标准 profile 名称为 `web` 和 `desktop`。自定义 profile 使用其自身名称。
|
||||
2. 编辑该 `package.json` 的 `dsh.profile.bundles`。移除所有 `@deepseek-ai/dsh-experimental-tool-worktree` 条目。若要通过统一开关保留工作树功能,添加一次 `@deepseek-ai/dsh-experimental-forge-bundle`;若已选中它,则保留该条目。若要关闭两项功能,则移除这两个组合包名称。保留其他所有条目和字段。
|
||||
3. 保留现有的 `cordis.patch.yml` 覆盖配置:`worktree`、`tool-worktree` 和 `optional-forge-panel` 的 id 均未改变。若只使用 Forge 面板,不向模型提供工作树工具,在现有 patch 列表中添加或更新以下条目:
|
||||
|
||||
```yaml
|
||||
- id: tool-worktree
|
||||
disabled: true
|
||||
```
|
||||
|
||||
4. 重启应用。在插件页确认**变更请求与工作树**的状态符合你的选择。没有禁用插件行的覆盖配置时,启用它会提供面板与 `create_worktree`,关闭后两者均移除。确认 profile 不再列出独立的工作树组合包。已有工作树、分支和 Session 记录均保留。
|
||||
|
||||
仅需要独立工具的 Headless profile 和自定义组合可以继续保留它,但不要同时选择 Forge 组合包。在 Web 或 Desktop 中,该独立选择仍然隐藏,需要通过 profile 文件管理。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.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 docs/upgrade-guide/v0.2.2-alpha.2/projection-released-checkpoint/guide.md
|
||||
/:
|
||||
en: 738f2376c5a8ebe5
|
||||
zh: 078f2a08d8b6ad4b
|
||||
/released-projection-units-stay-in-checkpoints:
|
||||
en: 9355d9de711acc7d
|
||||
zh: eb89da6b886a7556
|
||||
/released-projection-units-stay-in-checkpoints/change:
|
||||
en: 5944b6c7b72e15e4
|
||||
zh: 051d68e2eb5151d7
|
||||
/released-projection-units-stay-in-checkpoints/migration:
|
||||
en: 9f24ecf12748eb41
|
||||
zh: c07649e79a0174f9
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "`ctx.sessionProjections.checkpoint(session)` keeps a released unit's last row instead of omitting it."
|
||||
---
|
||||
|
||||
# Released projection units stay in checkpoints
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
Previously, when the last registrant of a projection key released it, through `handle.dispose()` or by unloading the owning fiber, the registry deleted the unit and `ctx.sessionProjections.checkpoint(session)` stopped returning its row.
|
||||
|
||||
Now the released unit stays in the registry as a predecessor. It leaves event drives, `snapshot()`, `summaryKeys()`, `restoreFloor()`, `restore()`, `viewCheckpoint()`, and `hydrate()`, and Sessions created afterward get no cell for it. `checkpoint(session)` still returns the last row the unit folded for each existing Session, and the registry no longer advances that row. Registering the same key again replaces the predecessor and refolds the log; see the [registration lifecycle](../../../../packages/session/session-projection/README.md).
|
||||
|
||||
This affects plugin code and tests that read `checkpoint()` after a projection's owner unloads, such as HMR tests that assert a released key is absent. `@deepseek-ai/dsh-session-projection-cache` stores these rows, so a graceful shutdown no longer drops the values that Web-layer plugins folded before their Sessions detach.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Find reads of `sessionProjections.checkpoint(...)` that run after a projection's owner unloads, including calls through an alias of `ctx.sessionProjections`.
|
||||
2. Replace an assertion that the released key is absent from an existing Session with one that its row keeps the pre-unload value:
|
||||
|
||||
```ts ignore-check
|
||||
const row = ctx.sessionProjections.checkpoint(session).myKey
|
||||
await owner.dispose()
|
||||
expect(ctx.sessionProjections.checkpoint(session)).toHaveProperty('myKey', row)
|
||||
```
|
||||
|
||||
3. To show that the unit left live participation, assert that a Session created after the unload has no row, `expect(ctx.sessionProjections.checkpoint(ctx.sessions.create())).not.toHaveProperty('myKey')`, or that an event appended after the unload leaves the row unchanged. A client-visible key is also absent from `snapshot(session).values`.
|
||||
4. Run the affected tests; they pass when no assertion expects a released key to disappear from an existing Session's checkpoint.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "`ctx.sessionProjections.checkpoint(session)` 保留已释放单元的最后一行,而不再省略它。"
|
||||
---
|
||||
|
||||
# 已释放的投影单元保留在检查点中
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
此前,投影 key 的最后一个注册方通过 `handle.dispose()` 或卸载所属 fiber 释放它时,注册表会删除该单元,`ctx.sessionProjections.checkpoint(session)` 也不再返回它的行。
|
||||
|
||||
现在,已释放的单元作为前任留在注册表中。它退出事件推进、`snapshot()`、`summaryKeys()`、`restoreFloor()`、`restore()`、`viewCheckpoint()` 和 `hydrate()`,此后新建的 Session 也不会为它建立 cell。`checkpoint(session)` 仍为每个已有 Session 返回该单元最后折叠出的行,注册表不再推进这一行。同一 key 再次注册时会替换前任并重新折叠日志;参见[注册生命周期](../../../../packages/session/session-projection/README.zh.md)。
|
||||
|
||||
这会影响在投影所属方卸载后读取 `checkpoint()` 的插件代码和测试,例如断言已释放 key 不存在的 HMR 测试。`@deepseek-ai/dsh-session-projection-cache` 会存储这些行,因此正常关停不再丢掉 Web 层插件在其 Session detach 之前已折叠出的值。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 找出在投影所属方卸载后执行的 `sessionProjections.checkpoint(...)` 读取,包括通过 `ctx.sessionProjections` 别名的调用。
|
||||
2. 把「已有 Session 中不存在已释放 key」的断言,改为断言它的行保持卸载前的值:
|
||||
|
||||
```ts ignore-check
|
||||
const row = ctx.sessionProjections.checkpoint(session).myKey
|
||||
await owner.dispose()
|
||||
expect(ctx.sessionProjections.checkpoint(session)).toHaveProperty('myKey', row)
|
||||
```
|
||||
|
||||
3. 如需证明该单元已退出实时参与,可断言卸载后新建的 Session 没有这一行,即 `expect(ctx.sessionProjections.checkpoint(ctx.sessions.create())).not.toHaveProperty('myKey')`,或断言卸载后追加的事件不会改变这一行。客户端可见的 key 也不会出现在 `snapshot(session).values` 中。
|
||||
4. 运行受影响的测试;当没有断言要求已释放 key 从已有 Session 的检查点中消失时,测试即通过。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.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 docs/upgrade-guide/v0.2.2-alpha.2/projection-snapshot-scope/guide.md
|
||||
/:
|
||||
en: 82a6e0261297097c
|
||||
zh: c86e496c258e72b2
|
||||
/projection-snapshot-scope:
|
||||
en: c8b08322a943d6b2
|
||||
zh: 31862a259b7738bb
|
||||
/projection-snapshot-scope/change:
|
||||
en: d95cd644b01419a1
|
||||
zh: 89038a6de9b74e8a
|
||||
/projection-snapshot-scope/migration:
|
||||
en: 408d88a4ea6fe3a9
|
||||
zh: 397de0dc2d8318df
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Projection snapshot reads compute only the returned units, the registry cached snapshot read is deprecated, and Session list summaries omit context pressure and breakdown."
|
||||
---
|
||||
|
||||
# Projection snapshot scope
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
`ctx.sessionProjections.snapshot(session, keys)` from `@deepseek-ai/dsh-session-projection` computes only the units it returns: with `keys` it computes only the selected units, and without `keys` it computes every unit that defines `wire` and no Host-only unit. A plugin can no longer rely on other units being computed after it calls `snapshot`. The registry's `cachedSnapshot(session, keys)` is deprecated; it returns only values another read already computed and may omit keys. The persisted cache read `ctx.sessionProjectionCache.cachedSnapshot(header, keys)` is unchanged. `session.list` summary `projections` no longer carry `contextBreakdown` or `contextPressure`; the `@deepseek-ai/dsh-token-meter` entries are now `SessionProjectionSummaryExcluded`. Plugins and SDK or wire clients that read these values are affected.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Read Host state of your own unit through the handle that `await ctx.sessionProjections.register(definition)` returns, with `handle.stateOf(session)`, instead of calling `snapshot` first and reading cells afterwards.
|
||||
2. Replace calls to the registry's `cachedSnapshot(session, keys)` with `snapshot(session, keys)`.
|
||||
3. Read `contextBreakdown` and `contextPressure` from an opened Session, for example the Session projection snapshot or follow baseline, instead of from `session.list` rows.
|
||||
4. Rebuild your plugins and clients, confirm that type checks pass without `no-deprecated` findings for `cachedSnapshot`, and confirm that an opened Session still shows its context meter.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user