diff --git a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml index 352ae4028d..e8bda28f27 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md /agent-note-adjacent-agents-share-one-steer-send-message-operation: - en: 758d31ae04ce0d4f - zh: 112475efbd52b9f6 + en: dbbce49e8075750f + zh: d7776487e70d5c69 /agent-note-adjacent-agents-share-one-steer-send-message-operation/problem: en: c988b8a3be4e6f19 zh: b58011fd8d2e878e diff --git a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md index e026432e29..30f72cf846 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md +++ b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md @@ -4,6 +4,8 @@ Status: implemented English | [中文](2026-08-27-adjacent-agent-steer-messaging.zh.md) +The adjacency and discovery restrictions below are superseded by [tree-wide communication](2026-10-16-tree-wide-agent-communication.md); the remaining decisions stay active. + ## Problem Continuable Agents originally used direction-specific model controls. A parent called `send_message({ subagent_id, message })`, which delegated to a FIFO `followup` service operation. A child instead received a child-scoped `report({ output })` tool, a `tool:report` system-prompt section, and deployment-selected quiet or waking delivery. The tools described one adjacent-Agent operation through different schemas, service paths, source attribution, and scheduling. diff --git a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md index 1acb29d0f7..afed2979ad 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md @@ -4,6 +4,8 @@ Status: implemented [English](2026-08-27-adjacent-agent-steer-messaging.md) | 中文 +邻接消息和发现范围已由[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)替代;其余决策仍然有效。 + ## 问题 可继续 Agent 最初使用方向专属的模型控制。parent 调用 `send_message({ subagent_id, message })`,委托给 FIFO `followup` 服务操作。child 则获得 child 作用域的 `report({ output })` 工具、`tool:report` 系统提示词 section,以及由部署选择的静默或唤醒投递。两个工具用不同 schema、服务路径、来源与调度描述同一个相邻 Agent 操作。 diff --git a/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.i18n.yaml index d962a60190..1c3735d30f 100644 --- a/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.i18n.yaml @@ -3,14 +3,14 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.md /agent-note-agent-teams-teammates-are-subagents: - en: 3a3c59709e08af73 - zh: 3ea1dd59c0f9866b + en: 8d5d6b1e90ab4780 + zh: fbc3f1b7656304a3 /agent-note-agent-teams-teammates-are-subagents/problem: en: ca2848e3bbc3a009 zh: 0551e32ff6d187d0 /agent-note-agent-teams-teammates-are-subagents/decision: - en: 5742777c069a9bb6 - zh: ffddb9c47183da7e + en: f8569461c69fe4d3 + zh: 05fa504abaf77a7b /agent-note-agent-teams-teammates-are-subagents/alternatives-considered: en: ef0cf648ff54784f zh: 2a0b3745a54e7072 diff --git a/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.md b/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.md index 5134b602d5..2c44b68a32 100644 --- a/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.md +++ b/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.md @@ -6,6 +6,8 @@ English | [中文](2026-10-15-agent-teams-as-subagents.zh.md) The name-free model interface below is superseded by [tree-wide agent names](2026-10-16-tree-wide-agent-names.md). Session identity, lineage authority, and Team reuse remain current. +The adjacency and discovery restrictions below are superseded by [tree-wide communication](2026-10-16-tree-wide-agent-communication.md); the remaining decisions stay active. + ## Problem Teammates already use the subagent runtime for creation, Session identity, catalog membership, capacity, cold resume, settlement, and interruption. A separate Team roster duplicates identity, provisioning recovery, and teardown. Team-scoped messaging and control tools shadow the standard subagent tools, forcing profiles to disable ordinary delegation and making fork tool schemas diverge from their parent. @@ -20,7 +22,7 @@ Every root Agent leads the Team identified by its Session id; every live subagen Task ownership uses `ownerId`, and reassignment accepts a Session id. Team persists only task changes. Messages use the target Agent inbox; historical `team/message/*` records remain readable but are never delivered or acknowledged. -This supersedes the roster, provisioning, recovery, and messaging decisions in [Agent Teams](../feature/2026-08-05-agent-teams.md) and the [Team direct inbox decision](../../archived/simplification/2026-09-26-team-direct-inbox.md). The task-board and shared-checkout decisions remain active. [Adjacent Agent messaging](2026-08-27-adjacent-agent-steer-messaging.md) includes sibling routing, while [required runtime-context identity](2026-10-13-team-identity-runtime-context.md) owns the role statement. The [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md) lists removed tools, methods, and configuration. +This supersedes the roster, provisioning, recovery, and messaging decisions in [Agent Teams](../feature/2026-08-05-agent-teams.md) and the [Team direct inbox decision](../../archived/simplification/2026-09-26-team-direct-inbox.md). The task-board and shared-checkout decisions remain active. [Adjacent Agent messaging](2026-08-27-adjacent-agent-steer-messaging.md) includes sibling routing, while [required runtime-context identity](2026-10-13-team-identity-runtime-context.md) owns the role statement. The [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md) lists removed tools, methods, and configuration. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.zh.md b/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.zh.md index 02bf310fae..58e3f21f77 100644 --- a/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.zh.md +++ b/.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.zh.md @@ -6,6 +6,8 @@ Status: implemented 下述不使用名字的模型接口已被[树内唯一 agent 名字](2026-10-16-tree-wide-agent-names.zh.md)取代。Session 身份、谱系权限和 Team 复用决策仍然有效。 +邻接消息和发现范围已由[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)替代;其余决策仍然有效。 + ## Problem 队友已经使用子代理运行时完成创建、Session 身份、目录成员关系、容量、冷恢复、结算和中断。独立 Team 成员列表重复了身份、创建恢复和释放职责。Team 作用域内的消息及控制工具覆盖标准子代理工具,迫使 profile 禁用普通委派,并使分叉工具模式偏离父级。 @@ -20,7 +22,7 @@ Status: implemented 任务所有权使用 `ownerId`,重新分配接受 Session id。Team 只持久化任务变更。消息使用目标 Agent 收件箱;历史 `team/message/*` 记录仍可读取,但不会被投递或确认。 -本决策取代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 的成员列表、创建、恢复和消息决策,以及 [Team 直接收件箱决策](../../archived/simplification/2026-09-26-team-direct-inbox.md)。任务板和共享工作树决策仍有效。[相邻 Agent 消息](2026-08-27-adjacent-agent-steer-messaging.zh.md)纳入同级路由,[必需运行时身份](2026-10-13-team-identity-runtime-context.zh.md)负责角色声明。[升级指南](../../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md)列出移除的工具、方法和配置。 +本决策取代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 的成员列表、创建、恢复和消息决策,以及 [Team 直接收件箱决策](../../archived/simplification/2026-09-26-team-direct-inbox.md)。任务板和共享工作树决策仍有效。[相邻 Agent 消息](2026-08-27-adjacent-agent-steer-messaging.zh.md)纳入同级路由,[必需运行时身份](2026-10-13-team-identity-runtime-context.zh.md)负责角色声明。[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md)列出移除的工具、方法和配置。 ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.i18n.yaml new file mode 100644 index 0000000000..8ac0f5b275 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-16-tree-wide-agent-communication.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.md +/agent-note-communication-follows-the-subagent-tree: + en: 849d806b901e9f04 + zh: 649b2984f844d510 +/agent-note-communication-follows-the-subagent-tree/problem: + en: c583c3cdb894e417 + zh: 6edc0354b79f4f26 +/agent-note-communication-follows-the-subagent-tree/decision: + en: 4e200408de98593f + zh: fe0e221d97ec61af +/agent-note-communication-follows-the-subagent-tree/alternatives-considered: + en: 470f9cf8c3e32af9 + zh: 73bbe29767c5d02a +/agent-note-communication-follows-the-subagent-tree/consequences: + en: ac643f43288e548a + zh: d35465401626b4aa diff --git a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.md b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.md new file mode 100644 index 0000000000..32f6834de4 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.md @@ -0,0 +1,29 @@ +# Agent Note: Communication follows the subagent tree + +Status: implemented + +English | [中文](2026-10-16-tree-wide-agent-communication.zh.md) + +## Problem + +A Team shares one task board across the root's entire subagent tree. Restricting discovery and messages to adjacent relatives forces deeper members to relay work through parents despite sharing tasks. + +## Decision + +`list_agents` starts at the caller's live root and lists that root and all reachable continuable descendants, including the caller. Parent names and depth remain relative to the root. The existing parent catalogs remain authoritative; no Team directory is stored separately. + +`send_message` accepts another root or continuable member of the same tree. The sender must be the exact live root or a resident continuable subagent. Cross-branch routing preserves the actual sender, verifies durable child descriptors and parent catalogs, and restores cold ancestors under their recorded parents. Ancestor restoration does not deliver the target's message to those ancestors. Final inbox admission rechecks the sender and target parent. Cancellation or rejection before acceptance releases new restoration work; accepted input follows normal Agent persistence. + +Parent ownership still governs capacity, restoration, and disposal. `interrupt_agent` retains ancestor-only authority. Ordinary Session forks lead independent trees; external and one-shot executions cannot receive follow-ups. + +This supersedes only the adjacency and discovery restrictions in [adjacent messaging](2026-08-27-adjacent-agent-steer-messaging.md) and [subagent teammates](2026-10-15-agent-teams-as-subagents.md). Their shared inbox, scheduling, attribution, lifecycle, and Team reuse decisions remain active. Model tools use [tree-wide names](2026-10-16-tree-wide-agent-names.md); host APIs retain Session ids. + +## Alternatives considered + +**Relay through parents.** This preserves the narrower routing implementation but makes the creation hierarchy determine who can collaborate. Shared work requires direct communication across branches. + +**Separate Team directory and delivery.** A second member list and message path would duplicate the existing catalogs, inbox, and restoration lifecycle. Tree membership supplies the required communication scope without another registry. + +## Consequences + +Discovery output grows with the whole tree. Cold delivery can consume several activation slots while restoring ancestors and rejects when capacity or history is unavailable. The sender's live ancestry must remain intact. Listing is a snapshot; it does not promise that a later send will succeed. Concurrent operations retain ordinary inbox acceptance and disposal ordering. diff --git a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.zh.md b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.zh.md new file mode 100644 index 0000000000..e692307826 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.zh.md @@ -0,0 +1,29 @@ +# Agent Note: 通信范围由子代理树决定 + +Status: implemented + +[English](2026-10-16-tree-wide-agent-communication.md) | 中文 + +## Problem + +一个 Team 在根的整棵子代理树中共享任务板。把发现和消息限定在相邻成员之间,会迫使深层成员在共享任务的同时,仍通过父级转发工作。 + +## Decision + +`list_agents` 从调用方的在线根开始,列出该根与所有可达的可继续后代,包括调用方。父级名字和深度始终相对根计算。现有父级目录仍是权威来源;不单独存储 Team 目录。 + +`send_message` 接受同一棵树中的其他根或可继续成员。发送者必须是确切在线根或驻留的可继续子代理。跨分支路由保留实际发送者,验证持久子级描述符和父级目录,并按记录的父级恢复冷祖先。祖先恢复不会向这些祖先投递发给目标的消息。最终收件箱准入重新检查发送者与目标父级。接受前的取消或拒绝会释放新建的恢复工作;已接受输入遵循普通 Agent 持久化。 + +父级所有权仍管理容量、恢复和释放。`interrupt_agent` 保留仅祖先可执行的权限。普通 Session 分叉领导独立树;外部执行和一次性执行不能接收后续消息。 + +本决策仅替代[相邻消息](2026-08-27-adjacent-agent-steer-messaging.zh.md)与[子代理队友](2026-10-15-agent-teams-as-subagents.zh.md)中的邻接和发现范围限制。它们的共享收件箱、调度、发送者归属、生命周期和 Team 复用决策仍有效。模型工具使用[树内名字](2026-10-16-tree-wide-agent-names.zh.md);宿主 API 保留 Session id。 + +## Alternatives considered + +**通过父级转发。** 这保留了更窄的路由实现,却让创建层级决定谁能协作。共享工作需要跨分支直接通信。 + +**单独的 Team 目录与投递。** 第二份成员列表和消息路径会重复现有目录、收件箱与恢复生命周期。树成员身份已经提供所需通信范围,无需另一个注册表。 + +## Consequences + +发现输出随整棵树增长。冷投递在恢复祖先时可能占用多个激活槽位,容量或历史不可用时会被拒绝。发送者的在线祖先链必须保持完整。列表是快照,不保证之后发送成功。并发操作保留普通收件箱接受与释放顺序。 diff --git a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.i18n.yaml index fdb691fbef..c4eae3cdfb 100644 --- a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.i18n.yaml @@ -15,8 +15,8 @@ en: edd75556ef338a6a zh: 559136d889bacdb1 /agent-note-tree-wide-agent-names/consequences: - en: 7810ae426c76bfcd - zh: 4b954c0f0672c534 + en: 49c32dc1569ecb31 + zh: 78d1bac09b0fa580 /agent-note-tree-wide-agent-names/testing: en: daffbc8dcd19b36d zh: 0940af582fc7feb9 diff --git a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.md b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.md index 768855ac60..6ccd123f46 100644 --- a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.md +++ b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.md @@ -24,7 +24,7 @@ This supersedes the name-free interface in [Agent Teams as subagents](2026-10-15 ## Consequences -Creation requires a name and rejects duplicates before dispatch. Independent roots can reuse names. Tool results, initial child guidance, message prefixes, and settlement notices use names. Internal Session and task-owner ids stay stable. The [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md) lists changed tool arguments and results. +Creation requires a name and rejects duplicates before dispatch. Independent roots can reuse names. Tool results, initial child guidance, message prefixes, and settlement notices use names. Internal Session and task-owner ids stay stable. The [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md) lists changed tool arguments and results. ## Testing diff --git a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.zh.md b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.zh.md index f616c92f93..c5aeddd7bd 100644 --- a/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.zh.md +++ b/.agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-names.zh.md @@ -24,7 +24,7 @@ Session id 提供持久执行身份,但模型协作时难以阅读。描述可 ## 影响 -创建必须提供名字,分发前拒绝重名。独立根可以复用名字。工具结果、子级初始指引、消息前缀与结束通知使用名字。内部 Session 和任务负责人 id 保持稳定。[升级指南](../../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md)列出变化的工具参数与结果。 +创建必须提供名字,分发前拒绝重名。独立根可以复用名字。工具结果、子级初始指引、消息前缀与结束通知使用名字。内部 Session 和任务负责人 id 保持稳定。[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md)列出变化的工具参数与结果。 ## 测试 diff --git a/apps/cli/package.json b/apps/cli/package.json index 1c1e6dc362..b32b66a1e4 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh", "description": "dsh CLI: profile launch, plugin management, and configuration inspection", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl index ecf80738ee..8ced02fb62 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl @@ -22,7 +22,7 @@ {"type":"session/title","data":{"title":"Start a background job.","messageSeqs":[3],"source":{"kind":"fallback"}}} {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"list-once","name":"list_agents","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"list-once","name":"list_agents","args":["{}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"list-once","name":"list_agents","arguments":"{}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":2,"step":1,"callId":"list-once","name":"list_agents","arguments":"{}"}} -{"type":"tool/result","data":{"turn":2,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"list-once"},"toolCallId":"list-once","content":[{"type":"text","text":"legacy:{{sessionId}} [inactive] parent=lead depth=1 — descriptorless child"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":2,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"list-once"},"toolCallId":"list-once","content":[{"type":"text","text":"lead [running] — team lead\nlegacy:{{sessionId}} [inactive] parent=lead depth=1 — descriptorless child"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"step/start","data":{"turn":2,"step":2}} {"type":"assistant/message","data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The catalogued subagent is ready. PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["The catalogued subagent is ready. PARENT_DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The catalogued subagent is ready. PARENT_DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs index de08759da9..7c1f5910a3 100644 --- a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs +++ b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs @@ -66,7 +66,7 @@ function researcher(messages) { if (!names.includes('list_agents')) return toolChunks([{ name: 'list_agents', args: {} }]) const sibling = messages.filter(message => message.role === 'tool') .flatMap(message => message.content.filter(block => block.type === 'text').flatMap(block => block.text.split('\n'))) - .find(line => line.endsWith('— sibling: Own deterministic implementation.'))?.split(' ')[0] + .find(line => line.endsWith('— Own deterministic implementation.'))?.split(' ')[0] if (sibling === undefined) throw new Error('list_agents did not expose the implementation sibling') if (!names.includes('team_task_create')) { return toolChunks([{ name: 'team_task_create', args: { diff --git a/apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts b/apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts index 3bdc2e1cc5..b1b2c88128 100644 --- a/apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts +++ b/apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts @@ -4,7 +4,7 @@ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' -import type { TokenUsage } from '@deepseek-ai/dsh-llm' +import { lastAssistantStreamChunk, type RetryPolicyConfig, type TokenUsage } from '@deepseek-ai/dsh-llm' import { DeepSeekHarness } from '@deepseek-ai/dsh-sdk-client' import type { SessionEvent } from '@deepseek-ai/dsh-session' import { describe, expect, it, onTestFinished } from 'vitest' @@ -22,6 +22,8 @@ interface ObservedRequest { step: number body: { system?: string + max_tokens: number + thinking: { type: string } messages: { role: string; content: { type: string; text?: string; tool?: { name: string } }[] }[] tools?: { name: string; defer_loading?: true }[] } @@ -70,7 +72,7 @@ function registryChanges(previous: ObservedRequest, request: ObservedRequest): { } describe.skipIf(!process.env.DEEPSEEK_API_KEY)('SDK native tool updates with real DeepSeek', () => { - it.each([false, true])('keeps the preceding conversation cached after a tool addition (prompt update: %s)', { retry: 0 }, async (updatePrompt) => { + it.each([false, true])('keeps the preceding conversation cached after a tool addition (prompt update: %s)', async (updatePrompt) => { const root = await mkdtemp(join(tmpdir(), 'dsh-sdk-tool-cache-')) onTestFinished(async () => { await rm(root, { recursive: true, force: true }) }) const sampleLabel = `CACHE_SAMPLE_${randomUUID()}` @@ -80,7 +82,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('SDK native tool updates with rea await writeFile(patch, JSON.stringify([ { id: 'llm-deepseek', config: { apiKeyEnv: 'DEEPSEEK_API_KEY', thinking: 'disabled', maxTokens: 1024, - retryPolicy: { maxRetries: 0 }, + retryPolicy: { mode: 'normal', maxRetries: 0 } satisfies RetryPolicyConfig, models: [{ id: 'deepseek-flash', systemPromptUpdate: 'in-history', toolUpdate: 'addition-only' }], } }, { id: 'tools', config: { mode: 'native' } }, @@ -101,13 +103,19 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('SDK native tool updates with rea const run = async (prompt: string) => { const result = await session.run(prompt) events.push(...result.events) + const failures = result.events.filter(event => event.type === 'assistant/attempt').map(event => ({ + turn: event.data.turn, + step: event.data.step, + reason: lastAssistantStreamChunk(event.data.stream, 'finish')?.reason, + })) + expect(failures, `Provider request failures: ${JSON.stringify(failures)}`).toEqual([]) return result.finalResponse } - await run('Do not call tools. Reply READY.') + expect(await run('Do not call tools. Reply READY.')).toContain('READY') // Unique user history makes hits on shared profile instructions insufficient. const records = Array.from({ length: 96 }, (_, index) => `record-${index}: ${randomUUID()}`).join('\n') - await run(`Keep these reference records. Do not call tools. Reply READY.\n${records}`) - await run('Keep the reference records. Do not call tools. Reply READY.') + expect(await run(`Keep these reference records. Do not call tools. Reply READY.\n${records}`)).toContain('READY') + expect(await run('Keep the reference records. Do not call tools. Reply READY.')).toContain('READY') expect(await run(`Use cache_tool_control with action add. After it returns, call ${sampleTool} exactly once and report the generated sample label. This is synthetic example data for this run. Call these separately in order.`)).toContain(sampleLabel) expectCalledBefore(await recordedCalls(callsPath), 'add', 'sample') expect(await run('Use cache_tool_control with action remove. Whether it removes the sample tool or reports that it is not registered, reply REMOVED and do not call the sample tool.')).toContain('REMOVED') @@ -115,7 +123,6 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('SDK native tool updates with rea expectCalledBefore(calls, 'sample', 'remove') const requests = (await readFile(evidencePath, 'utf8')).trim().split('\n').map(line => JSON.parse(line) as ObservedRequest) - expect(events.filter(event => event.type === 'assistant/attempt')).toHaveLength(0) expect(requests).toHaveLength(events.filter(event => event.type === 'assistant/message').length) const [initial, history, warm] = requests const addedIndex = requests.findIndex(request => request.stage === 'added') @@ -123,6 +130,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('SDK native tool updates with rea const added = requests[addedIndex] const removed = requests.find(request => request.stage === 'removed') if (!initial || !history || !warm || !before || !added || !removed) throw new Error('Missing cache transition requests') + expect(initial.body).toMatchObject({ max_tokens: 1024, thinking: { type: 'disabled' } }) const historyInput = totalInput(usageFor(history, events)) const precedingInput = totalInput(usageFor(before, events)) const cached = usageFor(added, events).cacheReadTokens ?? 0 diff --git a/apps/desktop-host/package.json b/apps/desktop-host/package.json index 570d4fc856..c7cec88e9d 100644 --- a/apps/desktop-host/package.json +++ b/apps/desktop-host/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-desktop-host", "description": "Private Node-mode host process for the Electron desktop application", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "private": true, "license": "MIT", "type": "module", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c7c1246e9a..811192c057 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-desktop", "description": "Electron desktop shell for a bundled dsh runtime and external plugins", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "private": true, "license": "MIT", "type": "module", diff --git a/apps/plugin-market-generator/package.json b/apps/plugin-market-generator/package.json index 65bf8927e7..7d2c32f224 100644 --- a/apps/plugin-market-generator/package.json +++ b/apps/plugin-market-generator/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-plugin-market-generator", "description": "Reference generator of plugin market index files from a list of npm packages and Git repositories", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "private": true, "type": "module", "scripts": { diff --git a/apps/web/package.json b/apps/web/package.json index e07f7b89dc..4224ebc7f9 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-frontend", "description": "Web application entry: vite build over the @deepseek-ai/dsh-client-web shell library; dist/ served by apps/cli's dsh web", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/apps/web/tests/bonus-notice.e2e.ts b/apps/web/tests/bonus-notice.e2e.ts index a338460b32..61124bccb1 100644 --- a/apps/web/tests/bonus-notice.e2e.ts +++ b/apps/web/tests/bonus-notice.e2e.ts @@ -22,7 +22,7 @@ const MODE = webSnapshotMode() const TOKEN = 'dsh_bonus_notice_test' const ACCOUNT_ID = 'bonus-user' -const ORDER_FIRST = '22222222-2222-4222-8222-222222222222' +const ORDER_FIRST = 'bonus-notice-first' const ORDER_LATER = '33333333-3333-4333-8333-333333333333' const ORDER_EN = '44444444-4444-4444-8444-444444444444' const ORDER_RETRY = '55555555-5555-4555-8555-555555555555' @@ -80,17 +80,17 @@ async function mockPlatform() { let failSummary = false /** Reply held back while the test controls when a read can answer. */ let heldReply: (() => void) | undefined - const grant = (orderId: string, amount: string): void => { + const grant = (orderId: string, amount: string, extraCopy = ''): void => { unnotified.unshift({ orderId, amount, - zh_CN: `已赠送您 ${amount} 元 DSH 体验赠金。`, - en_US: `You received a CNY ${amount} DSH trial credit.`, + zh_CN: `已赠送您 ${amount} 元 DSH 体验赠金。${extraCopy}`, + en_US: `You received a CNY ${amount} DSH trial credit.${extraCopy}`, }) } /** @param value - granted bonus. @param locale - request language. @returns its wire fields. */ const wire = (value: Granted, locale: string | undefined): unknown => ({ - order_id: value.orderId, campaign: 'dsh_login_bonus', amount: value.amount, currency: 'CNY', - granted_at: new Date().toISOString(), expires_at: expiresAt(), + order_id: value.orderId, expires_at: value.orderId === ORDER_FIRST ? null : expiresAt(), + ...(value.orderId === ORDER_FIRST ? { title: '平台通知' } : {}), msg: locale === 'en_US' ? value.en_US : value.zh_CN, }) const server: Server = createServer((req, res) => { @@ -182,7 +182,7 @@ async function mockPlatform() { /** @param page - page under test. @returns every rendered bonus notice card. */ function noticeCards(page: Page): Locator { - return page.locator('aside').filter({ hasText: '赠金已到账' }) + return page.locator('aside[data-front]') } /** @param card - a rendered notice card. @returns its non-empty visible lines joined for the golden. */ @@ -579,6 +579,75 @@ describe.skipIf(MODE === 'record')('web e2e: bonus notice', () => { await expect.poll(() => platform.acks.filter(item => item.orderId === ORDER_WEB).length).toBe(1) observations.push(`web.notice=${webNotice} acks=1`) await webPage.close() + platform.clearUnnotified() + platform.grant('stack-third', '23.00') + platform.grant('stack-second', '22.00', '\nMore details'.repeat(8)) + platform.grant('stack-first', '21.00') + const ackStart = platform.acks.length + const stackPage = await openDesktopPage('zh-CN') + await stackPage.emulateMedia({ colorScheme: 'light' }) + await stackPage.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) + const cards = noticeCards(stackPage) + await expect.poll(() => cards.count()).toBe(3) + await expect.poll(() => platform.acks.length).toBe(ackStart + 1) + expect(platform.acks.slice(ackStart).map(ack => ack.orderId)).toEqual(['stack-first']) + expect(await stackPage.getByRole('status').count()).toBe(1) + const second = cards.filter({ hasText: '22.00' }) + const secondNode = await second.elementHandle() + const frontBox = await cards.first().boundingBox() + const backBox = await second.boundingBox() + expect(frontBox!.width).toBeCloseTo(280, 0) + expect(backBox!.width).toBeCloseTo(272, 0) + expect(backBox!.y).toBeCloseTo(frontBox!.y - 4, 0) + const transition = await second.evaluate((element) => { + const style = getComputedStyle(element) + return `${style.transitionDuration} ${style.transitionTimingFunction}` + }) + expect(transition).toBe('0.25s ease-out') + observations.push(`stack.initial cards=3 acks=stack-first widths=280,272 offset=-4 transition=${transition}`) + const artifactDir = fileURLToPath(new URL('../../../.artifacts', import.meta.url)) + await mkdir(artifactDir, { recursive: true }) + await stackPage.screenshot({ path: join(artifactDir, 'bonus-notice-stack-light.png') }) + await stackPage.emulateMedia({ colorScheme: 'dark' }) + await expect.poll(() => stackPage.locator('body').getAttribute('data-ds-dark-theme')).not.toBeNull() + await stackPage.screenshot({ path: join(artifactDir, 'bonus-notice-stack-dark.png') }) + const promotion = await stackPage.evaluate(() => { + const front = document.querySelector('aside[data-front="true"]')! + const next = front.nextElementSibling as HTMLElement + const before = next.getBoundingClientRect() + front.querySelector('button')!.click() + const animation = next.getAnimations()[0]! + animation.pause() + animation.currentTime = 0 + const start = next.getBoundingClientRect() + animation.currentTime = 125 + const middle = next.getBoundingClientRect() + animation.finish() + const end = next.getBoundingClientRect() + return { before: { y: before.y, height: before.height }, start: { y: start.y, height: start.height }, + middle: { y: middle.y, height: middle.height }, end: { y: end.y, height: end.height } } + }) + expect(promotion.start.y).toBeCloseTo(promotion.before.y, 0) + expect(promotion.start.height).toBeCloseTo(promotion.before.height, 0) + expect(promotion.end.height).toBeGreaterThan(promotion.start.height + 100) + expect(promotion.middle.height).toBeGreaterThan(promotion.start.height) + expect(promotion.middle.height).toBeLessThan(promotion.end.height) + expect(promotion.middle.y).toBeLessThan(promotion.start.y) + expect(promotion.middle.y).toBeGreaterThan(promotion.end.y) + await expect.poll(() => cards.count()).toBe(2) + await expect.poll(async () => Math.round((await second.boundingBox())!.width)).toBe(280) + expect(await second.evaluate((element, previous) => element === previous, secondNode)).toBe(true) + expect(await second.getAttribute('data-front')).toBe('true') + await expect.poll(() => platform.acks.length).toBe(ackStart + 2) + expect(platform.acks.slice(ackStart).map(ack => ack.orderId)).toEqual(['stack-first', 'stack-second']) + observations.push('stack.promoted cards=2 acks=stack-first,stack-second width=280 same-node=true') + await second.getByRole('button', { name: '关闭', exact: true }).click() + await expect.poll(() => platform.acks.length).toBe(ackStart + 3) + expect(platform.acks.slice(ackStart).map(ack => ack.orderId)).toEqual(['stack-first', 'stack-second', 'stack-third']) + await cards.filter({ hasText: '23.00' }).getByRole('button', { name: '关闭', exact: true }).click() + await expect.poll(() => cards.count()).toBe(0) + observations.push('stack.dismissed cards=0 acks=stack-first,stack-second,stack-third') + await stackPage.close() await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'notice-flow.expected.md'), observations.join('\n'), MODE) for (const tripwire of tripwires) { diff --git a/apps/web/tests/default-model.e2e.ts b/apps/web/tests/default-model.e2e.ts index 72ebfb69c2..e8d1867f69 100644 --- a/apps/web/tests/default-model.e2e.ts +++ b/apps/web/tests/default-model.e2e.ts @@ -92,6 +92,70 @@ describe('web e2e: the composer model switch is the default for later sessions', await scaffold?.close() }) + it('exposes inline model labels with exact routes throughout the composer picker', async () => { + const trigger = page.getByRole('button', { name: /^选择模型/ }) + const label = '[data-provider-id][data-model-id]' + const readLabels = (surface: Locator) => surface.locator(label).evaluateAll(nodes => nodes.map(node => ({ + provider: node.getAttribute('data-provider-id'), model: node.getAttribute('data-model-id'), + name: node.textContent, slot: node.querySelector('[data-slot]')?.getAttribute('data-slot'), + inline: node.tagName === 'SPAN' && node.querySelector('div') === null, + }))) + const current = { + provider: START_ROUTE, model: START_MODEL, name: 'Origin Large', + slot: 'conversation.input.model.label', inline: true, + } + await expect.poll(() => readLabels(trigger)).toEqual([current]) + try { + await trigger.click() + const cell = page.getByRole('menuitem', { name: /^模型/ }) + expect(await readLabels(cell)).toEqual([current]) + await cell.click() + const menu = page.getByRole('menu', { name: '模型', exact: true }) + await compareOrRefreshGolden( + fileURLToPath(new URL('./expected/default-model/model-labels.expected.md', import.meta.url)), + JSON.stringify(await readLabels(menu), null, 2), + webSnapshotMode(), + ) + } finally { + await page.keyboard.press('Escape') + await page.keyboard.press('Escape') + } + }) + + it.each(['root', 'model'])('reanchors the %s menu when a label grows and shrinks without a window resize', async (pane) => { + const viewport = page.viewportSize()! + const trigger = page.getByRole('button', { name: /^选择模型/ }) + try { + await page.setViewportSize({ width: 390, height: 844 }) + await trigger.click() + if (pane === 'model') await page.getByRole('menuitem', { name: /^模型/ }).click() + const surface = page.getByRole(pane === 'model' ? 'group' : 'menu', { name: '模型与推理等级', exact: true }) + const label = surface.locator('[data-provider-id="origin-gateway"][data-model-id="origin-large"]') + await label.waitFor() + const original = await surface.boundingBox() + expect(original).not.toBeNull() + // A Slot descendant can resize independently of ModelSelect's props and state. + await label.evaluate((node) => { + const badge = node.ownerDocument.createElement('span') + badge.dataset.modelLabelResizeProbe = '' + badge.textContent = ' — Provider identification badge with a long channel name' + node.appendChild(badge) + }) + await expect.poll(async () => (await surface.boundingBox())!.width).toBeGreaterThan(original!.width + 50) + await expect.poll(async () => { + const box = (await surface.boundingBox())! + return box.x >= 12 && box.x + box.width <= 378 + }).toBe(true) + await label.locator('[data-model-label-resize-probe]').evaluate((node) => { node.remove() }) + await expect.poll(async () => (await surface.boundingBox())!.width).toBeCloseTo(original!.width) + await expect.poll(async () => (await surface.boundingBox())!.x).toBeCloseTo(original!.x) + } finally { + await page.keyboard.press('Escape') + await page.keyboard.press('Escape') + await page.setViewportSize(viewport) + } + }) + it('keeps command popup search borders transparent in both palettes', async () => { const composer = page.locator('[data-composer-input]').first() await page.getByRole('button', { name: '添加文件或调用指令', exact: true }).click() diff --git a/apps/web/tests/expected/bonus-notice/notice-flow.expected.md b/apps/web/tests/expected/bonus-notice/notice-flow.expected.md index 2100ef4682..c4ff30f121 100644 --- a/apps/web/tests/expected/bonus-notice/notice-flow.expected.md +++ b/apps/web/tests/expected/bonus-notice/notice-flow.expected.md @@ -1,11 +1,11 @@ startup.held cards=0 acks=0 -startup.notice=赠金已到账 / 已赠送您 5.00 元 DSH 体验赠金。 -startup.ack orders=22222222-2222-4222-8222-222222222222 +startup.notice=平台通知 / 已赠送您 5.00 元 DSH 体验赠金。 +startup.ack orders=bonus-notice-first startup.get locale=zh_CN query= -startup.ack method=POST path=/api/v0/users/ack_bonus_notified locale=zh_CN order_id=22222222-2222-4222-8222-222222222222 body="{\"order_id\":\"22222222-2222-4222-8222-222222222222\"}" +startup.ack method=POST path=/api/v0/users/ack_bonus_notified locale=zh_CN order_id=bonus-notice-first body="{\"order_id\":\"bonus-notice-first\"}" open.held cards=0 acks=1 gets=1 open.shown notice=赠金已到账 / 已赠送您 8.00 元 DSH 体验赠金。 -open.shown ack orders=22222222-2222-4222-8222-222222222222,33333333-3333-4333-8333-333333333333 +open.shown ack orders=bonus-notice-first,33333333-3333-4333-8333-333333333333 open.shown painted-under-settings=true cards=1 acks=2 open.get locale=zh_CN query= served=true gets=1 open.balance bonus-row=true amount=true dated=false @@ -31,3 +31,6 @@ failed.reached usages=2 gets=1 summaries=1 en.notice=Bonus credited / You received a CNY 9.00 DSH trial credit. en.get locale=en_US query= web.notice=赠金已到账 / 已赠送您 4.00 元 DSH 体验赠金。 acks=1 +stack.initial cards=3 acks=stack-first widths=280,272 offset=-4 transition=0.25s ease-out +stack.promoted cards=2 acks=stack-first,stack-second width=280 same-node=true +stack.dismissed cards=0 acks=stack-first,stack-second,stack-third diff --git a/apps/web/tests/expected/deepseek-messages-settings/cards.expected.md b/apps/web/tests/expected/deepseek-messages-settings/cards.expected.md index abd17a0782..97ec53d80b 100644 --- a/apps/web/tests/expected/deepseek-messages-settings/cards.expected.md +++ b/apps/web/tests/expected/deepseek-messages-settings/cards.expected.md @@ -30,7 +30,7 @@ - text: deepseek-flash - textbox "显示名称 1": - /placeholder: 显示名称 - - text: DeepSeek-V4.1-Flash + - text: DeepSeek-V4.1-Flash-1017 - button "模型选项 1" - button "删除模型 1" - textbox "模型 ID 2": diff --git a/apps/web/tests/expected/default-model/model-labels.expected.md b/apps/web/tests/expected/default-model/model-labels.expected.md new file mode 100644 index 0000000000..3638898e6d --- /dev/null +++ b/apps/web/tests/expected/default-model/model-labels.expected.md @@ -0,0 +1,37 @@ +[ + { + "provider": "deepseek-official", + "model": "deepseek-v4-flash", + "name": "DeepSeek-V4-Flash", + "slot": "conversation.input.model.label", + "inline": true + }, + { + "provider": "deepseek-official", + "model": "deepseek-v4-flash-vision-exp", + "name": "DeepSeek-V4-Flash-Vision-Exp", + "slot": "conversation.input.model.label", + "inline": true + }, + { + "provider": "origin-gateway", + "model": "origin-large", + "name": "Origin Large", + "slot": "conversation.input.model.label", + "inline": true + }, + { + "provider": "acme-gateway", + "model": "acme-large", + "name": "Acme Large", + "slot": "conversation.input.model.label", + "inline": true + }, + { + "provider": "acme-gateway", + "model": "acme-small", + "name": "Acme Small", + "slot": "conversation.input.model.label", + "inline": true + } +] diff --git a/apps/web/tests/expected/onboarding-deepseek-config/default-models.expected.md b/apps/web/tests/expected/onboarding-deepseek-config/default-models.expected.md index cff2fd1b37..c9bf074eab 100644 --- a/apps/web/tests/expected/onboarding-deepseek-config/default-models.expected.md +++ b/apps/web/tests/expected/onboarding-deepseek-config/default-models.expected.md @@ -30,7 +30,7 @@ - text: deepseek-flash - textbox "显示名称 1": - /placeholder: 显示名称 - - text: DeepSeek-V4.1-Flash + - text: DeepSeek-V4.1-Flash-1017 - button "模型选项 1" [expanded] - button "删除模型 1" - text: 上下文窗口 diff --git a/apps/web/tests/onboarding-deepseek-config.e2e.ts b/apps/web/tests/onboarding-deepseek-config.e2e.ts index f6978205c1..aa580b5f85 100644 --- a/apps/web/tests/onboarding-deepseek-config.e2e.ts +++ b/apps/web/tests/onboarding-deepseek-config.e2e.ts @@ -213,7 +213,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup await deepSeek.locator('xpath=ancestor::li').getByRole('button', { name: '编辑' }).click() await settings.getByText('自定义设置').click() expect(await settings.getByLabel('模型 ID 1').inputValue()).toBe('deepseek-flash') - expect(await settings.getByLabel('显示名称 1').inputValue()).toBe('DeepSeek-V4.1-Flash') + expect(await settings.getByLabel('显示名称 1').inputValue()).toBe('DeepSeek-V4.1-Flash-1017') expect(await settings.getByLabel('模型 ID 2').inputValue()).toBe('deepseek-v4-pro') expect(await settings.getByRole('button', { name: /删除模型/ }).count()).toBe(2) await settings.getByRole('button', { name: '模型选项 1' }).click() diff --git a/apps/web/tests/sidebar-right.e2e.ts b/apps/web/tests/sidebar-right.e2e.ts index b389687677..989b773556 100644 --- a/apps/web/tests/sidebar-right.e2e.ts +++ b/apps/web/tests/sidebar-right.e2e.ts @@ -375,6 +375,24 @@ describe('web e2e: shipped right Sidebar', () => { expect(answered).toBe(true) }) + it('keeps the frame unscrolled when PageUp moves the composer caret beside the collapsed panel', async () => { + onTestFailed(() => saveFailureShot(page, 'web-e2e-sidebar-right-pageup')) + const frame = page.locator('[class*="frame"]').first() + const composer = page.locator('textarea, [contenteditable="true"]').first() + // The expand button exists only while the panel is collapsed, when its + // docked hosts sit translated past the frame's right edge. + await expandOf(page).waitFor({ timeout: 15_000 }) + await composer.click() + await composer.pressSequentially('caret') + // Outside macOS, PageUp moves the caret and reveals it against the start + // edge of every scrollable ancestor, `overflow: hidden` ones included. + await page.keyboard.press('PageUp') + expect(await frame.evaluate(node => node.scrollLeft)).toBe(0) + await page.keyboard.press('ControlOrMeta+a') + await page.keyboard.press('Backspace') + await expect.poll(async () => (await composer.innerText()).trim()).toBe('') + }) + it('starts collapsed behind a header button and squeezes the conversation when opened', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-sidebar-right')) const frame = page.locator('[class*="frame"]').first() diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 61f51dfbb8..99adabfad0 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -41,8 +41,8 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:377`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:99`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | | `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:87`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `remotes` | -| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:95`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | -| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:91`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | +| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:97`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | +| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:93`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | | `deepseek-account/signed-out` | `emit` | [`packages/credentials/deepseek-account/src/index.ts:12`](../packages/credentials/deepseek-account/src/index.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | [`deepseek-account`](../packages/credentials/deepseek-account) | | `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `workspace-controller` | | `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 9b16b69863..d287781ee0 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -43,8 +43,8 @@ | `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:377`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:99`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | | `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:87`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `remotes` | -| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:95`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | -| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:91`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | +| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:97`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | +| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:93`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | | `deepseek-account/signed-out` | `emit` | [`packages/credentials/deepseek-account/src/index.ts:12`](../packages/credentials/deepseek-account/src/index.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | [`deepseek-account`](../packages/credentials/deepseek-account) | | `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `workspace-controller` | | `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | diff --git a/docs/module-graph.md b/docs/module-graph.md index 284116e967..62b71c9477 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -657,6 +657,7 @@ flowchart TD pkg_deepseek_account --> pkg_agent pkg_deepseek_account --> pkg_brand pkg_deepseek_account --> pkg_llm + pkg_deepseek_account --> pkg_util_values pkg_commands --> pkg_agent pkg_commands --> pkg_attachment pkg_commands --> pkg_brand @@ -1633,7 +1634,7 @@ flowchart TD | [`harness-dev-context`](../packages/context/harness-dev-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | | [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) | -| [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | +| [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`util-values`](../packages/util/values) | | [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | | [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 165641d138..4f052e3521 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -659,6 +659,7 @@ flowchart TD pkg_deepseek_account --> pkg_agent pkg_deepseek_account --> pkg_brand pkg_deepseek_account --> pkg_llm + pkg_deepseek_account --> pkg_util_values pkg_commands --> pkg_agent pkg_commands --> pkg_attachment pkg_commands --> pkg_brand @@ -1635,7 +1636,7 @@ flowchart TD | [`harness-dev-context`](../packages/context/harness-dev-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | | [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) | -| [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | +| [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`util-values`](../packages/util/values) | | [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | | [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | diff --git a/docs/subsystems/slots.md b/docs/subsystems/slots.md index 0a08dc4f9a..281d1a6113 100644 --- a/docs/subsystems/slots.md +++ b/docs/subsystems/slots.md @@ -161,6 +161,7 @@ Factory: conversation.content [ConversationContent] │ ├─ conversation.input.permission │ ├─ conversation.input.plan │ ├─ conversation.input.model +│ │ └─ conversation.input.model.label │ ├─ conversation.input.overlay │ ├─ conversation.composer.dock │ ├─ conversation.input.left diff --git a/docs/subsystems/slots.zh.md b/docs/subsystems/slots.zh.md index 372f01773c..16fccb7496 100644 --- a/docs/subsystems/slots.zh.md +++ b/docs/subsystems/slots.zh.md @@ -163,6 +163,7 @@ Factory: conversation.content [ConversationContent] │ ├─ conversation.input.permission │ ├─ conversation.input.plan │ ├─ conversation.input.model +│ │ └─ conversation.input.model.label │ ├─ conversation.input.overlay │ ├─ conversation.composer.dock │ ├─ conversation.input.left diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index 240e34124a..89faff6395 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -469,6 +469,14 @@ nameOf(agent: Agent): string */ resolveName(caller: Agent, name: string, signal?: AbortSignal): Promise +/** + * Resolve the root shared by an Agent and its teammates. + * @param caller - exact live Agent whose subagent ancestry selects the tree. + * @returns the live root; ordinary Session forks lead independent trees. + * @throws when the caller or its live ancestry is unavailable. + */ +rootOf(caller: Agent): Agent + /** * Join progressing descendants without cancelling them. Idle descendants whose * inboxes require a later wake stay resident and do not delay host completion. @@ -478,18 +486,17 @@ resolveName(caller: Agent, name: string, signal?: AbortSignal): Promise /** - * Steer one model-authored message to the sender's direct parent, direct - * continuable child, or continuable sibling under their shared live parent. A running target admits it at the nearest step boundary; - * an idle target starts a turn, and an absent direct child cold-resumes from - * persistence. The service derives durable sender attribution from the exact - * live sender. Caller cancellation stops only pre-acceptance work. - * @param sender - exact live Agent authorizing and originating the message. - * @param targetId - durable direct-parent, direct-child, or sibling session id. + * Steer one model-authored message to another member of the same subagent tree. + * A running target receives it at its next step; an idle target starts a turn. + * Cold targets and their continuable ancestors resume under their recorded + * parents. Sender identity is rechecked before inbox acceptance; cancellation + * stops only pre-acceptance work and never retracts an accepted message. + * @param sender - exact live root or resident continuable Agent originating the message. + * @param targetId - another root or continuable member's durable Session id. * @param content - model-authored content to deliver. * @param options - caller cancellation before inbox acceptance. * @returns the accepted message's inbox id. - * @throws when continuation services are unavailable, adjacency is rejected, - * or the message was not admitted. + * @throws when the target is outside the tree, cannot resume, or rejects input. */ async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions, ): Promise diff --git a/docs/subsystems/subagent.zh.md b/docs/subsystems/subagent.zh.md index 733f8f195a..065d45b1a9 100644 --- a/docs/subsystems/subagent.zh.md +++ b/docs/subsystems/subagent.zh.md @@ -471,6 +471,14 @@ nameOf(agent: Agent): string */ resolveName(caller: Agent, name: string, signal?: AbortSignal): Promise +/** + * Resolve the root shared by an Agent and its teammates. + * @param caller - exact live Agent whose subagent ancestry selects the tree. + * @returns the live root; ordinary Session forks lead independent trees. + * @throws when the caller or its live ancestry is unavailable. + */ +rootOf(caller: Agent): Agent + /** * Join progressing descendants without cancelling them. Idle descendants whose * inboxes require a later wake stay resident and do not delay host completion. @@ -480,18 +488,17 @@ resolveName(caller: Agent, name: string, signal?: AbortSignal): Promise /** - * Steer one model-authored message to the sender's direct parent, direct - * continuable child, or continuable sibling under their shared live parent. A running target admits it at the nearest step boundary; - * an idle target starts a turn, and an absent direct child cold-resumes from - * persistence. The service derives durable sender attribution from the exact - * live sender. Caller cancellation stops only pre-acceptance work. - * @param sender - exact live Agent authorizing and originating the message. - * @param targetId - durable direct-parent, direct-child, or sibling session id. + * Steer one model-authored message to another member of the same subagent tree. + * A running target receives it at its next step; an idle target starts a turn. + * Cold targets and their continuable ancestors resume under their recorded + * parents. Sender identity is rechecked before inbox acceptance; cancellation + * stops only pre-acceptance work and never retracts an accepted message. + * @param sender - exact live root or resident continuable Agent originating the message. + * @param targetId - another root or continuable member's durable Session id. * @param content - model-authored content to deliver. * @param options - caller cancellation before inbox acceptance. * @returns the accepted message's inbox id. - * @throws when continuation services are unavailable, adjacency is rejected, - * or the message was not admitted. + * @throws when the target is outside the tree, cannot resume, or rejects input. */ async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions, ): Promise diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index 17ee886d0f..32cad88a5c 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.i18n.yaml @@ -66,8 +66,8 @@ en: 76fbbf0fe38aef10 zh: 76fbbf0fe38aef10 /tool-schema-catalog/deepseek-ai-dsh-tools/run-code: - en: d03bca78d96757a7 - zh: 0e7cd389c38e2d9f + en: a2368a9d8136276e + zh: d1a4918fc45a7c87 /tool-schema-catalog/deepseek-ai-dsh-plan-mode: en: b500b530845e26a9 zh: b500b530845e26a9 @@ -243,8 +243,8 @@ en: 9190a32df5e10ddd zh: 6ece9cbd62eb4d8e /tool-schema-catalog/deepseek-ai-dsh-tool-subagent-control/list-agents: - en: 6caa1abcb5ac1aaf - zh: f99f93111177da5f + en: 40043afff6ae00c7 + zh: 004562c1ea21acdc /tool-schema-catalog/deepseek-ai-dsh-tool-subagent-control/send-message: en: 69c1c0a80c24e3a4 zh: 8d3e8b8b1137e5c5 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 60373cd81c..c1eb61c2a1 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -576,20 +576,20 @@ ask_user_question keeps the original blocking behavior by default; set `mode: ti ### `run_code` -Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. +Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the program to execute, and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. ```json { "type": "object", "properties": { - "description": { - "type": "string", - "description": "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Provide `description` before `code` in the arguments. Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"." - }, "code": { "type": "string", "description": "The TypeScript program to execute, following this tool's execution instructions." }, + "description": { + "type": "string", + "description": "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"." + }, "timeoutMs": { "type": "number", "description": "Positive elapsed-time budget in milliseconds, capped by the deployment maximum." @@ -608,8 +608,8 @@ Execute a TypeScript program against the available tools. Takes two required arg } }, "required": [ - "description", - "code" + "code", + "description" ] } ``` @@ -2152,7 +2152,7 @@ Source: [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/sub ### `list_agents` -List all your reachable continuable descendants and, when you are a continuable subagent, your parent and continuable siblings. Rows carry names, descriptions, and status; descendant rows also carry parent names and depth. You can message your parent, continuable siblings, and direct children. Deeper descendants accept only interrupt_agent. running means it is working; inactive means it is not currently working. You will be notified when a direct child finishes; there is no need to keep checking its status. Use send_message to continue the conversation. +List your entire Team: the root agent and all reachable continuable descendants, including yourself. Rows carry names, descriptions, and status; descendant rows carry parent names and depth from the root. You can message any other Team member whose branch supports continuation. You can interrupt only your own descendants. running means it is working; inactive means it is not currently working. You will be notified when a direct child finishes; there is no need to keep checking its status. Use send_message to continue the conversation. ```json { @@ -2173,7 +2173,7 @@ Send a message to an agent. A working agent receives it at its next step; an idl "properties": { "name": { "type": "string", - "description": "The name of your direct continuable child, or your direct parent or continuable sibling when you are a resident continuable child." + "description": "The name of another member of your Team, as shown by list_agents." }, "message": { "type": "string", diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 2aed3a2b9c..b216b58b00 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -580,20 +580,20 @@ ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才 ### `run_code` -针对可用工具执行 TypeScript 程序。接受两个必填参数:`description`,简要说明该程序做什么;以及 `code`,要执行的程序。请根据系统提示词中的声明,以 `await tools.name(args)` 形式调用工具。请筛选程序输出。含图片的子工具结果会在运行结束后附加。 +针对可用工具执行 TypeScript 程序。接受两个必填参数:`code`,要执行的程序;以及 `description`,简要说明该程序做什么。请根据系统提示词中的声明,以 `await tools.name(args)` 形式调用工具。请筛选程序输出。含图片的子工具结果会在运行结束后附加。 ```json { "type": "object", "properties": { - "description": { - "type": "string", - "description": "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Provide `description` before `code` in the arguments. Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"." - }, "code": { "type": "string", "description": "The TypeScript program to execute, following this tool's execution instructions." }, + "description": { + "type": "string", + "description": "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"." + }, "timeoutMs": { "type": "number", "description": "Positive elapsed-time budget in milliseconds, capped by the deployment maximum." @@ -612,8 +612,8 @@ ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才 } }, "required": [ - "description", - "code" + "code", + "description" ] } ``` @@ -2158,7 +2158,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, ### `list_agents` -列出全部可达的可继续后代;如果调用方是可继续子代理,还列出父代理和可继续同级。行包含名字、描述和状态;后代行还包含父级名字和深度。可以向父代理、可继续同级和直接子级发送消息,更深后代只接受 interrupt_agent。running 表示正在工作;inactive 表示当前没有工作。直属子代理结束时会收到通知,无需反复查询其状态。使用 send_message 继续对话。 +列出整个 Team:根代理及所有可达的可继续后代,包括调用方。行包含名字、描述和状态;后代行包含父级名字和相对根的深度。可以向分支支持继续执行的其他 Team 成员发送消息,只能中断自己的后代。running 表示正在工作;inactive 表示当前没有工作。直属子代理结束时会收到通知,无需反复查询其状态。使用 send_message 继续对话。 ```json { @@ -2179,7 +2179,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, "properties": { "name": { "type": "string", - "description": "The name of your direct continuable child, or your direct parent or continuable sibling when you are a resident continuable child." + "description": "The name of another member of your Team, as shown by list_agents." }, "message": { "type": "string", diff --git a/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.i18n.yaml new file mode 100644 index 0000000000..6ea64e85ce --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.i18n.yaml @@ -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-alpha.4/account-notification-fields/guide.md +/: + en: 9b6666d42852a11b + zh: b14c7d8590c54ba5 +/update-account-notification-consumers: + en: 586faed9ab20343f + zh: fb3cc099b39b0386 +/update-account-notification-consumers/change: + en: 20cc8ba42b771270 + zh: da0ca5f650c74117 +/update-account-notification-consumers/migration: + en: e9fe0c1ebba8acfc + zh: 05b94685fb4c5ead diff --git a/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.md b/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.md new file mode 100644 index 0000000000..b7724e5247 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "AccountBonusNotification exposes unvalidated optional metadata and permits notices without an expiry." +--- + +# Update account notification consumers + +English | [中文](guide.zh.md) + +## Change + +The public `AccountBonusNotification` returned by `DeepSeekAccount.getUnnotifiedBonuses()` and `account/getUnnotifiedBonuses` preserves `campaign`, `amount`, `currency`, and `grantedAt` as optional, unvalidated metadata typed as `JsonValue`. Notifications retain `orderId`, `message`, and `expiresAt`, and add an optional `title`. An empty `expiresAt` means the notification never expires. `orderId` accepts arbitrary strings rather than requiring UUIDs. + +## Migration + +1. Narrow the optional metadata values before using them; their presence and format are no longer guaranteed. Display `message` and use the client's default title when `title` is absent. +2. Treat an empty `expiresAt` as no expiry before parsing timestamps. Keep `orderId` unchanged when acknowledging a notification. +3. Update provider fixtures to use the current fields. Confirm that a notification with a non-UUID ID and empty expiry displays and can be acknowledged. diff --git a/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.zh.md new file mode 100644 index 0000000000..9a5fe7a2a2 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.4/account-notification-fields/guide.zh.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "AccountBonusNotification 提供未经校验的可选元数据,并允许通知永不过期。" +--- + +# 更新账号通知使用方 + +[English](guide.md) | 中文 + +## 变更 + +`DeepSeekAccount.getUnnotifiedBonuses()` 和 `account/getUnnotifiedBonuses` 返回的公开类型 `AccountBonusNotification` 保留 `campaign`、`amount`、`currency` 和 `grantedAt`,作为类型为 `JsonValue` 的可选元数据,不做校验。通知保留 `orderId`、`message` 和 `expiresAt`,并新增可选的 `title`。空字符串 `expiresAt` 表示通知永不过期。`orderId` 接受任意字符串,不再要求 UUID 格式。 + +## 迁移 + +1. 使用可选元数据前先收窄类型;这些值不再保证存在或符合原有格式。展示 `message`,在 `title` 缺省时使用客户端默认标题。 +2. 解析时间戳前,将空字符串 `expiresAt` 视为永不过期。确认通知时原样传递 `orderId`。 +3. 更新提供者测试夹具以使用当前字段。确认非 UUID 标识、空过期时间的通知可以展示并确认。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.i18n.yaml similarity index 74% rename from docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.i18n.yaml rename to docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.i18n.yaml index d416f239d0..30e78e5e41 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.i18n.yaml +++ b/docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.i18n.yaml @@ -1,16 +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-alpha.4/agent-team-subagents/guide.md +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md /: en: 9a266a4f19673f89 zh: 0b716c060f3e568d /agent-teams-uses-subagents-and-unique-agent-names: - en: e2fd55138832d6e1 - zh: 5fc33b77673c6ea0 + en: 563e943c3c4447d5 + zh: 254c0e49d2f2a302 /agent-teams-uses-subagents-and-unique-agent-names/change: - en: 12a436db46a7ecf2 - zh: 8cdb01c672e5a89a + en: 5bb67f34d859d1fc + zh: d31987a7d12e4103 /agent-teams-uses-subagents-and-unique-agent-names/migration: - en: 4999c1a628cfb22e - zh: 388d8df7d9fc45c3 + en: 84fd9ee43c23be82 + zh: c31fa1844e79cfab diff --git a/docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md b/docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md similarity index 83% rename from docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md rename to docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md index 0572d71558..78f35e3807 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md +++ b/docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md @@ -11,8 +11,8 @@ English | [中文](guide.zh.md) The experimental Agent Teams plugins share agent creation, discovery, messaging, and interruption with subagents. Start a teammate with `subagent` or `subagent_fork`, give it a tree-wide unique `name`, describe its responsibility with `description`, and use its returned name for messages and task assignment. Descriptions may repeat and never identify a recipient. -- `spawn_teammate` and the Team-scoped `send_message`, `list_agents`, and `interrupt_agent` are removed. The standard control tools use `name`, not `agent_id` or `target`. Continuable children of one live parent can message each other by name. -- `list_agents` takes no arguments and always lists all reachable continuable descendants, plus a continuable caller’s live parent and siblings. Remove the `scope` argument from scripted calls. Listing deeper descendants does not grant messaging or task-assignment authority. +- `spawn_teammate` and the Team-scoped `send_message`, `list_agents`, and `interrupt_agent` are removed. The standard control tools use `name`, not `agent_id` or `target`. Members of one root tree can message each other across branches by name; interruption remains ancestor-only. +- `list_agents` takes no arguments and lists the entire Team, including the root and caller. Remove scripted `scope` arguments. The root row uses `kind: root`; descendant rows use `kind: child`, with parent names and root-relative depth. Update consumers that expect `parent` or `sibling` rows. Discovery does not grant task-assignment authority. - The Team profile no longer disables the subagent tools. Team adds only task tools, `wait_agent`, and collaboration guidance. - `agent-team` rejects `maxMembers`, `maxMessageBytes`, and `disposalTimeoutMs`; `tool-agent-team` rejects `interrupt`, `freshProvider`, and `forkProvider`. - `ctx.agentTeams` removes `spawnTeammate()`, `sendMessage()`, `interrupt()`, and `listMembers()`. Host task views retain `ownerId` Session identities; model-facing task results expose `ownerName`, and task `owner` arguments use names. The root agent is always `lead`. @@ -22,6 +22,6 @@ The experimental Agent Teams plugins share agent creation, discovery, messaging, 1. Remove the obsolete configuration keys and any patch rows that disable subagent tools solely for Team support. 2. Replace `spawn_teammate({ name, description, prompt, context })` with `subagent({ name, description, prompt })`, or `subagent_fork` for inherited conversation history. Choose a unique lower-case name with letters, digits, underscores, or hyphens; start with a letter. `lead` and `unowned` are reserved. Duplicate names fail before creation. -3. Use `send_message({ name, message })` and `interrupt_agent({ name })`. Discover parent and sibling names with `list_agents`. +3. Use `send_message({ name, message })` and `interrupt_agent({ name })`. Discover all Team member names with `list_agents`. 4. Pass member names to task `owner` fields and read `ownerName` in tool results. Use `lead` for the root agent. 5. Replace removed service calls with `ctx.subagents.startActivation()`, `sendMessage()`, `interrupt()`, and `listChildren()`. Supply `name` to `startActivation()`; host control APIs still use Session ids. Creation and ordinary name lookup require a readable root catalog. A failed root registration leaves no usable named child; retry creation with the same name and a fresh child id. Start a Team-enabled profile and verify that a child's description appears in the Team panel and that a name-addressed message reaches it. diff --git a/docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md similarity index 83% rename from docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md rename to docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md index 862817e9eb..8b20c27892 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md +++ b/docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md @@ -11,8 +11,8 @@ description: "Agent Teams 使用标准 subagent 工具与唯一 agent 名字;T 实验性 Agent Teams 插件与 subagent 共享创建、发现、消息与中断。使用 `subagent` 或 `subagent_fork` 启动队友,为其指定树内唯一的 `name`,用 `description` 描述职责,并以返回的名字发送消息和分配任务。描述可以重复,不标识接收方。 -- `spawn_teammate` 及 Team 作用域的 `send_message`、`list_agents`、`interrupt_agent` 被移除。标准控制工具使用 `name`,而非 `agent_id` 或 `target`。同一在线父级的可继续子级可以按名字互发消息。 -- `list_agents` 不接受参数,始终列出所有可达的可继续后代,以及可继续调用方的在线父级和同级。脚本调用需移除 `scope` 参数。列出更深后代不授予消息或任务分配权限。 +- `spawn_teammate` 及 Team 作用域的 `send_message`、`list_agents`、`interrupt_agent` 被移除。标准控制工具使用 `name`,而非 `agent_id` 或 `target`。同一根树的成员可以按名字跨分支互发消息;中断仍仅允许祖先执行。 +- `list_agents` 不接受参数,列出整个 Team,包括根和调用方。脚本调用需移除 `scope` 参数。根行使用 `kind: root`;后代行使用 `kind: child`,带父级名字和相对根的深度。更新依赖 `parent` 或 `sibling` 行的消费者。发现成员不授予任务分配权限。 - Team profile 不再禁用 subagent 工具。Team 只增加任务工具、`wait_agent` 和协作指引。 - `agent-team` 拒绝 `maxMembers`、`maxMessageBytes` 与 `disposalTimeoutMs`;`tool-agent-team` 拒绝 `interrupt`、`freshProvider` 与 `forkProvider`。 - `ctx.agentTeams` 移除 `spawnTeammate()`、`sendMessage()`、`interrupt()` 与 `listMembers()`。宿主任务视图保留 `ownerId` Session 身份;面向模型的任务结果公开 `ownerName`,任务 `owner` 参数使用名字。根 agent 始终名为 `lead`。 @@ -22,6 +22,6 @@ description: "Agent Teams 使用标准 subagent 工具与唯一 agent 名字;T 1. 移除废弃配置键,以及仅为 Team 支持而禁用 subagent 工具的 patch 行。 2. 将 `spawn_teammate({ name, description, prompt, context })` 改为 `subagent({ name, description, prompt })`,需要继承会话历史时使用 `subagent_fork`。选择唯一的小写名字,只含字母、数字、下划线或连字符,并以字母开头。`lead` 和 `unowned` 为保留名。重名在创建前失败。 -3. 使用 `send_message({ name, message })` 与 `interrupt_agent({ name })`。通过 `list_agents` 发现父级和同级名字。 +3. 使用 `send_message({ name, message })` 与 `interrupt_agent({ name })`。通过 `list_agents` 发现所有 Team 成员名字。 4. 向任务 `owner` 字段传成员名字,读取工具结果中的 `ownerName`。根 agent 使用 `lead`。 5. 将移除的服务调用改为 `ctx.subagents.startActivation()`、`sendMessage()`、`interrupt()` 与 `listChildren()`。向 `startActivation()` 提供 `name`;宿主控制 API 仍使用 Session id。创建与普通名字查找要求根 catalog 可读。根登记失败不会留下可用的命名子级;可使用相同名字和新 child id 重试创建。启动启用 Team 的 profile,确认子级描述出现在 Team 面板,并且按名字发送的消息能够到达。 diff --git a/package.json b/package.json index 042d7eb56b..dba47d00ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-root", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "license": "MIT", "private": true, "type": "module", diff --git a/packages/acp/acp/package.json b/packages/acp/acp/package.json index 936f60429a..9ed2815f1f 100644 --- a/packages/acp/acp/package.json +++ b/packages/acp/acp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-acp", "description": "Automation-only Agent Client Protocol server for driving DeepSeek Harness agents over JSON-RPC stdio", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/account-controller/package.json b/packages/api/account-controller/package.json index e3c47d79f6..f36b8c7f2f 100644 --- a/packages/api/account-controller/package.json +++ b/packages/api/account-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-account-controller", "description": "Expose safe account operations over authenticated Remote", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/account-controller/tests/controller.spec.ts b/packages/api/account-controller/tests/controller.spec.ts index aa253ca386..2360bdc84e 100644 --- a/packages/api/account-controller/tests/controller.spec.ts +++ b/packages/api/account-controller/tests/controller.spec.ts @@ -51,8 +51,7 @@ it('delegates bonus reads and acknowledgements without altering their values', a const { provider, controller } = fixture() const accountId = 'account' as AccountUserId const orderId = 'order' as AccountBonusOrderId - const batch: AccountBonusBatch = { accountId, bonuses: [{ orderId, campaign: 'dsh_login_bonus', amount: '5.00', - currency: 'CNY', grantedAt: '2026-09-21T12:00:00Z', expiresAt: '2026-10-21T12:00:00Z', message: '已赠送您 5.00 元 DSH 体验赠金。' }] } + const batch: AccountBonusBatch = { accountId, bonuses: [{ orderId, expiresAt: '2026-10-21T12:00:00Z', message: '已赠送您 5.00 元 DSH 体验赠金。' }] } provider.getUnnotifiedBonuses.mockResolvedValueOnce(batch) expect(await controller.getUnnotifiedBonuses(client)).toBe(batch) expect(provider.getUnnotifiedBonuses).toHaveBeenCalledExactlyOnceWith(client) diff --git a/packages/api/gateway/package.json b/packages/api/gateway/package.json index d582e3a2e2..c7368f831c 100644 --- a/packages/api/gateway/package.json +++ b/packages/api/gateway/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-gateway", "description": "Typert Remote Host dispatcher and Client API endpoint", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/job-controller/package.json b/packages/api/job-controller/package.json index 52c691a824..b4bab39fb4 100644 --- a/packages/api/job-controller/package.json +++ b/packages/api/job-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-job-controller", "description": "Job Remote observation stream and the reference-counted client job-output service", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/remote-access/package.json b/packages/api/remote-access/package.json index 091571fb6b..19cd547325 100644 --- a/packages/api/remote-access/package.json +++ b/packages/api/remote-access/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-remote-access", "description": "Method-level Remote access control for non-owner Peers and the per-Peer access snapshot", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/remotes/package.json b/packages/api/remotes/package.json index ce79b4be96..db4779d521 100644 --- a/packages/api/remotes/package.json +++ b/packages/api/remotes/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-remotes", "description": "Remote BFF assembly for application-selected Host capabilities", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/session-controller/package.json b/packages/api/session-controller/package.json index 0021704902..c4df1980f2 100644 --- a/packages/api/session-controller/package.json +++ b/packages/api/session-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-session-controller", "description": "Session Remote commands, cold reads, and live control transport", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/settings-controller/package.json b/packages/api/settings-controller/package.json index 4ca0361a2f..f2f8da5d51 100644 --- a/packages/api/settings-controller/package.json +++ b/packages/api/settings-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-settings-controller", "description": "Remote owner for the configuration surfaces over the settings-domain seams", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/terminal-controller/package.json b/packages/api/terminal-controller/package.json index 0905960c66..0b070f33ad 100644 --- a/packages/api/terminal-controller/package.json +++ b/packages/api/terminal-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-terminal-controller", "description": "Session-owned interactive terminals with shell discovery, screen recovery and typed Remote control", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/workspace-controller/package.json b/packages/api/workspace-controller/package.json index 9210f16d13..9e0d068cd2 100644 --- a/packages/api/workspace-controller/package.json +++ b/packages/api/workspace-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-workspace-controller", "description": "Workspace Remote commands and reconnect-safe state transport", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/workspace-files/package.json b/packages/api/workspace-files/package.json index b3929c75eb..ef0b3b4281 100644 --- a/packages/api/workspace-files/package.json +++ b/packages/api/workspace-files/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-workspace-files", "description": "Workspace file service and Client resource provider: bounded reads, directory listing, and live metadata over the workspaceFiles Remote namespace", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/attachment/attachment-local/package.json b/packages/attachment/attachment-local/package.json index b26c8327ab..c63f16605d 100644 --- a/packages/attachment/attachment-local/package.json +++ b/packages/attachment/attachment-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-attachment-local", "description": "Private content-addressed DSH_HOME attachment storage", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/attachment/attachment/package.json b/packages/attachment/attachment/package.json index cf389c756e..fa8aaf47e2 100644 --- a/packages/attachment/attachment/package.json +++ b/packages/attachment/attachment/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-attachment", "description": "Durable immutable attachment storage seam for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/app-boot/package.json b/packages/boot/app-boot/package.json index 1977a920d2..4542c91d7c 100644 --- a/packages/boot/app-boot/package.json +++ b/packages/boot/app-boot/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-app-boot", "description": "Shared boot glue for the app bins: .env loading, fail-loud Loader guards, snapshot-aware config resolution, and the Loader boot sequence", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/cmdline/package.json b/packages/boot/cmdline/package.json index 9fbfb5cdbb..8b71c96a3d 100644 --- a/packages/boot/cmdline/package.json +++ b/packages/boot/cmdline/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-cmdline", "description": "Immutable command-line handoff from a dsh launcher to any app plugin that injects cmdlineArgs", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/config-editor/package.json b/packages/boot/config-editor/package.json index 4d22dea41b..4b859ce49b 100644 --- a/packages/boot/config-editor/package.json +++ b/packages/boot/config-editor/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-config-editor", "description": "Persist plugin configuration through profile patches and Loader reconciliation", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/hmr/package.json b/packages/boot/hmr/package.json index 0dcf9b49a7..08d2db60de 100644 --- a/packages/boot/hmr/package.json +++ b/packages/boot/hmr/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-hmr", "description": "Coordinated module and profile configuration hot reload", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/plugin-manager/package.json b/packages/boot/plugin-manager/package.json index 387a324956..a30bd23535 100644 --- a/packages/boot/plugin-manager/package.json +++ b/packages/boot/plugin-manager/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-plugin-manager", "description": "Current-profile plugin and bundle management shared by dsh CLI, Web and agent tools", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/plugin-market/package.json b/packages/boot/plugin-market/package.json index 1ed7cb7b4d..5a33df0ede 100644 --- a/packages/boot/plugin-market/package.json +++ b/packages/boot/plugin-market/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-plugin-market", "description": "Configured plugin market indexes for the Web Discover page and the plugin_market agent tool", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/browser-use/browser-use/package.json b/packages/browser-use/browser-use/package.json index 92206186e0..7e97d29921 100644 --- a/packages/browser-use/browser-use/package.json +++ b/packages/browser-use/browser-use/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-browser-use", "description": "Exclusive named browser-use provider registration", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/acp-app/package.json b/packages/bundle/acp-app/package.json index fde797834d..f0a9b650bf 100644 --- a/packages/bundle/acp-app/package.json +++ b/packages/bundle/acp-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-acp-app", "description": "The dsh ACP profile bundle: automation-only JSON-RPC stdio and process lifecycle over dsh-base", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/base/package.json b/packages/bundle/base/package.json index bad07101bb..370f446bef 100644 --- a/packages/bundle/base/package.json +++ b/packages/bundle/base/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-base", "description": "The shared dsh core as a profile bundle: the first patch layer of base-backed profiles, inserting core rows over the empty profile root", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/headless/package.json b/packages/bundle/headless/package.json index 3ed8f43f6b..fd4c587fab 100644 --- a/packages/bundle/headless/package.json +++ b/packages/bundle/headless/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-headless", "description": "The dsh one-shot bundle: a direct core Agent/Session runner over dsh-base with no Host, HTTP, or browser layer", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/sdk-app/README.i18n.yaml b/packages/bundle/sdk-app/README.i18n.yaml index fb39b99573..97cfc61347 100644 --- a/packages/bundle/sdk-app/README.i18n.yaml +++ b/packages/bundle/sdk-app/README.i18n.yaml @@ -24,11 +24,11 @@ en: 4f88e647c6df4f42 zh: 2bd3e4b0bdfe9412 /deepseek-ai-dsh-sdk-app/model-experience/sdk-model-context/what-the-model-sees: - en: 40f68e7d3f181ed6 - zh: 64b0d7c8bf63dfa1 + en: fc42eb5995eb2078 + zh: 4e47602e46ba5123 /deepseek-ai-dsh-sdk-app/model-experience/sdk-model-context/token-effect: - en: 92d8ecb761e3ca7b - zh: e14c71ee2c31e818 + en: 4a0cdca06d358f53 + zh: d27080dcb68c661c /deepseek-ai-dsh-sdk-app/model-experience/sdk-model-context/kv-cache-effect: en: 5b0e33295fb90e19 zh: 4d48ca75bb677202 diff --git a/packages/bundle/sdk-app/README.md b/packages/bundle/sdk-app/README.md index a149ec1254..d092832c1e 100644 --- a/packages/bundle/sdk-app/README.md +++ b/packages/bundle/sdk-app/README.md @@ -44,11 +44,11 @@ The Python runtime exposes explicitly downloaded authoring resources and Office #### What the model sees -The profile inherits the base system prompt, including the `You are an AI agent powered by DeepSeek Harness.` identity, without adding a task-specific persona. Each Session receives its current directory in required user-role context from [`dsh-working-directory`](../../session/working-directory/README.md). Default file tool schemas include `read`, `write`, and `edit`; they omit `str_replace_editor`. +The profile inherits the base system prompt, including the `You are an AI agent powered by DeepSeek Harness.` identity, and adds the same model-interpolated coding persona as the Web and headless profiles. Each Session receives its current directory in required user-role context from [`dsh-working-directory`](../../session/working-directory/README.md). Default file tool schemas include `read`, `write`, and `edit`; they omit `str_replace_editor`. #### Token effect -The base prompt sections and selected tool schemas determine token usage; this profile adds no persona text. +The base prompt sections and selected tool schemas determine token usage; this profile adds one coding-persona sentence. #### KV Cache effect diff --git a/packages/bundle/sdk-app/README.zh.md b/packages/bundle/sdk-app/README.zh.md index f244072935..6f801c385d 100644 --- a/packages/bundle/sdk-app/README.zh.md +++ b/packages/bundle/sdk-app/README.zh.md @@ -44,11 +44,11 @@ Python 运行时向本 profile 提供显式下载的创作资源和 Office sidec #### 模型看到什么 -profile 继承 base 系统提示词,包括 `You are an AI agent powered by DeepSeek Harness.` 身份说明,不额外添加特定任务的 persona。每个 Session 通过 [`dsh-working-directory`](../../session/working-directory/README.zh.md) 提供的必需 user-role 上下文接收当前目录。默认文件工具 schema 包含 `read`、`write` 和 `edit`,不包含 `str_replace_editor`。 +profile 继承 base 系统提示词,包括 `You are an AI agent powered by DeepSeek Harness.` 身份说明,并添加与 Web 和 headless profile 相同、按当前模型插值的 coding persona。每个 Session 通过 [`dsh-working-directory`](../../session/working-directory/README.zh.md) 提供的必需 user-role 上下文接收当前目录。默认文件工具 schema 包含 `read`、`write` 和 `edit`,不包含 `str_replace_editor`。 #### Token 影响 -base 提示词段落与所选工具 schema 决定 token 用量;此 profile 不额外添加 persona 文本。 +base 提示词段落与所选工具 schema 决定 token 用量;此 profile 额外添加一句 coding persona。 #### KV Cache 影响 diff --git a/packages/bundle/sdk-app/cordis.patch.yml b/packages/bundle/sdk-app/cordis.patch.yml index 004a81ce9b..af7f7409eb 100644 --- a/packages/bundle/sdk-app/cordis.patch.yml +++ b/packages/bundle/sdk-app/cordis.patch.yml @@ -1,5 +1,10 @@ # The SDK application over dsh-base. Stdout belongs exclusively to JSON-RPC. +- id: system-prompt + config: + personaPrefix: >- + You are a coding agent powered by the {{model}} model. + - id: session-title-llm disabled: true diff --git a/packages/bundle/sdk-app/package.json b/packages/bundle/sdk-app/package.json index e9aab5b18a..788a1bc3f1 100644 --- a/packages/bundle/sdk-app/package.json +++ b/packages/bundle/sdk-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-app", "description": "The dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-base", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/sdk-app/tests/sdk-app.spec.ts b/packages/bundle/sdk-app/tests/sdk-app.spec.ts index 2129154d41..2e040c2487 100644 --- a/packages/bundle/sdk-app/tests/sdk-app.spec.ts +++ b/packages/bundle/sdk-app/tests/sdk-app.spec.ts @@ -22,6 +22,9 @@ describe('dsh-sdk-app bundle', () => { ) as Array<{ id?: string; disabled?: boolean; insert?: Array<{ id?: string; inject?: string[]; name?: string }> }> expect(patches.find(patch => patch.id === 'hmr')).toMatchObject({ disabled: true }) expect(patches.find(patch => patch.id === 'session-title-llm')).toMatchObject({ disabled: true }) + expect(patches.find(patch => patch.id === 'system-prompt')).toMatchObject({ + config: { personaPrefix: 'You are a coding agent powered by the {{model}} model.' }, + }) const rows = patches.flatMap(patch => patch.insert ?? []) expect(rows.find(row => row.id === 'sdk-app-startup')?.name).toBe('@deepseek-ai/dsh-sdk-app') expect(rows.find(row => row.id === 'sdk-jsonrpc-server')?.inject).toEqual(['sdkAppStartup', 'loader']) diff --git a/packages/bundle/sdk-minimal/package.json b/packages/bundle/sdk-minimal/package.json index 954731516d..9c572f3150 100644 --- a/packages/bundle/sdk-minimal/package.json +++ b/packages/bundle/sdk-minimal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-minimal", "description": "The standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, and JSONL sessions", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/web-app/README.i18n.yaml b/packages/bundle/web-app/README.i18n.yaml index 1d8e07a01e..c8ff54a601 100644 --- a/packages/bundle/web-app/README.i18n.yaml +++ b/packages/bundle/web-app/README.i18n.yaml @@ -18,8 +18,8 @@ en: 3bd37b85139d2fa7 zh: ba86c807eb83866e /deepseek-ai-dsh-web-app/use-this-package/starting-the-web-gui: - en: e3cac911f080d8ef - zh: 60749e9f5c39c1bc + en: a1cf03cc8ad908c5 + zh: c06b89454172a47d /deepseek-ai-dsh-web-app/use-this-package/configuration: en: b1787804544f2587 zh: ce09d2c248ffd999 diff --git a/packages/bundle/web-app/README.md b/packages/bundle/web-app/README.md index 2305d402cd..23c4936ffc 100644 --- a/packages/bundle/web-app/README.md +++ b/packages/bundle/web-app/README.md @@ -40,7 +40,7 @@ dsh --profile web --no-open --port 8080 After startup you see a `dsh web:` line whose root URL carries a fresh process token. Unless `--no-open` or an SSH session suppresses it, the default browser opens that URL, receives a signed cookie, and redirects to the same directory without the token. You know it worked when the page loads and you can chat with the agent. Two failures to expect: if the frontend is not built, startup stops with a build hint (`pnpm run build` in a checkout); if the browser cannot be opened, a credential-free diagnostic prints to stderr while the server keeps running — open the printed startup URL yourself. -**Settings → Models** displays **DeepSeek**, using `DEEPSEEK_API_KEY`. The default is `deepseek-official` / `deepseek-flash` (DeepSeek-V4.1-Flash). The [DeepSeek plugin](../../llm/llm-deepseek/README.md#endpoint-and-wire-format) uses the Messages API. +**Settings → Models** displays **DeepSeek**, using `DEEPSEEK_API_KEY`. The default is `deepseek-official` / `deepseek-flash` (DeepSeek-V4.1-Flash-1017). The [DeepSeek plugin](../../llm/llm-deepseek/README.md#endpoint-and-wire-format) uses the Messages API. Saved model selections override the composition default. The settings card accepts a Messages-compatible API address and a credential reference. diff --git a/packages/bundle/web-app/README.zh.md b/packages/bundle/web-app/README.zh.md index 8d08ede8af..3598103591 100644 --- a/packages/bundle/web-app/README.zh.md +++ b/packages/bundle/web-app/README.zh.md @@ -40,7 +40,7 @@ dsh --profile web --no-open --port 8080 启动后你会看到 `dsh web:` 行,其根 URL 携带新的进程 token。除非 `--no-open` 或 SSH 会话抑制,否则默认浏览器会打开该 URL、取得签名 cookie,再重定向到不含认证参数的同一目录。页面加载且你可以与 agent 对话,就说明成功了。两种可预期的失败:前端未构建时,启动会以构建提示停止(checkout 中运行 `pnpm run build`);浏览器无法打开时,stderr 会打印不含凭据的诊断,但服务器会继续运行——请自行打开已打印的启动 URL。 -**设置 → 模型**显示 **DeepSeek**,使用 `DEEPSEEK_API_KEY`。默认模型为 `deepseek-official` / `deepseek-flash`(DeepSeek-V4.1-Flash)。[DeepSeek 插件](../../llm/llm-deepseek/README.zh.md#endpoint-and-wire-format)使用 Messages API。 +**设置 → 模型**显示 **DeepSeek**,使用 `DEEPSEEK_API_KEY`。默认模型为 `deepseek-official` / `deepseek-flash`(DeepSeek-V4.1-Flash-1017)。[DeepSeek 插件](../../llm/llm-deepseek/README.zh.md#endpoint-and-wire-format)使用 Messages API。 已保存的模型选择覆盖组合默认值。设置卡接受兼容 Messages 的 API 地址与凭据引用。 diff --git a/packages/bundle/web-app/package.json b/packages/bundle/web-app/package.json index 9949c36fea..72fb017899 100644 --- a/packages/bundle/web-app/package.json +++ b/packages/bundle/web-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-app", "description": "The dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, bash runtime variables, URL line)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/connection/package.json b/packages/client/connection/package.json index a1303455d1..a128d66101 100644 --- a/packages/client/connection/package.json +++ b/packages/client/connection/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-connection", "description": "Authenticated RPC transport and generation lifecycle", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/file-upload/package.json b/packages/client/file-upload/package.json index 5ede008af5..ef3c4ba1b5 100644 --- a/packages/client/file-upload/package.json +++ b/packages/client/file-upload/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-file-upload", "description": "Agent-scoped browser file upload, streaming intake, and staged receipt service", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/hmr/package.json b/packages/client/hmr/package.json index 0d4d80d579..c444980a5c 100644 --- a/packages/client/hmr/package.json +++ b/packages/client/hmr/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-hmr", "description": "Web client graph synchronization and rebuilt-bundle reload transport", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/locale/package.json b/packages/client/locale/package.json index 9293e95665..ff4ec9cb5a 100644 --- a/packages/client/locale/package.json +++ b/packages/client/locale/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-locale", "description": "Locale plugin: Host-backed preference, extensible language catalog, browser fallback, and typed built-in dictionaries", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/modules/package.json b/packages/client/modules/package.json index 80e72d3df3..0e1f8ead6b 100644 --- a/packages/client/modules/package.json +++ b/packages/client/modules/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-modules", "description": "Client module system, dual-face: node half composes the __DSH_BOOT__ entry graph (incremental dsh.client scan, bundle route, index tap, webPlugins service); browser half is the lazy-CJS module table the vendored cordis Loader consumes as its internal seam", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/product-analytics/package.json b/packages/client/product-analytics/package.json index b168224e92..4d3dc8baa5 100644 --- a/packages/client/product-analytics/package.json +++ b/packages/client/product-analytics/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-product-analytics", "description": "Desktop product event collection and authenticated Host reporting", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/resources/package.json b/packages/client/resources/package.json index 0c7f7475f3..c96a5fb21b 100644 --- a/packages/client/resources/package.json +++ b/packages/client/resources/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-resources", "description": "Unified client resource model: protocol-registered providers turn URL addresses into live values, consumed through the useResource global standard hook", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/shortcuts/package.json b/packages/client/shortcuts/package.json index 5715c055ab..9f54f356d2 100644 --- a/packages/client/shortcuts/package.json +++ b/packages/client/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-shortcuts", "description": "Application keyboard command registry and physical-key routing", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/store/package.json b/packages/client/store/package.json index 3e7be5b523..1233f641e2 100644 --- a/packages/client/store/package.json +++ b/packages/client/store/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-store", "description": "React-free observable and snapshot-store contracts with the shared Zustand/Immer engine", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-agent-preset/package.json b/packages/client/ui-agent-preset/package.json index 7f35f20e96..1ad2758573 100644 --- a/packages/client/ui-agent-preset/package.json +++ b/packages/client/ui-agent-preset/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-agent-preset", "description": "Agent-preset surfaces: the default for later sessions, this session's seat, and the composition editor", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-approval/package.json b/packages/client/ui-approval/package.json index 9b98bfcf78..33b599f553 100644 --- a/packages/client/ui-approval/package.json +++ b/packages/client/ui-approval/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-approval", "description": "Approval composer takeover over the scoped Remote Event waterfall", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-attachment/package.json b/packages/client/ui-attachment/package.json index 89aa2133d3..3d23e6ada8 100644 --- a/packages/client/ui-attachment/package.json +++ b/packages/client/ui-attachment/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-attachment", "description": "Dynamic attachment presentation plugin for conversation input, message-image, and trajectory image slots", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-brand-official/package.json b/packages/client/ui-brand-official/package.json index 1200f2ec9e..b1a79c2dd9 100644 --- a/packages/client/ui-brand-official/package.json +++ b/packages/client/ui-brand-official/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-brand-official", "description": "Official DeepSeek Harness brand occupants for the Web client's sidebar slots", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-chat/package.json b/packages/client/ui-chat/package.json index 600891f0ec..0a089e0642 100644 --- a/packages/client/ui-chat/package.json +++ b/packages/client/ui-chat/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-chat", "description": "Chat Conversation target, node definitions, renderers, and details surface", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-commands/package.json b/packages/client/ui-commands/package.json index 8b420220b8..537fabc545 100644 --- a/packages/client/ui-commands/package.json +++ b/packages/client/ui-commands/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-commands", "description": "Client command surface: global directory cache, '/' source, three command UI kinds, popupSelect registry", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-conversation/package.json b/packages/client/ui-conversation/package.json index af2dfe734b..6cc4a17f91 100644 --- a/packages/client/ui-conversation/package.json +++ b/packages/client/ui-conversation/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-conversation", "description": "Target-neutral Conversation assembly, shell, composer, queue, and view navigation", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-deliverables/package.json b/packages/client/ui-deliverables/package.json index cd27c21231..10f67e304f 100644 --- a/packages/client/ui-deliverables/package.json +++ b/packages/client/ui-deliverables/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-deliverables", "description": "Changed-files card with per-file comparison tabs, delivery cards, and clickable final-response file references for Web", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-directory-picker-browse/package.json b/packages/client/ui-directory-picker-browse/package.json index bc385b1d93..c86d9dedd7 100644 --- a/packages/client/ui-directory-picker-browse/package.json +++ b/packages/client/ui-directory-picker-browse/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-directory-picker-browse", "description": "In-app directory browsing surface: the workspace directory-flow owner rendering the host's listing and creation primitives", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-directory-picker-native/package.json b/packages/client/ui-directory-picker-native/package.json index 028cd7f5bb..9db39811c7 100644 --- a/packages/client/ui-directory-picker-native/package.json +++ b/packages/client/ui-directory-picker-native/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-directory-picker-native", "description": "Native directory-picker surface: the renderless workspace directory-flow occupant driving the local Desktop or Host OS chooser", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-dockkit/package.json b/packages/client/ui-dockkit/package.json index 4b0e96a932..f99e729afd 100644 --- a/packages/client/ui-dockkit/package.json +++ b/packages/client/ui-dockkit/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-dockkit", "description": "Docking layout kit: split-tree engine with invertible operations, and the React components that render and drive it (zero cordis)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-dockkit/src/components/DockSurface.tsx b/packages/client/ui-dockkit/src/components/DockSurface.tsx index 17ca70f0c9..4803955cb0 100644 --- a/packages/client/ui-dockkit/src/components/DockSurface.tsx +++ b/packages/client/ui-dockkit/src/components/DockSurface.tsx @@ -308,7 +308,8 @@ export type DockLayoutProps = DockSurfaceProps & TabRetention /** * Render one pane or two horizontal panes, and their floats, in one stable content tree. * @param props - layout, gestures and lazy body-retention policy. - * @returns the layout; the containing ancestors must not clip or establish a fixed-position containing block. + * @returns the layout; the containing ancestors must not establish a fixed-position containing block. + * They may clip `overflow`, which does not reach the fixed-position floats. */ export function DockLayout(props: DockLayoutProps): ReactNode { return diff --git a/packages/client/ui-goal/package.json b/packages/client/ui-goal/package.json index d311effb7a..5b7e8287ad 100644 --- a/packages/client/ui-goal/package.json +++ b/packages/client/ui-goal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-goal", "description": "Session goal surface: GoalBar docked above the composer, read from the goal session projection", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-input-trigger/package.json b/packages/client/ui-input-trigger/package.json index 99c77ef47c..66c9cc6de1 100644 --- a/packages/client/ui-input-trigger/package.json +++ b/packages/client/ui-input-trigger/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-input-trigger", "description": "Input trigger pipeline: '/' and '@' detection, candidate menu, pick routing to registered sources", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-jobs/package.json b/packages/client/ui-jobs/package.json index 568f0c4d9f..42fd65636d 100644 --- a/packages/client/ui-jobs/package.json +++ b/packages/client/ui-jobs/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-jobs", "description": "Session-header background-job list with on-demand streaming record panels", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "type": "module", "main": "lib/index.js", "types": "lib/types/index.d.ts", diff --git a/packages/client/ui-layout/package.json b/packages/client/ui-layout/package.json index 1364e73ff9..9b1ed12712 100644 --- a/packages/client/ui-layout/package.json +++ b/packages/client/ui-layout/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-layout", "description": "Shell plugin: three-column AppFrame with drag handles, ctx.layout viewing-state service (navigation + panels)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-message-feedback/package.json b/packages/client/ui-message-feedback/package.json index 53ead2c2a3..190e86a72a 100644 --- a/packages/client/ui-message-feedback/package.json +++ b/packages/client/ui-message-feedback/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-message-feedback", "description": "The Web feedback surface: per-message Like/Dislike in the assistant-message action strip and the feedback dialog behind both ratings and /feedback, backed by the messageFeedback and sessionFeedback Host Remotes", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-model-selection/README.i18n.yaml b/packages/client/ui-model-selection/README.i18n.yaml index f9e88d76d0..fc05dbc20d 100644 --- a/packages/client/ui-model-selection/README.i18n.yaml +++ b/packages/client/ui-model-selection/README.i18n.yaml @@ -20,6 +20,9 @@ /deepseek-ai-dsh-client-ui-model-selection/use-this-package/model-and-effort: en: 48f8157ffdfe6e69 zh: 64ec9a533e2aa8f4 +/deepseek-ai-dsh-client-ui-model-selection/use-this-package/model-label-extensions: + en: bc47871cf8b61451 + zh: 17d21da46604f071 /deepseek-ai-dsh-client-ui-model-selection/use-this-package/unroutable-sessions: en: 7f20428c747ff052 zh: 975e6de728e02fa0 diff --git a/packages/client/ui-model-selection/README.md b/packages/client/ui-model-selection/README.md index a32460cdb9..5c181ca2ac 100644 --- a/packages/client/ui-model-selection/README.md +++ b/packages/client/ui-model-selection/README.md @@ -45,6 +45,14 @@ Both entries group models by provider, with DeepSeek Account first and DeepSeek The composer replaces the model and effort text with the Models icon when the expanded controls cannot share one line, and restores the text when space permits. The full selection remains available in the trigger's accessible name, tooltip, and menu. +### Model label extensions + +`conversation.input.model.label` is a session-scoped, inline `single` slot for the composer trigger (`placement: 'trigger'`), the root menu's current value (`'menu'`), and each model option (`'option'`). Its owner props provide `provider: { id, name }`, the exact catalog `model`, `placement`, and `selected`. Provider names are localized display names; match channels by `provider.id`, and identify a model by the provider/model ID pair. Connection URLs and credentials are not passed to the slot. + +Contributions must render non-interactive inline content, such as a colored name or badge. The selector owns clicks, search, focus, disabled states, check marks, reasoning effort, and trigger accessibility text. An absent contribution, including after plugin disposal, displays the catalog name. The trigger and root-menu value use the slot only when the selected model is present in the displayed catalog; otherwise they keep the built-in placeholder. During refresh, retained catalog entries continue to use the slot. Open menus track label size changes to stay inside the viewport. `/model` command rows use the command popup's separate rendering API. + +Import this package's `/client` declarations type-only, then register through `ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({ name: 'conversation.input.model.label' }, ModelLabel))`. Derive `ModelLabel` props from `PropsRuntime<'conversation.input.model.label'>`; use provider IDs to choose CSS Module classes and retain `model.name` as the text. Registration follows the declaration and contributing plugin lifetimes. Without a component replacement, styles can target the enclosing label's `data-provider-id` and `data-model-id` attributes in all three placements. + ### Unroutable sessions Catalog availability does not block sending with a saved selection; request execution reports missing credentials or unavailable models. Refreshes and refresh failures retain the last displayed selection and groups. A Host reset clears that display. Sign-out hides the account provider from the picker while preserving the saved provider/model ID and reasoning effort. Signing in restores the catalog name when that model is available again. Existing session logs remain unchanged. diff --git a/packages/client/ui-model-selection/README.zh.md b/packages/client/ui-model-selection/README.zh.md index 21b13ec2d5..9755602564 100644 --- a/packages/client/ui-model-selection/README.zh.md +++ b/packages/client/ui-model-selection/README.zh.md @@ -45,6 +45,14 @@ composer 菜单中的模型名称和推理等级均使用 400(regular)字重 展开的控件无法排在同一行时,composer 将模型与推理强度文字替换为模型图标;空间足够后恢复文字。触发器的无障碍名称、提示和菜单仍提供完整选择。 +### 模型名称扩展 + +`conversation.input.model.label` 是会话作用域的内联 `single` slot,覆盖 composer 触发按钮(`placement: 'trigger'`)、根菜单中的当前值(`'menu'`)和各模型选项(`'option'`)。owner props 提供 `provider: { id, name }`、准确的目录 `model`、`placement` 和 `selected`。提供方名称是本地化显示名称;按 `provider.id` 匹配渠道,按提供方与模型 ID 的组合识别模型。slot 不接收连接 URL 或凭据。 + +扩展必须渲染非交互的内联内容,例如带颜色的名称或徽标。选择器负责点击、搜索、焦点、禁用状态、勾选标记、推理强度和触发按钮的无障碍文字。没有扩展(包括插件卸载后)时显示目录名称。触发按钮和根菜单当前值仅在所选模型存在于显示目录中时调用 slot,否则保留内置占位文字。刷新期间保留的目录条目仍调用 slot。菜单打开时会跟随名称内容的尺寸变化调整位置,保持在视口内。`/model` 命令行使用命令弹窗独立的渲染 API。 + +以 type-only 方式导入本包的 `/client` 声明,再通过 `ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({ name: 'conversation.input.model.label' }, ModelLabel))` 注册。`ModelLabel` 的 props 从 `PropsRuntime<'conversation.input.model.label'>` 派生;用提供方 ID 选择 CSS Module 类,并保留 `model.name` 作为文字。注册随声明和贡献插件的生命周期清理。不替换组件时,样式也可以在三个显示位置通过名称外层的 `data-provider-id` 和 `data-model-id` 属性定位。 + ### 不可路由的会话 目录可用性不会阻止使用已保存选择发送消息;请求执行负责报告凭据缺失或模型不可用。刷新及刷新失败期间保留上次显示的选择和分组。Host 重置时清空这些显示。退登后选择器隐藏账号提供方,同时保留已保存的提供方/模型 ID 和推理强度。再次登录且该模型可用时恢复目录名称。既有会话日志保持不变。 diff --git a/packages/client/ui-model-selection/package.json b/packages/client/ui-model-selection/package.json index cf4cc6bbac..88b60b035a 100644 --- a/packages/client/ui-model-selection/package.json +++ b/packages/client/ui-model-selection/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-model-selection", "description": "Model selection over the shared model catalog, Session projection, and session.selectModel", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-model-selection/src/client/ModelSelect.tsx b/packages/client/ui-model-selection/src/client/ModelSelect.tsx index 8d37dab5e3..c497bcbe77 100644 --- a/packages/client/ui-model-selection/src/client/ModelSelect.tsx +++ b/packages/client/ui-model-selection/src/client/ModelSelect.tsx @@ -37,8 +37,8 @@ import { IconCheckOutlineRegular, IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, IconCloseFillRegular, IconDataOutlineRegular, IconWarningOutlineRegular, Input, rankByName, StateDot, Toast, } from '@deepseek-ai/dsh-client-ui-primitives' -import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots' -import type { ModelSelectInjected } from './slots.ts' +import type { PropsLocale, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' +import type { ModelLabelOwner, ModelSelectInjected } from './slots.ts' import css from './ModelSelect.module.css' import { orderModelProviders } from './provider-order.ts' @@ -62,8 +62,9 @@ const MEASURE_STYLE: CSSProperties = { visibility: 'hidden', left: 0, top: 0 } * @returns the trigger and, while open, the two-level menu. */ export function ModelSelect( - { locked, available, directory, load, select, t }: - ModelSelectInjected & { locked: boolean } & PropsLocale<'model'>, + { locked, available, directory, load, select, t, renderSlot }: + ModelSelectInjected & Pick, 'locked'> & PropsLocale<'model'> + & Omit, 'SessionProvider'>, ) { const state = useSyncExternalStore( fn => directory.subscribe(fn), @@ -90,7 +91,14 @@ export function ModelSelect( const itemRefs = useRef<(HTMLButtonElement | null)[]>([]) const id = useId() - const groups = useMemo(() => orderModelProviders(state.groups), [state.groups]) + const accountProviderName = t('provider.account') + const groups = useMemo(() => orderModelProviders(state.groups).map(group => ({ + ...group, + provider: { + id: group.id, + name: group.id === 'deepseek-account' ? accountProviderName : group.name, + }, + })), [state.groups, accountProviderName]) const choices = useMemo(() => groups.flatMap(group => group.models.map(model => ({ group, @@ -205,15 +213,10 @@ export function ModelSelect( } }, [open, pane, activeModelIndex, visibleModels]) - // Portaled placement (the Menu primitive's portal rules: fixed from the - // anchor rect, measured before paint, clamped inside the viewport): above - // the trigger, right edges aligned. Depends on pane and directory state - // because pane switches and async catalog loads resize the card. - /* jscpd:ignore-start -- deliberate mirror of ui-primitives useAnchoredPosition: - that hook only places from the anchor's LEFT edge, while this card aligns - right edges (x = rect.right - width), so the measure-and-clamp plumbing repeats. */ + // The portal needs viewport coordinates from the trigger and the menu's + // measured size before paint. Slot content can resize without parent renders. useLayoutEffect(() => { - if (!open) { setMenuPos(null); return } + if (!available || !open) { setMenuPos(null); return } const place = (): void => { /* v8 ignore next 2 -- the trigger ref is attached whenever the menu is open. */ const rect = triggerRef.current?.getBoundingClientRect() @@ -227,17 +230,20 @@ export function ModelSelect( if (lh > 0) y = Math.min(Math.max(y, MARGIN), window.innerHeight - lh - MARGIN) setMenuPos({ left: x, top: y }) } - // First run measures the hidden pre-render (same commit as `open`), so - // the card lands placed before anything paints. place() window.addEventListener('scroll', place, true) window.addEventListener('resize', place) + const menu = menuRef.current + const observer = typeof ResizeObserver !== 'undefined' && menu !== null + ? new ResizeObserver(place) + : null + if (menu !== null) observer?.observe(menu) return () => { + observer?.disconnect() window.removeEventListener('scroll', place, true) window.removeEventListener('resize', place) } - }, [open, pane, state, query]) - /* jscpd:ignore-end */ + }, [available, open, pane, state, query]) if (!available) return null @@ -426,7 +432,16 @@ export function ModelSelect( : effortLabel === undefined ? t('trigger.aria', { model: modelLabel }) : t('trigger.ariaEffort', { model: modelLabel, effort: effortLabel }) - itemRefs.current = [] + const renderModelLabel = ( + provider: ModelLabelOwner['provider'], model: ModelLabelOwner['model'], + placement: ModelLabelOwner['placement'], selected: boolean, + ) => ( + + {renderSlot('conversation.input.model.label', { + provider, model, placement, selected, + }, { fallback: model.name })} + + ) let itemIndex = 0 let modelIndex = 0 const itemRef = () => { @@ -467,7 +482,9 @@ export function ModelSelect( }} > - {modelLabel} + + {currentChoice === undefined ? modelLabel : renderModelLabel(currentChoice.group.provider, currentChoice.model, 'trigger', true)} + {effortLabel !== undefined && {effortLabel}} {busy ? @@ -491,7 +508,9 @@ export function ModelSelect( <> {reasoning !== undefined && ( @@ -560,7 +579,7 @@ export function ModelSelect( > {filteredGroups.map((group) => { return ( - + {group.models.map((model) => { const index = modelIndex++ const selected = state.current?.provider === group.id && state.current.model === model.id @@ -587,7 +606,7 @@ export function ModelSelect( onClick={() => { choose({ provider: group.id, model: model.id }) }} > - {model.name} + {renderModelLabel(group.provider, model, 'option', selected)} {pending?.provider === group.id && pending.model === model.id diff --git a/packages/client/ui-model-selection/src/client/index.ts b/packages/client/ui-model-selection/src/client/index.ts index ca30921493..b298d2ba95 100644 --- a/packages/client/ui-model-selection/src/client/index.ts +++ b/packages/client/ui-model-selection/src/client/index.ts @@ -33,7 +33,7 @@ import { orderModelProviders } from './provider-order.ts' export { ModelDirectory } from './directory.ts' export type { ModelDirectoryState } from './directory.ts' export { ModelDirectoryResolver } from './service.ts' -export type { ModelSelectInjected } from './slots.ts' +export type { ModelLabelOwner, ModelSelectInjected } from './slots.ts' export type { ModelKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { @@ -172,6 +172,9 @@ export function apply(ctx: ClientContext): void { scope.slots.inject('conversation.input.model', () => scope.slots.register({ name: 'conversation.input.model', locale: NS, + children: { + 'conversation.input.model.label': { kind: 'single', scope: 'session', inline: true }, + }, inject: (sessionId): ModelSelectInjected => { const directory = models.directoryFor(sessionId) const available = sessions.subagentAddress(sessionId) === undefined diff --git a/packages/client/ui-model-selection/src/client/slots.ts b/packages/client/ui-model-selection/src/client/slots.ts index c3350214fc..47e1be2b0c 100644 --- a/packages/client/ui-model-selection/src/client/slots.ts +++ b/packages/client/ui-model-selection/src/client/slots.ts @@ -1,14 +1,47 @@ -/** - * ModelSelect's injected face. The target 'conversation.input.model' seat is - * declared (children table) and typed by ui-conversation's composer-bar - * entry; this package only contributes the single occupant, so no SlotMap - * merge lives here. - */ +/** Composer model selection injection and inline label extension. */ import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol' import type { ModelSelection } from '@deepseek-ai/dsh-api-remotes/client' +import type { ModelCatalogModel, ModelProviderGroup } from '@deepseek-ai/dsh-api-session-controller/types' import type { SnapshotStore } from '@deepseek-ai/dsh-client-store' import type { ModelDirectoryState } from './directory.ts' +/** Catalog data for a model name inside the composer's existing controls. */ +export interface ModelLabelOwner { + /** Provider identity and localized display name; contains no connection configuration. */ + provider: Pick + /** The exact provider's model catalog entry. */ + model: ModelCatalogModel + /** Trigger caption, root-menu current value, or selectable model row. */ + placement: 'trigger' | 'menu' | 'option' + /** Whether this provider/model pair is the current selection. */ + selected: boolean +} + +declare module '@deepseek-ai/dsh-client-ui-slots' { + interface SlotMap { + /** + * Non-interactive inline model label; selection controls remain owner-managed. + * Without a contribution, the composer displays the catalog name. + * @example + * return { + * inject: ['slots'], + * apply(ctx) { + * ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register( + * { name: 'conversation.input.model.label' }, + * ({ provider, model }) => React.createElement('span', { title: provider.name }, model.name), + * )) + * }, + * } + */ + 'conversation.input.model.label': { + kind: 'single' + scope: 'session' + inline: true + owner: ModelLabelOwner + } + } +} + /** Injected business face of the composer model seat. */ export interface ModelSelectInjected { /** Whether this session supports Agent-bound model inspection and selection. */ diff --git a/packages/client/ui-model-selection/tests/model-select.client.spec.tsx b/packages/client/ui-model-selection/tests/model-select.client.spec.tsx index b077cc9093..54c606f636 100644 --- a/packages/client/ui-model-selection/tests/model-select.client.spec.tsx +++ b/packages/client/ui-model-selection/tests/model-select.client.spec.tsx @@ -1,7 +1,7 @@ // @vitest-environment jsdom import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, onTestFinished, vi } from 'vitest' -import { RemoteError } from '@deepseek-ai/dsh-client-test-runtime' +import { RemoteError, SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime' import { SessionId } from '@deepseek-ai/dsh-session/types' import type { ModelSelection } from '@deepseek-ai/dsh-api-remotes/client' import { createSnapshotStore } from '@deepseek-ai/dsh-client-store' @@ -22,6 +22,8 @@ const t: ComponentProps['t'] = (key, params) => { : template.replace(/\{(\w+)\}/g, (match, name: string) => name in params ? String(params[name]) : match) } +const renderLabel: ComponentProps['renderSlot'] = (_name, _owner, options) => options?.fallback ?? null + const reasoning = { efforts: [ { id: 'off', name: 'Off' }, @@ -76,6 +78,76 @@ beforeEach(() => { afterEach(cleanup) +describe('ModelSelect label slot', () => { + it('distinguishes identical model names and ids by provider across all placements and restores labels on unload', async () => { + const runtime = await SlotTestRuntime.create() + onTestFinished(() => runtime.dispose()) + const sessionId = SessionId('model-label') + await runtime.sessions.add({ id: sessionId }) + const session = runtime.sessions.retainFor(runtime.ctx, sessionId) + const groups = ['trusted', 'external'].map(id => ({ + id, name: `${id} gateway`, models: [{ id: 'same-model', name: 'Same model' }], + })) + const directory = createSnapshotStore(state({ groups, current: { provider: 'trusted', model: 'same-model' } })) + const select = vi.fn(async (selection: ModelSelection) => { + directory.update((snapshot) => { snapshot.current = selection }) + return { ok: true as const, value: undefined } + }) + await runtime.root.declare({ + 'conversation.input.model.label': { kind: 'single', scope: 'session', inline: true }, + }, ({ renderSlot, SessionProvider }) => + + ) + runtime.renderRoot() + const trigger = screen.getByRole('button', { name: '选择模型,当前 Same model' }) + expect(trigger.textContent).toBe('Same model') + const plugin = await runtime.mount({ inject: ['slots'], apply(ctx) { + ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({ + name: 'conversation.input.model.label', + }, ({ provider, model, placement, selected }) => {provider.id}: {model.name})) + } }) + expect(screen.getByTestId('trigger-trusted').textContent).toBe('trusted: Same model') + expect(screen.getByTestId('trigger-trusted').title).toBe('trusted gateway') + expect(trigger.querySelector('div')).toBeNull() + fireEvent.click(trigger) + expect(screen.getByTestId('menu-trusted').textContent).toBe('trusted: Same model') + fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ })) + expect(screen.getByTestId('option-trusted').getAttribute('data-selected')).toBe('true') + expect(screen.getByTestId('option-external').getAttribute('data-selected')).toBe('false') + const external = screen.getByRole('menuitemradio', { name: 'external: Same model' }) + expect(external.querySelector('[data-provider-id="external"][data-model-id="same-model"]')).toBeTruthy() + expect(external.querySelector('div')).toBeNull() + fireEvent.keyDown(screen.getByRole('menuitemradio', { name: 'trusted: Same model' }), { key: 'ArrowDown' }) + expect(document.activeElement).toBe(external) + fireEvent.keyDown(external, { key: 'Tab' }) + await waitFor(() => { expect(screen.getByTestId('trigger-external')).toBeTruthy() }) + expect(select).toHaveBeenCalledWith({ provider: 'external', model: 'same-model' }) + await waitFor(() => { expect(document.activeElement).toBe(trigger) }) + await act(async () => { await plugin.dispose() }) + expect(runtime.slots.entries('conversation.input.model.label')).toHaveLength(0) + expect(trigger.textContent).toBe('Same model') + expect(trigger.querySelector('[data-provider-id="external"][data-model-id="same-model"]')).toBeTruthy() + fireEvent.click(trigger) + fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ })) + expect(screen.getAllByRole('menuitemradio').map(row => row.textContent)).toEqual(['Same model', 'Same model']) + }) + + it('does not decorate loading or missing selections with a stale provider', () => { + const directory = createSnapshotStore(state({ groups: [], status: 'loading' })) + const renderSlot = vi.fn(() => null) + render() + expect(renderSlot).not.toHaveBeenCalled() + expect(document.querySelector('[data-provider-id]')).toBeNull() + act(() => { directory.set(state({ groups: [] })) }) + expect(screen.getByRole('button', { name: '请选择模型' })).toBeTruthy() + expect(renderSlot).not.toHaveBeenCalled() + }) +}) + describe('ModelSelect reasoning effort', () => { it('renders effort names without descriptions and submits the effort as part of the session selection', async () => { const directory = createSnapshotStore(state()) @@ -83,7 +155,7 @@ describe('ModelSelect reasoning effort', () => { directory.set(state({ current: selection })) return { ok: true as const, value: undefined } }) - render( { }], current: { provider: 'provider', model: 'model' }, })) - render( { current: { provider: 'deepseek-official', model: 'removed-model' }, })) const select = vi.fn().mockResolvedValue({ ok: true, value: undefined }) - render( { it.each(['model', 'provider'])('hides the saved id and effort when the selected %s disappears', (removed) => { const directory = createSnapshotStore(state({ retainedEffort: 'High' })) - render() + render() expect(screen.getByRole('button', { name: /选择模型,当前/ }).textContent).toContain('DeepSeek-V4-Flash') act(() => { directory.update((snapshot) => { snapshot.groups = removed === 'provider' ? [] : snapshot.groups.map(group => ({ ...group, models: [] })) @@ -195,7 +267,7 @@ describe('ModelSelect reasoning effort', () => { groups: [], status: 'loading', })) - render( { directory.set(state({ groups, status: 'error', error: 'unrelated catalog refresh' })) return { ok: false as const, error } }) - render( { } }) }) - render() + render() const spinners = () => document.querySelectorAll('[data-state="ongoing"]') const trigger = screen.getByRole('button', { name: /选择模型|当前/ }) @@ -307,7 +379,7 @@ describe('ModelSelect reasoning effort', () => { directory.set(state({ status: 'selecting', pending: selection })) return new Promise(() => {}) }) - render() + render() fireEvent.click(screen.getByRole('button', { name: /选择模型|当前/ })) fireEvent.click(screen.getByRole('menuitem', { name: /推理等级/ })) @@ -323,7 +395,7 @@ describe('ModelSelect reasoning effort', () => { Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { configurable: true, get: () => 200 }) Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, get: () => 300 }) try { - const { container } = render( { it('renders no Agent-bound control for an addressed subagent session', () => { const load = vi.fn() - render( { describe('ModelSelect keyboard walk', () => { function mountOpen() { const select = vi.fn().mockResolvedValue({ ok: true, value: undefined }) - render( { }) it('Tab with the keyboard still on the trigger enters the menu at the value in use', () => { - render( { const directory = createSnapshotStore(state({ groups: [], failures: [], status: 'error', error: 'catalog down', })) - render( { it('focuses the first row when no model is checked in a small catalog', () => { // The session runs a model the catalog no longer lists: no row is checked. - render( { it.each([0, 1, 4, 5])('shows search only above four models (%i models)', (count) => { const groups = modelGroups(count) const current = { provider: 'deepseek-official', model: count > 1 ? 'model-2' : 'deepseek-v4-flash' } - render() const trigger = screen.getByRole('button', { name: /选择模型/ }) fireEvent.click(trigger) @@ -593,7 +665,7 @@ describe('ModelSelect catalog size', () => { }) it.each([0, 1, 4, 5])('opens an unselected %i-model catalog on search or its first row', (count) => { - render() const trigger = screen.getByRole('button', { name: '请选择模型' }) @@ -608,7 +680,7 @@ describe('ModelSelect catalog size', () => { it.each([true, false])('clears search and restores focus across 5 → 4 → 5 models (checked: %s)', (checked) => { const current = { provider: 'deepseek-official', model: checked ? 'model-3' : 'removed' } const directory = createSnapshotStore(state({ groups: modelGroups(5), current })) - render() + render() fireEvent.click(screen.getByRole('button', { name: /选择模型/ })) if (checked) fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ })) const search = screen.getByRole('searchbox') @@ -635,7 +707,7 @@ describe('ModelSelect catalog size', () => { it('moves actual row focus with arrows, leaves Enter native, and selects with Tab in a small catalog', async () => { const select = vi.fn().mockResolvedValue({ ok: true, value: undefined }) - render() const trigger = screen.getByRole('button', { name: /选择模型/ }) @@ -667,7 +739,7 @@ describe('ModelSelect catalog size', () => { it('keeps Tab selection available after hovering a small-catalog row', async () => { const select = vi.fn().mockResolvedValue({ ok: true, value: undefined }) - render() fireEvent.click(screen.getByRole('button', { name: /选择模型/ })) fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ })) @@ -682,7 +754,7 @@ describe('ModelSelect catalog size', () => { it.each(['Escape', 'Tab'])('leaves a small model pane with %s and returns to its root cell', (key) => { const select = vi.fn() - render() fireEvent.click(screen.getByRole('button', { name: /选择模型/ })) fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ })) @@ -698,7 +770,7 @@ describe('ModelSelect catalog size', () => { describe('ModelSelect search', () => { it('clears search when reopening an unselected large catalog', () => { - render() const trigger = screen.getByRole('button', { name: '请选择模型' }) @@ -718,7 +790,7 @@ describe('ModelSelect search', () => { const directory = createSnapshotStore(state({ groups: [ ...state().groups, { id: 'empty', name: 'Empty Provider', models: [] }, ] })) - render() + render() fireEvent.click(screen.getByRole('button', { name: /选择模型/ })) fireEvent.click(screen.getByRole('menuitem', { name: /模型/ })) expect(screen.queryByRole('group', { name: 'Empty Provider' })).toBeNull() @@ -731,7 +803,7 @@ describe('ModelSelect search', () => { const select = vi.fn() render(<> - ) const trigger = screen.getByRole('button', { name: /选择模型/ }) @@ -758,7 +830,7 @@ describe('ModelSelect search', () => { ] }, ], })) - render() + render() fireEvent.click(screen.getByRole('button', { name: /选择模型/ })) fireEvent.click(screen.getByRole('menuitem', { name: /模型/ })) const search = screen.getByRole('searchbox') @@ -810,7 +882,7 @@ describe('ModelSelect search', () => { { id: 'other', name: 'Other', models: [{ id: 'gemini', name: 'Gemini Flash' }] }, ] })) const select = vi.fn().mockResolvedValue({ ok: true, value: undefined }) - render() + render() const trigger = screen.getByRole('button', { name: /选择模型/ }) fireEvent.click(trigger) fireEvent.click(screen.getByRole('menuitem', { name: /模型/ })) @@ -856,7 +928,7 @@ describe('ModelSelect search', () => { it('hides the inherited effort until a listed model is selected', async () => { const directory = createSnapshotStore(state({ current: null, routable: false, retainedEffort: 'High' })) - render() + render() const trigger = screen.getByRole('button', { name: '请选择模型' }) expect(trigger.hasAttribute('disabled')).toBe(false) await expect(`${trigger.textContent}\n`).toMatchFileSnapshot('./expected/unselected-model.txt') @@ -880,7 +952,7 @@ it('places account and official models before third-party models', async () => { id, name: id, models: [1, 2].map(index => ({ id: `${id}-${index}`, name: `${id}-${index}` })), })) const directory = createSnapshotStore(state({ current: null, groups })) - render() + render() fireEvent.click(screen.getByRole('button', { name: '请选择模型' })) const names = screen.getAllByRole('menuitemradio').map(row => row.textContent) expect(names).toEqual([ @@ -896,7 +968,7 @@ it.each([en, zh])('localizes the account group while preserving external names', id, name: id === 'deepseek-account' ? 'DeepSeek Account' : 'My Gateway', models: [{ id: 'model', name: 'Model' }], })) - render( key in copy ? copy[key as keyof typeof copy] : key} />) fireEvent.click(screen.getByRole('button', { name: copy['trigger.selectAria'] })) @@ -910,7 +982,7 @@ it('restores the account model name after login without changing the saved route ] }] const selected = { provider: 'deepseek-account', model: 'deepseek-flash', reasoningEffort: 'high' } const directory = createSnapshotStore(state({ current: selected, groups, retainedEffort: 'High' })) - render() + render() expect(screen.getByRole('button', { name: /选择模型,当前/ }).textContent).toBe('DeepSeek FlashHigh') act(() => { directory.update((snapshot) => { snapshot.groups = []; snapshot.routable = false }) }) expect(screen.getByRole('button', { name: '请选择模型' }).textContent).toBe('请选择模型') diff --git a/packages/client/ui-open-in-app/package.json b/packages/client/ui-open-in-app/package.json index d60f650542..001694f121 100644 --- a/packages/client/ui-open-in-app/package.json +++ b/packages/client/ui-open-in-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-open-in-app", "description": "Web \"Open In...\" controls: the Session-header split button opening the workspace directory in an installed application, and the document preview's default-application controls for one file", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-permission-presets/package.json b/packages/client/ui-permission-presets/package.json index 2da0aa5520..b5219742e9 100644 --- a/packages/client/ui-permission-presets/package.json +++ b/packages/client/ui-permission-presets/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-permission-presets", "description": "Permission surfaces: a new-session default in General settings and a current-session /permission popup over the permissions projection", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-plan/package.json b/packages/client/ui-plan/package.json index f17c979ef2..98cb6b5d55 100644 --- a/packages/client/ui-plan/package.json +++ b/packages/client/ui-plan/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-plan", "description": "Plan mode controls, persistent transcript plan cards, and sidebar Markdown previews", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-plugin-manager/package.json b/packages/client/ui-plugin-manager/package.json index 08cb28d792..0dd38c5125 100644 --- a/packages/client/ui-plugin-manager/package.json +++ b/packages/client/ui-plugin-manager/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-plugin-manager", "description": "Plugin management for the dsh web client: the sidebar Plugins panel installs, enables, disables, retries, and composes installed plugin packages", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-plugin-market/package.json b/packages/client/ui-plugin-market/package.json index 4b0faffe5b..5042e318cb 100644 --- a/packages/client/ui-plugin-market/package.json +++ b/packages/client/ui-plugin-market/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-plugin-market", "description": "Discover tab of the dsh web Plugins page: browse configured plugin market indexes and install their bundles", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-primitives/package.json b/packages/client/ui-primitives/package.json index 20938d74c7..3195dd34fa 100644 --- a/packages/client/ui-primitives/package.json +++ b/packages/client/ui-primitives/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-primitives", "description": "Pure React atoms for the dsh web UI: controls, icons, markdown, and JSON inspectors (zero cordis)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-reference/package.json b/packages/client/ui-reference/package.json index 613dbbbb68..ad8b2287a6 100644 --- a/packages/client/ui-reference/package.json +++ b/packages/client/ui-reference/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-reference", "description": "Unified Web @file and @session reference source", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-renderer/package.json b/packages/client/ui-renderer/package.json index 9804270cc4..e05712763a 100644 --- a/packages/client/ui-renderer/package.json +++ b/packages/client/ui-renderer/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-renderer", "description": "Browser UI renderer: React slot bindings, ctx.uiRenderer, and the assembled application root", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-schedule/package.json b/packages/client/ui-schedule/package.json index 9093cf049a..e2bed1c82d 100644 --- a/packages/client/ui-schedule/package.json +++ b/packages/client/ui-schedule/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-schedule", "description": "Host task management page and Session reminder catalog", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "type": "module", "main": "lib/index.js", "types": "lib/types/index.d.ts", diff --git a/packages/client/ui-secret-mask/package.json b/packages/client/ui-secret-mask/package.json index 572cc78f84..a4984c9dfa 100644 --- a/packages/client/ui-secret-mask/package.json +++ b/packages/client/ui-secret-mask/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-secret-mask", "description": "Web display masking for recognized credentials in sent user text", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-session/package.json b/packages/client/ui-session/package.json index 805a0911eb..3e2730af04 100644 --- a/packages/client/ui-session/package.json +++ b/packages/client/ui-session/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-session", "description": "Session Controller adapter for React and session-scoped slots", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-account/README.i18n.yaml b/packages/client/ui-settings-account/README.i18n.yaml index 049c12e594..e62a128435 100644 --- a/packages/client/ui-settings-account/README.i18n.yaml +++ b/packages/client/ui-settings-account/README.i18n.yaml @@ -15,8 +15,8 @@ en: c26f81e8adf0128b zh: e11ab371f51f66bc /deepseek-ai-dsh-client-ui-settings-account/use-this-package: - en: a0e4b5bbef33ca6d - zh: 697978822aeddf6e + en: 45aa7b44d5424d9b + zh: 20c7d344c4e9bce0 /deepseek-ai-dsh-client-ui-settings-account/use-this-package/desktop-onboarding: en: c1c4db71fd09ca13 zh: a40c0a01e9951945 diff --git a/packages/client/ui-settings-account/README.md b/packages/client/ui-settings-account/README.md index 382667cb68..d79f8ac0f8 100644 --- a/packages/client/ui-settings-account/README.md +++ b/packages/client/ui-settings-account/README.md @@ -52,7 +52,7 @@ The account card’s More account information link has no underline and opens th Sign out first queries running account-token tasks and opens a confirmation dialog. The warning describes interruption when such tasks exist; otherwise it explains that data is retained and the account can be signed in again. Cancel, close, and Escape dismiss without signing out. Failed impact queries still open confirmation with an explicit unknown-task warning; failed sign-out keeps the dialog available for retry. -A granted bonus appears as a server-authored notice above the sidebar account launcher without taking focus. The client reads the unnotified bonus when an account becomes active and once per Settings entry; it never polls. The server orders the candidates and owns the copy, so the client displays the first one and renders its message verbatim. +A granted bonus appears as a server-authored notice above the sidebar account launcher without taking focus. The client reads the unnotified bonus when an account becomes active and once per Settings entry; it never polls. The server orders the candidates and owns the copy, so the client stacks every live notice in that order and renders its message verbatim. Only the foreground card is interactive and reports display for acknowledgement. Closing it promotes the next card with a 250ms ease-out position and scale transition, including changes in content height; reduced-motion preferences disable the transition. A server-supplied title replaces the localized bonus title; responses without title retain that default. The notice waits until Desktop onboarding no longer covers the workspace, including loading, completion saving, and the exit animation, and until model credential onboarding unmounts. Closing the application during onboarding leaves the bonus unacknowledged. In the main interface, the notice is painted as soon as the server offers it, including underneath the translucent settings panel, once the document is visible and the card has passed one presented frame. That painted frame is the whole display signal: the card measures no cover, so a card behind the open overlay counts exactly as any other, and closing the card counts as a display too. Displaying a card queues its acknowledgement, so one mounted card is acknowledged once. A failed acknowledgement retries with a capped backoff for the rest of the signed-in session and never surfaces as an account UI error. A read that fails leaves the visible card and its pending acknowledgement alone, so a transport reconnect keeps retrying. Nothing is written to browser storage. The second signed-in frame of one sign-in and a reconnected stream's replayed state are one session, not a new one, so they keep the card and its retries; signing out, replacing the credential with another account's, or unloading the plugin discards them. Identity-only updates do not reach the account UI stream. Every signed-in frame reads again, and a newer read supersedes one still in flight, so a credential swapped in place is named by a fresh read rather than inheriting the replaced account's; the batch's account id then drops the previous account's card and queue even with no intervening signed-out state. The next sign-in displays whatever the server still offers, including an order an earlier sign-in acknowledged, and a card the user has already seen stays up until it is closed, even when a later read returns no bonus. diff --git a/packages/client/ui-settings-account/README.zh.md b/packages/client/ui-settings-account/README.zh.md index 4d92e6fcf8..8a02f26ee3 100644 --- a/packages/client/ui-settings-account/README.zh.md +++ b/packages/client/ui-settings-account/README.zh.md @@ -52,7 +52,7 @@ Chat 通过 `shell.overlay` 中的宿主把已绑定 Session 的实时欠费交 退出登录先查询正在使用账号 token 的任务,再打开确认弹窗。有此类任务时提示执行将被打断,否则提示数据保留且可重新登录。取消、关闭和 Escape 均不退登。影响查询失败仍打开确认弹窗,并明确提示无法确认任务状态;退登失败保留弹窗以便重试。 -赠金到账时,侧栏账号入口上方显示一条由服务端提供文案的通知,不抢占焦点。客户端仅在账号进入登录态时、以及每次进入设置页时读取一次未通知赠金,不做轮询。候选顺序与文案均由服务端决定,客户端只展示第一条并原样渲染其消息。 +赠金到账时,侧栏账号入口上方显示一条由服务端提供文案的通知,不抢占焦点。客户端仅在账号进入登录态时、以及每次进入设置页时读取一次未通知赠金,不做轮询。候选顺序与文案均由服务端决定,客户端按该顺序层叠展示全部未过期通知并原样渲染消息。只有最前面的卡片可交互,并在实际展示后触发确认。关闭前卡后,下一张卡片通过 250ms ease-out 的位置与缩放过渡(包括内容高度变化)进入前台;偏好减少动态效果时禁用过渡。服务端提供的 title 替换本地化赠金标题;未返回 title 时沿用默认标题。 通知等待 Desktop 引导界面不再遮盖主界面且模型凭证引导卸载后才展示,Desktop 引导的等待范围包括加载、完成时的保存和退出动画。在引导期间关闭应用不会确认赠金通知。进入主界面后,服务端返回即绘制通知,包括在设置面板的半透明蒙层之下;同时要求文档可见且卡片已完成一帧绘制。这一帧绘制就是全部的展示信号:卡片不检测遮挡,因此处于打开蒙层之下的卡片与其他情形同样计数,用户关闭卡片同样计入展示。卡片展示后即排队确认,因此同一张挂载的卡只确认一次。确认失败会在本次登录会话内按带上限的退避重试,且不会在账号界面报错。读取失败不会影响已展示的卡片及其待确认队列,因此连接重连后仍会继续重试。不写入浏览器存储。同一次登录的第二个登录态帧以及重连后重放的登录态属于同一会话而非新会话,因此保留卡片与重试;只有退出登录、凭证被换成另一账号,或卸载插件才会丢弃。仅身份信息更新不会进入账号界面订阅流。每个登录态帧都会重新读取,且新的读取会取代仍在进行中的旧读取,因此就地更换的凭证会由一次全新的读取来识别,而不会沿用被替换账号的读取结果;读取结果中的账号 ID 随后会丢弃上一个账号的卡片与队列,即使没有中间的退出登录状态。下一次登录会展示服务端仍然下发的内容,包括上一次登录已确认过的订单;用户已看到的卡片会保留到关闭为止,即使之后的读取没有返回赠金。 diff --git a/packages/client/ui-settings-account/package.json b/packages/client/ui-settings-account/package.json index a79538e709..370e8d572a 100644 --- a/packages/client/ui-settings-account/package.json +++ b/packages/client/ui-settings-account/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-account", "description": "Manage DeepSeek login and open Platform billing pages", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-account/src/client/AccountMenu.tsx b/packages/client/ui-settings-account/src/client/AccountMenu.tsx index cae2e1169b..f2274e3058 100644 --- a/packages/client/ui-settings-account/src/client/AccountMenu.tsx +++ b/packages/client/ui-settings-account/src/client/AccountMenu.tsx @@ -9,7 +9,7 @@ import { SignOutDialog } from './SignOutDialog.tsx' import { SignInDialog } from './SignInDialog.tsx' import { LogoutIcon } from './LogoutIcon.tsx' import { AccountAvatar } from './AccountAvatar.tsx' -import { AccountNoticeCard } from './AccountNotice.tsx' +import { AccountNoticeStack } from './AccountNotice.tsx' import css from './AccountMenu.module.css' /** Account launcher composed by the settings shell. */ @@ -57,8 +57,8 @@ export function AccountMenu({ return
{signInNotice > 0 && { setSignInNotice(0) }} />} {expiryNotice && { setExpiryNotice(false) }} />} - {signedIn && !account.desktopOnboardingVisible && !account.onboarding && account.notice && } Date.now() @@ -44,29 +46,26 @@ function afterPaint(run: () => void): () => void { } } -/** @param props - notice copy and identity, sidebar anchor, and display callbacks. - * @returns a non-modal notice above the account launcher until closed. +/** @param props - server-ordered notices, localized fallback copy, anchor, and display callbacks. + * @returns a non-modal stack whose foreground card alone can report display or be closed. */ -export function AccountNoticeCard({ notice, anchor, title, closeLabel, onShown, onDismiss }: { - notice: BonusNotice +export function AccountNoticeStack({ notices, anchor, title, closeLabel, onShown, onDismiss }: { + notices: readonly BonusNotice[] anchor: RefObject title: string closeLabel: string - /** Called once, after the visible card has passed a presented frame. */ + /** Called after the foreground card has passed a visible presented frame. */ onShown: (orderId: BonusNotice['orderId']) => void - /** Called when the user closes the card, which counts as having seen it. */ + /** Called when the user closes the foreground card. */ onDismiss: (orderId: BonusNotice['orderId']) => void }) { const [position, setPosition] = useState<{ left: number; bottom: number }>() - const reported = useRef(false) useLayoutEffect(() => { - if (!anchor.current || !live(notice.expiresAt)) return + if (!anchor.current) return const element = anchor.current const update = () => { const rect = element.getBoundingClientRect() - // The launcher row starts one 10px inset inside the sidebar, which is the - // card's fixed left inset; the width itself does not follow the sidebar. - setPosition({ left: Math.max(8, Math.min(rect.left, window.innerWidth - 268)), + setPosition({ left: Math.max(8, Math.min(rect.left, window.innerWidth - 288)), bottom: Math.max(8, window.innerHeight - rect.top + 4) }) } update() @@ -79,9 +78,29 @@ export function AccountNoticeCard({ notice, anchor, title, closeLabel, onShown, window.removeEventListener('resize', update) window.removeEventListener('scroll', update, true) } - }, [anchor, notice.orderId, notice.expiresAt]) + }, [anchor]) + const liveNotices = notices.filter(notice => live(notice.expiresAt)) + return createPortal(
+ {liveNotices.map((notice, depth) => )} +
, document.body) +} + +/** One stable card node remains mounted as the cards before it are dismissed. */ +function NoticeCard({ notice, depth, ready, title, closeLabel, onShown, onDismiss }: { + notice: BonusNotice + depth: number + ready: boolean + title: string + closeLabel: string + onShown: (orderId: BonusNotice['orderId']) => void + onDismiss: (orderId: BonusNotice['orderId']) => void +}) { + const foreground = depth === 0 + const reported = useRef(false) useEffect(() => { - if (position === undefined || reported.current || !live(notice.expiresAt)) return + if (!ready || !foreground || reported.current || !live(notice.expiresAt)) return let cancel: (() => void) | undefined const attempt = () => { if (reported.current || cancel !== undefined || !live(notice.expiresAt)) return @@ -99,11 +118,9 @@ export function AccountNoticeCard({ notice, anchor, title, closeLabel, onShown, document.removeEventListener('visibilitychange', attempt) cancel?.() } - }, [position, notice.orderId, notice.expiresAt, onShown]) - // Rendering nothing keeps an expired notice off screen without disturbing the - // hook order above. - if (!live(notice.expiresAt)) return null - return createPortal( } diff --git a/packages/client/ui-settings-account/src/client/AccountSection.tsx b/packages/client/ui-settings-account/src/client/AccountSection.tsx index 35db5a39dc..e65252994e 100644 --- a/packages/client/ui-settings-account/src/client/AccountSection.tsx +++ b/packages/client/ui-settings-account/src/client/AccountSection.tsx @@ -15,8 +15,8 @@ import css from './AccountSection.module.css' /** Safe account snapshot shared by the settings page and launcher. */ export interface AccountSnapshot { - /** Server-authored bonus notice awaiting display, absent when none is available. */ - notice?: BonusNotice + /** Server-authored notices in front-to-back order, absent when none are available. */ + notices?: readonly BonusNotice[] /** Latest Host state, absent until the stream responds. */ view: AccountView | undefined diff --git a/packages/client/ui-settings-account/src/client/bonus-notices.ts b/packages/client/ui-settings-account/src/client/bonus-notices.ts index 0795539055..e19f740b49 100644 --- a/packages/client/ui-settings-account/src/client/bonus-notices.ts +++ b/packages/client/ui-settings-account/src/client/bonus-notices.ts @@ -1,10 +1,10 @@ /** - * Lifecycle of bonus notices: one displayed notice at a time, one read result in + * Lifecycle of bonus notices: one foreground card in a stack, one read result in * force at a time, and one acknowledgement per displayed card. Reads happen when * the account becomes active and when the user asks for a refresh, never on a * timer; the server owns which bonus is unnotified and what its copy says. * - * An order is acknowledged once its card reports a presented frame: the card has + * An order is acknowledged once its foreground card reports a presented frame: the card has * a position, the document is visible, and it painted one frame. The card measures * no cover, so a card painted under the open Settings overlay reports like any * other, and closing the card counts as a display too. @@ -18,16 +18,18 @@ * @module @deepseek-ai/dsh-client-ui-settings-account/src/client/bonus-notices */ import type { - AccountBonusBatch, AccountBonusNotification, AccountBonusOrderId, AccountUserId, + AccountBonusBatch, AccountBonusOrderId, AccountUserId, } from '@deepseek-ai/dsh-deepseek-account/types' /** Notice eligible for display; the message is server-authored for the active locale. */ export interface BonusNotice { + /** Server-localized plain-text title; absent responses use the localized bonus title. */ + readonly title?: string /** Server order identity. */ readonly orderId: AccountBonusOrderId /** Server-localized plain text, including any award amount or expiration. */ readonly message: string - /** Server expiration, re-checked at display time so a notice cannot appear after its award expired. */ + /** Server expiration, re-checked at display time; an empty string means no expiration. */ readonly expiresAt: string } @@ -50,8 +52,8 @@ export interface BonusNoticeControllerOptions extends BonusNoticeTiming { * @returns false when the account changed or signed out; a protocol failure rejects. */ acknowledge: (accountId: AccountUserId, orderId: AccountBonusOrderId) => Promise - /** @param notice - notice to display, or null when none is shown. */ - publish: (notice: BonusNotice | null) => void + /** @param notices - notices in front-to-back order; empty when none are shown. */ + publish: (notices: readonly BonusNotice[]) => void } /** Commands the account plugin issues to the bonus notice lifecycle. */ @@ -88,6 +90,7 @@ interface NoticeScope { * @returns whether the bonus can no longer be shown. */ function expired(expiresAt: string, at: number): boolean { + if (expiresAt === '') return false const expires = Date.parse(expiresAt) // An unparsable timestamp is not evidence of expiry, so the notice still shows. return !Number.isNaN(expires) && expires <= at @@ -101,7 +104,7 @@ function expired(expiresAt: string, at: number): boolean { export function createBonusNoticeController(options: BonusNoticeControllerOptions): BonusNoticeController { let scope: NoticeScope | undefined /** Notice currently published to the sidebar, including the expiration checked at display time. */ - let current: BonusNotice | undefined + let current: readonly BonusNotice[] = [] /** Newest read; an older read still in flight has its result dropped once a newer one starts. */ let readGeneration = 0 let ackTimer: ReturnType | undefined @@ -110,14 +113,16 @@ export function createBonusNoticeController(options: BonusNoticeControllerOption /** Orders the card on screen already reported, so one mount cannot acknowledge twice. */ const reported = new Set() const clearAckTimer = (): void => { if (ackTimer !== undefined) { clearTimeout(ackTimer); ackTimer = undefined } } - const setCurrent = (notice: BonusNotice | undefined): void => { - if (notice === undefined && current === undefined) return - if (notice?.orderId === current?.orderId && notice?.message === current?.message - && notice?.expiresAt === current?.expiresAt) return - // A different order means a different card, so its report is a fresh one. - if (notice?.orderId !== current?.orderId) reported.clear() - current = notice - options.publish(notice ?? null) + const setCurrent = (notices: readonly BonusNotice[]): void => { + if (notices.length === current.length && notices.every((notice, index) => { + const previous = current[index] + return notice.orderId === previous?.orderId && notice.message === previous.message + && notice.title === previous.title && notice.expiresAt === previous.expiresAt + })) return + const retained = new Set(notices.map(notice => notice.orderId)) + for (const orderId of reported) if (!retained.has(orderId)) reported.delete(orderId) + current = notices + options.publish(notices) } const scheduleAck = (delay: number): void => { clearAckTimer() @@ -132,9 +137,8 @@ export function createBonusNoticeController(options: BonusNoticeControllerOption const startAcks = (): void => { if (scope !== undefined && scope.pending.length > 0) scheduleAck(0) } /** - * Apply one read result. A newly offered order replaces the on-screen card; - * an offer equal to the on-screen card refreshes its copy in place, and an - * empty batch leaves a card the user has already seen alone. + * Apply the server-ordered stack, excluding expired notices. An empty batch + * retains only the foreground card if the user has already seen it. */ const apply = (scopeAtRead: NoticeScope, batch: AccountBonusBatch): void => { if (scopeAtRead.accountId !== batch.accountId) { @@ -143,22 +147,16 @@ export function createBonusNoticeController(options: BonusNoticeControllerOption scopeAtRead.accountId = batch.accountId scopeAtRead.pending.length = 0 resetAcks() - setCurrent(undefined) + setCurrent([]) } - const candidate: AccountBonusNotification | undefined = batch.bonuses[0] - if (candidate === undefined) { - // No candidate: a card the user has already seen stays until it is closed, - // while a candidate that never reached a presented frame is withdrawn. - if (current !== undefined && !reported.has(current.orderId)) setCurrent(undefined) + const candidates = batch.bonuses.filter(notice => !expired(notice.expiresAt, Date.now())) + if (candidates.length === 0) { + // Keep an already presented foreground card until dismissal; unseen cards are withdrawn. + const foreground = current[0] + setCurrent(foreground !== undefined && reported.has(foreground.orderId) ? [foreground] : []) return } - if (candidate.orderId === current?.orderId) { - // Same order still on screen: refresh its copy, and validate expiration at display time. - setCurrent({ orderId: candidate.orderId, message: candidate.message, expiresAt: candidate.expiresAt }) - return - } - if (expired(candidate.expiresAt, Date.now())) return - setCurrent({ orderId: candidate.orderId, message: candidate.message, expiresAt: candidate.expiresAt }) + setCurrent(candidates) } /** @@ -225,7 +223,7 @@ export function createBonusNoticeController(options: BonusNoticeControllerOption /** Queue a presented order, so an acknowledgement interrupted by a failure retries within this sign-in. */ const report = (orderId: AccountBonusOrderId): void => { const scopeAtReport = scope - if (scopeAtReport === undefined || current?.orderId !== orderId || reported.has(orderId)) return + if (scopeAtReport === undefined || current[0]?.orderId !== orderId || reported.has(orderId)) return reported.add(orderId) if (!scopeAtReport.pending.includes(orderId)) scopeAtReport.pending.push(orderId) ackDelay = options.ackRetryDelayMs @@ -238,7 +236,7 @@ export function createBonusNoticeController(options: BonusNoticeControllerOption resetAcks() scope = undefined reported.clear() - setCurrent(undefined) + setCurrent([]) } return { @@ -256,15 +254,16 @@ export function createBonusNoticeController(options: BonusNoticeControllerOption return runRead(scope) }, shown(orderId) { - if (current?.orderId !== orderId) return - if (expired(current.expiresAt, Date.now())) return + setCurrent(current.filter(notice => !expired(notice.expiresAt, Date.now()))) + if (current[0]?.orderId !== orderId) return report(orderId) }, dismiss(orderId) { - if (current?.orderId !== orderId) return + setCurrent(current.filter(notice => !expired(notice.expiresAt, Date.now()))) + if (current[0]?.orderId !== orderId) return // Closing the card is the user seeing it, so queue its acknowledgement before withdrawing. report(orderId) - setCurrent(undefined) + setCurrent(current.slice(1).filter(notice => !expired(notice.expiresAt, Date.now()))) }, } } diff --git a/packages/client/ui-settings-account/src/client/index.ts b/packages/client/ui-settings-account/src/client/index.ts index 890b61311b..7668d46116 100644 --- a/packages/client/ui-settings-account/src/client/index.ts +++ b/packages/client/ui-settings-account/src/client/index.ts @@ -78,10 +78,9 @@ export function apply(ctx: Context): void { if (!result.ok) throw new Error('account bonus acknowledgement failed') return result.value }, - publish: (notice) => { - const { notice: _replaced, ...withoutNotice } = snapshot - // exactOptionalPropertyTypes distinguishes an absent notice from an undefined one. - publish(notice === null ? withoutNotice : { ...withoutNotice, notice }) + publish: (notices) => { + const { notices: _replaced, ...withoutNotices } = snapshot + publish(notices.length === 0 ? withoutNotices : { ...withoutNotices, notices }) }, }) ctx.effect(() => () => { notices.end() }, 'account: bonus notice lifetime') @@ -129,9 +128,9 @@ export function apply(ctx: Context): void { avatar.reset() const initialize = frame.value.status === 'credential-stored' && frame.value.attempt?.phase === 'succeeded' && snapshot.view?.attempt?.phase !== 'succeeded' - const { notice, ...previous } = snapshot + const { notices: previousNotices, ...previous } = snapshot publish({ ...previous, view: frame.value, details: undefined, avatar: undefined, failed: false, - ...(frame.value.status === 'credential-stored' && notice ? { notice } : {}) }) + ...(frame.value.status === 'credential-stored' && previousNotices ? { notices: previousNotices } : {}) }) browser.update(frame.value) frame.accept() if (frame.value.status === 'credential-stored') notices.begin() diff --git a/packages/client/ui-settings-account/src/client/notice-transition.ts b/packages/client/ui-settings-account/src/client/notice-transition.ts new file mode 100644 index 0000000000..80c2c008ee --- /dev/null +++ b/packages/client/ui-settings-account/src/client/notice-transition.ts @@ -0,0 +1,34 @@ +/** Animate a notification promotion across the old and new card dimensions. */ +import { flushSync } from 'react-dom' + +/** + * Close a card and animate surviving siblings from their currently displayed rectangles. + * @param card - mounted foreground card whose direct parent owns the stack. + * @param dismiss - synchronous notification-store update for the close gesture. + */ +export function dismissNotice(card: HTMLElement, dismiss: () => void): void { + if (typeof card.animate !== 'function' || getComputedStyle(card).transitionDuration === '0s') { + dismiss() + return + } + const stack = card.parentElement as HTMLElement + const before = [...stack.querySelectorAll('aside')] + .map(element => ({ element, rect: element.getBoundingClientRect() })) + for (const { element } of before) { + for (const animation of element.getAnimations()) animation.cancel() + element.style.transition = 'none' + } + flushSync(dismiss) + for (const { element, rect } of before) { + if (!element.isConnected) continue + const transform = getComputedStyle(element).transform + element.style.transform = 'none' + const next = element.getBoundingClientRect() + element.style.transform = '' + element.animate([ + { transform: `translate(${rect.x + rect.width / 2 - next.x - next.width / 2}px, ${rect.y - next.y}px) scale(${rect.width / next.width}, ${rect.height / next.height})` }, + { transform }, + ], { duration: 250, easing: 'ease-out' }) + element.style.transition = '' + } +} diff --git a/packages/client/ui-settings-account/tests/account.client.spec.tsx b/packages/client/ui-settings-account/tests/account.client.spec.tsx index 310bd3aac2..e6c72c0989 100644 --- a/packages/client/ui-settings-account/tests/account.client.spec.tsx +++ b/packages/client/ui-settings-account/tests/account.client.spec.tsx @@ -612,7 +612,7 @@ it('paints the bonus notice under the open settings panel and reports its displa let snapshot: AccountSnapshot = operations.hooks.account.getSnapshot() const view = render(await accountMenu(operations, () => snapshot, true)) expect(screen.queryByRole('status')).toBeNull() - snapshot = { ...snapshot, notice } + snapshot = { ...snapshot, notices: [notice] } view.rerender(await accountMenu(operations, () => snapshot, true)) // The settings overlay is translucent, so the card paints underneath it instead of waiting for the exit. await vi.waitFor(() => { expect(screen.getByRole('status').textContent).toBe(`${en.bonusNoticeTitle}${notice.message}`) }) @@ -717,7 +717,7 @@ it.each(['desktopOnboardingVisible', 'onboarding'] as const)('defers bonus displ vi.stubGlobal('ResizeObserver', class { observe() {} disconnect() {} }) const operations = operationsOf({ status: 'credential-stored', attempt: null }) const notice = { orderId: 'order-onboarding' as BonusNotice['orderId'], message: 'Awarded 5.00', expiresAt: '2099-01-01T00:00:00Z' } - let snapshot: AccountSnapshot = { ...operations.hooks.account.getSnapshot(), notice, [field]: true } + let snapshot: AccountSnapshot = { ...operations.hooks.account.getSnapshot(), notices: [notice], [field]: true } const view = render(await accountMenu(operations, () => snapshot, false)) expect(screen.queryByText(notice.message)).toBeNull() expect(operations.bonusNoticeShown).not.toHaveBeenCalled() diff --git a/packages/client/ui-settings-account/tests/apply.client.spec.ts b/packages/client/ui-settings-account/tests/apply.client.spec.ts index bff9c03b3e..0146bbfa4a 100644 --- a/packages/client/ui-settings-account/tests/apply.client.spec.ts +++ b/packages/client/ui-settings-account/tests/apply.client.spec.ts @@ -35,8 +35,7 @@ function bonus(orderId: string, message = 'Awarded 5.00'): AccountBonusBatch { return { accountId: 'account-user' as AccountUserId, bonuses: [{ - orderId: orderId as AccountBonusOrderId, campaign: 'dsh_login_bonus', amount: '5.00', currency: 'CNY', - grantedAt: '2026-09-21T12:00:00Z', expiresAt: '2099-01-01T00:00:00Z', message, + orderId: orderId as AccountBonusOrderId, expiresAt: '2099-01-01T00:00:00Z', message, }], } } @@ -526,7 +525,7 @@ it('reads the unnotified bonus in the active locale and acknowledges only after c.mock.remote.account.ackBonusNotified.mockResolvedValue(ok(true)) c.mock.streams.push('account/watch', stored) await vi.waitFor(() => { - expect(actions.hooks.account.getSnapshot().notice).toMatchObject({ orderId, message: 'Awarded 5.00' }) + expect(actions.hooks.account.getSnapshot().notices?.[0]).toMatchObject({ orderId, message: 'Awarded 5.00' }) }) expect(c.mock.remote.account.getUnnotifiedBonuses).toHaveBeenCalledWith(expect.objectContaining({ locale: 'en' })) // A successful read is not a display, so nothing may be acknowledged yet. @@ -536,7 +535,7 @@ it('reads the unnotified bonus in the active locale and acknowledges only after expect(c.mock.remote.account.ackBonusNotified).toHaveBeenCalledWith('account-user', orderId, expect.objectContaining({ locale: 'en' })) }) actions.bonusNoticeDismissed(orderId as AccountBonusOrderId) - expect(actions.hooks.account.getSnapshot().notice).toBeUndefined() + expect(actions.hooks.account.getSnapshot().notices?.[0]).toBeUndefined() }, 60_000) it('keeps the notice and its acknowledgement retry across repeated signed-in frames', async ({ start }) => { @@ -548,7 +547,7 @@ it('keeps the notice and its acknowledgement retry across repeated signed-in fra c.mock.remote.account.getUnnotifiedBonuses.mockResolvedValue(ok(bonus(orderId))) c.mock.remote.account.ackBonusNotified.mockResolvedValue({ ok: false, error: new RemoteError('gateway/internal', 'offline', {}) }) c.mock.streams.push('account/watch', stored) - await vi.waitFor(() => { expect(actions.hooks.account.getSnapshot().notice).toMatchObject({ orderId }) }) + await vi.waitFor(() => { expect(actions.hooks.account.getSnapshot().notices?.[0]).toMatchObject({ orderId }) }) actions.bonusNoticeShown(orderId as AccountBonusOrderId) await vi.waitFor(() => { expect(c.mock.remote.account.ackBonusNotified.mock.calls.length).toBeGreaterThan(0) }) // One sign-in commits the credential and then updates the attempt; a reconnected @@ -558,7 +557,7 @@ it('keeps the notice and its acknowledgement retry across repeated signed-in fra await vi.waitFor(() => { expect(actions.hooks.account.getSnapshot().view).toEqual(stored) }) // The displayed card stays, no second read replaces it, and the failed // acknowledgement keeps backing off instead of stopping until sign-out. - expect(actions.hooks.account.getSnapshot().notice).toMatchObject({ orderId }) + expect(actions.hooks.account.getSnapshot().notices?.[0]).toMatchObject({ orderId }) expect(c.mock.remote.account.getUnnotifiedBonuses).toHaveBeenCalledOnce() await vi.waitFor(() => { expect(c.mock.remote.account.ackBonusNotified.mock.calls.length).toBeGreaterThan(1) }, { timeout: 30_000 }) }, 60_000) @@ -573,12 +572,12 @@ it('keeps the notice absent when the bonus read is refused, then shows the next }) c.mock.streams.push('account/watch', stored) await vi.waitFor(() => { expect(c.mock.remote.account.getUnnotifiedBonuses).toHaveBeenCalledOnce() }) - expect(actions.hooks.account.getSnapshot().notice).toBeUndefined() + expect(actions.hooks.account.getSnapshot().notices?.[0]).toBeUndefined() expect(c.mock.remote.account.ackBonusNotified).not.toHaveBeenCalled() c.mock.remote.account.getUnnotifiedBonuses.mockResolvedValue(ok(bonus(orderId))) await actions.refreshAccount() await vi.waitFor(() => { - expect(actions.hooks.account.getSnapshot().notice).toMatchObject({ orderId, message: 'Awarded 5.00' }) + expect(actions.hooks.account.getSnapshot().notices?.[0]).toMatchObject({ orderId, message: 'Awarded 5.00' }) }) }, 60_000) @@ -589,11 +588,11 @@ it('drops the previous account notice and stops reading after sign-out', async ( c.mock.remote.account.getUnnotifiedBonuses.mockResolvedValue(ok(bonus('4c1b0000-0000-4000-8000-000000000001'))) c.mock.streams.push('account/watch', stored) await vi.waitFor(() => { - expect(actions.hooks.account.getSnapshot().notice?.message).toBe('Awarded 5.00') + expect(actions.hooks.account.getSnapshot().notices?.[0]?.message).toBe('Awarded 5.00') }) c.mock.streams.push('account/watch', view) await vi.waitFor(() => { expect(actions.hooks.account.getSnapshot().view).toEqual(view) }) - expect(actions.hooks.account.getSnapshot().notice).toBeUndefined() + expect(actions.hooks.account.getSnapshot().notices?.[0]).toBeUndefined() // Signing out ends the lifecycle, so no further read follows. const reads = c.mock.remote.account.getUnnotifiedBonuses.mock.calls.length await new Promise((resolve) => { setTimeout(resolve, 20) }) diff --git a/packages/client/ui-settings-account/tests/bonus-notices.client.spec.ts b/packages/client/ui-settings-account/tests/bonus-notices.client.spec.ts index 160424c268..6ab5b50104 100644 --- a/packages/client/ui-settings-account/tests/bonus-notices.client.spec.ts +++ b/packages/client/ui-settings-account/tests/bonus-notices.client.spec.ts @@ -22,10 +22,6 @@ function batch(accountId: string, orderId: string, expiresAt = '2099-01-01T00:00 accountId: accountId as AccountUserId, bonuses: [{ orderId: orderId as AccountBonusOrderId, - campaign: 'dsh_login_bonus', - amount: '5.00', - currency: 'CNY', - grantedAt: '2026-09-21T12:00:00Z', expiresAt, message: 'Server copy 5.00', }], @@ -35,13 +31,14 @@ function batch(accountId: string, orderId: string, expiresAt = '2099-01-01T00:00 function setup() { const read = vi.fn(async (): Promise => null) const acknowledge = vi.fn(async (): Promise => true) + const stacks: (readonly BonusNotice[])[] = [] const published: (BonusNotice | null)[] = [] const controller = createBonusNoticeController({ - ...TIMING, read, acknowledge, publish: (notice) => { published.push(notice) }, + ...TIMING, read, acknowledge, publish: (notices) => { stacks.push(notices); published.push(notices[0] ?? null) }, }) /** @returns the most recently published notice. */ const latest = (): BonusNotice | null | undefined => published.at(-1) - return { controller, read, acknowledge, published, latest } + return { controller, read, acknowledge, published, latest, stacks } } afterEach(() => { vi.restoreAllMocks(); vi.useRealTimers() }) @@ -168,9 +165,9 @@ it('refreshes the copy of the order already on screen when a read offers it agai controller.begin() await vi.waitFor(() => { expect(latest()).toMatchObject({ orderId: 'order-1', message: 'Server copy 5.00' }) }) // The same order arrives with new copy: its card updates in place instead of being replaced. - read.mockResolvedValue({ ...batch('account-a', 'order-1'), bonuses: [{ ...batch('account-a', 'order-1').bonuses[0]!, message: 'Updated copy' }] }) + read.mockResolvedValue({ ...batch('account-a', 'order-1'), bonuses: [{ ...batch('account-a', 'order-1').bonuses[0]!, title: 'Updated title', message: 'Updated copy' }] }) await controller.refresh() - expect(latest()).toMatchObject({ orderId: 'order-1', message: 'Updated copy' }) + expect(latest()).toMatchObject({ orderId: 'order-1', title: 'Updated title', message: 'Updated copy' }) controller.end() }) @@ -524,3 +521,92 @@ it('does not record a display when the award expired before the card could rende expect(acknowledge).not.toHaveBeenCalled() controller.end() }) + +it('displays and acknowledges a notice without expiration', async () => { + const { controller, read, acknowledge, latest } = setup() + read.mockResolvedValue(batch('account-a', 'order-1', '')) + controller.begin() + await vi.waitFor(() => { expect(latest()).toMatchObject({ orderId: 'order-1', expiresAt: '' }) }) + controller.shown('order-1' as AccountBonusOrderId) + await vi.waitFor(() => { expect(acknowledge).toHaveBeenCalledWith('account-a', 'order-1') }) + controller.end() +}) + +it('publishes every live notice and only acknowledges the foreground as dismissals advance the stack', async () => { + const { controller, read, acknowledge, stacks } = setup() + const bonuses = ['first', 'second', 'third'].flatMap(id => batch('account-a', id, '').bonuses) + read.mockResolvedValue({ accountId: 'account-a' as AccountUserId, bonuses }) + controller.begin() + await vi.waitFor(() => { expect(stacks.at(-1)).toEqual(bonuses) }) + controller.shown('second' as AccountBonusOrderId) + controller.dismiss('third' as AccountBonusOrderId) + expect(acknowledge).not.toHaveBeenCalled() + expect(stacks.at(-1)).toEqual(bonuses) + controller.shown('first' as AccountBonusOrderId) + await vi.waitFor(() => { expect(acknowledge).toHaveBeenCalledExactlyOnceWith('account-a', 'first') }) + controller.dismiss('first' as AccountBonusOrderId) + expect(stacks.at(-1)).toEqual(bonuses.slice(1)) + expect(acknowledge).toHaveBeenCalledTimes(1) + controller.shown('second' as AccountBonusOrderId) + await vi.waitFor(() => { expect(acknowledge).toHaveBeenLastCalledWith('account-a', 'second') }) + controller.dismiss('second' as AccountBonusOrderId) + expect(stacks.at(-1)).toEqual(bonuses.slice(2)) + controller.dismiss('third' as AccountBonusOrderId) + expect(stacks.at(-1)).toEqual([]) + await vi.waitFor(() => { expect(acknowledge).toHaveBeenLastCalledWith('account-a', 'third') }) + controller.end() +}) + +it('drops queued notices that expire before promotion without acknowledging them', async () => { + const { controller, read, acknowledge, stacks } = setup() + read.mockResolvedValue({ accountId: 'account-a' as AccountUserId, bonuses: [ + ...batch('account-a', 'first', '').bonuses, + ...batch('account-a', 'expired', '2099-01-01T00:00:00Z').bonuses, + ...batch('account-a', 'last', '').bonuses, + ] }) + controller.begin() + await vi.waitFor(() => { expect(stacks.at(-1)).toHaveLength(3) }) + vi.spyOn(Date, 'now').mockReturnValue(Date.parse('2100-01-01T00:00:00Z')) + controller.dismiss('first' as AccountBonusOrderId) + expect(stacks.at(-1)?.map(notice => notice.orderId)).toEqual(['last']) + await vi.waitFor(() => { expect(acknowledge).toHaveBeenCalledExactlyOnceWith('account-a', 'first') }) + controller.end() +}) + +it('publishes a title-only refresh without acknowledging the same card twice', async () => { + const { controller, read, acknowledge, latest } = setup() + read.mockResolvedValue(batch('account-a', 'first', '')) + controller.begin() + await vi.waitFor(() => { expect(latest()?.orderId).toBe('first') }) + controller.shown('first' as AccountBonusOrderId) + await vi.waitFor(() => { expect(acknowledge).toHaveBeenCalledTimes(1) }) + read.mockResolvedValue({ accountId: 'account-a' as AccountUserId, + bonuses: batch('account-a', 'first', '').bonuses.map(notice => ({ ...notice, title: 'Updated title' })) }) + await controller.refresh() + expect(latest()?.title).toBe('Updated title') + controller.shown('first' as AccountBonusOrderId) + expect(acknowledge).toHaveBeenCalledTimes(1) + controller.end() +}) + +it('does not read or acknowledge when refreshed outside an active account lifecycle', async () => { + const { controller, read, acknowledge } = setup() + await controller.refresh() + expect(read).not.toHaveBeenCalled() + expect(acknowledge).not.toHaveBeenCalled() +}) + +it('closes the visible notice when the preceding card expires before its display callback', async () => { + const { controller, read, acknowledge, stacks } = setup() + read.mockResolvedValue({ accountId: 'account-a' as AccountUserId, bonuses: [ + ...batch('account-a', 'first', '2099-01-01T00:00:00Z').bonuses, + ...batch('account-a', 'second', '').bonuses, + ] }) + controller.begin() + await vi.waitFor(() => { expect(stacks.at(-1)).toHaveLength(2) }) + vi.spyOn(Date, 'now').mockReturnValue(Date.parse('2100-01-01T00:00:00Z')) + controller.dismiss('second' as AccountBonusOrderId) + expect(stacks.at(-1)).toEqual([]) + await vi.waitFor(() => { expect(acknowledge).toHaveBeenCalledExactlyOnceWith('account-a', 'second') }) + controller.end() +}) diff --git a/packages/client/ui-settings-account/tests/notice-transition.client.spec.ts b/packages/client/ui-settings-account/tests/notice-transition.client.spec.ts new file mode 100644 index 0000000000..0f69d1b0bf --- /dev/null +++ b/packages/client/ui-settings-account/tests/notice-transition.client.spec.ts @@ -0,0 +1,49 @@ +// @vitest-environment jsdom +/** Promotion preserves the displayed rectangle while the next card changes content height. */ +import { afterEach, expect, it, vi } from 'vitest' +import { dismissNotice } from '../src/client/notice-transition.ts' + +afterEach(() => { document.body.replaceChildren(); vi.restoreAllMocks() }) + +it.each(['unavailable', 'reduced motion'])('closes without animation when animation is %s', (mode) => { + const card = document.createElement('aside') + document.body.append(card) + const animate = vi.fn() + if (mode === 'reduced motion') { + card.animate = animate + card.style.transitionDuration = '0s' + } + const dismiss = vi.fn(() => { card.remove() }) + dismissNotice(card, dismiss) + expect(dismiss).toHaveBeenCalledOnce() + expect(animate).not.toHaveBeenCalled() +}) + +it('animates a surviving card from its old position and height and cancels an interrupted transition', () => { + const stack = document.createElement('div') + const first = document.createElement('aside') + const next = document.createElement('aside') + stack.append(first, next) + document.body.append(stack) + first.style.transitionDuration = '0.25s' + const animateFirst = vi.fn() + const animateNext = vi.fn() + first.animate = animateFirst + next.animate = animateNext + first.getAnimations = () => [] + const cancel = vi.fn() + const interrupted: Pick = { cancel } + next.getAnimations = () => [interrupted as Animation] + vi.spyOn(next, 'getBoundingClientRect') + .mockReturnValueOnce(new DOMRect(14, 196, 272, 100)) + .mockReturnValueOnce(new DOMRect(10, 100, 280, 200)) + next.style.transform = 'translateY(0px) scaleX(1)' + dismissNotice(first, () => { first.remove() }) + expect(cancel).toHaveBeenCalledOnce() + expect(animateFirst).not.toHaveBeenCalled() + expect(animateNext).toHaveBeenCalledWith([ + { transform: 'translate(0px, 96px) scale(0.9714285714285714, 0.5)' }, + { transform: 'translateY(0px) scaleX(1)' }, + ], { duration: 250, easing: 'ease-out' }) + expect(next.style.transition).toBe('') +}) diff --git a/packages/client/ui-settings-account/tests/notice.client.spec.tsx b/packages/client/ui-settings-account/tests/notice.client.spec.tsx index 9266591a69..5fbbfc3994 100644 --- a/packages/client/ui-settings-account/tests/notice.client.spec.tsx +++ b/packages/client/ui-settings-account/tests/notice.client.spec.tsx @@ -9,7 +9,7 @@ import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { act } from 'react' import { afterEach, beforeEach, expect, it, vi } from 'vitest' import type { AccountBonusOrderId } from '@deepseek-ai/dsh-deepseek-account/types' -import { AccountNoticeCard } from '../src/client/AccountNotice.tsx' +import { AccountNoticeStack } from '../src/client/AccountNotice.tsx' const disconnect = vi.fn() let visibility: 'visible' | 'hidden' = 'visible' @@ -36,8 +36,8 @@ function mount(id: string, onShown = vi.fn(), onDismiss = vi.fn()) { const anchor = createRef() const view = render(<>
- ) return { view, onShown, onDismiss } @@ -138,3 +138,44 @@ it.each([true, false])('cancels the presentation task when the card unmounts fir act(() => { vi.runAllTimers() }) expect(onShown).not.toHaveBeenCalled() }) + +it('keeps back cards mounted but inaccessible and reports them only after promotion', async () => { + vi.stubGlobal('ResizeObserver', class { observe() {} disconnect = disconnect }) + const anchor = createRef() + const onShown = vi.fn() + const onDismiss = vi.fn() + const notices = ['first', 'second', 'third'].map(id => ({ + orderId: id as AccountBonusOrderId, title: id, message: `${id} copy`, expiresAt: '', + })) + const tree = (remaining: typeof notices) => <> +
+ + + const view = render(tree(notices)) + const second = screen.getByText('second copy').closest('aside') + expect(document.querySelectorAll('aside')).toHaveLength(3) + expect(screen.getAllByRole('status')).toHaveLength(1) + expect(second?.querySelector('button')?.tabIndex).toBe(-1) + await presentFrames() + expect(onShown).toHaveBeenCalledExactlyOnceWith('first') + fireEvent.click(screen.getByRole('button', { name: 'Close' })) + expect(onDismiss).toHaveBeenCalledExactlyOnceWith('first') + view.rerender(tree(notices.slice(1))) + expect(screen.getByText('second copy').closest('aside')).toBe(second) + expect(second?.querySelector('button')?.tabIndex).toBe(0) + expect(onShown).toHaveBeenCalledTimes(1) + await presentFrames() + expect(onShown).toHaveBeenLastCalledWith('second') + expect(onShown).toHaveBeenCalledTimes(2) +}) + +it('does not acknowledge a notice before an anchor is mounted', async () => { + const onShown = vi.fn() + render(()} title="News" closeLabel="Close" + notices={[{ orderId: 'notice' as AccountBonusOrderId, message: 'News', expiresAt: '' }]} + onShown={onShown} onDismiss={vi.fn()} />) + await presentFrames() + expect(onShown).not.toHaveBeenCalled() + expect(screen.queryByRole('status')).toBeNull() +}) diff --git a/packages/client/ui-settings-agent-loop/package.json b/packages/client/ui-settings-agent-loop/package.json index b48efdc710..983d9d2a29 100644 --- a/packages/client/ui-settings-agent-loop/package.json +++ b/packages/client/ui-settings-agent-loop/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-agent-loop", "description": "Settings page of the agent loop on the dsh web client's Plugins page: the parallel tool-call cap of the agent-loop namespace", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-general/package.json b/packages/client/ui-settings-general/package.json index 425d28a799..93ed3dd071 100644 --- a/packages/client/ui-settings-general/package.json +++ b/packages/client/ui-settings-general/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-general", "description": "Settings ownerless-copy and product onboarding plugin: the General section, shell trigger/header chrome content, settings dictionaries, and the versioned welcome notice", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-models/package.json b/packages/client/ui-settings-models/package.json index 00401c684e..6ed28685ec 100644 --- a/packages/client/ui-settings-models/package.json +++ b/packages/client/ui-settings-models/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-models", "description": "Models settings and shared product-onboarding dialogs over existing settings and credential joins", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-plugin-inventory/package.json b/packages/client/ui-settings-plugin-inventory/package.json index 4c4fb57521..117863e622 100644 --- a/packages/client/ui-settings-plugin-inventory/package.json +++ b/packages/client/ui-settings-plugin-inventory/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-plugin-inventory", "description": "Read-only Cordis Loader inventory tab in Web Plugins settings", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-plugins/package.json b/packages/client/ui-settings-plugins/package.json index 8e497f5299..e3cd99cebf 100644 --- a/packages/client/ui-settings-plugins/package.json +++ b/packages/client/ui-settings-plugins/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-plugins", "description": "Built-in plugins settings section for the dsh web client: the Settings navigation entry and the tab chrome feature-owned tabs register into", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-session-log/package.json b/packages/client/ui-settings-session-log/package.json index b657db4479..d680d4a936 100644 --- a/packages/client/ui-settings-session-log/package.json +++ b/packages/client/ui-settings-session-log/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-session-log", "description": "General settings control for Session-log upload with DeepSeek API requests", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-shell/package.json b/packages/client/ui-settings-shell/package.json index ac7a8dcd6c..071aaaa71b 100644 --- a/packages/client/ui-settings-shell/package.json +++ b/packages/client/ui-settings-shell/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-shell", "description": "Settings page of the shell executor on the dsh web client's Plugins page: the command timeout and the per-stream output cap of the shell namespace", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-subagent/package.json b/packages/client/ui-settings-subagent/package.json index 121827acf6..9b5ec9a2b3 100644 --- a/packages/client/ui-settings-subagent/package.json +++ b/packages/client/ui-settings-subagent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-subagent", "description": "Settings page of Subagent delegation on the dsh web client's Plugins page: recursion depth, parallel capacity, and the models agents may choose for subagents", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-web-search/package.json b/packages/client/ui-settings-web-search/package.json index 9be0af668c..65f985de38 100644 --- a/packages/client/ui-settings-web-search/package.json +++ b/packages/client/ui-settings-web-search/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-web-search", "description": "Settings page of the DeepSeek web-search provider on the dsh web client's Plugins page: its API key, endpoint, and per-request search budget", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings/package.json b/packages/client/ui-settings/package.json index 83ac9cbf88..3921d7a715 100644 --- a/packages/client/ui-settings/package.json +++ b/packages/client/ui-settings/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings", "description": "Settings domain base plugin: shared configuration forms and the canonical settings slot-type contract", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-shortcuts/package.json b/packages/client/ui-shortcuts/package.json index 866036d5e1..be4cc31f65 100644 --- a/packages/client/ui-shortcuts/package.json +++ b/packages/client/ui-shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-shortcuts", "description": "Keyboard shortcut reference, recording, and local preference editing", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-browser/package.json b/packages/client/ui-sidebar-browser/package.json index 950dcbdcdb..f035568893 100644 --- a/packages/client/ui-sidebar-browser/package.json +++ b/packages/client/ui-sidebar-browser/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-browser", "description": "Sandboxed Web browser tabs for the right Sidebar", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-documentpreview/package.json b/packages/client/ui-sidebar-documentpreview/package.json index 8801ed4ec4..732f286f5b 100644 --- a/packages/client/ui-sidebar-documentpreview/package.json +++ b/packages/client/ui-sidebar-documentpreview/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-documentpreview", "description": "Extensible Sidebar previews for Office documents, spreadsheets, Markdown, code, images, PDF, HTML, and plain text", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-files/package.json b/packages/client/ui-sidebar-files/package.json index 45476245fd..c2eedc544d 100644 --- a/packages/client/ui-sidebar-files/package.json +++ b/packages/client/ui-sidebar-files/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-files", "description": "Workspace file tree tab type for the right Sidebar: lazy directory listing over the workspaceFiles Remote namespace, opening files into the Sidebar", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-right/package.json b/packages/client/ui-sidebar-right/package.json index 0212d4533a..b7fd54b910 100644 --- a/packages/client/ui-sidebar-right/package.json +++ b/packages/client/ui-sidebar-right/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-right", "description": "Right Sidebar: the docking surface's session-bound state, its panel and header expand control, and the navigation service over it", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-right/src/client/shell/SidebarRight.module.css b/packages/client/ui-sidebar-right/src/client/shell/SidebarRight.module.css index 88105b2e50..cde47579ee 100644 --- a/packages/client/ui-sidebar-right/src/client/shell/SidebarRight.module.css +++ b/packages/client/ui-sidebar-right/src/client/shell/SidebarRight.module.css @@ -38,11 +38,17 @@ display: flex; flex-direction: column; min-width: 0; + /* Hidden docked items sit past the frame's right edge. Unclipped, they widen + the frame's scrollable overflow, and a caret reveal (PageUp in a text field + outside macOS) scrolls the `overflow: hidden` frame sideways. `clip` keeps + that overflow here without making the panel a scroll container, and does + not clip fixed floating children, whose containing block is the viewport. */ + overflow: clip; pointer-events: none; } -/* Only docked Grid items slide. Their common ancestor remains untransformed, - unclipped and without a stacking context, so fixed floating children escape. */ +/* Only docked Grid items slide. Their common ancestor remains untransformed + and without a stacking context, so fixed floating children escape. */ .panel :global([data-dockkit-host='dock']), .panel :global([data-dockkit-empty]), .panel :global([data-dockkit-divider]) { diff --git a/packages/client/ui-sidebar-terminal/package.json b/packages/client/ui-sidebar-terminal/package.json index 355fe4b863..89715663d3 100644 --- a/packages/client/ui-sidebar-terminal/package.json +++ b/packages/client/ui-sidebar-terminal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-terminal", "description": "Interactive shell tabs for the right Sidebar", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar/package.json b/packages/client/ui-sidebar/package.json index 0f2cd8c211..92c241c3e2 100644 --- a/packages/client/ui-sidebar/package.json +++ b/packages/client/ui-sidebar/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar", "description": "Sidebar plugin: session multi-level tree, search, grouping, state dots", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-skill/package.json b/packages/client/ui-skill/package.json index 52239047c6..43f02435b9 100644 --- a/packages/client/ui-skill/package.json +++ b/packages/client/ui-skill/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-skill", "description": "Web skill references and the dedicated skill tool row", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-slots/package.json b/packages/client/ui-slots/package.json index 0ea200b9f7..138b623657 100644 --- a/packages/client/ui-slots/package.json +++ b/packages/client/ui-slots/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-slots", "description": "Slot registry pure core: typed ordinary Slots and reusable Component Factories, derived props, Store seats, and renderer installation", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-subagent/package.json b/packages/client/ui-subagent/package.json index fa3ae08ec0..8171118192 100644 --- a/packages/client/ui-subagent/package.json +++ b/packages/client/ui-subagent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-subagent", "description": "Subagent conversation catalog, continuation routing UI, and '@' reference source", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-theme/package.json b/packages/client/ui-theme/package.json index 6d8979785b..6ea119966d 100644 --- a/packages/client/ui-theme/package.json +++ b/packages/client/ui-theme/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-theme", "description": "Theme plugin: Host bootstrap for the pre-plugin palette; DOM-free ThemeRuntime for light/dark/system state; --dsw-* token styles and Appearance settings row", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-tool/package.json b/packages/client/ui-tool/package.json index 9db958772f..5fb8bd024a 100644 --- a/packages/client/ui-tool/package.json +++ b/packages/client/ui-tool/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-tool", "description": "Client Tool call-tree renderer and keyed per-tool presentation slot", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-trajectory/package.json b/packages/client/ui-trajectory/package.json index a46d92dd8d..acd9bbb0f1 100644 --- a/packages/client/ui-trajectory/package.json +++ b/packages/client/ui-trajectory/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-trajectory", "description": "Trajectory event ledger with an interactive timing overview: pure-consumer plugin registering into the conversation ViewMap (no service)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-user-questions/package.json b/packages/client/ui-user-questions/package.json index 130f136298..763557aa3b 100644 --- a/packages/client/ui-user-questions/package.json +++ b/packages/client/ui-user-questions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-user-questions", "description": "Web ask_user_question composer takeover and plan-review presentation UI", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-workflow-run/package.json b/packages/client/ui-workflow-run/package.json index 5cadc49d54..327c6d5b96 100644 --- a/packages/client/ui-workflow-run/package.json +++ b/packages/client/ui-workflow-run/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-workflow-run", "description": "Durable workflow-run Conversation Node and nested member disclosure for dsh web", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-workspace/package.json b/packages/client/ui-workspace/package.json index 412ad30690..c8aac50021 100644 --- a/packages/client/ui-workspace/package.json +++ b/packages/client/ui-workspace/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-workspace", "description": "Workspace picker plugin: one WorkspacePicker registered into the sidebar and empty-state workspace slots", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/web/package.json b/packages/client/web/package.json index eac9e5b8e3..8203dd8b17 100644 --- a/packages/client/web/package.json +++ b/packages/client/web/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-web", "description": "Web boot kernel: static module table, Cordis loader, framework-free boot page, and UI-renderer handoff", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/command-compact/package.json b/packages/compaction/command-compact/package.json index 157e6ffb83..8f5782c936 100644 --- a/packages/compaction/command-compact/package.json +++ b/packages/compaction/command-compact/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-command-compact", "description": "Human-facing slash command for explicit session compaction", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/compaction-basic/package.json b/packages/compaction/compaction-basic/package.json index b361f747cf..b6b5324b77 100644 --- a/packages/compaction/compaction-basic/package.json +++ b/packages/compaction/compaction-basic/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-compaction-basic", "description": "Token-meter-driven compaction policy and LLM summarization backend for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/compaction-image-offload/package.json b/packages/compaction/compaction-image-offload/package.json index 87ba09ad7c..39e5b5a9c2 100644 --- a/packages/compaction/compaction-image-offload/package.json +++ b/packages/compaction/compaction-image-offload/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-compaction-image-offload", "description": "Durable image offload for image-capable routes: replace over-budget request images with placeholders and retry", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/compaction-tool-result-pruner/package.json b/packages/compaction/compaction-tool-result-pruner/package.json index 6fd3cb6876..d581320cf8 100644 --- a/packages/compaction/compaction-tool-result-pruner/package.json +++ b/packages/compaction/compaction-tool-result-pruner/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-compaction-tool-result-pruner", "description": "Replay-safe model-free head/middle/tail pruning for tool-result surface nodes", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/compaction/package.json b/packages/compaction/compaction/package.json index 5938c69959..4fa417ff68 100644 --- a/packages/compaction/compaction/package.json +++ b/packages/compaction/compaction/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-compaction", "description": "Abstract compaction service seam (ctx.compaction) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/computer-use/computer-use/package.json b/packages/computer-use/computer-use/package.json index 41ea4fcca4..aec8a12115 100644 --- a/packages/computer-use/computer-use/package.json +++ b/packages/computer-use/computer-use/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-computer-use", "description": "Exclusive named computer-use provider registration", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/agent-instructions/package.json b/packages/context/agent-instructions/package.json index c451b67521..cc76ef0145 100644 --- a/packages/context/agent-instructions/package.json +++ b/packages/context/agent-instructions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-instructions", "description": "Workspace context loader for AGENTS.md/CLAUDE.md instruction files", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/file-reference-local/package.json b/packages/context/file-reference-local/package.json index e0a0cd0843..b766c4945b 100644 --- a/packages/context/file-reference-local/package.json +++ b/packages/context/file-reference-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-file-reference-local", "description": "Local-filesystem ctx.fileReferences provider with bounded fuzzy indexes", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/file-reference/package.json b/packages/context/file-reference/package.json index f620b96957..26d7ab65b4 100644 --- a/packages/context/file-reference/package.json +++ b/packages/context/file-reference/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-file-reference", "description": "File-reference discovery contract and shared @file grammar", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/harness-dev-context/package.json b/packages/context/harness-dev-context/package.json index 265570855b..8152c0da50 100644 --- a/packages/context/harness-dev-context/package.json +++ b/packages/context/harness-dev-context/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-harness-dev-context", "description": "Model context naming the DeepSeek Harness checkout and Web GUI URL without letting either change the system prompt", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/session-reference/package.json b/packages/context/session-reference/package.json index a63ae81927..fd58930f71 100644 --- a/packages/context/session-reference/package.json +++ b/packages/context/session-reference/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-reference", "description": "Cross-session snapshot references and durable untrusted model context (ctx.sessionReferenceResolver)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/time-context/package.json b/packages/context/time-context/package.json index a0411f7569..844ecf02a5 100644 --- a/packages/context/time-context/package.json +++ b/packages/context/time-context/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-time-context", "description": "Durable per-step context with the current time and elapsed time", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/tmux-context/package.json b/packages/context/tmux-context/package.json index edad348b00..f9d1696fdb 100644 --- a/packages/context/tmux-context/package.json +++ b/packages/context/tmux-context/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tmux-context", "description": "Opt-in durable per-step context with this agent's tmux pane and window location", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/agent-default-model/package.json b/packages/core/agent-default-model/package.json index 400182ddcc..04b2fccf4a 100644 --- a/packages/core/agent-default-model/package.json +++ b/packages/core/agent-default-model/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-default-model", "description": "Default model selection shared by Agent entry points", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/agent-loop/package.json b/packages/core/agent-loop/package.json index 6ddc5130ca..6d8224b5e0 100644 --- a/packages/core/agent-loop/package.json +++ b/packages/core/agent-loop/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-loop", "description": "The concrete agent loop plugin for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/agent-tool-presentation/package.json b/packages/core/agent-tool-presentation/package.json index ddcd12bf32..8542657f6f 100644 --- a/packages/core/agent-tool-presentation/package.json +++ b/packages/core/agent-tool-presentation/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-tool-presentation", "description": "Agent-plane presentation selector: composes one agent's tools as PTC mode or native tool calls", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/agent/package.json b/packages/core/agent/package.json index 33fc0e74d5..c049839b9e 100644 --- a/packages/core/agent/package.json +++ b/packages/core/agent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent", "description": "Agent interface, registry, initiator scope, and event vocabulary for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/scope/package.json b/packages/core/scope/package.json index e315ecb280..026bbf1b86 100644 --- a/packages/core/scope/package.json +++ b/packages/core/scope/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-scope", "description": "Scoped-context registration primitive (scope tags, scope-filtered event dispatch) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/session/package.json b/packages/core/session/package.json index a925ee3e40..e73d75bf94 100644 --- a/packages/core/session/package.json +++ b/packages/core/session/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session", "description": "Event-sourced session store for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/system-prompt/package.json b/packages/core/system-prompt/package.json index cf09031ac8..3570e27250 100644 --- a/packages/core/system-prompt/package.json +++ b/packages/core/system-prompt/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-system-prompt", "description": "System prompt assembly registry for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/tools/README.i18n.yaml b/packages/core/tools/README.i18n.yaml index 2817538d44..f90a284f26 100644 --- a/packages/core/tools/README.i18n.yaml +++ b/packages/core/tools/README.i18n.yaml @@ -72,8 +72,8 @@ en: eaefd7303f22977d zh: 9c425f9501128224 /deepseek-ai-dsh-tools/model-experience/ptc-mode-schema-and-system-prompt/what-the-model-sees: - en: 90467f091cbeb8a7 - zh: 6d7816a02c934f08 + en: 3e170a222d746e98 + zh: 1ea442fc59874cc5 /deepseek-ai-dsh-tools/model-experience/ptc-mode-schema-and-system-prompt/what-the-model-sees/typescript-ptc-mode-sdk-instructions-with-bash: en: 3a8df15e4ae32f9e zh: fc4a800ba363e835 diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index db88aad341..a5a7044916 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -179,16 +179,16 @@ Prefix-stable while visible definitions and their order are unchanged. Registrat #### What the model sees -PTC mode exposes the generated [`run_code` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tools), the SDK instructions below, and the generated exact SDK block for the loaded runtime's language. Both runtime languages list `description` before `code` and request that generation order in the shared parameter description; this is guidance, not an enforced JSON member order. The TypeScript instructions identify generated declarations as program-only bindings. When the current `bash` parameter schema accepts the example arguments, they also show a complete `run_code` call around `tools.bash(...)`. The `tools:sdk` section uses first-party order 5000 and disables prompt-variable interpolation, preserving literal `{{…}}` text in tool descriptions and schemas for both runtime languages. Under `ptc`, the prompt also carries the `tools:ptc-only` rule earlier in the first-party order, so the model reads which tools it may call before it reads what each one is for. +PTC mode exposes the generated [`run_code` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tools), the SDK instructions below, and the generated exact SDK block for the loaded runtime's language. Both runtime languages list `code` before `description`; this is presentation order, not an enforced JSON member order. The TypeScript instructions use a return-value example for function-body runtimes and a printed-output example for REPL runtimes. They identify generated declarations as program-only bindings. When the current `bash` parameter schema accepts the example arguments, they also show a complete `run_code` call around `tools.bash(...)`. The `tools:sdk` section uses first-party order 5000 and disables prompt-variable interpolation, preserving literal `{{…}}` text in tool descriptions and schemas for both runtime languages. Under `ptc`, the prompt also carries the `tools:ptc-only` rule earlier in the first-party order, so the model reads which tools it may call before it reads what each one is for. ##### TypeScript PTC mode SDK instructions with bash ```markdown ## Writing code for run_code -`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`: +`run_code` takes two required arguments: `code`, the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped), and `description`, a short summary of what the program does. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`: -`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })` +`run_code({ code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })", description: "Show current directory" })` Inside the program: diff --git a/packages/core/tools/README.zh.md b/packages/core/tools/README.zh.md index b8fe64c4f0..6316543088 100644 --- a/packages/core/tools/README.zh.md +++ b/packages/core/tools/README.zh.md @@ -179,16 +179,16 @@ ctx.tools.register(defineTool({ #### 模型看到什么 -PTC mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tools)、下方 SDK 说明,以及按所加载运行时语言生成的精确 SDK 块。两种运行时语言都将 `description` 列在 `code` 前,并在共用的参数描述中要求按此顺序生成;这是指引,不强制 JSON 成员顺序。TypeScript 说明会把生成声明明确标为只能在程序内使用的绑定。当当前 `bash` 参数 schema 接受示例参数时,说明还会给出以 `run_code` 包住 `tools.bash(...)` 的完整调用。`tools:sdk` 段使用 first-party 顺序 5000,并关闭提示词变量插值,使两种运行时语言都原样保留工具描述和 schema 中的 `{{…}}` 文本。在 `ptc` 下,提示词还会带上处于更早 first-party 顺序的 `tools:ptc-only` 规则,让模型先读到「可以调用哪些工具」再读「每个工具做什么」。 +PTC mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tools)、下方 SDK 说明,以及按所加载运行时语言生成的精确 SDK 块。两种运行时语言都将 `code` 列在 `description` 前;这是呈现顺序,不强制 JSON 成员顺序。TypeScript 说明对函数体运行时使用返回值示例,对 REPL 运行时使用打印输出示例,并把生成声明明确标为只能在程序内使用的绑定。当当前 `bash` 参数 schema 接受示例参数时,说明还会给出以 `run_code` 包住 `tools.bash(...)` 的完整调用。`tools:sdk` 段使用 first-party 顺序 5000,并关闭提示词变量插值,使两种运行时语言都原样保留工具描述和 schema 中的 `{{…}}` 文本。在 `ptc` 下,提示词还会带上处于更早 first-party 顺序的 `tools:ptc-only` 规则,让模型先读到「可以调用哪些工具」再读「每个工具做什么」。 ##### 带 bash 的 TypeScript PTC mode SDK 说明 ```markdown ## Writing code for run_code -`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`: +`run_code` takes two required arguments: `code`, the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped), and `description`, a short summary of what the program does. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`: -`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })` +`run_code({ code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })", description: "Show current directory" })` Inside the program: diff --git a/packages/core/tools/package.json b/packages/core/tools/package.json index c896879886..6f103ceb8e 100644 --- a/packages/core/tools/package.json +++ b/packages/core/tools/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tools", "description": "Tool registry and execution pipeline for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/tools/src/index.ts b/packages/core/tools/src/index.ts index 0970c7e2b0..ca939d4dfb 100644 --- a/packages/core/tools/src/index.ts +++ b/packages/core/tools/src/index.ts @@ -59,10 +59,10 @@ declare module '@deepseek-ai/dsh-llm' { */ const PTC_ONLY_INSTRUCTION = `\`${RUN_CODE_NAME}\` is the only tool you can call directly — a tool call naming any other tool fails. Reach every tool the SDK declares below from inside the program.` -const SDK_RENDERERS: Record string> = { +const SDK_RENDERERS: Record string> = { typescript: renderToolsSdk, python: renderToolsSdkPy, -} satisfies Record string> +} satisfies Record string> export { defineTool, @@ -911,7 +911,7 @@ export class ToolRuntime extends Service { const render = SDK_RENDERERS[runtime.language] /* v8 ignore next -- requirePtcRuntime rejects an unknown language before this runs. */ if (render === undefined) throw new Error(`dsh-tools: no SDK renderer for ${runtime.language}`) - return render(this.sdkSchemas(context.scope)) + return render(this.sdkSchemas(context.scope), runtime.programFormat) }, } } diff --git a/packages/core/tools/src/ptc.ts b/packages/core/tools/src/ptc.ts index d2ec751c10..b3d85428c1 100644 --- a/packages/core/tools/src/ptc.ts +++ b/packages/core/tools/src/ptc.ts @@ -47,17 +47,26 @@ interface RunCodeFlavor { } /** Keep tool-call rules here; the runtime owns evaluation syntax and state retention. */ -function runCodeFlavor(language: string): RunCodeFlavor { +function runCodeFlavor(language: string, execution = { + programDescription: 'the program to execute', + outputInstruction: 'Curate the program output.', + codeDescription: `The ${language} program to execute, following this tool's execution instructions.`, +}): RunCodeFlavor { return { description: `Execute a ${language} program against the available tools. Takes two required arguments: ` - + '`description`, a short summary of what the program does, and `code`, the program to execute. ' + + `\`code\`, ${execution.programDescription}, and \`description\`, a short summary of what the program does. ` + 'Call tools as `await tools.name(args)` per the declarations in the system prompt. ' - + 'Curate the program output. Image-bearing subtool results are attached after the run.', - codeDescription: `The ${language} program to execute, following this tool's execution instructions.`, + + `${execution.outputInstruction} Image-bearing subtool results are attached after the run.`, + codeDescription: execution.codeDescription, } } const TYPESCRIPT_FLAVOR = runCodeFlavor('TypeScript') +const TYPESCRIPT_FUNCTION_FLAVOR = runCodeFlavor('TypeScript', { + programDescription: 'the BODY of an async function (erasable syntax only; top-level `await` and `return` work)', + outputInstruction: 'Only what you print or return is program output — curate it.', + codeDescription: 'The program: the body of an async TypeScript function.', +}) const PYTHON_FLAVOR = runCodeFlavor('Python') /** @@ -85,7 +94,7 @@ const RUN_CODE_FLAVORS: Record = { */ const RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION = 'Clear, concise description of what this program does in active voice, ' - + '5-10 words (shown in the UI). Provide `description` before `code` in the arguments. ' + + '5-10 words (shown in the UI). ' + 'Examples: "Count TODO markers across packages"; ' + '"Read failing test and its fixture"; "Rename config key in every cordis.yml".' @@ -145,7 +154,8 @@ function resolveFlavor(peekRuntime: () => PtcRuntime | undefined): RunCodeFlavor const known = Object.keys(RUN_CODE_FLAVORS).map(name => JSON.stringify(name)).join(', ') throw new Error(`dsh-tools: no run_code schema flavor registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`) } - return flavor + return runtime.language === 'typescript' && runtime.programFormat === 'function-body' + ? TYPESCRIPT_FUNCTION_FLAVOR : flavor } /** @@ -306,8 +316,8 @@ export interface RunCodeBridgeOptions { } /** - * Build the `run_code` {@link ToolDefinition}: required `description` and - * `code` parameters, executed through the dispatch bridge described + * Build the `run_code` {@link ToolDefinition}: required `code` and + * `description` parameters, executed through the dispatch bridge described * above. The * registry reserves it as presentation infrastructure under non-native modes, * outside the filterable global/scoped capability layers. @@ -331,12 +341,12 @@ export function createRunCodeTool(registry: ToolRuntime, options: RunCodeBridgeO // independent (one required string `code`). description: TYPESCRIPT_FLAVOR.description, parameters: { + code: { type: 'string', required: true, description: TYPESCRIPT_FLAVOR.codeDescription }, description: { type: 'string', required: true, description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION, }, - code: { type: 'string', required: true, description: TYPESCRIPT_FLAVOR.codeDescription }, ...RUN_CODE_CONTROLS, }, output: { @@ -773,8 +783,8 @@ export function createRunCodeTool(registry: ToolRuntime, options: RunCodeBridgeO // Recompile through the same spec→schema projection defineTool used, so // the emitted schema always matches the validated specification. get: () => parameterSchemaSpecToJsonSchema({ - description: { type: 'string', required: true, description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION }, code: { type: 'string', required: true, description: resolveFlavor(peekRuntime).codeDescription }, + description: { type: 'string', required: true, description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION }, ...controlParameters(peekRuntime()), }), }) diff --git a/packages/core/tools/src/py-types.ts b/packages/core/tools/src/py-types.ts index 1f0f6e1067..65110f6b51 100644 --- a/packages/core/tools/src/py-types.ts +++ b/packages/core/tools/src/py-types.ts @@ -732,7 +732,7 @@ export function jsonSchemaToPy(schema: unknown): string { /** The fixed model-facing usage contract rendered above the declarations. */ const SDK_INSTRUCTIONS = `## Writing code for run_code -\`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code\`, the program to execute. At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program: +\`run_code\` takes two required arguments: \`code\`, the program to execute, and \`description\`, a short summary of what the program does. At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program: - Call tools as \`await tools.name(args)\` — subscript access for exotic, reserved, or underscore-leading names: \`await tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value (each method's return type below). Tool arguments must be lossless JSON. - A FAILED tool call raises \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose message is human-readable — wrap in \`try/except\` to handle and continue. diff --git a/packages/core/tools/src/ts-types.ts b/packages/core/tools/src/ts-types.ts index 91abe9945d..1c1444e0b1 100644 --- a/packages/core/tools/src/ts-types.ts +++ b/packages/core/tools/src/ts-types.ts @@ -246,19 +246,30 @@ export function jsonSchemaToTs(schema: unknown, indent = 0): string { } } -/** The fixed model-facing usage contract rendered above the declarations (see the PTC mode Agent Note's "What the model sees"). */ -const SDK_INSTRUCTIONS = `## Writing code for run_code +/** Render the argument and call instructions for the selected execution syntax. */ +function renderSdkInstructions(programFormat: 'function-body' | 'repl'): string { + const programDescription = programFormat === 'function-body' + ? 'the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped)' + : 'the program to execute' + return `## Writing code for run_code -\`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code\`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.` +\`run_code\` takes two required arguments: \`code\`, ${programDescription}, and \`description\`, a short summary of what the program does. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.` +} -const SDK_PROGRAM_INSTRUCTIONS = `Inside the program: +/** Render tool execution and output instructions for the selected execution syntax. */ +function renderSdkProgramInstructions(programFormat: 'function-body' | 'repl'): string { + const outputInstruction = programFormat === 'function-body' + ? 'Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output.' + : 'Only values emitted as program output enter the conversation.' + return `Inside the program: - Call tools as \`await tools.name(args)\` — quoted access for exotic names: \`tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON. - A FAILED tool call rejects with \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose \`message\` is human-readable — \`try/catch\` it to handle and continue. - Independent read-only calls MAY overlap under \`Promise.all\` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with \`await\`. -- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need. +- ${outputInstruction} A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need. Program-only SDK bindings:` +} /** Whether one string schema accepts the literal used by the bash example. */ function acceptsExampleString(schema: JsonSchemaNode | undefined, value: string): boolean { @@ -268,7 +279,7 @@ function acceptsExampleString(schema: JsonSchemaNode | undefined, value: string) } /** Render the bash example only when its literal arguments satisfy the current parameter schema. */ -function renderBashExample(schemas: ToolSdkSchema[]): string { +function renderBashExample(schemas: ToolSdkSchema[], programFormat: 'function-body' | 'repl'): string { const bash = schemas.find(schema => schema.name === 'bash') if (bash === undefined) return '' const parameters = bash.parameters as JsonSchemaNode @@ -279,7 +290,10 @@ function renderBashExample(schemas: ToolSdkSchema[]): string { const needsDescription = required.includes('description') if (needsDescription && !acceptsExampleString(parameters.properties?.description, 'Show current directory')) return '' const description = needsDescription ? "description: 'Show current directory', " : '' - return ` When no separate \`bash\` schema is supplied, invoke a declared \`bash\` binding inside \`run_code\`:\n\n\`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ ${description}command: 'pwd' }))" })\`` + const program = programFormat === 'function-body' + ? `return await tools.bash({ ${description}command: 'pwd' })` + : `console.log(await tools.bash({ ${description}command: 'pwd' }))` + return ` When no separate \`bash\` schema is supplied, invoke a declared \`bash\` binding inside \`run_code\`:\n\n\`run_code({ code: "${program}", description: "Show current directory" })\`` } /** @@ -292,9 +306,10 @@ function renderBashExample(schemas: ToolSdkSchema[]): string { * by name, so the input never carries a duplicate. * @param schemas - the tool schemas to declare (the caller excludes * `run_code` itself). + * @param programFormat - Runtime syntax; function bodies support returning a result, REPLs print it. * @returns the complete section text. */ -export function renderToolsSdk(schemas: ToolSdkSchema[]): string { +export function renderToolsSdk(schemas: ToolSdkSchema[], programFormat: 'function-body' | 'repl' = 'function-body'): string { const sorted = [...schemas].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0) const argsMembers: string[] = [] const outputMembers: string[] = [] @@ -313,5 +328,7 @@ export function renderToolsSdk(schemas: ToolSdkSchema[]): string { ['declare const tools: {', ' [K in ToolName]: (args: ToolArgsMap[K]) => Promise;', '}'].join('\n'), ].join('\n\n') const jsonValue = 'type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }' - return `${SDK_INSTRUCTIONS}${renderBashExample(sorted)}\n\n${SDK_PROGRAM_INSTRUCTIONS}\n\n\`\`\`ts\n${jsonValue}\n\n${declaration}\n\`\`\`` + const instructions = renderSdkInstructions(programFormat) + const programInstructions = renderSdkProgramInstructions(programFormat) + return `${instructions}${renderBashExample(sorted, programFormat)}\n\n${programInstructions}\n\n\`\`\`ts\n${jsonValue}\n\n${declaration}\n\`\`\`` } diff --git a/packages/core/tools/tests/ptc.spec.ts b/packages/core/tools/tests/ptc.spec.ts index 5d2f888674..c1e4475140 100644 --- a/packages/core/tools/tests/ptc.spec.ts +++ b/packages/core/tools/tests/ptc.spec.ts @@ -457,31 +457,31 @@ describe('mode-aware wire contribution', () => { const sdk = assembly.sections.find(section => section.name === 'tools:sdk') expect(sdk?.text).toContain('class Tools(Protocol):') expect(sdk?.text).toContain('async def echo(self, args:') - expect(sdk?.text).toContain('and `code`, the program to execute') + expect(sdk?.text).toContain('and `description`, a short summary of what the program does') expect(assembly.tools.map(tool => tool.name)).toEqual([RUN_CODE_NAME]) expect(assembly.tools[0]?.description).toContain('Execute a Python program') }) - it.each(['typescript', 'python'])('requests description before code for a %s runtime', async (language) => { + it.each(['typescript', 'python'])('lists code before description for a %s runtime', async (language) => { const { ctx, systemPrompt } = await setup({ mode: 'ptc', runtime: { language } }) try { const parameters = ctx.tools.get(RUN_CODE_NAME)!.parameters expect(Object.keys(parameters.properties as Record).slice(0, 2)) - .toEqual(['description', 'code']) - expect(parameters).toHaveProperty('required', ['description', 'code']) + .toEqual(['code', 'description']) + expect(parameters).toHaveProperty('required', ['code', 'description']) expect(parameters).toHaveProperty('properties.description.description', - expect.stringContaining('Provide `description` before `code` in the arguments.')) + expect.not.stringContaining('Provide `description` before `code`')) const assembly = await systemPrompt.assemble() expect(assembly.tools.find(tool => tool.name === RUN_CODE_NAME)?.parameters).toEqual(parameters) expect(assembly.tools.find(tool => tool.name === RUN_CODE_NAME)?.description) - .toContain('arguments: `description`, a short summary of what the program does, and `code`') + .toContain('arguments: `code`') // defineTool validates its captured static schema, independently of the runtime parameters getter. const rejected = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('argument-order'), name: RUN_CODE_NAME, arguments: {}, }) expect(rejected).toHaveProperty('error.message', - 'invalid arguments: missing required property "description"; missing required property "code"') + 'invalid arguments: missing required property "code"; missing required property "description"') } finally { await ctx.fiber.dispose() } @@ -493,13 +493,29 @@ describe('mode-aware wire contribution', () => { const assembly = await systemPrompt.assemble() const runCodeSchema = assembly.tools.find(tool => tool.name === RUN_CODE_NAME) expect(runCodeSchema?.description).toContain('Execute a TypeScript program') - expect(runCodeSchema?.description).toContain('`code`, the program to execute') + expect(runCodeSchema?.description).toContain('`code`, the BODY of an async function') // Both required arguments are named here, not only in the parameter // schema: prose that describes the call as "pass the program" is what // leads a model to emit `{code}` alone and fail INVALID_ARGS. expect(runCodeSchema?.description).toContain('`description`') const codeParam = (runCodeSchema?.parameters as { properties: { code: { description: string } } }).properties.code - expect(codeParam.description).toBe("The TypeScript program to execute, following this tool's execution instructions.") + expect(codeParam.description).toBe('The program: the body of an async TypeScript function.') + }) + + it('keeps REPL instructions while ordering code before description', async () => { + const { ctx, runtime, systemPrompt } = await setup({ mode: 'ptc' }) + Object.defineProperty(runtime, 'programFormat', { value: 'repl' }) + try { + const assembly = await systemPrompt.assemble() + const schema = assembly.tools.find(tool => tool.name === RUN_CODE_NAME)! + expect(schema.parameters).toHaveProperty('required', ['code', 'description']) + expect(schema.description).not.toContain('BODY of an async function') + expect(schema.description).toContain('`code`, the program to execute') + expect(assembly.sections.find(section => section.name === 'tools:sdk')?.text) + .not.toContain('the body of an async TypeScript function') + } finally { + await ctx.fiber.dispose() + } }) it('emits a Python-flavored run_code schema under a python runtime (matches the SDK language)', async () => { diff --git a/packages/core/tools/tests/py-types.spec.ts b/packages/core/tools/tests/py-types.spec.ts index f940c45e42..2b988194fa 100644 --- a/packages/core/tools/tests/py-types.spec.ts +++ b/packages/core/tools/tests/py-types.spec.ts @@ -151,7 +151,7 @@ describe('renderToolsSdkPy', () => { expect(text).toContain('# tools["my-mcp.tool"](args: dict[str, Any]) -> str') expect(text).toContain('# tools["class"](args: dict[str, Any]) -> str') // Fixed instruction lines the model relies on. - expect(text).toContain('and `code`, the program to execute') + expect(text).toContain('and `description`, a short summary of what the program does') // The binding boundary: `tools`/`ToolCallError` are bound, the TypedDicts // are not. Both halves are pinned — dropping either one turns a correct // contract into a wrong one (a model that reads only "STATIC STUB" would @@ -166,11 +166,11 @@ describe('renderToolsSdkPy', () => { expect(text).toContain('tools: Tools') }) - it('lists the required description before the program', () => { + it('lists the program before its description', () => { const text = renderToolsSdkPy([bash]) expect(text).toContain('`code`') expect(text).toContain('`description`') - expect(text).toContain('two required arguments: `description`, a short summary of what the program does, and `code`') + expect(text).toContain('two required arguments: `code`') }) it('renders required as plain fields and optional as NotRequired, with per-field description comments', () => { diff --git a/packages/core/tools/tests/ts-types.spec.ts b/packages/core/tools/tests/ts-types.spec.ts index a0f4c81ff0..818e1fa423 100644 --- a/packages/core/tools/tests/ts-types.spec.ts +++ b/packages/core/tools/tests/ts-types.spec.ts @@ -146,28 +146,36 @@ describe('renderToolsSdk', () => { expect(text).toContain('[K in ToolName]: (args: ToolArgsMap[K]) => Promise;') expect(text).toContain('/** Run a shell command. */') // The fixed instruction lines the model relies on. - expect(text).toContain('and `code`, the program to execute') + expect(text).toContain('and `description`, a short summary of what the program does') expect(text).toContain('rejects with `ToolCallError`') expect(text).toContain('MAY overlap under `Promise.all`') expect(text).toContain('lossless JSON') }) - it('lists the required description before the program', () => { + it('lists the program before its description', () => { const text = renderToolsSdk([bash]) expect(text).toContain('`code`') expect(text).toContain('`description`') - expect(text).toContain('two required arguments: `description`, a short summary of what the program does, and `code`') + expect(text).toContain('two required arguments: `code`') }) it('keeps generated bindings inside a run_code program', () => { const text = renderToolsSdk([bash]) expect(text).toContain('A declaration does not make its name a directly callable tool') expect(text).toContain('only names supplied as separate tool schemas may be called directly') - expect(text).toContain('`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: \'Show current directory\', command: \'pwd\' }))" })`') + expect(text).toContain('`run_code({ code: "return await tools.bash({ description: \'Show current directory\', command: \'pwd\' })", description: "Show current directory" })`') expect(text).toContain('Program-only SDK bindings:') expect(text).not.toContain('The available tools:') }) + it('keeps REPL output syntax distinct from function bodies', () => { + const text = renderToolsSdk([bash], 'repl') + expect(text).toContain('run_code({ code: "console.log(await tools.bash(') + expect(text).not.toContain('return await tools.bash(') + expect(text).not.toContain('the body of an async TypeScript function') + expect(text).not.toContain('Emit results with `return`') + }) + it('only shows a bash example accepted by the declared binding', () => { expect(renderToolsSdk([exotic])).not.toContain('tools.bash(') @@ -214,11 +222,10 @@ describe('renderToolsSdk', () => { expect(renderToolsSdk([constrained])).toContain('tools.bash({ command: \'pwd\' })') }) - it('uses an output example accepted by both Node execution modes', () => { + it('uses a returned result in function-body mode', () => { const text = renderToolsSdk([bash]) - expect(text).toContain('console.log(await tools.bash(') - expect(text).not.toContain('return await tools.bash') - expect(text).not.toContain('body of an async TypeScript function') + expect(text).toContain('return await tools.bash(') + expect(text).toContain('body of an async TypeScript function') expect(text).not.toContain('TypeScript REPL statements') }) diff --git a/packages/credentials/authorization/package.json b/packages/credentials/authorization/package.json index cbf9a5221c..3978d202a0 100644 --- a/packages/credentials/authorization/package.json +++ b/packages/credentials/authorization/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-authorization", "description": "Authorization seam (ctx.authorization): plugin-owned flows that obtain a credential through a conversation with the human", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/credentials/credentials-local/package.json b/packages/credentials/credentials-local/package.json index ca6c84e366..71fd332c09 100644 --- a/packages/credentials/credentials-local/package.json +++ b/packages/credentials/credentials-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-credentials-local", "description": "File-backed credentials provider ($DSH_HOME/.env under the live process environment) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/credentials/credentials/package.json b/packages/credentials/credentials/package.json index caaa90a0bb..0a57ab8608 100644 --- a/packages/credentials/credentials/package.json +++ b/packages/credentials/credentials/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-credentials", "description": "Abstract credential seam (ctx.credentials): settings carry references to secrets, providers own the values", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/credentials/deepseek-account-platform/README.i18n.yaml b/packages/credentials/deepseek-account-platform/README.i18n.yaml index 67220428cd..9e6ffe9905 100644 --- a/packages/credentials/deepseek-account-platform/README.i18n.yaml +++ b/packages/credentials/deepseek-account-platform/README.i18n.yaml @@ -15,8 +15,8 @@ en: af856ba79fe31b8e zh: 15b7d1a5d1856de2 /deepseek-ai-dsh-deepseek-account-platform/use-this-package: - en: 03ce750274b355f6 - zh: 0bfb86570c686e17 + en: 56bee240f50c293a + zh: 6c4c5de6088c9d79 /deepseek-ai-dsh-deepseek-account-platform/understand-the-implementation: en: 76d2da2dab100ac3 zh: 2c9404f27915daab diff --git a/packages/credentials/deepseek-account-platform/README.md b/packages/credentials/deepseek-account-platform/README.md index 394322d273..104b597c9a 100644 --- a/packages/credentials/deepseek-account-platform/README.md +++ b/packages/credentials/deepseek-account-platform/README.md @@ -38,7 +38,7 @@ Token resolution refuses requests while local sign-out is in progress. Successfu getProfile / getBalance sends the stored grant in the x-dsh-auth-token header to GET /auth-api/v0/users/current and GET /api/v0/users/get_user_summary on platformOrigin. The grant issuer must match that origin. Host projects only the account UID, profile name, avatar URL, phone or email exactly as masked by Platform, and normal_wallets / bonus_wallets currency/balance strings; response tokens and other fields are discarded. A balance or bonus-amount string is accepted when it matches the numeric grammar Platform's Web client already passes to big.js, which allows an omitted integer or fraction part and a decimal exponent, so values such as 0E-16 and 5.0000000000000000 stay valid; the original string is retained and NaN, Infinity and malformed text are rejected. Bonus wallets remain separate from recharge balances. Credential changes and disposal invalidate in-flight queries. -getUnnotifiedBonuses reads GET /api/v0/users/get_unnotified_bonuses and ackBonusNotified posts POST /api/v0/users/ack_bonus_notified with a JSON body naming the order on the same origin, each sending the five client headers built from the caller's metadata. Both resolve the account identity for the captured grant first, so a signed-out or switched account yields null or false instead of acknowledging another account's bonus, and a read publishes only after the whole query settled under one credential lifetime. The Host forwards the bonus list in Platform order, maps each msg to message, and discards nothing else; malformed payloads, other HTTP failures and other business failures throw, while a credential rejection (HTTP 401 or response code `40003`) invalidates the current grant and yields null or false. +getUnnotifiedBonuses reads GET /api/v0/users/get_unnotified_bonuses and ackBonusNotified posts POST /api/v0/users/ack_bonus_notified with a JSON body naming the order on the same origin, each sending the five client headers built from the caller's metadata. Both resolve the account identity for the captured grant first, so a signed-out or switched account yields null or false instead of acknowledging another account's bonus, and a read publishes only after the whole query settled under one credential lifetime. The Host forwards the bonus list in Platform order, retains the string fields order_id, expires_at and msg, and maps them to orderId, expiresAt and message. An optional string title is forwarded unchanged; null or absent titles use the client default. An empty, null or absent expires_at becomes an empty expiresAt and means the notice never expires. Order IDs need not be UUIDs; campaign, amount, currency and granted_at are optional, unvalidated metadata forwarded as campaign, amount, currency and grantedAt; malformed payloads, other HTTP failures and other business failures throw, while a credential rejection (HTTP 401 or response code `40003`) invalidates the current grant and yields null or false. Balance requests use balanceTimeoutMs (30,000ms by default); timeout returns a failed outcome without clearing the account or manufacturing a zero balance. Configure platformOrigin, allowLoopbackHttp, requestTimeoutMs, balanceTimeoutMs, and attemptTimeoutMs in the plugin row. HTTP is accepted only for explicitly enabled loopback development. The provider registers /oauth/callback on the existing Host webServer before auth_init, validates state, exchanges an S256 PKCE code once, and commits a grant before redirecting to auth_exchange.biz_data.authorized_url. Browser destinations default to the configured platform origin and always require the fixed /dsh/authorize or /dsh/authorized path. The completion URL sets `login_source` to the initiating client (`web` or `desktop`) and preserves other Platform-supplied query parameters. Device identity is a separate random UUID record shared by processes using the same credential store; device_model reports OS and architecture. diff --git a/packages/credentials/deepseek-account-platform/README.zh.md b/packages/credentials/deepseek-account-platform/README.zh.md index 611752c3d8..7e9b181868 100644 --- a/packages/credentials/deepseek-account-platform/README.zh.md +++ b/packages/credentials/deepseek-account-platform/README.zh.md @@ -38,7 +38,7 @@ getPlatformSession 仅在已存授权的 issuer 与 platformOrigin 一致时导 getProfile / getBalance 将保存的授权 token 通过 x-dsh-auth-token 请求头,向 platformOrigin 上的 GET /auth-api/v0/users/current 和 GET /api/v0/users/get_user_summary 发起请求。授权签发来源必须与该来源一致。Host 只投影账号 UID、资料名称、头像 URL、由 Platform 脱敏的手机号或邮箱(原样保留),以及 normal_wallets / bonus_wallets 的币种和余额字符串,丢弃响应 token 与其他字段。余额和赠金金额字符串只要符合 Platform Web 客户端交给 big.js 的数字文法即可通过,该文法和 big.js 一致,允许省略整数或小数部分以及十进制指数,因此 0E-16、5.0000000000000000 这类值仍然有效;原始字符串会保留,NaN、Infinity 和非法文本仍被拒绝。赠送钱包不计入充值余额。凭证变化和销毁会使进行中的查询失效。 -getUnnotifiedBonuses 读取同一来源上的 GET /api/v0/users/get_unnotified_bonuses,ackBonusNotified 向 POST /api/v0/users/ack_bonus_notified 发送仅含订单号的 JSON 正文,两者都按调用方元数据组装这五个客户端请求头。二者都先为捕获到的授权解析账号身份,因此退登或切号时返回 null 或 false,而不会确认另一个账号的赠金;一次读取只有在同一凭证生命周期内全部结算后才发布。Host 按 Platform 返回顺序转发赠金列表,把每条的 msg 映射为 message,其余字段不额外丢弃;格式错误、其他 HTTP 失败和其他业务失败都会抛出,而凭据被拒绝(HTTP 401 或响应码 `40003`)会使当前授权失效并返回 null 或 false。 +getUnnotifiedBonuses 读取同一来源上的 GET /api/v0/users/get_unnotified_bonuses,ackBonusNotified 向 POST /api/v0/users/ack_bonus_notified 发送仅含订单号的 JSON 正文,两者都按调用方元数据组装这五个客户端请求头。二者都先为捕获到的授权解析账号身份,因此退登或切号时返回 null 或 false,而不会确认另一个账号的赠金;一次读取只有在同一凭证生命周期内全部结算后才发布。Host 按 Platform 返回顺序转发赠金列表,保留字符串字段 order_id、expires_at 和 msg,并映射为 orderId、expiresAt 和 message。可选的字符串 title 原样转发;null 或缺省表示使用客户端默认标题。expires_at 为空字符串、null 或缺省时,统一转为空字符串 expiresAt,表示通知永不过期。订单号不要求 UUID 格式;campaign、amount、currency 和 granted_at 均为可选元数据,不做校验,分别原样转发为 campaign、amount、currency 和 grantedAt;格式错误、其他 HTTP 失败和其他业务失败都会抛出,而凭据被拒绝(HTTP 401 或响应码 `40003`)会使当前授权失效并返回 null 或 false。 余额请求使用 balanceTimeoutMs(默认 30,000ms);超时返回失败结果,不清除账号,也不制造零余额。在插件行配置 platformOrigin、allowLoopbackHttp、requestTimeoutMs、balanceTimeoutMs 和 attemptTimeoutMs。HTTP 仅用于显式启用的本机开发。提供者先在现有 Host webServer 注册 /oauth/callback,再调用 auth_init;校验 state、使用 S256 PKCE 授权码兑换一次,并在跳转 auth_exchange.biz_data.authorized_url 前提交授权记录。浏览器地址默认要求匹配配置的平台来源,并始终要求固定的 /dsh/authorize 或 /dsh/authorized 路径。完成页地址将 `login_source` 设为发起登录的客户端类型(`web` 或 `desktop`),并保留平台返回的其他查询参数。设备标识是独立的随机 UUID 记录,由使用同一凭证存储的进程共享;device_model 报告操作系统和架构。 diff --git a/packages/credentials/deepseek-account-platform/package.json b/packages/credentials/deepseek-account-platform/package.json index c3fd5ad374..7916afcde3 100644 --- a/packages/credentials/deepseek-account-platform/package.json +++ b/packages/credentials/deepseek-account-platform/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-deepseek-account-platform", "description": "Authorize DeepSeek accounts through browser PKCE", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/credentials/deepseek-account-platform/src/details.ts b/packages/credentials/deepseek-account-platform/src/details.ts index f80cdee953..d7c90b9ac3 100644 --- a/packages/credentials/deepseek-account-platform/src/details.ts +++ b/packages/credentials/deepseek-account-platform/src/details.ts @@ -15,9 +15,11 @@ const wallet = z.object({ currency: z.enum(['CNY', 'USD']), balance: z.string().regex(decimal), }) const summary = z.object({ normal_wallets: z.array(wallet), bonus_wallets: z.array(wallet) }) +// Protocol responses are JSON-decoded; metadata keeps any JSON value without field validation. +const bonusMetadata = z.custom>().optional() const bonus = z.object({ - order_id: z.uuid(), campaign: z.string(), amount: z.string().regex(decimal), - currency: z.enum(['CNY', 'USD']), granted_at: z.string(), expires_at: z.string(), msg: z.string(), + campaign: bonusMetadata, amount: bonusMetadata, currency: bonusMetadata, granted_at: bonusMetadata, + order_id: z.string(), title: z.string().nullish().transform(value => value ?? undefined), expires_at: z.string().nullish().transform(value => value ?? ''), msg: z.string(), }) // The shared response reader already unwraps biz_data, so the payload is the bonus list itself. const unnotified = z.array(bonus) @@ -97,8 +99,12 @@ export async function readUnnotifiedBonuses(origin: string, token: string, signal: AbortSignal, headers: Record): Promise { const parsed = accountPayload(unnotified, await requestUnnotifiedBonuses(origin, token, signal, headers), 'bonus') return parsed.map(row => ({ - orderId: row.order_id as AccountBonusOrderId, campaign: row.campaign, amount: row.amount, - currency: row.currency, grantedAt: row.granted_at, expiresAt: row.expires_at, message: row.msg, + ...(row.campaign === undefined ? {} : { campaign: row.campaign }), + ...(row.amount === undefined ? {} : { amount: row.amount }), + ...(row.currency === undefined ? {} : { currency: row.currency }), + ...(row.granted_at === undefined ? {} : { grantedAt: row.granted_at }), + orderId: row.order_id as AccountBonusOrderId, expiresAt: row.expires_at, message: row.msg, + ...(row.title === undefined ? {} : { title: row.title }), })) } diff --git a/packages/credentials/deepseek-account-platform/tests/bonus.spec.ts b/packages/credentials/deepseek-account-platform/tests/bonus.spec.ts index c451b49a4e..959af7bc3f 100644 --- a/packages/credentials/deepseek-account-platform/tests/bonus.spec.ts +++ b/packages/credentials/deepseek-account-platform/tests/bonus.spec.ts @@ -206,17 +206,34 @@ it('reads unnotified bonuses with the platform origin, grant header, and locale ]) }) -it('accepts exponent and full-precision bonus amounts through the Platform numeric grammar', async () => { +it.each([ + ['absent', {}, {}], + ['unvalidated', { campaign: null, amount: 'not-a-number', currency: 'EUR', granted_at: 42 }, + { campaign: null, amount: 'not-a-number', currency: 'EUR', grantedAt: 42 }], +])('accepts opaque order IDs with %s unused fields and acknowledges the same ID', async (_name, extra, expected) => { const f = await fixture() await f.grant('test-account-token') - f.bonuses([ - { order_id: ORDER, campaign: 'dsh_login_bonus', amount: '5.0000000000000000', currency: 'CNY', - granted_at: '2026-09-21T12:00:00Z', expires_at: '2026-10-21T12:00:00Z', msg: 'fixture' }, - { order_id: SECOND_ORDER, campaign: 'dsh_login_bonus', amount: '1E+3', currency: 'CNY', - granted_at: '2026-09-20T12:00:00Z', expires_at: '2026-10-20T12:00:00Z', msg: 'fixture' }, - ]) + f.bonuses([{ order_id: 'model-update-1', expires_at: '2099-01-01T00:00:00Z', title: 'Model news', msg: 'Model updated', ...extra }]) const batch = await f.account.getUnnotifiedBonuses(clientMetadata('en')) - expect(batch?.bonuses.map(bonus => bonus.amount)).toEqual(['5.0000000000000000', '1E+3']) + expect(batch?.bonuses).toEqual([ + { orderId: 'model-update-1', expiresAt: '2099-01-01T00:00:00Z', title: 'Model news', message: 'Model updated', ...expected }, + ]) + const notice = batch!.bonuses[0]! + expect(await f.account.ackBonusNotified(batch!.accountId, notice.orderId, clientMetadata('en'))).toBe(true) + expect(f.requests.at(-1)?.body).toBe(JSON.stringify({ order_id: 'model-update-1' })) +}) + +it.each([ + ['empty', { expires_at: '' }], + ['null', { expires_at: null }], + ['absent', {}], +])('accepts %s expiry as a notice that never expires', async (_name, expiry) => { + const f = await fixture() + await f.grant('test-account-token') + f.bonuses([{ order_id: 'notice', msg: 'News', ...expiry }]) + expect((await f.account.getUnnotifiedBonuses(clientMetadata('en')))?.bonuses).toEqual([ + { orderId: 'notice', expiresAt: '', message: 'News' }, + ]) }) it.each([ @@ -234,9 +251,9 @@ it.each([ }) it.each([ - ['a non-UUID order id', [{ order_id: 'order', campaign: 'c', amount: '5.00', currency: 'CNY', granted_at: 'a', expires_at: 'b', msg: 'm' }]], - ['a non-decimal amount', [{ order_id: ORDER, campaign: 'c', amount: 'five', currency: 'CNY', granted_at: 'a', expires_at: 'b', msg: 'm' }]], - ['an unknown currency', [{ order_id: ORDER, campaign: 'c', amount: '5.00', currency: 'EUR', granted_at: 'a', expires_at: 'b', msg: 'm' }]], + ['a non-string title', [{ order_id: 'order', title: 42, expires_at: 'b', msg: 'm' }]], + ['a non-string order id', [{ order_id: 42, expires_at: 'b', msg: 'm' }]], + ['a non-string expiry', [{ order_id: 'order', expires_at: 42, msg: 'm' }]], ['a missing message', [{ order_id: ORDER, campaign: 'c', amount: '5.00', currency: 'CNY', granted_at: 'a', expires_at: 'b' }]], ['a payload that is not a list', { bonuses: [] }], ])('rejects a malformed unnotified payload: %s', async (_name, payload) => { @@ -387,3 +404,16 @@ it('refuses a cached identity that carries no Platform id', async () => { f.failProfile() await expect(f.account.ackBonusNotified(USER as AccountUserId, ORDER as AccountBonusOrderId, clientMetadata('en'))).rejects.toThrow('account: protocol') }) + +it('treats a null title as absent without rejecting other notifications', async () => { + const f = await fixture() + await f.grant('test-account-token') + f.bonuses([ + { order_id: 'untitled', title: null, msg: 'News' }, + { order_id: 'titled', title: 'Update', msg: 'Details' }, + ]) + expect((await f.account.getUnnotifiedBonuses(clientMetadata('en')))?.bonuses).toEqual([ + { orderId: 'untitled', expiresAt: '', message: 'News' }, + { orderId: 'titled', title: 'Update', expiresAt: '', message: 'Details' }, + ]) +}) diff --git a/packages/credentials/deepseek-account-platform/tests/diagnostics.spec.ts b/packages/credentials/deepseek-account-platform/tests/diagnostics.spec.ts index 7ade4bc6f0..fe2a60b14d 100644 --- a/packages/credentials/deepseek-account-platform/tests/diagnostics.spec.ts +++ b/packages/credentials/deepseek-account-platform/tests/diagnostics.spec.ts @@ -177,7 +177,7 @@ it.each([ it('reports invalid bonus fields without logging bonus data', async () => { const output = vi.spyOn(console, 'error').mockImplementation(() => undefined) const fetcher = vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(JSON.stringify({ - code: 0, data: { biz_code: 0, biz_data: [{ order_id: 'private-order', msg: 'private-message' }] }, + code: 0, data: { biz_code: 0, biz_data: [{ order_id: 42, msg: 'private-message' }] }, }))) try { await expect(readUnnotifiedBonuses('https://platform.deepseek.com', 'private-token', diff --git a/packages/credentials/deepseek-account/package.json b/packages/credentials/deepseek-account/package.json index 97c66da83c..3204ed0b1d 100644 --- a/packages/credentials/deepseek-account/package.json +++ b/packages/credentials/deepseek-account/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-deepseek-account", "description": "Read account state and resolve official API credentials", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, @@ -35,13 +35,15 @@ "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-brand": "workspace:*", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-llm": "workspace:*" + "@deepseek-ai/dsh-llm": "workspace:*", + "@deepseek-ai/dsh-util-values": "workspace:*" }, "devDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-brand": "workspace:*", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-llm": "workspace:*" + "@deepseek-ai/dsh-llm": "workspace:*", + "@deepseek-ai/dsh-util-values": "workspace:*" }, "peerDependenciesMeta": { "@deepseek-ai/dsh-agent": { diff --git a/packages/credentials/deepseek-account/src/types.ts b/packages/credentials/deepseek-account/src/types.ts index 025fdb6e4e..39f1f086fb 100644 --- a/packages/credentials/deepseek-account/src/types.ts +++ b/packages/credentials/deepseek-account/src/types.ts @@ -1,6 +1,7 @@ /** Client-supplied request identity and client-safe account state; credentials never cross this projection. */ import type {} from '@deepseek-ai/cordis' import type { Branded } from '@deepseek-ai/dsh-brand' +import type { JsonValue } from '@deepseek-ai/dsh-util-values' /** Identity of the requesting UI for one account operation; the Host derives Platform request headers from it. */ export interface AccountClientMetadata { @@ -65,14 +66,15 @@ export interface AccountDetails { export type AccountBonusOrderId = Branded<'AccountBonusOrderId'> /** One granted bonus Platform has not yet recorded as displayed; copy is server-authored. */ export interface AccountBonusNotification { + /** Server-localized plain-text title; absent responses use the localized bonus title. */ + readonly title?: string readonly orderId: AccountBonusOrderId - readonly campaign: string - /** Granted decimal amount, preserving server precision; never the remaining balance. */ - readonly amount: string - readonly currency: 'CNY' | 'USD' - /** Grant time as supplied by Platform. */ - readonly grantedAt: string - /** Expiry as supplied by Platform. */ + /** Optional Platform metadata, forwarded without validation. */ + readonly campaign?: JsonValue + readonly amount?: JsonValue + readonly currency?: JsonValue + readonly grantedAt?: JsonValue + /** Platform expiry timestamp; an empty string means the notice never expires. */ readonly expiresAt: string /** Server-localized plain text ready for display. */ readonly message: string diff --git a/packages/deliverables/tool-present/package.json b/packages/deliverables/tool-present/package.json index 8acb591637..47bc693d64 100644 --- a/packages/deliverables/tool-present/package.json +++ b/packages/deliverables/tool-present/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-present", "description": "Explicit workspace file delivery declarations for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/deliverables/workspace-changes/package.json b/packages/deliverables/workspace-changes/package.json index 0e35903786..837f5293d5 100644 --- a/packages/deliverables/workspace-changes/package.json +++ b/packages/deliverables/workspace-changes/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-workspace-changes", "description": "Per-turn workspace file changes recorded from git working-tree snapshots and whole-file captures, with per-file comparisons, for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/document/office-to-pdf/package.json b/packages/document/office-to-pdf/package.json index 9d1894d131..352f49e521 100644 --- a/packages/document/office-to-pdf/package.json +++ b/packages/document/office-to-pdf/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-office-to-pdf", "description": "Shared Office-to-PDF conversion with bounded queues and caching", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/agent-team-profile/README.i18n.yaml b/packages/experimental/agent-team-profile/README.i18n.yaml index 46c6685a3f..ce7d182de5 100644 --- a/packages/experimental/agent-team-profile/README.i18n.yaml +++ b/packages/experimental/agent-team-profile/README.i18n.yaml @@ -18,8 +18,8 @@ en: bbff250eba0f1b89 zh: 0a618f05cc88ee2b /deepseek-ai-dsh-experimental-agent-team-profile/use-this-package/install-into-a-profile: - en: 22e022969e8de91b - zh: 5c657e55a8d6b168 + en: 97ebf498b612d2fa + zh: 631aafa5bce6086f /deepseek-ai-dsh-experimental-agent-team-profile/use-this-package/what-you-get: en: 9494ef152ee1ee43 zh: e9c5129490a1fae1 diff --git a/packages/experimental/agent-team-profile/README.md b/packages/experimental/agent-team-profile/README.md index 9fcce49bde..34c6b2e753 100644 --- a/packages/experimental/agent-team-profile/README.md +++ b/packages/experimental/agent-team-profile/README.md @@ -42,7 +42,7 @@ Enable Agent Teams on the Web or Desktop Plugins page to activate both tools and dsh plugin --profile web add @deepseek-ai/dsh-experimental-agent-team-profile ``` -In an existing profile’s `package.json`, keep `@deepseek-ai/dsh-experimental-agent-team-profile` in `dsh.profile.bundles` and remove the separate `@deepseek-ai/dsh-experimental-agent-team-web-profile` entry. User patches targeting `ui-agent-team` still apply. A user patch that sets a removed Team key keeps that Team row from activating; the [upgrade guide](../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md) lists the removed keys and tools. +In an existing profile’s `package.json`, keep `@deepseek-ai/dsh-experimental-agent-team-profile` in `dsh.profile.bundles` and remove the separate `@deepseek-ai/dsh-experimental-agent-team-web-profile` entry. User patches targeting `ui-agent-team` still apply. A user patch that sets a removed Team key keeps that Team row from activating; the [upgrade guide](../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md) lists the removed keys and tools. ### What you get diff --git a/packages/experimental/agent-team-profile/README.zh.md b/packages/experimental/agent-team-profile/README.zh.md index 69585e9d8d..d8211e21a4 100644 --- a/packages/experimental/agent-team-profile/README.zh.md +++ b/packages/experimental/agent-team-profile/README.zh.md @@ -42,7 +42,7 @@ profile 必须已经包含 `@deepseek-ai/dsh-base`,本层依赖其中的 Subag dsh plugin --profile web add @deepseek-ai/dsh-experimental-agent-team-profile ``` -已有 profile 的 `package.json` 中,`dsh.profile.bundles` 应只保留 `@deepseek-ai/dsh-experimental-agent-team-profile`,删除独立的 `@deepseek-ai/dsh-experimental-agent-team-web-profile` 条目。用户 patch 中的 `ui-agent-team` 配置仍然有效。设置已移除 Team 键的用户 patch 会使对应 Team 配置行无法激活;[升级指南](../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md)列出已移除的键与工具。 +已有 profile 的 `package.json` 中,`dsh.profile.bundles` 应只保留 `@deepseek-ai/dsh-experimental-agent-team-profile`,删除独立的 `@deepseek-ai/dsh-experimental-agent-team-web-profile` 条目。用户 patch 中的 `ui-agent-team` 配置仍然有效。设置已移除 Team 键的用户 patch 会使对应 Team 配置行无法激活;[升级指南](../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md)列出已移除的键与工具。 ### 获得的功能 diff --git a/packages/experimental/agent-team-profile/package.json b/packages/experimental/agent-team-profile/package.json index 45fd9af0dc..9e8cbaebe9 100644 --- a/packages/experimental/agent-team-profile/package.json +++ b/packages/experimental/agent-team-profile/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-agent-team-profile", "description": "Agent Teams collaboration, tools, and Web UI in one experimental bundle", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/agent-team/README.i18n.yaml b/packages/experimental/agent-team/README.i18n.yaml index 6433b77a59..6c97d7bd22 100644 --- a/packages/experimental/agent-team/README.i18n.yaml +++ b/packages/experimental/agent-team/README.i18n.yaml @@ -21,14 +21,14 @@ en: 12d03bedc2a7be57 zh: 27fc0dc8fef2340b /deepseek-ai-dsh-experimental-agent-team/use-this-package/smallest-working-setup: - en: 0eab809f5f1af167 - zh: 21a5960927603bf2 + en: 7de9699e542fa729 + zh: 5e1870e4ae95cdb1 /deepseek-ai-dsh-experimental-agent-team/use-this-package/teammates: en: 77cbb59eee00c536 zh: 1cc55a924392b4dd /deepseek-ai-dsh-experimental-agent-team/use-this-package/messages-between-teammates: - en: 301a1b1d29dd7323 - zh: 6936fb87e017d701 + en: 3b7918eb5b41c7a7 + zh: d7bd34b6a2487902 /deepseek-ai-dsh-experimental-agent-team/use-this-package/shared-task-board: en: a365f36c0aa710a5 zh: af7b2b71add4319b @@ -72,8 +72,8 @@ en: 35f9e2968ab635f5 zh: f599a75e88228d83 /deepseek-ai-dsh-experimental-agent-team/further-exploration: - en: bddcdd7f9e1da560 - zh: 442039edb8ee664e + en: c724cb00120bb613 + zh: 857ff34c3297a222 /deepseek-ai-dsh-experimental-agent-team/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 @@ -90,8 +90,8 @@ en: e5c406af31bf0c67 zh: 987e49e0f3e5af69 /deepseek-ai-dsh-experimental-agent-team/known-limitations-and-deferred-work: - en: e101287168ed5875 - zh: 909da68681f2b408 + en: e36a6d5941e39580 + zh: eb2930c689dcd78a /deepseek-ai-dsh-experimental-agent-team/known-limitations-and-deferred-work/dev-note: en: 498e4cf9fe5741a7 zh: 9d2bab630e655c99 diff --git a/packages/experimental/agent-team/README.md b/packages/experimental/agent-team/README.md index ef92cddf60..2f6294f8a6 100644 --- a/packages/experimental/agent-team/README.md +++ b/packages/experimental/agent-team/README.md @@ -49,7 +49,7 @@ For example, ask the model to start a subagent to review the diff and track its |---|---|---| | `maxTasks` | `256` | Maximum non-deleted tasks on the board | -The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-experimental-agent-team) is the exhaustive source for every accepted field and its JSDoc. A row that sets `maxMembers`, `maxMessageBytes`, or `disposalTimeoutMs` does not activate, and dsh names the key in its startup warning; the [upgrade guide](../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md) lists the replacements. +The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-experimental-agent-team) is the exhaustive source for every accepted field and its JSDoc. A row that sets `maxMembers`, `maxMessageBytes`, or `disposalTimeoutMs` does not activate, and dsh names the key in its startup warning; the [upgrade guide](../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md) lists the replacements. ### Teammates @@ -59,7 +59,7 @@ Every root agent leads its own Team, and every live subagent below it, at any de ### Messages between teammates -Members use standard `send_message` with agent names: the Lead reaches direct children, and resident continuable children reach their parent and continuable siblings. Steer delivers into the target inbox and can cold-resume children or siblings. Team stores no messages; accepted messages follow ordinary Agent persistence. +Members use standard `list_agents` to discover the whole Team and `send_message` to contact any other member by name. The subagent runtime owns routing, sender attribution, and cold restoration. Team stores no messages; accepted messages follow ordinary Agent persistence. ### Shared task board @@ -161,7 +161,7 @@ Read these pages when the package-level contract is not enough. They move from t - [tool-agent-team package](../tool-agent-team/README.md) — the task and wait tools, policy, and identity every member receives. - [Subagent runtime](../../subagent/subagent/README.md) — continuable children, messaging, interruption, and lifecycle that teammates use. - [Subagent teammates Agent Note](../../../.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.md) — why teammates are subagents and what Team keeps. -- [Upgrade guide](../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md) — replacements for the removed Team tools, service methods, and configuration keys. +- [Upgrade guide](../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md) — replacements for the removed Team tools, service methods, and configuration keys. - [Experimental package reference](../AGENTS.md) — placement, publication, and dependency isolation. ----- @@ -194,7 +194,9 @@ These limits describe what a team cannot do yet or what needs special operationa - **Experimental prototype with no stability promise** — the package is public, but its contracts can change freely while it incubates. - **One process and one shared checkout** — members share cwd and observe edits immediately; this package provides no worktree, remote member, merge, or filesystem lock. - **Advisory write scopes** — Bash, formatters, code generators, and direct external writers can bypass filesystem version checks; Leads must coordinate ownership and review the final diff. -- **Roster covers direct continuable children** — deeper descendants share tasks and waits but have no roster row; they can claim tasks under their own ids and message only their direct parent, children, and continuable siblings. +- **Roster covers direct continuable children** — deeper descendants share tasks and waits but have no roster row; they can claim tasks under their own ids. + +- **Communication stays within one tree** — members cannot message unrelated Teams; only ancestors can interrupt descendants. - **Membership needs a live lineage** — a subagent whose parent Agent is not loaded is outside every Team, so its Team calls fail with `TEAM_NOT_MEMBER`. - **No automatic ownership release** — inactivity, interruption, process exit, and failed work do not release a task owner. - **Historical queued messages stay undelivered** — `team/message/queued` records without a matching delivery remain readable in the Lead log, but no member receives them. diff --git a/packages/experimental/agent-team/README.zh.md b/packages/experimental/agent-team/README.zh.md index 1d7ecf2d1f..5f12db8097 100644 --- a/packages/experimental/agent-team/README.zh.md +++ b/packages/experimental/agent-team/README.zh.md @@ -49,7 +49,7 @@ kind: "package-reference" |---|---|---| | `maxTasks` | `256` | 任务板上最多的未删除任务数 | -生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-experimental-agent-team)是每个受支持字段及其 JSDoc 的穷尽式真源。设置 `maxMembers`、`maxMessageBytes` 或 `disposalTimeoutMs` 的配置行不会激活,dsh 会在启动警告中指出该键;[升级指南](../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md)列出替代方式。 +生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-experimental-agent-team)是每个受支持字段及其 JSDoc 的穷尽式真源。设置 `maxMembers`、`maxMessageBytes` 或 `disposalTimeoutMs` 的配置行不会激活,dsh 会在启动警告中指出该键;[升级指南](../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md)列出替代方式。 ### Teammate @@ -59,7 +59,7 @@ kind: "package-reference" ### teammate 之间的消息 -成员使用标准 `send_message` 按 agent 名字 发消息:Lead 联系直接子级,驻留的可继续子级联系父级和可继续同级。Steer 投递到目标收件箱,并可冷恢复子级或同级。Team 不存储消息;已接受消息遵循普通 Agent 持久化。 +成员使用标准 `list_agents` 发现整个 Team,通过 `send_message` 按名字联系任意其他成员。子代理运行时负责路由、发送者归属与冷恢复。Team 不存储消息;已接受的消息遵循普通 Agent 持久化。 ### 共享任务板 @@ -161,7 +161,7 @@ Mailbox 投影与 checkpoint 准入保留本地声明的校验器之外获准内 - [tool-agent-team 包](../tool-agent-team/README.zh.md)——每个成员获得的任务与等待工具、策略和身份。 - [Subagent 运行时](../../subagent/subagent/README.zh.md)——teammate 使用的可继续子级、消息、中断与生命周期。 - [subagent teammate Agent Note](../../../.agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.zh.md)——为何 teammate 是subagent,以及 Team 保留了什么。 -- [升级指南](../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md)——已移除的 Team 工具、服务方法与配置键的替代方式。 +- [升级指南](../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md)——已移除的 Team 工具、服务方法与配置键的替代方式。 - [实验包参考](../AGENTS.md)——位置、公开发布与依赖隔离。 ----- @@ -194,7 +194,9 @@ Mailbox 投影与 checkpoint 准入保留本地声明的校验器之外获准内 - **实验原型,无稳定性承诺**——本包公开发布,但孵化期间约定仍可自由变更。 - **单进程、共享 checkout**——成员共享 cwd,修改立即可见;本包不提供 worktree、远端成员、merge 或文件锁。 - **write scope 仅作提示**——Bash、formatter、代码生成器与直接外部写入可以绕过文件版本检查;Lead 必须协调 owner 并检查最终 diff。 -- **成员列表覆盖直接可继续子级**——更深的后代共享任务和等待,但不显示成员行;它们可按自身 id 认领任务,并且只能联系直接父级、子级和可继续同级。 +- **成员列表覆盖直接可继续子级**——更深的后代共享任务和等待,但不显示成员行;它们可按自身 id 认领任务。 + +- **通信限定在一棵树内**——成员不能向无关 Team 发消息;只有祖先能中断后代。 - **成员关系需要 live 谱系**——parent Agent 未加载的 subagent 不属于任何 Team,因此其 Team 调用以 `TEAM_NOT_MEMBER` 失败。 - **不会自动释放 owner**——成员不活动、interrupt、进程退出与工作失败都不会释放任务 owner。 - **历史排队消息保持未投递**——没有对应投递记录的 `team/message/queued` 记录仍可在 Lead 日志中读取,但不会有成员收到它们。 diff --git a/packages/experimental/agent-team/package.json b/packages/experimental/agent-team/package.json index 7fa9a3d399..a46f608f67 100644 --- a/packages/experimental/agent-team/package.json +++ b/packages/experimental/agent-team/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-agent-team", "description": "Implicit-root Agent Teams roster of subagents, shared task DAG, and change waits", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/api-speech-to-text/package.json b/packages/experimental/api-speech-to-text/package.json index 4df0b02f21..ff8c9961cc 100644 --- a/packages/experimental/api-speech-to-text/package.json +++ b/packages/experimental/api-speech-to-text/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-api-speech-to-text", "description": "Authenticated experimental speech transcription for browser clients", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/auto-review/package.json b/packages/experimental/auto-review/package.json index 38916cdfd3..9e00cb45a5 100644 --- a/packages/experimental/auto-review/package.json +++ b/packages/experimental/auto-review/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-auto-review", "description": "Per-tool LLM authorization review for the DeepSeek Harness Auto permission preset", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "repository": { "type": "git", diff --git a/packages/experimental/badge-skill-bundle/package.json b/packages/experimental/badge-skill-bundle/package.json index 92dc3b13de..605c35124e 100644 --- a/packages/experimental/badge-skill-bundle/package.json +++ b/packages/experimental/badge-skill-bundle/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-badge-skill-bundle", "description": "Let agents add a “powered by dsh” badge to documents, pull requests, and other outputs.", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/browser-use-chrome-devtools-mcp/package.json b/packages/experimental/browser-use-chrome-devtools-mcp/package.json index fa1ab7375c..2913786013 100644 --- a/packages/experimental/browser-use-chrome-devtools-mcp/package.json +++ b/packages/experimental/browser-use-chrome-devtools-mcp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-browser-use-chrome-devtools-mcp", "description": "Experimental per-Session Chromium browser tools through chrome-devtools-mcp", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/browser-use-playwright-mcp/package.json b/packages/experimental/browser-use-playwright-mcp/package.json index ecaa318f6f..2f23d10208 100644 --- a/packages/experimental/browser-use-playwright-mcp/package.json +++ b/packages/experimental/browser-use-playwright-mcp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-browser-use-playwright-mcp", "description": "Experimental per-Session Chromium browser tools through @playwright/mcp", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/browser-use-runtime/package.json b/packages/experimental/browser-use-runtime/package.json index 9715e09ddd..140ce93254 100644 --- a/packages/experimental/browser-use-runtime/package.json +++ b/packages/experimental/browser-use-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-browser-use-runtime", "description": "Session-owned browser resource lifecycles and MCP integration for experimental providers", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/browser-use-stagehand-native/package.json b/packages/experimental/browser-use-stagehand-native/package.json index a30099c51a..85c6a35197 100644 --- a/packages/experimental/browser-use-stagehand-native/package.json +++ b/packages/experimental/browser-use-stagehand-native/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-browser-use-stagehand-native", "description": "Experimental Stagehand browser tools with separately configured native models", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/claude-code-mods/package.json b/packages/experimental/claude-code-mods/package.json index c2b79d69ef..5c8ce62ebb 100644 --- a/packages/experimental/claude-code-mods/package.json +++ b/packages/experimental/claude-code-mods/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-claude-code-mods", "description": "Experimental bridge: load Claude Code mods (hooks modules) and run their hook chains on DeepSeek Harness extension points", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/client-ui-agent-team/package.json b/packages/experimental/client-ui-agent-team/package.json index 41fbcda6f9..87da7e9780 100644 --- a/packages/experimental/client-ui-agent-team/package.json +++ b/packages/experimental/client-ui-agent-team/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-client-ui-agent-team", "description": "Web Agent Teams roster, task board, and teammate navigation", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/client-ui-claude-code-mods/package.json b/packages/experimental/client-ui-claude-code-mods/package.json index c8829cad48..cd6d72d2e7 100644 --- a/packages/experimental/client-ui-claude-code-mods/package.json +++ b/packages/experimental/client-ui-claude-code-mods/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-client-ui-claude-code-mods", "description": "Web band above the prompt for Claude Code mods: draws each session's mod tree and sends button presses back to the bridge", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/client-ui-cot-translation/package.json b/packages/experimental/client-ui-cot-translation/package.json index 414a7ce6e7..3c668162dd 100644 --- a/packages/experimental/client-ui-cot-translation/package.json +++ b/packages/experimental/client-ui-cot-translation/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-client-ui-cot-translation", "description": "Optional machine translation of expanded reasoning, preserving original Session text", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/client-ui-voice-input/package.json b/packages/experimental/client-ui-voice-input/package.json index 749d8707c9..2aeb3e00d3 100644 --- a/packages/experimental/client-ui-voice-input/package.json +++ b/packages/experimental/client-ui-voice-input/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-client-ui-voice-input", "description": "Record speech and insert editable text into the conversation draft", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/computer-use-cua-driver-mcp/package.json b/packages/experimental/computer-use-cua-driver-mcp/package.json index 08163c4f0d..c68d9c7bf0 100644 --- a/packages/experimental/computer-use-cua-driver-mcp/package.json +++ b/packages/experimental/computer-use-cua-driver-mcp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp", "description": "Experimental computer use through an installed Cua Driver MCP executable", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/computer-use-cua-driver-native/package.json b/packages/experimental/computer-use-cua-driver-native/package.json index 41cca2b144..afe462d506 100644 --- a/packages/experimental/computer-use-cua-driver-native/package.json +++ b/packages/experimental/computer-use-cua-driver-native/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-computer-use-cua-driver-native", "description": "Experimental computer-use provider embedding the Cua Driver native npm SDK", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/cot-translation-bundle/package.json b/packages/experimental/cot-translation-bundle/package.json index 59f7de4890..f1bb3c59b1 100644 --- a/packages/experimental/cot-translation-bundle/package.json +++ b/packages/experimental/cot-translation-bundle/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-cot-translation-bundle", "description": "Experimental machine translation of expanded reasoning", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/cua-macos/package.json b/packages/experimental/cua-macos/package.json index 38517f608b..97cc20e5e3 100644 --- a/packages/experimental/cua-macos/package.json +++ b/packages/experimental/cua-macos/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-cua-macos", "description": "macOS native provider for shared Computer Use application and window objects", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/cua/package.json b/packages/experimental/cua/package.json index 0309152adb..e355d406bd 100644 --- a/packages/experimental/cua/package.json +++ b/packages/experimental/cua/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-cua", "description": "Shared Computer Use application objects, authorization, and logged tool execution", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/hook-protocol/package.json b/packages/experimental/hook-protocol/package.json index 8f0c6a346e..6dc6bf3ef6 100644 --- a/packages/experimental/hook-protocol/package.json +++ b/packages/experimental/hook-protocol/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-hook-protocol", "description": "Shared Claude Code / Codex hook wire protocol: matcher engine, stdin/exit-code/stdout codec, multi-hook merge, and hook/* session events", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/hooks-claude-code/package.json b/packages/experimental/hooks-claude-code/package.json index 1e375f0975..2f24dcfb95 100644 --- a/packages/experimental/hooks-claude-code/package.json +++ b/packages/experimental/hooks-claude-code/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-hooks-claude-code", "description": "Bridge plugin: run a Claude Code hooks.json / settings hook config on the DeepSeek Harness interception seams", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/hooks-codex/package.json b/packages/experimental/hooks-codex/package.json index 9b32aa78c8..ca3fad59ef 100644 --- a/packages/experimental/hooks-codex/package.json +++ b/packages/experimental/hooks-codex/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-hooks-codex", "description": "Bridge plugin: run a Codex hooks.json hook config on the DeepSeek Harness interception seams", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/inspector-profile/package.json b/packages/experimental/inspector-profile/package.json index 80990bea26..537d33b0fa 100644 --- a/packages/experimental/inspector-profile/package.json +++ b/packages/experimental/inspector-profile/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-inspector-profile", "description": "Optional Web bundle for raw Session logs and Chat node inspection", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/inspector/package.json b/packages/experimental/inspector/package.json index 971c157b3b..ab31e31e09 100644 --- a/packages/experimental/inspector/package.json +++ b/packages/experimental/inspector/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-inspector", "description": "Experimental cross-realm CDP hub for Host debugging and Client Runtime inspection", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/plugin-market-bundle/package.json b/packages/experimental/plugin-market-bundle/package.json index 1a8f27ae1a..e1e7f69a94 100644 --- a/packages/experimental/plugin-market-bundle/package.json +++ b/packages/experimental/plugin-market-bundle/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-plugin-market-bundle", "description": "Experimental custom plugin markets: a Discover tab that browses and installs bundles from plugin market indexes", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/ptc-runtime-python/package.json b/packages/experimental/ptc-runtime-python/package.json index 2ee4eba2c6..8b39770edf 100644 --- a/packages/experimental/ptc-runtime-python/package.json +++ b/packages/experimental/ptc-runtime-python/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-ptc-runtime-python", "description": "CPython subprocess implementation of the DeepSeek Harness PTC execution seam", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "repository": { "type": "git", "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", diff --git a/packages/experimental/ralph-bundle/package.json b/packages/experimental/ralph-bundle/package.json index be83ff3020..929515337e 100644 --- a/packages/experimental/ralph-bundle/package.json +++ b/packages/experimental/ralph-bundle/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-ralph-bundle", "description": "Run explicitly requested repeated work with fresh agents.", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/session-inspector/package.json b/packages/experimental/session-inspector/package.json index 2bc4b14ad5..90f3929f12 100644 --- a/packages/experimental/session-inspector/package.json +++ b/packages/experimental/session-inspector/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-session-inspector", "description": "Experimental virtualized Session log and live Chat group/node inspectors", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/session-search/package.json b/packages/experimental/session-search/package.json index 3c3b1c36b4..d73b728515 100644 --- a/packages/experimental/session-search/package.json +++ b/packages/experimental/session-search/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-session-search", "description": "Give Agents tools to search and read earlier sessions from the current workspace.", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/session-title-all-prompts-llm/package.json b/packages/experimental/session-title-all-prompts-llm/package.json index 4acd46b6ea..5d27c162da 100644 --- a/packages/experimental/session-title-all-prompts-llm/package.json +++ b/packages/experimental/session-title-all-prompts-llm/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-title-all-prompts-llm", "description": "All-user-messages LLM provider plugin for DeepSeek Harness session titles", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/session-titles-bundle/package.json b/packages/experimental/session-titles-bundle/package.json index c564c6545c..c052cc10e7 100644 --- a/packages/experimental/session-titles-bundle/package.json +++ b/packages/experimental/session-titles-bundle/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-session-titles-bundle", "description": "Refresh session titles as new human prompts arrive.", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/skill-badge/package.json b/packages/experimental/skill-badge/package.json index eb85d48da2..30c817e06b 100644 --- a/packages/experimental/skill-badge/package.json +++ b/packages/experimental/skill-badge/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-skill-badge", "description": "Bundled dsh badge skill provider for DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/speech-to-text-sensevoice/package.json b/packages/experimental/speech-to-text-sensevoice/package.json index 8fad41acd7..e5978b6bf1 100644 --- a/packages/experimental/speech-to-text-sensevoice/package.json +++ b/packages/experimental/speech-to-text-sensevoice/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-speech-to-text-sensevoice", "description": "Local SenseVoice ONNX transcription with a managed sherpa-onnx process", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/speech-to-text/package.json b/packages/experimental/speech-to-text/package.json index b695adf78a..e19cfb7e10 100644 --- a/packages/experimental/speech-to-text/package.json +++ b/packages/experimental/speech-to-text/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-speech-to-text", "description": "Experimental speech recognition with independently selectable providers", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/terminal-bundle/package.json b/packages/experimental/terminal-bundle/package.json index 8ed98077da..0fcbcd3b21 100644 --- a/packages/experimental/terminal-bundle/package.json +++ b/packages/experimental/terminal-bundle/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-terminal-bundle", "description": "Keep shell processes alive across tool calls.", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/tool-agent-team/README.i18n.yaml b/packages/experimental/tool-agent-team/README.i18n.yaml index 5c577e0bdb..601b094b2b 100644 --- a/packages/experimental/tool-agent-team/README.i18n.yaml +++ b/packages/experimental/tool-agent-team/README.i18n.yaml @@ -21,8 +21,8 @@ en: c27d5457e1183675 zh: 3c36d513e6400446 /deepseek-ai-dsh-experimental-tool-agent-team/use-this-package/smallest-working-example: - en: 8dca5e3fa72a3bb7 - zh: 51cdf2eb834dd746 + en: 8d114962d7a4786f + zh: 2dd794e95ef87c08 /deepseek-ai-dsh-experimental-tool-agent-team/use-this-package/what-the-model-can-do: en: 624b472c31463fbc zh: a066b1d9c577461e @@ -81,8 +81,8 @@ en: d78dd9222b456c0e zh: 53ac8c403c7b8afd /deepseek-ai-dsh-experimental-tool-agent-team/model-experience/task-and-wait-tool-schemas/what-the-model-sees: - en: f66d6638425ca815 - zh: 3d64f4a53c074b72 + en: c93f3d9a17c74c13 + zh: 952bd88f17972328 /deepseek-ai-dsh-experimental-tool-agent-team/model-experience/task-and-wait-tool-schemas/token-effect: en: 625799dbf6992c66 zh: cf32cf61d1200121 diff --git a/packages/experimental/tool-agent-team/README.md b/packages/experimental/tool-agent-team/README.md index 63acc779f6..9e7fab9d1d 100644 --- a/packages/experimental/tool-agent-team/README.md +++ b/packages/experimental/tool-agent-team/README.md @@ -40,7 +40,7 @@ The plugin takes no configuration; mount it beside the Team domain as in the [ag name: '@deepseek-ai/dsh-experimental-tool-agent-team' ``` -A row that sets `interrupt`, `freshProvider`, or `forkProvider` does not activate, and dsh names the key in its startup warning. The [upgrade guide](../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.md) maps the removed tools and keys to their replacements, and the generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-experimental-tool-agent-team) records the empty configuration. +A row that sets `interrupt`, `freshProvider`, or `forkProvider` does not activate, and dsh names the key in its startup warning. The [upgrade guide](../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.md) maps the removed tools and keys to their replacements, and the generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-experimental-tool-agent-team) records the empty configuration. Ask the Lead to start a subagent to review the diff, add a review task, and wait for its result. The `description` explains the subagent’s work; the returned id addresses it. @@ -122,13 +122,13 @@ Every Team member's system prompt carries one `team:policy` section; the Lead an ##### Team policy ```markdown -Agent Teams is available in this session. Teammates are subagents. Use agent names with send_message to reach your direct continuable children or, as a continuable subagent, your parent and continuable siblings. Descriptions explain responsibilities and may repeat. +Agent Teams is available in this session. Teammates are subagents. Use list_agents to discover the entire Team and send_message with an agent name to contact any other member whose branch supports continuation. Descriptions explain responsibilities and may repeat. The Team Lead and all teammates share the same working directory and filesystem. Edits are immediately visible to every member. Split write work into disjoint scopes, record expected write scopes on shared tasks, and use task dependencies when work must be ordered. Write-scope overlap is advisory, not a lock. Prefer read/edit/write for file changes. If a file operation returns FS_STALE_VERSION, read the current file, rebase your intended change onto the new content, and retry. Bash, formatters, code generators, and scripts are not fully protected by the filesystem version guard; coordinate them explicitly and have the Lead review the final diff and run tests. -Use agent names as task owners; the root agent is named lead. The Lead can reassign to itself, its direct continuable children, or historical members; other descendants claim tasks themselves. list_agents shows direct contacts and all reachable continuable descendants; discovery does not grant message or assignment authority. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. A successful send does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, make sure another required member is running; use send_message first for an inactive direct contact, or coordinate through its parent for a deeper member. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member is running. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer. +Use agent names as task owners; the root agent is named lead. The Lead can reassign to itself, its direct continuable children, or historical members; other descendants claim tasks themselves. list_agents includes the root and all reachable continuable descendants, with parent names and depth; discovery does not grant assignment authority. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. A successful send does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, make sure another required member is running; use send_message first to wake a required inactive member. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member is running. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer. ``` #### Token effect @@ -157,7 +157,7 @@ Identity arrives in a runtime-context snapshot after the request prefix, so a `s #### What the model sees -Every Team member receives the same five schemas, listed in the [generated tool catalog](../../../docs/tool-catalog.md#deepseek-aidsh-experimental-tool-agent-team). Results are compact JSON task views, task pages, or wait values. When no other member is running, `wait_agent` returns `noProgress` with reason `no-active-peer` and the message `No other Team member is running. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake a required inactive direct contact before waiting again; coordinate deeper members through their parent.` Lead-only `reassign` is enforced at execution, not by a different schema. +Every Team member receives the same five schemas, listed in the [generated tool catalog](../../../docs/tool-catalog.md#deepseek-aidsh-experimental-tool-agent-team). Results are compact JSON task views, task pages, or wait values. When no other member is running, `wait_agent` returns `noProgress` with reason `no-active-peer` and the message `No other Team member is running. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake a required inactive member before waiting again.` Lead-only `reassign` is enforced at execution, not by a different schema. #### Token effect diff --git a/packages/experimental/tool-agent-team/README.zh.md b/packages/experimental/tool-agent-team/README.zh.md index 20a6761ccd..b3256b7a52 100644 --- a/packages/experimental/tool-agent-team/README.zh.md +++ b/packages/experimental/tool-agent-team/README.zh.md @@ -40,7 +40,7 @@ kind: "package-reference" name: '@deepseek-ai/dsh-experimental-tool-agent-team' ``` -设置 `interrupt`、`freshProvider` 或 `forkProvider` 的配置行不会激活,dsh 会在启动警告中指出该键。[升级指南](../../../docs/upgrade-guide/v0.2.1-alpha.4/agent-team-subagents/guide.zh.md)把已移除的工具与键对应到替代方式,生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-experimental-tool-agent-team)记录空配置。 +设置 `interrupt`、`freshProvider` 或 `forkProvider` 的配置行不会激活,dsh 会在启动警告中指出该键。[升级指南](../../../docs/upgrade-guide/v0.2.1-rc.1/agent-team-subagents/guide.zh.md)把已移除的工具与键对应到替代方式,生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-experimental-tool-agent-team)记录空配置。 让 Lead 启动子代理审查差异、创建审查任务并等待结果。`description` 说明子代理的工作;返回的 id 用于寻址。 @@ -122,13 +122,13 @@ member scope 上的一个 `team:policy` 段落和一个必需的 `team:identity` ##### Team 策略 ```markdown -Agent Teams is available in this session. Teammates are subagents. Use agent names with send_message to reach your direct continuable children or, as a continuable subagent, your parent and continuable siblings. Descriptions explain responsibilities and may repeat. +Agent Teams is available in this session. Teammates are subagents. Use list_agents to discover the entire Team and send_message with an agent name to contact any other member whose branch supports continuation. Descriptions explain responsibilities and may repeat. The Team Lead and all teammates share the same working directory and filesystem. Edits are immediately visible to every member. Split write work into disjoint scopes, record expected write scopes on shared tasks, and use task dependencies when work must be ordered. Write-scope overlap is advisory, not a lock. Prefer read/edit/write for file changes. If a file operation returns FS_STALE_VERSION, read the current file, rebase your intended change onto the new content, and retry. Bash, formatters, code generators, and scripts are not fully protected by the filesystem version guard; coordinate them explicitly and have the Lead review the final diff and run tests. -Use agent names as task owners; the root agent is named lead. The Lead can reassign to itself, its direct continuable children, or historical members; other descendants claim tasks themselves. list_agents shows direct contacts and all reachable continuable descendants; discovery does not grant message or assignment authority. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. A successful send does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, make sure another required member is running; use send_message first for an inactive direct contact, or coordinate through its parent for a deeper member. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member is running. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer. +Use agent names as task owners; the root agent is named lead. The Lead can reassign to itself, its direct continuable children, or historical members; other descendants claim tasks themselves. list_agents includes the root and all reachable continuable descendants, with parent names and depth; discovery does not grant assignment authority. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. A successful send does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, make sure another required member is running; use send_message first to wake a required inactive member. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member is running. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer. ``` #### Token 影响 @@ -157,7 +157,7 @@ Use agent names as task owners; the root agent is named lead. The Lead can reass #### 模型看到什么 -每个 Team 成员都收到相同的五个 schema,列在[生成的工具目录](../../../docs/tool-catalog.zh.md#deepseek-aidsh-experimental-tool-agent-team)中。结果是紧凑 JSON 任务视图、任务分页或等待值。当没有其他成员 running 时,`wait_agent` 返回 reason 为 `no-active-peer` 的 `noProgress` 以及消息 `No other Team member is running. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake a required inactive direct contact before waiting again; coordinate deeper members through their parent.`。仅限 Lead 的 `reassign` 在执行时落实,而不是通过不同的 schema。 +每个 Team 成员都收到相同的五个 schema,列在[生成的工具目录](../../../docs/tool-catalog.zh.md#deepseek-aidsh-experimental-tool-agent-team)中。结果是紧凑 JSON 任务视图、任务分页或等待值。当没有其他成员 running 时,`wait_agent` 返回 reason 为 `no-active-peer` 的 `noProgress` 以及消息 `No other Team member is running. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake a required inactive member before waiting again.`。仅限 Lead 的 `reassign` 在执行时落实,而不是通过不同的 schema。 #### Token 影响 diff --git a/packages/experimental/tool-agent-team/package.json b/packages/experimental/tool-agent-team/package.json index 9add7daa25..fd11d45aa8 100644 --- a/packages/experimental/tool-agent-team/package.json +++ b/packages/experimental/tool-agent-team/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-tool-agent-team", "description": "Scoped model-facing Agent Teams tools over ctx.agentTeams", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/tool-agent-team/src/index.ts b/packages/experimental/tool-agent-team/src/index.ts index 0ee5f4308b..6788e841ec 100644 --- a/packages/experimental/tool-agent-team/src/index.ts +++ b/packages/experimental/tool-agent-team/src/index.ts @@ -24,15 +24,15 @@ export const Config: z = z.object({}) const REMOVED_CONFIG_KEYS = ['interrupt', 'freshProvider', 'forkProvider'] as const /** Model-facing collaboration guidance shared by Lead and teammates. */ -const TEAM_POLICY = `Agent Teams is available in this session. Teammates are subagents. Use agent names with send_message to reach your direct continuable children or, as a continuable subagent, your parent and continuable siblings. Descriptions explain responsibilities and may repeat. +const TEAM_POLICY = `Agent Teams is available in this session. Teammates are subagents. Use list_agents to discover the entire Team and send_message with an agent name to contact any other member whose branch supports continuation. Descriptions explain responsibilities and may repeat. The Team Lead and all teammates share the same working directory and filesystem. Edits are immediately visible to every member. Split write work into disjoint scopes, record expected write scopes on shared tasks, and use task dependencies when work must be ordered. Write-scope overlap is advisory, not a lock. Prefer read/edit/write for file changes. If a file operation returns FS_STALE_VERSION, read the current file, rebase your intended change onto the new content, and retry. Bash, formatters, code generators, and scripts are not fully protected by the filesystem version guard; coordinate them explicitly and have the Lead review the final diff and run tests. -Use agent names as task owners; the root agent is named lead. The Lead can reassign to itself, its direct continuable children, or historical members; other descendants claim tasks themselves. list_agents shows direct contacts and all reachable continuable descendants; discovery does not grant message or assignment authority. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. A successful send does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, make sure another required member is running; use send_message first for an inactive direct contact, or coordinate through its parent for a deeper member. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member is running. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer.` +Use agent names as task owners; the root agent is named lead. The Lead can reassign to itself, its direct continuable children, or historical members; other descendants claim tasks themselves. list_agents includes the root and all reachable continuable descendants, with parent names and depth; discovery does not grant assignment authority. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. A successful send does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, make sure another required member is running; use send_message first to wake a required inactive member. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member is running. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer.` -const NO_ACTIVE_PEER_MESSAGE = 'No other Team member is running. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake a required inactive direct contact before waiting again; coordinate deeper members through their parent.' +const NO_ACTIVE_PEER_MESSAGE = 'No other Team member is running. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake a required inactive member before waiting again.' /** One shared task, matching the public `TeamTaskView`. */ const TASK_VIEW_SCHEMA = { diff --git a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts index 980f083156..c058f30b2b 100644 --- a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts +++ b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts @@ -321,7 +321,7 @@ describe('dsh-tool-team', () => { timedOut: false, noProgress: { reason: 'no-active-peer', - message: 'No other Team member is running. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake a required inactive direct contact before waiting again; coordinate deeper members through their parent.', + message: 'No other Team member is running. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake a required inactive member before waiting again.', }, }) for (const timeout_ms of [9_999, 3_600_001, Number.MAX_SAFE_INTEGER + 1]) { diff --git a/packages/experimental/tool-ralph/package.json b/packages/experimental/tool-ralph/package.json index 6a2e7e235f..230b43c038 100644 --- a/packages/experimental/tool-ralph/package.json +++ b/packages/experimental/tool-ralph/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-ralph", "description": "Model-facing fresh-agent Ralph loop over the workflow and subagent seams", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/tool-session-query/package.json b/packages/experimental/tool-session-query/package.json index 62590f4cbb..7655cb2d26 100644 --- a/packages/experimental/tool-session-query/package.json +++ b/packages/experimental/tool-session-query/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-session-query", "description": "Workspace-authorized model-facing session history search, trace, and event read tools", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/tool-terminal/package.json b/packages/experimental/tool-terminal/package.json index 76fce409a6..056e021123 100644 --- a/packages/experimental/tool-terminal/package.json +++ b/packages/experimental/tool-terminal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-terminal", "description": "Six model-facing persistent PTY tools with owner isolation and generic background-job integration", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/tool-worktree/package.json b/packages/experimental/tool-worktree/package.json index e63f78d334..403297d4b2 100644 --- a/packages/experimental/tool-worktree/package.json +++ b/packages/experimental/tool-worktree/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-tool-worktree", "description": "Model tool that creates and enters a new Git worktree", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/translator/package.json b/packages/experimental/translator/package.json index 03650a27cf..1e662b829e 100644 --- a/packages/experimental/translator/package.json +++ b/packages/experimental/translator/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-translator", "description": "Machine translation with reusable Session results", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/voice-input-bundle/package.json b/packages/experimental/voice-input-bundle/package.json index 7e6efbd187..630d11c969 100644 --- a/packages/experimental/voice-input-bundle/package.json +++ b/packages/experimental/voice-input-bundle/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-voice-input-bundle", "description": "Experimental voice input with local SenseVoice; downloads its runtime on first use", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "icon": "./icon.svg", "publishConfig": { "access": "public" diff --git a/packages/experimental/webhook-github/package.json b/packages/experimental/webhook-github/package.json index 191dca396d..ec4f12fd53 100644 --- a/packages/experimental/webhook-github/package.json +++ b/packages/experimental/webhook-github/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-webhook-github", "description": "Signed GitHub HTTP webhook adapter for the DeepSeek Harness webhook runtime", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/webhook/package.json b/packages/experimental/webhook/package.json index 3621ab6a5c..1f01f4b1a5 100644 --- a/packages/experimental/webhook/package.json +++ b/packages/experimental/webhook/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-webhook", "description": "Fire-and-forget webhook rule runtime that creates Workspace-backed DeepSeek Harness Sessions", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/webworker-packer/package.json b/packages/experimental/webworker-packer/package.json index a1d1336328..187bec13cb 100644 --- a/packages/experimental/webworker-packer/package.json +++ b/packages/experimental/webworker-packer/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-webworker-packer", "description": "Build-time packer for the browser runtime's base VFS image and ordered data-overlay archives", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/webworker-runtime/package.json b/packages/experimental/webworker-runtime/package.json index 2222ec4a7a..c9be49d489 100644 --- a/packages/experimental/webworker-runtime/package.json +++ b/packages/experimental/webworker-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-webworker-runtime", "description": "Browser-only harness runtime: in-memory VFS, module transform and loader, postMessage tunnel, and the dedicated Web Worker assembly, with the Node-compatibility layer that lets the host tree run unchanged", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/worktree/package.json b/packages/experimental/worktree/package.json index 57edad51a5..9f5e9208ea 100644 --- a/packages/experimental/worktree/package.json +++ b/packages/experimental/worktree/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-worktree", "description": "Creates a named Git worktree and changes the calling Session working directory", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/extensions/cordis-client-runner/package.json b/packages/extensions/cordis-client-runner/package.json index c3429ae652..6f7da47073 100644 --- a/packages/extensions/cordis-client-runner/package.json +++ b/packages/extensions/cordis-client-runner/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-cordis-client-runner", "description": "Browser half of dynamic dual-half plugin packages: event subscription, closure evaluation, guard facade, and loader entries", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts index ce9bbd92d8..de1387b27a 100644 --- a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts +++ b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts @@ -1231,6 +1231,49 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [ example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.model\', () => ctx.slots.register(\n { name: \'conversation.input.model\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}', source: 'packages/client/ui-conversation/src/client/contract/slots.ts:298', }, + { + key: 'conversation.input.model.label', + kind: 'single', + scope: 'session', + summary: 'Non-interactive inline model label; selection controls remain owner-managed.', + doc: 'Non-interactive inline model label; selection controls remain owner-managed.\nWithout a contribution, the composer displays the catalog name.', + registerOptions: [], + ownerProps: [ + '/** Catalog data for a model name inside the composer\'s existing controls. */\nexport interface ModelLabelOwner {\n /** Provider identity and localized display name; contains no connection configuration. */\n provider: Pick\n /** The exact provider\'s model catalog entry. */\n model: ModelCatalogModel\n /** Trigger caption, root-menu current value, or selectable model row. */\n placement: \'trigger\' | \'menu\' | \'option\'\n /** Whether this provider/model pair is the current selection. */\n selected: boolean\n}', + ], + ownerPropsReferences: [ + 'ModelCatalogModel', + 'ModelProviderGroup', + ], + standardProps: [ + 'useRemoteAccess: (action: RemoteEndpoint) => boolean', + 'useResource: UseResource', + 'useWorkspaces: SnapshotSelectorHook', + 'usePanelInfo: UsePanelInfo', + 'useSessions: UseSessions', + 'useSessionStatus: UseSessionStatus', + 'useSessionRetainInfo: UseSessionRetainInfo', + 'useSessionAccess: (sessionId: SessionId, action: RemoteEndpoint) => boolean', + 'useWorkspaceAccess: (workspaceId: WorkspaceId, action: RemoteEndpoint) => boolean', + 'useChat: UseChat', + 'useConversation: UseConversation', + 'useInput: SnapshotSelectorHook', + 'inputActions: InputActions', + 'useSession: SessionSnapshotSelector', + 'sessionId: SessionId', + 'useProjection: UseProjection', + 'useAccess: (action: RemoteEndpoint) => boolean', + 'useTrajectory: UseTrajectory', + ], + keyDomain: '', + hookContext: '', + slotInject: '', + declaredBy: 'an entry in \'conversation.input.model\' (client-ui-model-selection), so it exists while that entry is mounted', + occupants: [], + replaceRisk: 'none', + example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.model.label\', () => ctx.slots.register(\n { name: \'conversation.input.model.label\' },\n ({ provider, model }) => React.createElement(\'span\', { title: provider.name }, model.name),\n ))\n },\n}', + source: 'packages/client/ui-model-selection/src/client/slots.ts:36', + }, { key: 'conversation.input.overlay', kind: 'list', diff --git a/packages/extensions/cordis-host-runner/package.json b/packages/extensions/cordis-host-runner/package.json index b27e6de52b..fd314ebf7c 100644 --- a/packages/extensions/cordis-host-runner/package.json +++ b/packages/extensions/cordis-host-runner/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-cordis-host-runner", "description": "Dynamic package definition registry, host-half sandbox lifecycle, and invoke handler table for model-mounted dual-half packages", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/extensions/tool-cordis/package.json b/packages/extensions/tool-cordis/package.json index 7a4bfdbf5a..fd3fd7b4d9 100644 --- a/packages/extensions/tool-cordis/package.json +++ b/packages/extensions/tool-cordis/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-cordis", "description": "Read-only runtime API inspection for Harness plugin development", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index 871ecb7068..b7319fd3b3 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -3030,6 +3030,13 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ returns: 'the durable Session identity for the consuming operation to authorize.', throws: ['when the name is absent or the caller\'s live tree is unavailable.'], }, + { + signature: 'rootOf(caller: Agent): Agent', + description: 'Resolve the root shared by an Agent and its teammates.', + parameters: [{ name: 'caller', description: 'exact live Agent whose subagent ancestry selects the tree.' }], + returns: 'the live root; ordinary Session forks lead independent trees.', + throws: ['when the caller or its live ancestry is unavailable.'], + }, { signature: 'async waitForChildren(parent: Agent): Promise', description: 'Join progressing descendants without cancelling them. Idle descendants whose inboxes require a later wake stay resident and do not delay host completion.', @@ -3038,10 +3045,10 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, { signature: 'async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions, ): Promise', - description: 'Steer one model-authored message to the sender\'s direct parent, direct continuable child, or continuable sibling under their shared live parent. A running target admits it at the nearest step boundary; an idle target starts a turn, and an absent direct child cold-resumes from persistence. The service derives durable sender attribution from the exact live sender. Caller cancellation stops only pre-acceptance work.', - parameters: [{ name: 'sender', description: 'exact live Agent authorizing and originating the message.' }, { name: 'targetId', description: 'durable direct-parent, direct-child, or sibling session id.' }, { name: 'content', description: 'model-authored content to deliver.' }, { name: 'options', description: 'caller cancellation before inbox acceptance.' }], + description: 'Steer one model-authored message to another member of the same subagent tree. A running target receives it at its next step; an idle target starts a turn. Cold targets and their continuable ancestors resume under their recorded parents. Sender identity is rechecked before inbox acceptance; cancellation stops only pre-acceptance work and never retracts an accepted message.', + parameters: [{ name: 'sender', description: 'exact live root or resident continuable Agent originating the message.' }, { name: 'targetId', description: 'another root or continuable member\'s durable Session id.' }, { name: 'content', description: 'model-authored content to deliver.' }, { name: 'options', description: 'caller cancellation before inbox acceptance.' }], returns: 'the accepted message\'s inbox id.', - throws: ['when continuation services are unavailable, adjacency is rejected, or the message was not admitted.'], + throws: ['when the target is outside the tree, cannot resume, or rejects input.'], }, { signature: 'interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void', @@ -4658,7 +4665,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'AccountBonusNotification', - declaration: 'export interface AccountBonusNotification {\n readonly orderId: AccountBonusOrderId;\n readonly campaign: string;\n readonly amount: string;\n readonly currency: \'CNY\' | \'USD\';\n readonly grantedAt: string;\n readonly expiresAt: string;\n readonly message: string;\n}', + declaration: 'export interface AccountBonusNotification {\n readonly title?: string;\n readonly orderId: AccountBonusOrderId;\n readonly campaign?: JsonValue;\n readonly amount?: JsonValue;\n readonly currency?: JsonValue;\n readonly grantedAt?: JsonValue;\n readonly expiresAt: string;\n readonly message: string;\n}', }, { name: 'AccountBonusOrderId', @@ -7870,7 +7877,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'SubagentRuntime', - declaration: 'export class SubagentRuntime extends TypertRemoteService {\n static Config;\n static inject;\n constructor(ctx: Context, private config: Config);\n resolveMaxDepth(configured?: number | \'provider-managed\'): number | undefined;\n startActivation(spec: SubagentActivationSpec & {\n readonly childId: SessionId;\n }): Promise;\n startActivation(spec: SubagentActivationSpec): Promise;\n async startActivation(spec: SubagentActivationSpec): Promise;\n nameOf(agent: Agent): string;\n resolveName(caller: Agent, name: string, signal?: AbortSignal): Promise;\n async waitForChildren(parent: Agent): Promise;\n async sendMessage(sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions): Promise;\n interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void;\n async drainDescendants(parents: readonly Agent[]): Promise;\n async drainChildren(parent: Agent, childIds: readonly SessionId[]): Promise;\n listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise;\n listDescendants(rootSessionId: SessionId, signal?: AbortSignal): Promise;\n @Remote(\'prompt\')\n async prompt(request: SubagentPromptRequest, signal: AbortSignal): Promise;\n @Remote(\'interru /* …truncated — full shape in source */', + declaration: 'export class SubagentRuntime extends TypertRemoteService {\n static Config;\n static inject;\n constructor(ctx: Context, private config: Config);\n resolveMaxDepth(configured?: number | \'provider-managed\'): number | undefined;\n startActivation(spec: SubagentActivationSpec & {\n readonly childId: SessionId;\n }): Promise;\n startActivation(spec: SubagentActivationSpec): Promise;\n async startActivation(spec: SubagentActivationSpec): Promise;\n nameOf(agent: Agent): string;\n resolveName(caller: Agent, name: string, signal?: AbortSignal): Promise;\n rootOf(caller: Agent): Agent;\n async waitForChildren(parent: Agent): Promise;\n async sendMessage(sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions): Promise;\n interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void;\n async drainDescendants(parents: readonly Agent[]): Promise;\n async drainChildren(parent: Agent, childIds: readonly SessionId[]): Promise;\n listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise;\n listDescendants(rootSessionId: SessionId, signal?: AbortSignal): Promise;\n @Remote(\'prompt\')\n async prompt(request: SubagentPromptRequest, signal: AbortSignal): Promise ctx.tools.get('read', scope) === undefined ? '' - : 'Use the read tool — not shell commands like cat, sed, head, or tail — to inspect text files. Use offset and limit to read a line range or continue a large file.', + : 'Use the read tool — not shell commands like cat — to inspect text files. Use offset and limit to continue reading large files.', }) ctx.tools.register(defineTool({ diff --git a/packages/fs/tool-fs/tests/tools.spec.ts b/packages/fs/tool-fs/tests/tools.spec.ts index 83d22de89d..2ea469c3f8 100644 --- a/packages/fs/tool-fs/tests/tools.spec.ts +++ b/packages/fs/tool-fs/tests/tools.spec.ts @@ -1066,7 +1066,7 @@ async function guidanceScope(ctx: Context) { } const originalGuidance = { - read: 'Use the read tool — not shell commands like cat, sed, head, or tail — to inspect text files. Use offset and limit to read a line range or continue a large file.', + read: 'Use the read tool — not shell commands like cat — to inspect text files. Use offset and limit to continue reading large files.', write: 'Read an existing file before overwriting it with write (the default fs-observation-policy requires it) and prefer edit for targeted changes.', edit: 'Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session.', } diff --git a/packages/fs/tool-str-replace-editor/package.json b/packages/fs/tool-str-replace-editor/package.json index 6f7fe48547..d3ea59c1a2 100644 --- a/packages/fs/tool-str-replace-editor/package.json +++ b/packages/fs/tool-str-replace-editor/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-str-replace-editor", "description": "Model-facing view, create, literal replace, and line insert tool over the Harness filesystem service", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/goal/command-goal/package.json b/packages/goal/command-goal/package.json index 0e0f04b171..158899b605 100644 --- a/packages/goal/command-goal/package.json +++ b/packages/goal/command-goal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-command-goal", "description": "Human-facing slash command for persisted same-session goals", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/goal/goal-round-driver/package.json b/packages/goal/goal-round-driver/package.json index b51c188750..47d1e1a645 100644 --- a/packages/goal/goal-round-driver/package.json +++ b/packages/goal/goal-round-driver/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-goal-round-driver", "description": "Race-fenced same-session goal-round driver", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/goal/goal/package.json b/packages/goal/goal/package.json index 8f1a960439..0dd48dd463 100644 --- a/packages/goal/goal/package.json +++ b/packages/goal/goal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-goal", "description": "Event-sourced same-session goal state and lifecycle service for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/goal/tool-goal/package.json b/packages/goal/tool-goal/package.json index 868c577377..5bcf9e7269 100644 --- a/packages/goal/tool-goal/package.json +++ b/packages/goal/tool-goal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-goal", "description": "Model-facing same-session goal tools with execution-time authority checks", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/guard/repeat-tool-reminder/package.json b/packages/guard/repeat-tool-reminder/package.json index 17ab614206..cdaed0d0a0 100644 --- a/packages/guard/repeat-tool-reminder/package.json +++ b/packages/guard/repeat-tool-reminder/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-repeat-tool-reminder", "description": "Repeat-tool-call guard plugin: advisory reminders when an agent loops on identical tool calls", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/guard/timeout-policy/package.json b/packages/guard/timeout-policy/package.json index d4f798f478..56a4c0e74d 100644 --- a/packages/guard/timeout-policy/package.json +++ b/packages/guard/timeout-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-call-timeout-policy", "description": "Tool-call timeout policy: a tools/execute wrapper that arms a per-tool deadline on exec.signal and returns TOOL_TIMEOUT when it wins", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/directory-picker-auto/package.json b/packages/host/directory-picker-auto/package.json index 7330a1df88..cd4d49bd21 100644 --- a/packages/host/directory-picker-auto/package.json +++ b/packages/host/directory-picker-auto/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-directory-picker-auto", "description": "Adaptive chooser of the directory-picker seam: resolves the host situation at boot and mounts the native or browse backend for the DeepSeek Harness web GUI host", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/directory-picker-browse/package.json b/packages/host/directory-picker-browse/package.json index 39bd53f702..dfe539972a 100644 --- a/packages/host/directory-picker-browse/package.json +++ b/packages/host/directory-picker-browse/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-directory-picker-browse", "description": "In-app browsing backend of the directory-picker seam (listing/creation primitives over the host filesystem)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/directory-picker-native/package.json b/packages/host/directory-picker-native/package.json index 7404e7f35e..8714e0c467 100644 --- a/packages/host/directory-picker-native/package.json +++ b/packages/host/directory-picker-native/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-directory-picker-native", "description": "Native-OS-chooser backend of the directory-picker seam for the DeepSeek Harness web GUI host", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/directory-picker/package.json b/packages/host/directory-picker/package.json index dbe295059c..e25ac65ea3 100644 --- a/packages/host/directory-picker/package.json +++ b/packages/host/directory-picker/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-directory-picker", "description": "Abstract workspace-directory picking seam (ctx.directoryPicker) for the DeepSeek Harness web GUI host", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/frontend-static/package.json b/packages/host/frontend-static/package.json index 56c3f758c6..6d71ebfe41 100644 --- a/packages/host/frontend-static/package.json +++ b/packages/host/frontend-static/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-frontend-static", "description": "SPA dist server for the Web shell: owns the webserver fallback seat, serving explicit index entries and static assets with traversal rejection and 404 misses", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/open-in-app/package.json b/packages/host/open-in-app/package.json index 197809be00..1d4d689406 100644 --- a/packages/host/open-in-app/package.json +++ b/packages/host/open-in-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-open-in-app", "description": "Host half of open-in-app: resolved application catalog, icons, and the launch endpoint as three webServer routes", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/plugin-inventory/package.json b/packages/host/plugin-inventory/package.json index 19f402019b..ebb1c5f284 100644 --- a/packages/host/plugin-inventory/package.json +++ b/packages/host/plugin-inventory/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-plugin-inventory", "description": "Read-only Remote projection of current Cordis Loader plugin state", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/product-telemetry-otel/package.json b/packages/host/product-telemetry-otel/package.json index 1f19f186ec..a329de59e2 100644 --- a/packages/host/product-telemetry-otel/package.json +++ b/packages/host/product-telemetry-otel/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-product-telemetry-otel", "description": "Explicit product usage events exported through OpenTelemetry HTTP logs", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/webserver/package.json b/packages/host/webserver/package.json index 5bdee3e5ae..35590faec0 100644 --- a/packages/host/webserver/package.json +++ b/packages/host/webserver/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-webserver", "description": "Web route-registration plugin: HTTP and upgrade routes, index transform taps, and static dist fallback; knows no harness concepts", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/identity/anonymous-user-id/package.json b/packages/identity/anonymous-user-id/package.json index 00cd0da5a9..cf15496efe 100644 --- a/packages/identity/anonymous-user-id/package.json +++ b/packages/identity/anonymous-user-id/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-anonymous-user-id", "description": "Shared anonymous user identity for DeepSeek Harness telemetry and feedback correlation", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/commands/package.json b/packages/interaction/commands/package.json index 36acaabb15..56066b41ac 100644 --- a/packages/interaction/commands/package.json +++ b/packages/interaction/commands/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-commands", "description": "Plugin-owned human command registry for DeepSeek Harness UIs", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/permission-presets/package.json b/packages/interaction/permission-presets/package.json index f38ce78c27..8426b5dbfc 100644 --- a/packages/interaction/permission-presets/package.json +++ b/packages/interaction/permission-presets/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-permission-presets", "description": "User-facing permission presets (ctx.permissionPresets) for the DeepSeek Harness: one product-level Permissions select bundling the sandbox-mode and approval-policy knobs, written through to their own session events", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/tool-ask-user/package.json b/packages/interaction/tool-ask-user/package.json index 65719ca8f1..2cf097b48f 100644 --- a/packages/interaction/tool-ask-user/package.json +++ b/packages/interaction/tool-ask-user/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-ask-user", "description": "Model-facing ask_user_question tool over the ctx.userQuestions seam", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/user-approval/package.json b/packages/interaction/user-approval/package.json index 1fbdfa7929..91f6d03ca1 100644 --- a/packages/interaction/user-approval/package.json +++ b/packages/interaction/user-approval/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-user-approval", "description": "User-approval seam (ctx.approval) for the DeepSeek Harness: one-shot permission decisions dispatched to composed answerers over the approval/request waterfall, fail-closed by default", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/user-questions/package.json b/packages/interaction/user-questions/package.json index 4bd9437d00..a8a70718d8 100644 --- a/packages/interaction/user-questions/package.json +++ b/packages/interaction/user-questions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-user-questions", "description": "Abstract user-questions seam (ctx.userQuestions) for asking the human during agent runs", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/jobs/jobs-local/package.json b/packages/jobs/jobs-local/package.json index d740b03c8e..a3b7eede3c 100644 --- a/packages/jobs/jobs-local/package.json +++ b/packages/jobs/jobs-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-jobs-local", "description": "Process-local implementation of the DeepSeek Harness background job registry seam", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/jobs/jobs/package.json b/packages/jobs/jobs/package.json index 49769beee8..8a4a985d21 100644 --- a/packages/jobs/jobs/package.json +++ b/packages/jobs/jobs/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-jobs", "description": "Background job registry (ctx.jobs) for the DeepSeek Harness — shared ids, owner isolation, polling, cancellation, and completion listeners for long-running tool work", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/jobs/tool-jobs/package.json b/packages/jobs/tool-jobs/package.json index 853bad705b..3777df97fd 100644 --- a/packages/jobs/tool-jobs/package.json +++ b/packages/jobs/tool-jobs/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-jobs", "description": "Model-facing background job control tools (job_output, job_list, job_kill) over the ctx.jobs registry", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/deepseek-llm-api-extensions/package.json b/packages/llm/deepseek-llm-api-extensions/package.json index d53ca93c43..e9bbc52567 100644 --- a/packages/llm/deepseek-llm-api-extensions/package.json +++ b/packages/llm/deepseek-llm-api-extensions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-deepseek-llm-api-extensions", "description": "Additive request-field registry for the official DeepSeek LLM API adapter", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm-deepseek-account/package.json b/packages/llm/llm-deepseek-account/package.json index 80eb137897..7b3f58f0c0 100644 --- a/packages/llm/llm-deepseek-account/package.json +++ b/packages/llm/llm-deepseek-account/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-deepseek-account", "description": "DeepSeek account provider authentication and discovery", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm-deepseek-account/tests/resolve-catalog.spec.ts b/packages/llm/llm-deepseek-account/tests/resolve-catalog.spec.ts index 4306193146..e1c7ebc6a8 100644 --- a/packages/llm/llm-deepseek-account/tests/resolve-catalog.spec.ts +++ b/packages/llm/llm-deepseek-account/tests/resolve-catalog.spec.ts @@ -28,7 +28,7 @@ describe('without a listing', () => { it('offers the bundled catalog', () => { const catalog = resolve(undefined) expect(catalog.offered.map(model => [model.id, model.name, model.contextWindow])).toEqual([ - ['deepseek-flash', 'DeepSeek-V4.1-Flash', 1_000_000], ['deepseek-v4-pro', 'DeepSeek-V4-Pro', 1_000_000], + ['deepseek-flash', 'DeepSeek-V4.1-Flash-1017', 1_000_000], ['deepseek-v4-pro', 'DeepSeek-V4-Pro', 1_000_000], ]) expect(catalog.resolution).toEqual(catalog.offered) expect(catalog.refusals.size).toBe(0) diff --git a/packages/llm/llm-deepseek-api-key/package.json b/packages/llm/llm-deepseek-api-key/package.json index 82510819ad..3b8052933e 100644 --- a/packages/llm/llm-deepseek-api-key/package.json +++ b/packages/llm/llm-deepseek-api-key/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-deepseek-api-key", "description": "DeepSeek api-key provider authentication and discovery", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm-deepseek/package.json b/packages/llm/llm-deepseek/package.json index 762301b366..ac1ca512fb 100644 --- a/packages/llm/llm-deepseek/package.json +++ b/packages/llm/llm-deepseek/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-deepseek", "description": "DeepSeek Messages adapter", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm-deepseek/src/models.ts b/packages/llm/llm-deepseek/src/models.ts index 15d0c78253..1b2d8ab241 100644 --- a/packages/llm/llm-deepseek/src/models.ts +++ b/packages/llm/llm-deepseek/src/models.ts @@ -6,7 +6,7 @@ import type { DeepSeekCatalogModel } from './types.ts' export const DEFAULT_MODELS: DeepSeekCatalogModel[] = [ { id: 'deepseek-flash', - name: 'DeepSeek-V4.1-Flash', + name: 'DeepSeek-V4.1-Flash-1017', contextWindow: DEFAULT_CONTEXT_WINDOW, inputModalities: ['text', 'image'], systemPromptUpdate: 'in-history', diff --git a/packages/llm/llm-deepseek/tests/adapter.spec.ts b/packages/llm/llm-deepseek/tests/adapter.spec.ts index c6a1948ec0..bd1905aaa0 100644 --- a/packages/llm/llm-deepseek/tests/adapter.spec.ts +++ b/packages/llm/llm-deepseek/tests/adapter.spec.ts @@ -141,7 +141,7 @@ describe('direct Messages HTTP', () => { expect(llm.providerInfo('deepseek-official')).toEqual({ id: 'deepseek-official', name: 'DeepSeek' }) expect(await llm.listModels('deepseek-official')).toEqual([]) expect(await llm.resolveModel('deepseek-official', 'deepseek-flash')).toMatchObject({ - name: 'DeepSeek-V4.1-Flash', inputModalities: ['text', 'image'], systemPromptUpdate: 'in-history', + name: 'DeepSeek-V4.1-Flash-1017', inputModalities: ['text', 'image'], systemPromptUpdate: 'in-history', }) expect(await llm.resolveModel('deepseek-official', MODEL)).toMatchObject({ id: MODEL }) expect(llm.imageRequestPricing('deepseek-official', MODEL)).toBeDefined() diff --git a/packages/llm/llm-deepseek/tests/runtime.spec.ts b/packages/llm/llm-deepseek/tests/runtime.spec.ts index 54cec3fe36..96c296e637 100644 --- a/packages/llm/llm-deepseek/tests/runtime.spec.ts +++ b/packages/llm/llm-deepseek/tests/runtime.spec.ts @@ -1794,7 +1794,7 @@ describe('plugin registration and config', () => { await ctx.plugin(LlmDeepSeek, { baseURL: 'http://127.0.0.1:1' }) expect(ctx.llm.listProviders()).toEqual([{ id: 'deepseek-official', name: 'DeepSeek' }]) await expect(ctx.llm.listModels('deepseek-official')).resolves.toEqual([ - { provider: 'deepseek-official', id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash', inputModalities: ['text', 'image'] }, + { provider: 'deepseek-official', id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash-1017', inputModalities: ['text', 'image'] }, { provider: 'deepseek-official', id: 'deepseek-v4-pro', @@ -1807,7 +1807,7 @@ describe('plugin registration and config', () => { .resolves.toMatchObject({ provider: 'deepseek-official', id: 'deepseek-flash', - name: 'DeepSeek-V4.1-Flash', + name: 'DeepSeek-V4.1-Flash-1017', inputModalities: ['text', 'image'], systemPromptUpdate: 'in-history', context: { contextWindow: 1_000_000 }, @@ -1922,7 +1922,7 @@ describe('plugin registration and config', () => { await ctx.plugin(LlmRuntime) LlmDeepSeek.apply(ctx, LlmDeepSeek.Config({ baseURL: 'http://127.0.0.1:1' })) await expect(ctx.llm.listModels('deepseek-official')).resolves.toEqual([ - { provider: 'deepseek-official', id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash', inputModalities: ['text', 'image'] }, + { provider: 'deepseek-official', id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash-1017', inputModalities: ['text', 'image'] }, { provider: 'deepseek-official', id: 'deepseek-v4-pro', @@ -2314,7 +2314,7 @@ describe('plugin registration and config', () => { const adapter = adapterOf() expect(adapter).toBeInstanceOf(DeepSeekAdapter) await expect(adapter.listModels('deepseek-official')).resolves.toEqual([]) - await expect(adapter.resolveModel('deepseek-official', 'deepseek-flash')).resolves.toMatchObject({ name: 'DeepSeek-V4.1-Flash' }) + await expect(adapter.resolveModel('deepseek-official', 'deepseek-flash')).resolves.toMatchObject({ name: 'DeepSeek-V4.1-Flash-1017' }) }) it('resolves connection facts and the credential exactly once per stream call', async () => { diff --git a/packages/llm/llm-pi-ai/package.json b/packages/llm/llm-pi-ai/package.json index 5c3698ba78..fdee0d479b 100644 --- a/packages/llm/llm-pi-ai/package.json +++ b/packages/llm/llm-pi-ai/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-pi-ai", "description": "pi-ai-backed DeepSeek adapter for the DeepSeek Harness LLM seam (design-verification twin of dsh-llm-deepseek)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm-retry/package.json b/packages/llm/llm-retry/package.json index dca3593891..15d32285bc 100644 --- a/packages/llm/llm-retry/package.json +++ b/packages/llm/llm-retry/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-retry", "description": "Provider-routed LLM request retry policy for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm/package.json b/packages/llm/llm/package.json index 3d107ea528..ef2e5156da 100644 --- a/packages/llm/llm/package.json +++ b/packages/llm/llm/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm", "description": "Provider-neutral LLM service interface for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/plugin-package-inventory-deepseek/package.json b/packages/llm/plugin-package-inventory-deepseek/package.json index 57a18fbad9..e76868ec87 100644 --- a/packages/llm/plugin-package-inventory-deepseek/package.json +++ b/packages/llm/plugin-package-inventory-deepseek/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-plugin-package-inventory-deepseek", "description": "Active Loader-backed plugin package inventory for official DeepSeek LLM API requests", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/token-meter/package.json b/packages/llm/token-meter/package.json index 7e211f247e..8da2f8f2e9 100644 --- a/packages/llm/token-meter/package.json +++ b/packages/llm/token-meter/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-token-meter", "description": "Replay-aware token measurement service (ctx.tokenMeter) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/lsp/lsp-stdio/package.json b/packages/lsp/lsp-stdio/package.json index 3361826448..374685f757 100644 --- a/packages/lsp/lsp-stdio/package.json +++ b/packages/lsp/lsp-stdio/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-lsp-stdio", "description": "Generic stdio language-server provider for the DeepSeek Harness LSP capability seam (ctx.lsp) — spawns configured servers, translates JSON-RPC, and serves transient-open goToDefinition/findReferences/goToImplementation/hover queries in the host filesystem namespace", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/lsp/lsp/package.json b/packages/lsp/lsp/package.json index 88c70bdaa1..ef0f817775 100644 --- a/packages/lsp/lsp/package.json +++ b/packages/lsp/lsp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-lsp", "description": "Abstract LSP capability seam (ctx.lsp) for the DeepSeek Harness — language-server provider registry keyed by branded id and extension mapping, order-independent per-query selection, normalized definition/references/implementation/hover requests and results, and the LspError taxonomy", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/lsp/tool-lsp/package.json b/packages/lsp/tool-lsp/package.json index e147b997d2..f64876f471 100644 --- a/packages/lsp/tool-lsp/package.json +++ b/packages/lsp/tool-lsp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-lsp", "description": "Model-facing lsp tool over the DeepSeek Harness LSP capability seam (ctx.lsp) \u2014 one read-only tool with goToDefinition/findReferences/goToImplementation/hover operations, one-based UTF-16 cursor coordinates, bounded location rendering, and hover normalization", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/mcp/mcp-client/package.json b/packages/mcp/mcp-client/package.json index d9fcece76d..26c098892b 100644 --- a/packages/mcp/mcp-client/package.json +++ b/packages/mcp/mcp-client/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-mcp-client", "description": "MCP client bridge: connects to MCP servers and registers their tools on ctx.tools", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/mcp/mcp-resources/package.json b/packages/mcp/mcp-resources/package.json index 0676ad68ed..3f004b275b 100644 --- a/packages/mcp/mcp-resources/package.json +++ b/packages/mcp/mcp-resources/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-mcp-resources", "description": "Scoped MCP resource discovery and reading through shared model tools", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/plan/plan-mode/package.json b/packages/plan/plan-mode/package.json index 7d91ed1602..c82177c243 100644 --- a/packages/plan/plan-mode/package.json +++ b/packages/plan/plan-mode/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-plan-mode", "description": "Logged per-agent plan mode with deployment guidance, a direct slash command, and a user-reviewed exit", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/preset/agent-preset-registry/package.json b/packages/preset/agent-preset-registry/package.json index 3f259cfdeb..c3c6e11414 100644 --- a/packages/preset/agent-preset-registry/package.json +++ b/packages/preset/agent-preset-registry/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-preset-registry", "description": "Declarative Agent preset registry and profile-backed editing", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/preset/agent-preset/package.json b/packages/preset/agent-preset/package.json index 9c604f66ae..c4a8e860ce 100644 --- a/packages/preset/agent-preset/package.json +++ b/packages/preset/agent-preset/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-preset", "description": "Declare an Agent capability composition in Cordis YAML", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/preset/persona/package.json b/packages/preset/persona/package.json index e0a0328bce..1d913b9c28 100644 --- a/packages/preset/persona/package.json +++ b/packages/preset/persona/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-persona", "description": "Composition-authored deployment persona section for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/ptc-runtime/ptc-runtime-node/package.json b/packages/ptc-runtime/ptc-runtime-node/package.json index 1115706424..542e83247f 100644 --- a/packages/ptc-runtime/ptc-runtime-node/package.json +++ b/packages/ptc-runtime/ptc-runtime-node/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-ptc-runtime-node", "description": "Node PTC execution with per-call sandboxed processes or in-memory REPL state", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/ptc-runtime/ptc-runtime/package.json b/packages/ptc-runtime/ptc-runtime/package.json index dee480ff69..b99ded4e7d 100644 --- a/packages/ptc-runtime/ptc-runtime/package.json +++ b/packages/ptc-runtime/ptc-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-ptc-runtime", "description": "Abstract PTC execution seam (ctx.ptcRuntime) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/sandbox/sandbox-local/package.json b/packages/sandbox/sandbox-local/package.json index b168c81841..4c827bfb7d 100644 --- a/packages/sandbox/sandbox-local/package.json +++ b/packages/sandbox/sandbox-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox-local", "description": "Local process-sandbox backends for the DeepSeek Harness sandbox seam: bwrap, the npm-distributed landlock-run launcher, macOS Seatbelt, or the Windows ACL restricted-token runner — functionally probed, fail-closed", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/sandbox/sandbox-policy/package.json b/packages/sandbox/sandbox-policy/package.json index bd9dc62423..df7dc67ec1 100644 --- a/packages/sandbox/sandbox-policy/package.json +++ b/packages/sandbox/sandbox-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox-policy", "description": "Per-call sandbox policy resolver and current model context: deployment fallbacks plus each session's mode and workspace root, shared by every enforcing capability family", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/sandbox/sandbox-windows-acl/package.json b/packages/sandbox/sandbox-windows-acl/package.json index 5a5f3a3ef9..e2a57de913 100644 --- a/packages/sandbox/sandbox-windows-acl/package.json +++ b/packages/sandbox/sandbox-windows-acl/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox-windows-acl", "description": "Windows ACL write-restriction sandbox backend (restricted-token spawn with capability-SID write allowlist) for the DeepSeek Harness sandbox seam", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/sandbox/sandbox/package.json b/packages/sandbox/sandbox/package.json index 690184d0d0..19c8ee3e39 100644 --- a/packages/sandbox/sandbox/package.json +++ b/packages/sandbox/sandbox/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox", "description": "Abstract process-sandbox seam (ctx.sandbox) for the DeepSeek Harness: same-world confinement vocabulary and the SandboxProvider contract", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/schedule/schedule/package.json b/packages/schedule/schedule/package.json index 74cccd5a0b..36e53717d8 100644 --- a/packages/schedule/schedule/package.json +++ b/packages/schedule/schedule/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-schedule", "description": "Host-wide durable reminders with shared management and original-Session delivery", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/schedule/tool-schedule/package.json b/packages/schedule/tool-schedule/package.json index 3068cfe2de..06ad288a02 100644 --- a/packages/schedule/tool-schedule/package.json +++ b/packages/schedule/tool-schedule/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-schedule", "description": "Model-facing reminder management tools (schedule_create, schedule_list, schedule_update, schedule_delete) over the Host ctx.schedule service", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/sdk/client/package.json b/packages/sdk/client/package.json index 32560a78bd..f176083f9c 100644 --- a/packages/sdk/client/package.json +++ b/packages/sdk/client/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-client", "description": "TypeScript client SDK for driving a DeepSeek Harness runtime subprocess over stdio JSON-RPC: the DeepSeekHarness high-level turns API and the lower-level HarnessClient", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/sdk/protocol/package.json b/packages/sdk/protocol/package.json index 36e1a687cc..3c3f4771ea 100644 --- a/packages/sdk/protocol/package.json +++ b/packages/sdk/protocol/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-protocol", "description": "Shared wire protocol for the DeepSeek Harness SDK runtime: the newline-delimited JSON-RPC stdio transport and the named request, result, and notification types spoken between the runtime server and SDK clients", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/sdk/server/package.json b/packages/sdk/server/package.json index 272fb47150..c21bb85d76 100644 --- a/packages/sdk/server/package.json +++ b/packages/sdk/server/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-jsonrpc-server", "description": "Stdio JSON-RPC server plugin for out-of-process DeepSeek Harness SDK clients", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session-query/session-log-export/package.json b/packages/session-query/session-log-export/package.json index 404de34148..cf496b0e6d 100644 --- a/packages/session-query/session-log-export/package.json +++ b/packages/session-query/session-log-export/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-log-export", "description": "Web Session-log export command and shared download dialog", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session-query/session-query-sqlite/package.json b/packages/session-query/session-query-sqlite/package.json index 0ca9381a49..1def29fa7c 100644 --- a/packages/session-query/session-query-sqlite/package.json +++ b/packages/session-query/session-query-sqlite/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-query-sqlite", "description": "Concrete ctx.sessionQuery backend with SQLite FTS5 search", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session-query/session-query/package.json b/packages/session-query/session-query/package.json index aedb8bbfa3..ae1700883a 100644 --- a/packages/session-query/session-query/package.json +++ b/packages/session-query/session-query/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-query", "description": "Combined session query service contract with concrete reads, traces, and filters", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-checkpoint-policy/package.json b/packages/session/session-checkpoint-policy/package.json index 2673fcd02b..70f8fae396 100644 --- a/packages/session/session-checkpoint-policy/package.json +++ b/packages/session/session-checkpoint-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-checkpoint-policy", "description": "Semantic session durability checkpoints before model requests and tool side effects", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-catalog/package.json b/packages/session/session-format-catalog/package.json index 32767841e8..b5d8be1403 100644 --- a/packages/session/session-format-catalog/package.json +++ b/packages/session/session-format-catalog/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-catalog", "description": "Build-static first-party Session format codec and migration catalog", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-v0-to-v1/package.json b/packages/session/session-format-v0-to-v1/package.json index 7222125ea3..3e97cfa8cb 100644 --- a/packages/session/session-format-v0-to-v1/package.json +++ b/packages/session/session-format-v0-to-v1/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-v0-to-v1", "description": "Frozen released-v0 Session codec and identity migration to v1", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-v1-to-v2/package.json b/packages/session/session-format-v1-to-v2/package.json index 9829b94adb..9e43bc8475 100644 --- a/packages/session/session-format-v1-to-v2/package.json +++ b/packages/session/session-format-v1-to-v2/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-v1-to-v2", "description": "Frozen released-v1 Session codec and assistant-stream migration to v2", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-v2-to-v3/package.json b/packages/session/session-format-v2-to-v3/package.json index 5c912d402b..8dca8d9479 100644 --- a/packages/session/session-format-v2-to-v3/package.json +++ b/packages/session/session-format-v2-to-v3/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-v2-to-v3", "description": "Streaming system-prompt, canonical-envelope and PTC migration into V3", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-v3-to-v4/package.json b/packages/session/session-format-v3-to-v4/package.json index 56b64bfe0a..8babd43a0d 100644 --- a/packages/session/session-format-v3-to-v4/package.json +++ b/packages/session/session-format-v3-to-v4/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-v3-to-v4", "description": "Streaming tool-role migration and delivery validation from Session V3 to V4", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format/package.json b/packages/session/session-format/package.json index 8de29742a7..433d7dc772 100644 --- a/packages/session/session-format/package.json +++ b/packages/session/session-format/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format", "description": "Streaming adjacent Session format migration machinery", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-log-deepseek/package.json b/packages/session/session-log-deepseek/package.json index c22beb4544..6f8ce6f2be 100644 --- a/packages/session/session-log-deepseek/package.json +++ b/packages/session/session-log-deepseek/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-log-deepseek", "description": "Incremental lossless session-log request extension for the official DeepSeek LLM API", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-persistence-jsonl/package.json b/packages/session/session-persistence-jsonl/package.json index 3d78433aff..871b0e5002 100644 --- a/packages/session/session-persistence-jsonl/package.json +++ b/packages/session/session-persistence-jsonl/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-persistence-jsonl", "description": "JSONL durable session persistence backend for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-persistence/package.json b/packages/session/session-persistence/package.json index 10478000ac..3b915eb1b1 100644 --- a/packages/session/session-persistence/package.json +++ b/packages/session/session-persistence/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-persistence", "description": "Abstract durable session persistence seam (ctx.sessionPersistence) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-projection-cache/package.json b/packages/session/session-projection-cache/package.json index b611138920..f1c3f5501c 100644 --- a/packages/session/session-projection-cache/package.json +++ b/packages/session/session-projection-cache/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-projection-cache", "description": "Persisted projection cache (ctx.sessionProjectionCache): durable per-session checkpoint records on the session_projcache storage domain (per-record layout), throttled write-behind, and the cached listing read", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-projection/package.json b/packages/session/session-projection/package.json index edf7dc38a4..5e758f56ff 100644 --- a/packages/session/session-projection/package.json +++ b/packages/session/session-projection/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-projection", "description": "Session-projection seam: the merge-extensible projection type table, the provider contract, and the ctx.sessionProjections registry serving whole current values of log-derived per-session state", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-stats/package.json b/packages/session/session-stats/package.json index 5f26bd6183..30df327edb 100644 --- a/packages/session/session-stats/package.json +++ b/packages/session/session-stats/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-stats", "description": "Whole-log conversation counts and wall times projection (sessionStats) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-telemetry-otel/package.json b/packages/session/session-telemetry-otel/package.json index f20f617b66..7ac5bacd94 100644 --- a/packages/session/session-telemetry-otel/package.json +++ b/packages/session/session-telemetry-otel/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-telemetry-otel", "description": "Feedback-authorized Session logs over byte-bounded OpenTelemetry HTTP requests", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-telemetry/package.json b/packages/session/session-telemetry/package.json index 54ed052543..71c4d046ca 100644 --- a/packages/session/session-telemetry/package.json +++ b/packages/session/session-telemetry/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-telemetry", "description": "SessionTelemetryBackend seam for the DeepSeek Harness: session-event capture, projection, redaction, and handoff to a reporting backend", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-title-first-prompt-llm/package.json b/packages/session/session-title-first-prompt-llm/package.json index 4a9682a696..55ee510610 100644 --- a/packages/session/session-title-first-prompt-llm/package.json +++ b/packages/session/session-title-first-prompt-llm/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-title-first-prompt-llm", "description": "First-message LLM provider plugin for DeepSeek Harness session titles", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-title-llm/package.json b/packages/session/session-title-llm/package.json index 58a6860731..f12be199de 100644 --- a/packages/session/session-title-llm/package.json +++ b/packages/session/session-title-llm/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-title-llm", "description": "Shared LLM generation policy for DeepSeek Harness session-title providers", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-title/package.json b/packages/session/session-title/package.json index be692e609e..c00e183093 100644 --- a/packages/session/session-title/package.json +++ b/packages/session/session-title/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-title", "description": "Log-backed session title service and provider registry for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-turn-outline/package.json b/packages/session/session-turn-outline/package.json index 43d10802f5..a00bf68b00 100644 --- a/packages/session/session-turn-outline/package.json +++ b/packages/session/session-turn-outline/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-turn-outline", "description": "Whole-log turn outline projection (turnOutline) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/tool-working-directory/package.json b/packages/session/tool-working-directory/package.json index 36250ec5f4..7f6322e981 100644 --- a/packages/session/tool-working-directory/package.json +++ b/packages/session/tool-working-directory/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-working-directory", "description": "Read and change the active Session working directory", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/working-directory/package.json b/packages/session/working-directory/package.json index 300d0c47bc..7c6b540d73 100644 --- a/packages/session/working-directory/package.json +++ b/packages/session/working-directory/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-working-directory", "description": "Session working directories with durable changes and model context", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/settings/settings/package.json b/packages/settings/settings/package.json index 177b769b92..ed527d9478 100644 --- a/packages/settings/settings/package.json +++ b/packages/settings/settings/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-settings", "description": "Abstract user-settings seam (ctx.settings) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/bash-local/package.json b/packages/shell/bash-local/package.json index 1df5aa2bb6..4fcd8cf17a 100644 --- a/packages/shell/bash-local/package.json +++ b/packages/shell/bash-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-bash-local", "description": "Local-subprocess implementation of the DeepSeek Harness bash executor seam", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/bash-sandbox/package.json b/packages/shell/bash-sandbox/package.json index 10f2751c74..5f4ac60db7 100644 --- a/packages/shell/bash-sandbox/package.json +++ b/packages/shell/bash-sandbox/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-bash-sandbox", "description": "Sandbox-consuming implementation of the DeepSeek Harness bash executor seam (confines every command via ctx.sandbox, reports denial/enforcement result facts)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/pwsh-local/package.json b/packages/shell/pwsh-local/package.json index 600775d5ae..15723ee00f 100644 --- a/packages/shell/pwsh-local/package.json +++ b/packages/shell/pwsh-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-pwsh-local", "description": "Local PowerShell implementation of the DeepSeek Harness bash executor seam", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/pwsh-sandbox/package.json b/packages/shell/pwsh-sandbox/package.json index 948dbd83c6..ec4c99ea7a 100644 --- a/packages/shell/pwsh-sandbox/package.json +++ b/packages/shell/pwsh-sandbox/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-pwsh-sandbox", "description": "Sandbox-consuming implementation of the DeepSeek Harness PowerShell executor seam (confines every command via ctx.sandbox, reports denial/enforcement result facts)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/shell-env/package.json b/packages/shell/shell-env/package.json index ab3ea4b194..5096f2ab26 100644 --- a/packages/shell/shell-env/package.json +++ b/packages/shell/shell-env/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-shell-env", "description": "Tool-independent managed DSH_* shell environment registry", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/shell/package.json b/packages/shell/shell/package.json index bfde1a648c..2b2c30d83d 100644 --- a/packages/shell/shell/package.json +++ b/packages/shell/shell/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-shell", "description": "Abstract bash executor seam (ctx.shell) for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/tool-bash-persistent/package.json b/packages/shell/tool-bash-persistent/package.json index 3c4c9ff736..502672ffa2 100644 --- a/packages/shell/tool-bash-persistent/package.json +++ b/packages/shell/tool-bash-persistent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-bash-persistent", "description": "Model-facing owner-scoped persistent Bash tool backed by the Harness PTY service", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/tool-bash/package.json b/packages/shell/tool-bash/package.json index 781f4facdd..906c4657f1 100644 --- a/packages/shell/tool-bash/package.json +++ b/packages/shell/tool-bash/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-bash", "description": "Model-facing bash tool with optional generic background-job and sandbox-escalation support", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/tool-pwsh-persistent/package.json b/packages/shell/tool-pwsh-persistent/package.json index 44a6c7a2ef..bf36b382ce 100644 --- a/packages/shell/tool-pwsh-persistent/package.json +++ b/packages/shell/tool-pwsh-persistent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-pwsh-persistent", "description": "Model-facing owner-scoped persistent PowerShell tool backed by the Harness PTY service", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/tool-pwsh/package.json b/packages/shell/tool-pwsh/package.json index 4d9572bef3..4e101e21d3 100644 --- a/packages/shell/tool-pwsh/package.json +++ b/packages/shell/tool-pwsh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-pwsh", "description": "Model-facing pwsh tool over the bash executor seam", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/skill-filesystem/package.json b/packages/skill/skill-filesystem/package.json index 4799dfb506..92608a40e7 100644 --- a/packages/skill/skill-filesystem/package.json +++ b/packages/skill/skill-filesystem/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-skill-filesystem", "description": "Local filesystem skill provider for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/skill-office/package.json b/packages/skill/skill-office/package.json index fe751bfab6..61f21050cb 100644 --- a/packages/skill/skill-office/package.json +++ b/packages/skill/skill-office/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-skill-office", "description": "Bundled Word, PowerPoint, and Excel workflows and LibreOffice Kit operations", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/skill/package.json b/packages/skill/skill/package.json index a7a247c1f6..0962e68533 100644 --- a/packages/skill/skill/package.json +++ b/packages/skill/skill/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-skill", "description": "Agent skill provider registry for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/tool-skill/package.json b/packages/skill/tool-skill/package.json index 82051a8171..e4f4a0dea3 100644 --- a/packages/skill/tool-skill/package.json +++ b/packages/skill/tool-skill/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-skill", "description": "Model-facing skill loading tool for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/tool-workspace-dependencies/package.json b/packages/skill/tool-workspace-dependencies/package.json index 3ac0f04645..30a1a0025a 100644 --- a/packages/skill/tool-workspace-dependencies/package.json +++ b/packages/skill/tool-workspace-dependencies/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-workspace-dependencies", "description": "The load_workspace_dependencies tool: absolute paths into a bundled Python, Node.js, and pnpm payload", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/spill/spill-local/package.json b/packages/spill/spill-local/package.json index 91295d4b17..ae1ce7383d 100644 --- a/packages/spill/spill-local/package.json +++ b/packages/spill/spill-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-spill-local", "description": "Local-filesystem implementation of the DeepSeek Harness spill storage seam (private session-scoped files)", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/spill/spill-policy/package.json b/packages/spill/spill-policy/package.json index 68ea4830ba..b88b3df2c0 100644 --- a/packages/spill/spill-policy/package.json +++ b/packages/spill/spill-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-spill-policy", "description": "Token-budgeted tool-result retention with recoverable text and image paths", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/spill/spill/package.json b/packages/spill/spill/package.json index 3293e8ff1b..00fd376d01 100644 --- a/packages/spill/spill/package.json +++ b/packages/spill/spill/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-spill", "description": "Abstract spill storage seam (ctx.spillStore) for the DeepSeek Harness — save oversized tool text and return a retrieval locator", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/ssh/fs-ssh/package.json b/packages/ssh/fs-ssh/package.json index 6ea7ac87c9..37ecc2df3f 100644 --- a/packages/ssh/fs-ssh/package.json +++ b/packages/ssh/fs-ssh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-fs-ssh", "description": "Filesystem provider over the shared POSIX SSH helper", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/ssh/sandbox-ssh/package.json b/packages/ssh/sandbox-ssh/package.json index c757b4ec19..72354fc821 100644 --- a/packages/ssh/sandbox-ssh/package.json +++ b/packages/ssh/sandbox-ssh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox-ssh", "description": "Remote POSIX sandbox argv provider over the shared SSH helper", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/ssh/ssh-helper-runtime/package.json b/packages/ssh/ssh-helper-runtime/package.json index 1fbdeccea2..cba5e59a90 100644 --- a/packages/ssh/ssh-helper-runtime/package.json +++ b/packages/ssh/ssh-helper-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-ssh-helper-runtime", "description": "Private executable carrier for the POSIX SSH helper and managed workers", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "private": true, "type": "module", "main": "lib/index.js", diff --git a/packages/ssh/ssh/package.json b/packages/ssh/ssh/package.json index 36cab186d0..17d866b0e0 100644 --- a/packages/ssh/ssh/package.json +++ b/packages/ssh/ssh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-ssh", "description": "Shared OpenSSH connection and versioned POSIX remote helper", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/ssh/subprocess-ssh/package.json b/packages/ssh/subprocess-ssh/package.json index 46d70838fc..b62b47322a 100644 --- a/packages/ssh/subprocess-ssh/package.json +++ b/packages/ssh/subprocess-ssh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subprocess-ssh", "description": "Subprocess and terminal provider over the shared POSIX SSH helper", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/storage/storage-domain/package.json b/packages/storage/storage-domain/package.json index 05a67fc324..1d68883ed1 100644 --- a/packages/storage/storage-domain/package.json +++ b/packages/storage/storage-domain/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-storage-domain", "description": "Domain data form (ctx.storage.domain): schema-validated, event-emitting KV domains over storage backends for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/storage/storage-json/package.json b/packages/storage/storage-json/package.json index 2569659752..98765ce102 100644 --- a/packages/storage/storage-json/package.json +++ b/packages/storage/storage-json/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-storage-json", "description": "JSON file KV storage backend for the DeepSeek Harness storage hub", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/storage/storage-sqlite/package.json b/packages/storage/storage-sqlite/package.json index e137775577..9fbae7fb51 100644 --- a/packages/storage/storage-sqlite/package.json +++ b/packages/storage/storage-sqlite/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-storage-sqlite", "description": "SQLite storage backend (kv facet) for the DeepSeek Harness storage hub", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/storage/storage/package.json b/packages/storage/storage/package.json index 3b7b4aefc6..6f415a001f 100644 --- a/packages/storage/storage/package.json +++ b/packages/storage/storage/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-storage", "description": "Storage hub (ctx.storage): named backend registry plus mounted data-form facilities for the DeepSeek Harness", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-acp/package.json b/packages/subagent/subagent-acp/package.json index 5d9324fd1d..14dc6eebb9 100644 --- a/packages/subagent/subagent-acp/package.json +++ b/packages/subagent/subagent-acp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-acp", "description": "Out-of-process ACP subagent backend: drives a child agent in a spawned subprocess over the Agent Client Protocol", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-claude-code/package.json b/packages/subagent/subagent-claude-code/package.json index 5dc2d5fec4..073b95314c 100644 --- a/packages/subagent/subagent-claude-code/package.json +++ b/packages/subagent/subagent-claude-code/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-claude-code", "description": "One-shot Claude Code subagent provider over the official Agent SDK", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-codex/package.json b/packages/subagent/subagent-codex/package.json index f28edf4f18..51ec3ea0e6 100644 --- a/packages/subagent/subagent-codex/package.json +++ b/packages/subagent/subagent-codex/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-codex", "description": "One-shot Codex subagent provider over the official app-server protocol", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-dsh-sdk/package.json b/packages/subagent/subagent-dsh-sdk/package.json index 67733db85d..a913cfd1f8 100644 --- a/packages/subagent/subagent-dsh-sdk/package.json +++ b/packages/subagent/subagent-dsh-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-dsh-sdk", "description": "Out-of-process SDK subagent backend: drives a child DeepSeek Harness runtime subprocess over stdio JSON-RPC through the TypeScript SDK client", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-fork-in-process/package.json b/packages/subagent/subagent-fork-in-process/package.json index 1bf4ea561a..148baa1a75 100644 --- a/packages/subagent/subagent-fork-in-process/package.json +++ b/packages/subagent/subagent-fork-in-process/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-fork-in-process", "description": "In-process fork subagent backend: runs a child agent seeded with a prefix of the parent's log", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-spawn-in-process/package.json b/packages/subagent/subagent-spawn-in-process/package.json index d34961cbc3..b2e0297b99 100644 --- a/packages/subagent/subagent-spawn-in-process/package.json +++ b/packages/subagent/subagent-spawn-in-process/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-spawn-in-process", "description": "In-process spawn subagent backend: runs a fresh child agent on ctx.agents", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent/README.i18n.yaml b/packages/subagent/subagent/README.i18n.yaml index c88d00ac94..38bbcac045 100644 --- a/packages/subagent/subagent/README.i18n.yaml +++ b/packages/subagent/subagent/README.i18n.yaml @@ -33,8 +33,8 @@ en: a740e5e9bdd661d9 zh: 712a5458fc8b3615 /deepseek-ai-dsh-subagent/use-this-package/messaging-interrupting-and-discovering: - en: 2d651b7dbe68ad83 - zh: 819c360e418c7fe8 + en: 342d149e6c03eea9 + zh: ce5f8a827f2dfda2 /deepseek-ai-dsh-subagent/use-this-package/failure-and-recovery: en: 245fb0d5c3df5ccf zh: e69eed9e5070ce25 @@ -45,8 +45,8 @@ en: ed526b6f34a3d6bf zh: 47a90717b89d4c79 /deepseek-ai-dsh-subagent/understand-the-implementation/source-map: - en: 410b6215e3a29221 - zh: d17d6ea102fe8f87 + en: 6f9d064dc4da622b + zh: b8d5e60838535c28 /deepseek-ai-dsh-subagent/understand-the-implementation/provider-preparation-and-structured-output: en: f78c5e931f3a3676 zh: 3c63f637dbaa8101 @@ -57,8 +57,8 @@ en: d338e68edd438f57 zh: 0496090cc567b19d /deepseek-ai-dsh-subagent/understand-the-implementation/ownership-and-invariants: - en: 4e22346c9e9197d1 - zh: 9afe58cf57eedcc1 + en: d806363ca210b3c6 + zh: 645ee6ff7adaf140 /deepseek-ai-dsh-subagent/further-exploration: en: 5b0f408dd901d90a zh: e11866d5fd878327 @@ -93,8 +93,8 @@ en: 59e848fca82a46fb zh: fcd6a8a2bb58609f /deepseek-ai-dsh-subagent/known-limitations-and-deferred-work: - en: fae8f84638614022 - zh: dc6fbc8ac3e2284b + en: aca9a599985f4a93 + zh: aa2174f9e21747e6 /deepseek-ai-dsh-subagent/known-limitations-and-deferred-work/dev-note: en: 25cc0909348666bb zh: 95517f12de100049 diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index 77c55a9515..440441d271 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -69,7 +69,7 @@ In-process spawn accepts an independent `agentPreset`; fork and external backend ### Messaging, interrupting, and discovering -Every exact live Agent can use `sendMessage()` with a direct continuable child; a resident continuable child can also message its direct parent or another continuable child of that parent by Session id. Sibling delivery uses the shared live parent’s authority and preserves the actual sender’s id. Descriptions are display text and may repeat. A working target receives the Agent message through Steer at its nearest step; an idle target starts a turn, and direct children and siblings can be cold-resumed. The parent can also interrupt a running descendant or list its children at any time. A browser continuation prompt independently selects Queue or Steer and may carry image parts: the Host admits and persists each image batch through the attachment store before the child inbox accepts the message, and refuses delivery when the child's declared model does not accept image input. Direct-child discovery reads the parent-owned `subagentCatalog` projection. `listChildren(parentSessionId, signal?)` owns a live-preferred Session observation and returns committed children asynchronously without reading child logs. Named descendants require ancestor observations up to the root to verify registration. It forwards cancellation and releases the observation after materialization. Materialization preserves parent event order in O(D) time for D facts. External entries are leaves and require no child Session observation. Descendant discovery recursively reads local child catalogs in parent event order, observing each reachable child catalog plus its ancestry when name registration requires it. It skips branches whose catalogs cannot be read and returns diagnostics for them; neither path loads or resumes a child Agent. +`sendMessage()` lets the live root and resident continuable subagents contact any other root or continuable member in the same tree by Session id. The target receives Steer input at its next step or starts an idle turn. Cold targets and their continuable ancestors resume under their recorded parents; the message retains its actual sender. `rootOf()` resolves the caller’s live root. Interruption still requires ancestor authority. Browser prompts independently select Queue or Steer and admit image batches through the attachment store; incompatible target models reject images. Direct-child discovery reads `subagentCatalog` through `listChildren(parentSessionId, signal?)`, owning and releasing a live-preferred Session observation. Recursive discovery preserves catalog order, observes each reachable catalog and the ancestry needed to verify name registration, skips external leaves, and reports unreadable branches without activating Agents. ### Failure and recovery @@ -99,8 +99,8 @@ This section explains how the service is built and where the observable behavior | [`src/index.ts`](src/index.ts) | Service entry: provider registry, start and continuation API, lifecycle events | | [`src/manager.ts`](src/manager.ts) | Unified startup, message admission, cold resume, parent ownership, settlement, and disposal | | [`src/activation.ts`](src/activation.ts) | Execution records, capacity slots, child locks, and local output capture | -| [`src/continuation-messages.ts`](src/continuation-messages.ts) | Adjacent-Agent messages, return guidance, and settlement notices | -| [`src/internal.ts`](src/internal.ts) | Host-only Queue and Steer adapters plus standard adjacent-Agent messaging markers | +| [`src/continuation-messages.ts`](src/continuation-messages.ts) | Team messages, return guidance, and settlement notices | +| [`src/internal.ts`](src/internal.ts) | Host-only Queue and Steer adapters plus standard Team messaging markers | | [`src/structured.ts`](src/structured.ts) | Activation-scoped structured capture and guards | | [`src/types.ts`](src/types.ts) | Public request, result, and provider contracts | | [`src/descriptor.ts`](src/descriptor.ts) | Versioned `subagent/descriptor` session-event vocabulary | @@ -136,7 +136,7 @@ Logs without that selection before the first own descriptor, including historica - **The manager owns accepted work** — unpublished failures roll back; published handles cancel their exact activation and await child-first disposal. - **Registration is effect-scoped** — removing a provider blocks new starts but never revokes accepted runs. -- **Agent-message authority follows live lineage** — `sendMessage()` requires the exact live sender; every sender may target a direct continuable child. A resident continuable sender may also target its direct parent or a continuable sibling under the shared live parent’s authority. +- **Agent-message authority follows the tree** — the exact live sender and target must belong to one root. Cold delivery verifies continuable descriptors and parent catalogs before restoring ancestors. Final inbox admission rechecks the sender and target parent; failed restoration releases newly created activations. - **The descriptor is log-only** — a session event absent from model history and retained across compaction; the writer records the resolved child provider, model, and reasoning effort for cold resume. Historical one-shot descriptors remain readable. - **This runtime answers archive admission for children** ([seam](../../workspace/workspace/README.md)) — `workspace/session-activity` reports the live subagent descendants inside a turn as the `subagent` family, found by the durable lineage this package records (`parentSession` with the subagent origin, any depth, never a fork) and labelled from each child's descriptor through a live Session observation when the Session query service is composed, otherwise by id; `workspace/session-stop` cancels each of them with the parent cause, one at a time, so one child refusing its cancel is logged while its siblings still stop. The parent's own turn, its jobs, and the archived-lineage step gate belong to the API Session Controller. @@ -206,7 +206,7 @@ These limits define when the seam is a poor fit or needs special operational car - **Descendant reads are sequential** — each reachable catalog, including one-shot children, takes one observation. A cold Session without a valid prepared observation requires a full-log read; large cold trees can accumulate storage latency. - **External children execute once** — ACP, DSH SDK, Codex, and Claude Code have no local child Session and accept no follow-up input. Their parent-owned records remain discoverable after disposal. -- **Messaging is limited to parents, children, and siblings** — targets use Session ids. A resident continuable child can reach its direct parent and continuable siblings while the parent is live; deeper descendants and unrelated trees are not message targets. Browser prompts use a separate human Queue-or-Steer control path. +- **Messaging stays within one tree** — targets use Session ids and must be the root or continuable members. Cold ancestors consume activation capacity. Browser prompts retain their separate human Queue-or-Steer control path. - **A direct parent must remain live for child-to-parent delivery** — the service has no durable parent mailbox; a missing parent rejects the message instead of accepting work it cannot wake. - **Wake gap during cancellation convergence** — a follow-up accepted after an interrupt signal but before the Agent becomes idle stays queued until another waking send. - **Pending injected context retains an Activation** — settlement conservatively treats every Inbox occurrence as unfinished. Context parked after the Agent becomes idle keeps the child and its live ancestors resident until a waking delivery claims it, a queue mutation removes it, or manager teardown discards it. Host completion joins active work, pending child creation, and resource cleanup, but does not wait for a subtree containing only idle parked inboxes; it neither completes those activation results nor discards their messages. diff --git a/packages/subagent/subagent/README.zh.md b/packages/subagent/subagent/README.zh.md index 431444f56c..b636b9da52 100644 --- a/packages/subagent/subagent/README.zh.md +++ b/packages/subagent/subagent/README.zh.md @@ -69,7 +69,7 @@ agent 名字不可变,在整棵 agent 树内唯一;根名为 `lead`。创建 ### 消息、中断与发现 -每个确切在线 Agent 都可以对直接可继续 child 使用 `sendMessage()`;驻留的可继续 child 还可以按 Session id 向直接 parent 或该 parent 的其他可继续 child 发消息。同级投递使用共同在线 parent 的权限,并保留实际发送方的 id。描述用于展示,可以重复。正在工作的目标通过 Steer 在最近 step 接收 Agent 消息;空闲目标启动轮次,且直接 child 和同级均可冷恢复。parent 也可以随时中断正在运行的后代或列举自己的子级。浏览器发出的继续执行 prompt 会独立选择 Queue 或 Steer,并且可以携带图片部分:Host 先通过附件存储完成整批图片的准入与持久化,子级 inbox 才接受这条消息;当子级声明的模型不接受图片输入时拒绝投递。 直接子级发现读取 parent 自有的 `subagentCatalog` projection。`listChildren(parentSessionId, signal?)` 持有一次优先实时来源的 Session 观察,异步返回已提交子级,不读取子级日志。命名后代需要向上观察祖先直到根,以验证登记。它转发取消信号,并在物化后释放观察。物化以 O(D) 时间保留 D 条事实的父日志事件顺序。外部条目是叶节点,不需要观察子 Session。后代发现按父事件顺序递归读取子级目录,观察每个可达子级目录,并在名字登记验证需要时观察其祖先。无法读取目录的分支会被跳过,并返回诊断;两条路径都不加载或恢复子级 Agent。 +`sendMessage()` 允许在线根和驻留的可继续子代理,按 Session id 联系同一棵树中的任意其他根或可继续成员。目标在下一步接收 Steer 输入,或在空闲时开始轮次。冷目标及其可继续祖先按记录的父级恢复;消息保留实际发送者。`rootOf()` 解析调用方的在线根。中断仍要求祖先权限。浏览器提示词独立选择 Queue 或 Steer,经附件存储接受图片批次;不兼容的目标模型拒绝图片。直接子级发现通过 `listChildren(parentSessionId, signal?)` 读取 `subagentCatalog`,持有并释放优先在线的 Session observation。递归发现保留目录顺序,观察每个可达目录及验证名称登记所需的祖先,跳过外部叶子,并报告不可读分支而不激活 Agent。 ### 失败与恢复 @@ -99,8 +99,8 @@ agent 名字不可变,在整棵 agent 树内唯一;根名为 `lead`。创建 | [`src/index.ts`](src/index.ts) | 服务入口:提供方注册表、启动与继续 API、生命周期事件 | | [`src/manager.ts`](src/manager.ts) | 统一管理启动、消息准入、冷恢复、父子所有权、结算与释放 | | [`src/activation.ts`](src/activation.ts) | 执行期记录、容量槽、子级锁与本地输出捕获 | -| [`src/continuation-messages.ts`](src/continuation-messages.ts) | 相邻 Agent 消息、返回指引与结算通知 | -| [`src/internal.ts`](src/internal.ts) | Host 专用 Queue 与 Steer 适配器,以及标准相邻 Agent 消息标记 | +| [`src/continuation-messages.ts`](src/continuation-messages.ts) | Team 成员消息、返回指引与结算通知 | +| [`src/internal.ts`](src/internal.ts) | Host 专用 Queue 与 Steer 适配器,以及标准Team 成员消息标记 | | [`src/structured.ts`](src/structured.ts) | Activation 局部结构化捕获与保护 | | [`src/types.ts`](src/types.ts) | 公开的请求、结果与提供方约定 | | [`src/descriptor.ts`](src/descriptor.ts) | 版本化的 `subagent/descriptor` 会话事件词汇 | @@ -136,7 +136,7 @@ agent 名字不可变,在整棵 agent 树内唯一;根名为 `lead`。创建 - **管理器拥有已接受工作**——未发布的失败会回滚;已发布句柄取消对应 activation,并等待子级优先释放。 - **注册受 effect 作用域约束**——移除提供方会阻止新启动,但绝不撤销已接受的运行。 -- **Agent 消息权限来自在线谱系**——`sendMessage()` 要求确切在线 sender;每个 sender 都可以指定直接可继续 child。驻留的可继续 sender 还可以向直接 parent 发消息,或使用共同在线 parent 的权限向可继续同级发消息。 +- **Agent 消息权限由树决定**——确切在线发送者和目标必须属于同一个根。冷投递在恢复祖先之前验证可继续描述符和父级目录。最终收件箱准入重新检查发送者与目标父级;恢复失败会释放新建激活。 - **描述符仅进日志**——它是会话事件,不进入模型历史,并跨压缩(compaction)保留;写入器记录解析后的子级提供方、模型与推理强度,用于冷恢复。历史 one-shot 描述符仍可读取。 - **本 runtime 为子代理回答归档准入**([接缝](../../workspace/workspace/README.zh.md))——`workspace/session-activity` 把回合中的在线子代理子孙作为 `subagent` 族报告:按本包记录的持久化血缘查找(带 subagent 来源的 `parentSession`,任意深度,从不包括 fork),组合了 Session query 服务时经一次活会话 observation 从各 child 的描述符取名称,否则只报 id;`workspace/session-stop` 以父级原因逐个取消它们,一个拒绝取消的 child 只记日志,其兄弟仍会停止。父级自身的回合、它的任务以及已归档血缘的步骤门禁归 API Session Controller。 @@ -206,7 +206,7 @@ You are a delegated subagent: your permission scope was fixed when you were star - **后代读取串行执行**——每个可达目录(包括一次性子级)都需要一次观察。冷 Session 缺少有效的 prepared 观察时需要读取完整日志;大型冷会话树可能累积存储延迟。 - **外部子级执行一次**——ACP、DSH SDK、Codex 与 Claude Code 没有本地子 Session,也不接受后续输入。释放后仍可发现父级拥有的执行记录。 -- **消息限于父级、子级和同级**——目标使用 Session id。驻留的可继续 child 在 parent 在线时可以联系直接 parent 和可继续同级;更深的后代与无关树不是消息目标。浏览器提示使用独立的人类 Queue 或 Steer 控制路径。 +- **消息限定在一棵树内**——目标使用 Session id,必须是根或可继续成员。冷祖先占用激活容量。浏览器提示词保留独立的人类 Queue 或 Steer 控制路径。 - **child 到 parent 的投递要求直接 parent 保持在线**——服务没有持久 parent mailbox;parent 缺失时会拒绝消息,而非接受无法唤醒的工作。 - **取消收敛期间存在唤醒缺口**——中断信号发出后、Agent 进入 idle 前被接受的后续消息会保持排队,直到另一条唤醒发送到达。 - **待处理的注入上下文会保留 Activation**——settlement 会保守地把每个 Inbox occurrence 都视为未完成。Agent 进入 idle 后停放的上下文会让 child 及其在线祖先继续驻留,直到唤醒投递将其 claim、queue 变更将其移除,或 manager teardown 将其丢弃。宿主完成检查等待活动工作、待创建子级与资源清理,但不会等待仅包含空闲停放 Inbox 的子树;它既不结算这些 activation 的结果,也不丢弃其消息。 diff --git a/packages/subagent/subagent/package.json b/packages/subagent/subagent/package.json index e324f19c17..a467f0b87f 100644 --- a/packages/subagent/subagent/package.json +++ b/packages/subagent/subagent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent", "description": "Abstract subagent seam (ctx.subagents): named-provider registry for delegating to child agents", - "version": "0.2.1-alpha.4", + "version": "0.2.1-rc.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent/src/index.ts b/packages/subagent/subagent/src/index.ts index 1859ebe812..efcde5cb0d 100644 --- a/packages/subagent/subagent/src/index.ts +++ b/packages/subagent/subagent/src/index.ts @@ -14,7 +14,7 @@ * `startActivation` establishes every child under the activation manager, which * owns execution, result delivery, and resource release. Local providers seed * resumable Agents; external providers return a single execution handle. - * `sendMessage` steers between adjacent local Agents without exposing + * `sendMessage` steers between local Agents in one Team without exposing * whether a child is resident. Discovery combines real child Sessions with * parent-owned records for external executions. * Direct discovery reads parent catalogs; descendants recursively read reachable @@ -305,6 +305,16 @@ export class SubagentRuntime extends TypertRemoteService { return this.requireManager().names.resolve(caller, name, signal) } + /** + * Resolve the root shared by an Agent and its teammates. + * @param caller - exact live Agent whose subagent ancestry selects the tree. + * @returns the live root; ordinary Session forks lead independent trees. + * @throws when the caller or its live ancestry is unavailable. + */ + rootOf(caller: Agent): Agent { + return this.requireManager().rootOf(caller) + } + /** * Join progressing descendants without cancelling them. Idle descendants whose * inboxes require a later wake stay resident and do not delay host completion. @@ -316,18 +326,17 @@ export class SubagentRuntime extends TypertRemoteService { } /** - * Steer one model-authored message to the sender's direct parent, direct - * continuable child, or continuable sibling under their shared live parent. A running target admits it at the nearest step boundary; - * an idle target starts a turn, and an absent direct child cold-resumes from - * persistence. The service derives durable sender attribution from the exact - * live sender. Caller cancellation stops only pre-acceptance work. - * @param sender - exact live Agent authorizing and originating the message. - * @param targetId - durable direct-parent, direct-child, or sibling session id. + * Steer one model-authored message to another member of the same subagent tree. + * A running target receives it at its next step; an idle target starts a turn. + * Cold targets and their continuable ancestors resume under their recorded + * parents. Sender identity is rechecked before inbox acceptance; cancellation + * stops only pre-acceptance work and never retracts an accepted message. + * @param sender - exact live root or resident continuable Agent originating the message. + * @param targetId - another root or continuable member's durable Session id. * @param content - model-authored content to deliver. * @param options - caller cancellation before inbox acceptance. * @returns the accepted message's inbox id. - * @throws when continuation services are unavailable, adjacency is rejected, - * or the message was not admitted. + * @throws when the target is outside the tree, cannot resume, or rejects input. */ async sendMessage( sender: Agent, diff --git a/packages/subagent/subagent/src/internal.ts b/packages/subagent/subagent/src/internal.ts index 6221e8354c..b5dd0e287e 100644 --- a/packages/subagent/subagent/src/internal.ts +++ b/packages/subagent/subagent/src/internal.ts @@ -11,11 +11,11 @@ import type { ToolDefinition } from '@deepseek-ai/dsh-tools' import type SubagentRuntime from './index.ts' import type { SubagentDelivery } from './control-types.ts' -/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */ +/** Process-stable identity carried only by the standard Team messaging tool. */ export const adjacentAgentSendMessageTool = Symbol.for('dsh.subagent.adjacentAgentSendMessageTool') /** - * Mark the standard adjacent-Agent messaging tool without changing its model-visible schema. + * Mark the standard Team messaging tool without changing its model-visible schema. * @param definition - the standard `send_message` definition. * @returns the same definition with its internal identity installed. */ @@ -25,7 +25,7 @@ export function markAdjacentAgentSendMessageTool(definition: ToolDefinition): To } /** - * Test whether one visible definition is the standard adjacent-Agent messaging tool. + * Test whether one visible definition is the standard Team messaging tool. * @param definition - the scope-resolved `send_message` candidate. * @returns whether the definition carries the internal standard-tool identity. */ diff --git a/packages/subagent/subagent/src/manager.ts b/packages/subagent/subagent/src/manager.ts index 0472a15011..594b2b7318 100644 --- a/packages/subagent/subagent/src/manager.ts +++ b/packages/subagent/subagent/src/manager.ts @@ -48,17 +48,13 @@ import { ActivationPool, ChildLock, activationAgent, captureLocalResult, observe import type { Activation, ActivationResource, LocalActivation } from './activation.ts' /** Inputs shared by model steering and human prompt delivery. */ -type ChildDeliveryOptions = - | { - readonly delivery: 'steer' - /** - * A provided host source is preserved on the user message; omission attributes - * an adjacent-Agent message to the parent. - */ - readonly source?: MessageSource - readonly signal: AbortSignal - } - | { readonly delivery: 'queue'; readonly source: MessageSource; readonly signal: AbortSignal } +type ChildDeliveryOptions = { + readonly delivery: 'steer' | 'queue' + readonly source: MessageSource + readonly signal: AbortSignal + /** Recheck the original model caller at final inbox admission. */ + readonly authorize?: () => void +} /** Package-private hooks supplied by the owning service. */ interface SubagentHost { @@ -554,14 +550,36 @@ export class SubagentManager { } /** - * Deliver a message to the sender's direct parent, direct continuable child, - * or continuable sibling. Sibling delivery uses the shared live parent's - * authority and preserves the sender's identity; an absent child cold-resumes. - * @param sender - exact live Agent authorizing and originating the message. - * @param targetId - durable direct-parent, direct-child, or sibling Session id. - * @param content - model-authored content to deliver. - * @param options - caller cancellation before acceptance. - * @returns the accepted message's inbox id. + * Resolve uninterrupted live subagent ancestry, excluding ordinary fork ancestry. + * @param caller - exact live Agent selecting the tree. + * @returns the live root Agent. + * @throws when the caller or an ancestor is unavailable. + */ + rootOf(caller: Agent): Agent { + if (this.ctx.agents.get(caller.id) !== caller) { + throw new SubagentError('Team membership requires the exact live caller', 'UNAUTHORIZED') + } + let root = caller + const seen = new Set() + while (root.session.header.origin === 'subagent') { + const parentId = root.session.header.parentSession + const parent = parentId === undefined ? undefined : this.ctx.agents.get(parentId) + seen.add(root.id) + if (parent === undefined || seen.has(parent.id)) { + throw new SubagentError('Team membership requires the live parent tree', 'UNAUTHORIZED') + } + root = parent + } + return root + } + + /** + * Deliver within one subagent tree, preserving the sender and the target's owner. + * @param sender - exact live root or resident continuable Agent. + * @param targetId - another member's durable Session id. + * @param content - model-authored message blocks. + * @param options - cancellation before inbox acceptance. + * @returns the accepted inbox message id. */ async sendMessage( sender: Agent, @@ -570,8 +588,8 @@ export class SubagentManager { options: SubagentSendMessageOptions, ): Promise { options.signal.throwIfAborted() - if (this.ctx.agents.get(sender.id) !== sender) { - throw new SubagentError('message delivery requires the exact live sender agent', 'UNAUTHORIZED') + if (this.ctx.agents.get(sender.id) !== sender || sender.id === targetId) { + throw new SubagentError('message delivery requires a live sender and another Team member', 'UNAUTHORIZED') } this.assertAdmitting(sender) const activation = this.resident.get(sender.id) @@ -579,28 +597,105 @@ export class SubagentManager { if (resident !== undefined && resident.parent.id === targetId) { return this.sendToParent(resident, sender, content) } - if (sender.session.header.parentSession === targetId) { - throw new SubagentError( - `agent "${sender.id}" is not a resident continuable child and cannot send to parent "${targetId}"`, - 'UNAUTHORIZED', - ) + if (sender.session.header.origin === 'subagent' && resident === undefined) { + throw new SubagentError(`agent "${sender.id}" is not a resident continuable child`, 'UNAUTHORIZED') } - if (resident !== undefined && targetId !== sender.id - && this.catalogOf(resident.parent.session).some(child => child.id === targetId && child.mode === 'continuable' - && (child.name === undefined || this.names.isRegistered(resident.parent, child.name, child.id)))) { - const message = createAgentMessage(sender, content, this.names.nameOf(sender)) - return this.deliverToChild(resident.parent, targetId, [...message.content], { - signal: options.signal, - delivery: 'steer', - source: message.source, + const target = this.resident.get(targetId) + if (target?.kind === 'external') requireLocalActivation(target) + const root = this.rootOf(sender) + const authorize = (): void => { + this.assertAdmitting(sender) + if (this.rootOf(sender) !== root) { + throw new SubagentError('the sender no longer belongs to this Team', 'UNAUTHORIZED') + } + } + const message = createAgentMessage(sender, content, this.names.nameOf(sender)) + if (targetId === root.id) { + authorize() + this.assertAdmitting(root) + this.sendAgentMessage(root, message) + return message.id + } + if (this.catalogOf(sender.session).some(child => child.id === targetId)) { + return this.deliverToChild(sender, targetId, [...message.content], { + signal: options.signal, delivery: 'steer', source: message.source, authorize, }) } - return this.deliverToChild(sender, targetId, content, { - signal: options.signal, - delivery: 'steer', + const path = await this.messagePath(root, targetId, options.signal) + authorize() + return this.deliverInTree(root, path, [...message.content], { + signal: options.signal, delivery: 'steer', source: message.source, authorize, }) } + /** Resolve a target through its durable continuable parents, without activating another tree. */ + private async messagePath(root: Agent, targetId: SessionId, signal: AbortSignal): Promise<[SessionId, ...SessionId[]]> { + const path: [SessionId, ...SessionId[]] = [targetId] + const seen = new Set() + let id = targetId + while (id !== root.id && !seen.has(id)) { + signal.throwIfAborted() + seen.add(id) + using child = await this.observeContinuation(id, signal) + const parentId = child.header.parentSession + const identity = child.projections?.values.subagent + if (parentId === undefined || identity?.mode !== 'continuable' || identity.seq < child.inheritedEventCount) break + using parent = await this.observeContinuation(parentId, signal) + if (!parent.projections?.values.subagentCatalog?.some(entry => entry.id === id && entry.mode === 'continuable')) break + id = parentId + if (id !== root.id) path.unshift(id) + } + if (id !== root.id) throw new SubagentError(`agent "${targetId}" is not a continuable member of this Team`, 'UNAUTHORIZED') + return path + } + + /** Restore cold ancestors without sending them the target's message; retain their child ownership. */ + private async deliverInTree( + parent: Agent, + path: readonly [SessionId, ...SessionId[]], + content: ContentBlock[], + options: ChildDeliveryOptions, + ): Promise { + const [childId, nextChild, ...rest] = path + if (nextChild === undefined) return this.deliverToChild(parent, childId, content, options) + this.assertAdmitting(parent) + const releaseHold = this.holdOwnership(parent, childId) + try { + while (true) { + const delivered = await this.locks.run(childId, async () => { + let activation = this.resident.get(childId) + if (activation?.closing !== undefined) { + return { closing: activation.closing } + } + const restored = activation === undefined + activation ??= await this.restoreChild(parent, childId, options.signal) + try { + this.authorizeLineage(parent, childId, activation.parent.id) + const result = await this.deliverInTree(requireLocalActivation(activation).handle.agent, [nextChild, ...rest], content, options) + this.announce(activation) + return { messageId: result } + } catch (error) { + if (restored) { + try { + await this.dispose(activation) + } catch (cleanupError: unknown) { + this.ctx.logger.warn(`subagent ancestor restoration rollback failed: ${failureMessage(cleanupError)}`) + } + } + throw error + } + }) + if ('messageId' in delivered) return delivered.messageId + await Promise.allSettled([delivered.closing]) + options.signal.throwIfAborted() + this.assertAdmitting(parent) + } + } catch (error) { + releaseHold() + throw error + } + } + /** * Queue one human-authored prompt as a distinct direct-child turn. * @param parent - exact live direct parent authorizing delivery. @@ -1362,17 +1457,24 @@ export class SubagentManager { content: ContentBlock[], options: ChildDeliveryOptions, ): Promise { + const activation = await this.restoreChild(parent, childId, options.signal) + return this.submitMaterialized(activation, content, options, parent) + } + + /** Read continuation history, preserving cancellation and reporting unavailable storage. */ + private async observeContinuation(childId: SessionId, signal: AbortSignal): Promise { const query = this.requireSessionQuery() - let observation: SessionObservation try { - observation = await query.observeSession(childId, { - signal: options.signal, - }) + return await query.observeSession(childId, { signal }) } catch (error: unknown) { - options.signal.throwIfAborted() + signal.throwIfAborted() throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error }) } - using source = observation + } + + /** Reconstruct one child under its exact parent; the caller owns acceptance or rollback. */ + private async restoreChild(parent: Agent, childId: SessionId, signal: AbortSignal): Promise { + using source = await this.observeContinuation(childId, signal) this.assertAdmitting(parent) this.authorizeLineage(parent, childId, source.header.parentSession) const ownEvents = source.events.slice(source.inheritedEventCount) @@ -1411,14 +1513,14 @@ export class SubagentManager { persona: descriptor.persona, toolFilter: descriptor.toolFilter, }, - signal: options.signal, + signal, }) } catch (error: unknown) { - options.signal.throwIfAborted() + signal.throwIfAborted() if (error instanceof SubagentError) throw error throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error }) } - return await this.submitMaterialized(activation, content, options, parent) + return activation } /** Admit a materialized child, commit its creation fact, and release it on failure. */ @@ -1459,10 +1561,9 @@ export class SubagentManager { options: ChildDeliveryOptions, parent: Agent, ): MessageId { - const message = options.source === undefined - ? createAgentMessage(parent, content, this.names.nameOf(parent)) - : createUserMessage({ content, source: options.source }) + const message = createUserMessage({ content, source: options.source }) options.signal.throwIfAborted() + options.authorize?.() this.assertAdmitting(parent) this.authorizeLineage(parent, activation.childId, activation.parent.id) this.acquireOwnership(parent, activation.childId) diff --git a/packages/subagent/subagent/src/names.ts b/packages/subagent/subagent/src/names.ts index 656f0a0a61..cac0f0f6eb 100644 --- a/packages/subagent/subagent/src/names.ts +++ b/packages/subagent/subagent/src/names.ts @@ -35,7 +35,7 @@ export class SubagentNames { if (!subagentNameSchema.safeParse(name).success) { throw new SubagentError('agent name must start with a lower-case letter and contain only lower-case letters, digits, underscores, or hyphens; lead and unowned are reserved', 'INVALID_AGENT_NAME') } - const root = this.rootOf(parent) + const root = this.ctx.subagents.rootOf(parent) signal.throwIfAborted() const names = this.committed(root) const pending = this.pending.get(root) ?? new Set() @@ -53,7 +53,7 @@ export class SubagentNames { return { commit: (childId, publish) => { signal.throwIfAborted() - if (this.rootOf(parent) !== root) throw new SubagentError('agent tree changed during creation', 'AGENT_TREE_UNAVAILABLE') + if (this.ctx.subagents.rootOf(parent) !== root) throw new SubagentError('agent tree changed during creation', 'AGENT_TREE_UNAVAILABLE') checkId(childId) publish() if (parent !== root) { @@ -91,7 +91,7 @@ export class SubagentNames { */ async resolve(caller: Agent, name: string, signal?: AbortSignal): Promise { signal?.throwIfAborted() - const root = this.rootOf(caller) + const root = this.ctx.subagents.rootOf(caller) if (name === 'lead') return root.id const id = this.committed(root).get(name) if (id !== undefined) return id @@ -117,29 +117,11 @@ export class SubagentNames { * @returns whether the root catalog records that assignment. */ isRegistered(caller: Agent, name: string, id: SessionId): boolean { - return this.committed(this.rootOf(caller)).get(name) === id + return this.committed(this.ctx.subagents.rootOf(caller)).get(name) === id } private committed(root: Agent): ReadonlyMap { return new Map([['lead', root.id], ...catalogNames(liveCatalogState(this.ctx.get('sessionProjections'), root.session), root.id)]) } - /** Resolve only uninterrupted live subagent ancestry, never a fork's inherited parent. */ - private rootOf(caller: Agent): Agent { - if (this.ctx.agents.get(caller.id) !== caller) { - throw new SubagentError('agent naming requires the exact live caller', 'UNAUTHORIZED') - } - let root = caller - const seen = new Set() - while (root.session.header.origin === 'subagent') { - const parentId = root.session.header.parentSession - const parent = parentId === undefined ? undefined : this.ctx.agents.get(parentId) - seen.add(root.id) - if (parent === undefined || seen.has(parent.id)) { - throw new SubagentError('agent naming requires the live parent tree', 'AGENT_TREE_UNAVAILABLE') - } - root = parent - } - return root - } } diff --git a/packages/subagent/subagent/tests/continuation.spec.ts b/packages/subagent/subagent/tests/continuation.spec.ts index b2601fa0a6..ada5bc392f 100644 --- a/packages/subagent/subagent/tests/continuation.spec.ts +++ b/packages/subagent/subagent/tests/continuation.spec.ts @@ -2865,7 +2865,7 @@ describe('continuable adjacent-Agent delivery', () => { signal: testSignal, })).rejects.toMatchObject({ code: 'UNAUTHORIZED', - message: `agent "${childId}" is not a resident continuable child and cannot send to parent "${parent.id}"`, + message: `agent "${childId}" is not a resident continuable child`, }) await handle.dispose() diff --git a/packages/subagent/subagent/tests/names.spec.ts b/packages/subagent/subagent/tests/names.spec.ts index d3c205ee12..2a306ede4f 100644 --- a/packages/subagent/subagent/tests/names.spec.ts +++ b/packages/subagent/subagent/tests/names.spec.ts @@ -254,7 +254,7 @@ describe('tree-wide agent names', () => { const session = ctx.sessions.create(SessionId('orphan'), { meta: { origin: 'subagent' } }) const child = { ...parent, id: session.id, session } onTestFinished(ctx.agents.enter(child, undefined)) - expect(() => names.reserve(child, 'writer', signal)).toThrow(expect.objectContaining({ code: 'AGENT_TREE_UNAVAILABLE' })) + expect(() => names.reserve(child, 'writer', signal)).toThrow(expect.objectContaining({ code: 'UNAUTHORIZED' })) }) it('rejects stale callers, detached children, and a missing identity projection', async () => { @@ -264,7 +264,7 @@ describe('tree-wide agent names', () => { const child = { ...parent, id: session.id, session } onTestFinished(ctx.agents.enter(child, undefined)) expect(names.nameOf(child)).toBe('legacy:orphan') - expect(() => names.reserve(child, 'writer', signal)).toThrow(expect.objectContaining({ code: 'AGENT_TREE_UNAVAILABLE' })) + expect(() => names.reserve(child, 'writer', signal)).toThrow(expect.objectContaining({ code: 'UNAUTHORIZED' })) const nestedSession = ctx.sessions.create(SessionId('nested'), { meta: { origin: 'subagent', parentSession: parent.id } }) const nested = { ...parent, id: nestedSession.id, session: nestedSession } onTestFinished(ctx.agents.enter(nested, undefined)) diff --git a/packages/subagent/subagent/tests/sibling-messages.spec.ts b/packages/subagent/subagent/tests/sibling-messages.spec.ts index a77f158052..6e247133ca 100644 --- a/packages/subagent/subagent/tests/sibling-messages.spec.ts +++ b/packages/subagent/subagent/tests/sibling-messages.spec.ts @@ -1,5 +1,5 @@ import { mountWorkingDirectoryFixture } from './working-directory-fixture.ts' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, onTestFinished, vi } from 'vitest' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -16,6 +16,8 @@ import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-a import SubagentRuntime from '../src/index.ts' import { loadStoredSession } from './persistence-helpers.ts' import { TestSessionQuery } from './test-session-query.ts' +import { managerState } from './manager-internals.ts' +import { requireLocalActivation } from '../src/activation.ts' /** A model reply, or a turn that stays open until its Agent is cancelled. */ type Reply = StreamChunk[] | 'hang' @@ -58,6 +60,7 @@ afterEach(async () => { /** Boot persistence, the Agent loop, one spawn provider, and the routed model. */ async function setup(replies: Record) { const ctx = new Context() + disposers.push(() => ctx.fiber.dispose()) await mountAgentLoopTestDependencies(ctx) const root = mkdtempSync(join(tmpdir(), 'dsh-subagent-siblings-')) roots.push(root) @@ -70,13 +73,13 @@ async function setup(replies: Record) { const adapter = new RoutedAdapter(new Map(Object.entries(replies))) ctx.llm.registerAdapter(['mock'], adapter) const parent = await ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' }) - disposers.push(() => ctx.fiber.dispose()) + ctx.on('agent/pre-step', async ({ agent }, next) => agent === parent ? { kind: 'reject' } : next()) return { ctx, parent, adapter } } const signal = new AbortController().signal -/** Start one caller-delivery child whose history carries `TASK:`. */ +/** Start one caller-delivery child whose history carries 'TASK:'. */ async function start(ctx: Context, parent: Agent, marker: string, description = 'shared responsibility'): Promise { const started = await ctx.subagents.startActivation({ name: marker, provider: 'spawn', @@ -148,3 +151,235 @@ describe('subagent sibling messages', () => { await Promise.all([settled(ctx, a), settled(ctx, b)]) }) }) + + +describe('tree-wide messages', () => { + it('keeps ordinary forks independent and rejects stale callers or broken ancestry', async () => { + const { ctx, parent } = await setup({}) + expect(ctx.subagents.rootOf(parent)).toBe(parent) + expect(() => ctx.subagents.rootOf({ ...parent })).toThrow(expect.objectContaining({ code: 'UNAUTHORIZED' })) + const fork = ctx.sessions.create(SessionId('ordinary-fork'), { meta: { parentSession: parent.id } }) + const caller = { ...parent, id: fork.id, session: fork } + onTestFinished(ctx.agents.enter(caller, undefined)) + expect(ctx.subagents.rootOf(caller)).toBe(caller) + for (const [id, parentSession] of [ + ['orphan', undefined], ['offline-parent', SessionId('absent')], ['cycle', SessionId('cycle')], + ] as const) { + const session = ctx.sessions.create(SessionId(id), { + meta: { origin: 'subagent', ...parentSession === undefined ? {} : { parentSession } }, + }) + const child = { ...parent, id: session.id, session } + onTestFinished(ctx.agents.enter(child, undefined)) + expect(() => ctx.subagents.rootOf(child)).toThrow(expect.objectContaining({ code: 'UNAUTHORIZED' })) + } + }) + + it('delivers across branches, to deeper descendants, and back to the Lead', async () => { + const { ctx, parent } = await setup({ a: ['hang'], b: ['hang'], leaf: ['hang'] }) + const a = await start(ctx, parent, 'a') + const sender = await running(ctx, a) + const b = await start(ctx, parent, 'b') + const branch = await running(ctx, b) + const leafId = await start(ctx, branch, 'leaf') + const leaf = await running(ctx, leafId) + const received: Array<{ target: SessionId; sender: SessionId; text: string }> = [] + ctx.on('agent/inbox/inserted', ({ agent, message }) => { + if (message.source.kind === 'agent-message') received.push({ + target: agent.id, sender: message.source.senderSessionId, + text: message.content.flatMap(block => block.type === 'text' ? [block.text] : []).join(''), + }) + }) + await ctx.subagents.sendMessage(sender, leafId, [{ type: 'text', text: 'cousin' }], { signal }) + await ctx.subagents.sendMessage(parent, leafId, [{ type: 'text', text: 'grandchild' }], { signal }) + await ctx.subagents.sendMessage(leaf, parent.id, [{ type: 'text', text: 'grandparent' }], { signal }) + expect(received).toEqual([ + { target: leafId, sender: a, text: 'Agent a sent a message: cousin' }, + { target: leafId, sender: parent.id, text: 'Agent lead sent a message: grandchild' }, + { target: parent.id, sender: leafId, text: 'Agent leaf sent a message: grandparent' }, + ]) + expect(() => { ctx.subagents.interrupt(leafId, { kind: 'ancestor', agent: sender }) }) + .toThrow(expect.objectContaining({ code: 'UNAUTHORIZED' })) + await expect(ctx.subagents.sendMessage(sender, a, [], { signal })).rejects.toMatchObject({ code: 'UNAUTHORIZED' }) + }) + + it('restores an inactive branch under its original parent to reach a cold descendant', async () => { + const { ctx, parent, adapter } = await setup({ + a: ['hang'], branch: ['hang', textResponse('branch ack')], leaf: [textResponse('first'), textResponse('reply')], + }) + const a = await start(ctx, parent, 'a') + const sender = await running(ctx, a) + const branchId = await start(ctx, parent, 'branch') + const branch = await running(ctx, branchId) + const leafId = await start(ctx, branch, 'leaf') + await settled(ctx, leafId) + ctx.subagents.interrupt(branchId, { kind: 'ancestor', agent: parent }) + await settled(ctx, branchId) + const resumed: Array<{ id: SessionId; parent: SessionId | undefined }> = [] + ctx.on('agent/created', ({ agent }) => { resumed.push({ id: agent.id, parent: agent.session.header.parentSession }) }) + + await ctx.subagents.sendMessage(sender, leafId, [{ type: 'text', text: 'cold cousin' }], { signal }) + + const messages = await storedUserMessages(ctx, leafId) + const delivered = messages.find(event => event.data.source.kind === 'agent-message')! + expect(textOf(delivered)).toBe('Agent a sent a message: cold cousin') + expect(delivered.data.source).toEqual({ kind: 'agent-message', form: 'relay', senderSessionId: a }) + await settled(ctx, branchId) + expect(resumed).toEqual([{ id: branchId, parent: parent.id }, { id: leafId, parent: branchId }]) + expect(adapter.routed.filter(request => request.sessionId === leafId)).toHaveLength(2) + const branchMessages = await storedUserMessages(ctx, branchId) + expect(branchMessages.some(event => textOf(event).includes('cold cousin'))).toBe(false) + }) + + it('rechecks the sender after asynchronous target discovery', async () => { + const { ctx, parent } = await setup({ a: ['hang'], b: ['hang'] }) + const a = await start(ctx, parent, 'a') + const sender = await running(ctx, a) + const b = await start(ctx, parent, 'b') + const target = await running(ctx, b) + const observe = ctx.sessionQuery.observeSession.bind(ctx.sessionQuery) + let stale = false + const get = ctx.agents.get.bind(ctx.agents) + const registry = vi.spyOn(ctx.agents, 'get').mockImplementation(id => id === a && stale ? undefined : get(id)) + const query = vi.spyOn(ctx.sessionQuery, 'observeSession').mockImplementation(async (...args) => { + const observation = await observe(...args) + stale = true + return observation + }) + try { + await expect(ctx.subagents.sendMessage(sender, b, [{ type: 'text', text: 'rejected' }], { signal })) + .rejects.toMatchObject({ code: 'UNAUTHORIZED' }) + expect(target.inbox.nextStep).toHaveLength(0) + } finally { + registry.mockRestore() + query.mockRestore() + } + }) + it.each([[false, false], [true, false], [false, true]])('releases only newly restored ancestors when the target cannot resume (resident=%s, cleanup failure=%s)', async (resident, failCleanup) => { + const { ctx, parent } = await setup({ branch: ['hang'], leaf: [textResponse('first')] }) + const branchId = await start(ctx, parent, 'branch') + const branch = await running(ctx, branchId) + const leafId = await start(ctx, branch, 'leaf') + await settled(ctx, leafId) + if (!resident) { + ctx.subagents.interrupt(branchId, { kind: 'ancestor', agent: parent }) + await settled(ctx, branchId) + } + const warnings: string[] = [] + ctx.logger.warn = (message: string) => { warnings.push(message) } + if (failCleanup) ctx.on('subagent/start', ({ id }) => { + if (id !== branchId) return + const handle = requireLocalActivation(managerState(ctx).resident.get(id)!).handle + const dispose = handle.dispose.bind(handle) + handle.dispose = async () => { + await dispose() + throw new Error('ancestor cleanup report failed') + } + }) + const observe = ctx.sessionQuery.observeSession.bind(ctx.sessionQuery) + let leafReads = 0 + const query = vi.spyOn(ctx.sessionQuery, 'observeSession').mockImplementation(async (...args) => { + if (args[0] === leafId && ++leafReads === 2) throw new Error('target storage unavailable') + return observe(...args) + }) + try { + await expect(ctx.subagents.sendMessage(parent, leafId, [{ type: 'text', text: 'rejected' }], { signal })) + .rejects.toMatchObject({ code: 'NOT_RESUMABLE' }) + expect(ctx.agents.get(leafId)).toBeUndefined() + expect(ctx.agents.get(branchId)).toBe(resident ? branch : undefined) + if (failCleanup) expect(warnings.join(' ')).toContain('ancestor restoration rollback failed') + } finally { + query.mockRestore() + } + }) + + it('requires the parent catalog to attest every target edge', async () => { + const { ctx, parent } = await setup({ branch: ['hang'], leaf: ['hang'] }) + const branchId = await start(ctx, parent, 'branch') + const branch = await running(ctx, branchId) + const leafId = await start(ctx, branch, 'leaf') + const leaf = await running(ctx, leafId) + const observe = ctx.sessionQuery.observeSession.bind(ctx.sessionQuery) + const query = vi.spyOn(ctx.sessionQuery, 'observeSession').mockImplementation(async (...args) => { + const result = await observe(...args) + return args[0] !== branchId ? result : { + ...result, + projections: { ...result.projections!, values: { ...result.projections!.values, subagentCatalog: [] } }, + } + }) + try { + await expect(ctx.subagents.sendMessage(parent, leafId, [{ type: 'text', text: 'rejected' }], { signal })) + .rejects.toMatchObject({ code: 'UNAUTHORIZED' }) + expect(leaf.inbox.nextStep).toHaveLength(0) + } finally { + query.mockRestore() + } + }) + + it('rejects a replaced root after asynchronous target discovery', async () => { + const { ctx, parent } = await setup({ a: ['hang'], b: ['hang'] }) + const a = await start(ctx, parent, 'a') + const sender = await running(ctx, a) + const b = await start(ctx, parent, 'b') + const target = await running(ctx, b) + const observe = ctx.sessionQuery.observeSession.bind(ctx.sessionQuery) + let replaced = false + const replacement = new Proxy(parent, {}) + const get = ctx.agents.get.bind(ctx.agents) + const registry = vi.spyOn(ctx.agents, 'get').mockImplementation(id => id === parent.id && replaced ? replacement : get(id)) + const query = vi.spyOn(ctx.sessionQuery, 'observeSession').mockImplementation(async (...args) => { + const observation = await observe(...args) + replaced = true + return observation + }) + try { + await expect(ctx.subagents.sendMessage(sender, b, [{ type: 'text', text: 'rejected' }], { signal })) + .rejects.toMatchObject({ code: 'UNAUTHORIZED' }) + expect(target.inbox.nextStep).toHaveLength(0) + } finally { + registry.mockRestore() + query.mockRestore() + } + }) + + it.each([false, true])('waits for a closing ancestor before restoring it (cleanup failure=%s)', async (failCleanup) => { + const { ctx, parent } = await setup({ branch: ['hang', textResponse('ack')], leaf: [textResponse('first'), textResponse('reply')] }) + const branchId = await start(ctx, parent, 'branch') + const branch = await running(ctx, branchId) + const leafId = await start(ctx, branch, 'leaf') + await settled(ctx, leafId) + const activation = managerState(ctx).resident.get(branchId)! + const handle = requireLocalActivation(activation).handle + const dispose = handle.dispose.bind(handle) + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + handle.dispose = async () => { + entered.resolve(undefined) + await release.promise + await dispose() + if (failCleanup) throw new Error('cleanup report failed') + } + const drained = ctx.subagents.drainChildren(parent, [branchId]).catch((error: unknown) => error) + let delivery: Promise | undefined + try { + await entered.promise + const waiting = vi.spyOn(activation.closing!, 'then') + delivery = ctx.subagents.sendMessage(parent, leafId, [{ type: 'text', text: 'after close' }], { signal }) + await vi.waitFor(() => { expect(waiting).toHaveBeenCalled() }) + waiting.mockRestore() + let unlocked = false + const access = managerState(ctx).locks.run(branchId, async () => { unlocked = true }) + await vi.waitFor(() => { expect(unlocked).toBe(true) }) + await access + release.resolve(undefined) + await delivery + const messages = await storedUserMessages(ctx, leafId) + expect(messages.some(message => textOf(message) === 'Agent lead sent a message: after close')).toBe(true) + await settled(ctx, branchId) + } finally { + release.resolve(undefined) + await drained + await delivery + } + }) + +}) diff --git a/packages/subagent/tool-subagent-control/README.i18n.yaml b/packages/subagent/tool-subagent-control/README.i18n.yaml index 6273f564ed..80e04699cb 100644 --- a/packages/subagent/tool-subagent-control/README.i18n.yaml +++ b/packages/subagent/tool-subagent-control/README.i18n.yaml @@ -9,8 +9,8 @@ en: 465d36925c792167 zh: 269cac314f638827 /deepseek-ai-dsh-tool-subagent-control/summary: - en: 5b225b5c13d38808 - zh: 185508ceb2ad7d88 + en: c18c57e7cb626ca6 + zh: c8afe38ef9db232b /deepseek-ai-dsh-tool-subagent-control/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb @@ -21,14 +21,14 @@ en: 4a52c81317ec9001 zh: ebd9f1ba920da50a /deepseek-ai-dsh-tool-subagent-control/use-this-package/send-message: - en: 91882e24297b19f2 - zh: 4d7bd27e76e825e7 + en: 597ba7236ac0c161 + zh: 06209691f7df651d /deepseek-ai-dsh-tool-subagent-control/use-this-package/interrupt-agent: en: caf1c74171c10bf9 zh: 5031c99d8724e2b1 /deepseek-ai-dsh-tool-subagent-control/use-this-package/list-agents: - en: 3b6b212b133f94ce - zh: 725d4875bda0ce22 + en: 0a94b7351a55c776 + zh: fce8840fe7e0d933 /deepseek-ai-dsh-tool-subagent-control/understand-the-implementation: en: c5cdf01a78c990f2 zh: 53c180d726ce4502 @@ -36,14 +36,14 @@ en: 8d317409d4a6029c zh: 045538831dbaa0bb /deepseek-ai-dsh-tool-subagent-control/understand-the-implementation/delivery-and-signal-ownership: - en: 6f47d91f63f8007c - zh: 1a3c63e18ece210b + en: ff5b23d62bc27b56 + zh: 26eab34b92f8b012 /deepseek-ai-dsh-tool-subagent-control/understand-the-implementation/listing-projection: - en: 05a40d0cd5acd045 - zh: 1914d078d4263753 + en: ee3136b09f0c9ef2 + zh: 5cbe189bb857fdc7 /deepseek-ai-dsh-tool-subagent-control/understand-the-implementation/source-map: - en: c86b31cecfd505a0 - zh: 14250cb3bf3e614f + en: db4a6499e5344cb1 + zh: 7ecff8d2ac204449 /deepseek-ai-dsh-tool-subagent-control/further-exploration: en: df2bf39d0ffffd43 zh: 4cd02551f7433c90 @@ -78,11 +78,11 @@ en: ab964e307bd37dca zh: 03f975252f838a42 /deepseek-ai-dsh-tool-subagent-control/model-experience/delivery-result/what-the-model-sees: - en: cbce90fab7d1700a - zh: 0b68d13bb4d4e03d + en: 5b50b601447c7378 + zh: 766f040863ba024c /deepseek-ai-dsh-tool-subagent-control/model-experience/delivery-result/token-effect: - en: 9fcc3cae6b23815d - zh: fcc2a9fe0dcb2909 + en: 2f8fb7fc2b1afca1 + zh: 5e008f5b97babb2d /deepseek-ai-dsh-tool-subagent-control/model-experience/delivery-result/kv-cache-effect: en: f5d5f97a153f7c70 zh: 3d6bc05b9eadfbc2 @@ -90,17 +90,17 @@ en: 094e55d258f2ec2e zh: f364bc9046530bff /deepseek-ai-dsh-tool-subagent-control/model-experience/listing-result/what-the-model-sees: - en: f888710beb3d325e - zh: 1869a221626dd2ce + en: a3524d40d2b1c9af + zh: bec98179547a65aa /deepseek-ai-dsh-tool-subagent-control/model-experience/listing-result/token-effect: - en: d3ced2fbde9382ef - zh: 8c7e904ed30f7353 + en: cf5518c977befa64 + zh: 4735a2d3e965874f /deepseek-ai-dsh-tool-subagent-control/model-experience/listing-result/kv-cache-effect: en: 4a629edb36a17e32 zh: 5a7539cfbf404c54 /deepseek-ai-dsh-tool-subagent-control/known-limitations-and-deferred-work: - en: 053847e4b5f93871 - zh: f0b5ae067bc02c28 + en: ae88df703efcfa3c + zh: 025b705f4fc9a70d /deepseek-ai-dsh-tool-subagent-control/known-limitations-and-deferred-work/dev-note: en: e67f633dc6fe7773 zh: 3bb0c20c93f16f98 diff --git a/packages/subagent/tool-subagent-control/README.md b/packages/subagent/tool-subagent-control/README.md index 9ece598020..526d55b58b 100644 --- a/packages/subagent/tool-subagent-control/README.md +++ b/packages/subagent/tool-subagent-control/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-tool-subagent-control` adds the global control tools for continuable children: `send_message` steers between a parent and child or between siblings, `interrupt_agent` stops a descendant’s current turn while keeping its inbox and descendants intact, and the separately loadable `list-agents` plugin discovers agents by name and description. Parents and continuable children inherit the same tool definitions and ordering. +`dsh-tool-subagent-control` lets agents discover and message every member of their Team by name. `interrupt_agent` stops only the caller’s descendants, retaining their inboxes and children. The separately loadable `list-agents` plugin lists the whole tree with descriptions, parent names, depth, and activity. All members inherit the same tool definitions and ordering. ## Table of Contents @@ -45,7 +45,7 @@ This package takes no configuration: the root plugin provides `send_message` and ### send_message -Sends a message to the agent `name`. Any exact live Agent may target its direct continuable child; a resident continuable child may also target its direct parent or continuable siblings. Sibling delivery uses the shared live parent’s authority and preserves the actual sender. A working target receives the message at its nearest step boundary through Steer; an inactive child or sibling starts or resumes a turn. Success returns inbox acceptance (`messageId`), never a reply. An unsupported target, unavailable parent, unknown name, missing descriptor, or rejected admission returns an error. +Sends a message to another Team member by `name`. The caller must be the live root or a resident continuable subagent. A working target receives Steer input at its next step; an inactive target starts or resumes a turn. Cold ancestors resume under their recorded parents without receiving the target’s message. Success returns inbox acceptance (`messageId`), never a reply. Self, unrelated trees, unsupported targets, unknown names, unavailable history, and rejected admission return errors. ### interrupt_agent @@ -53,7 +53,7 @@ Sends a message to the agent `name`. Any exact live Agent may target its direct ### list_agents -Lists all reachable continuable descendants in stable pre-order, with each row’s direct parent name and depth. A continuable subagent with a live parent first sees that parent and its continuable siblings. Live status is `running` or `inactive`. External entries are omitted without reading child Sessions; readable one-shot children are traversal nodes but not output rows. Unknown modes and unreadable child catalogs produce diagnostics. Ordinary Session forks are not catalog entries. +Lists the root and all reachable continuable descendants, including the caller, in stable pre-order. Descendant rows carry their direct parent id and depth from the root. Live status is `running` or `inactive`. External entries are omitted without reading child Sessions; readable one-shot children remain traversal nodes. Unknown modes and unreadable catalogs produce diagnostics. Ordinary Session forks lead separate Teams. ----- @@ -71,18 +71,18 @@ Thin adapters resolve names through `ctx.subagents.resolveName()` before calling ### Delivery and signal ownership -The tool forwards its execution signal, which owns admission only until inbox acceptance. Accepted messages cannot be cancelled through this tool. Every message is framed as `Agent sent a message:` and recorded with `{ kind: 'agent-message', form: 'relay', senderSessionId: sender.id }`, including sibling delivery under the parent’s authority. Attribution never grants authority. +The execution signal owns work only until inbox acceptance. Accepted messages cannot be cancelled through this tool. Every message is framed as `Agent sent a message:` and recorded with `{ kind: 'agent-message', form: 'relay', senderSessionId: sender.id }`. Tree membership authorizes communication; the target’s actual parent still owns restoration and disposal. ### Listing projection -`list_agents` traverses the caller’s catalogs without a cursor and refines status through the live Agent registry. A continuable caller’s live parent catalog supplies the parent and sibling rows. Descendant traversal preserves catalog order; unknown modes remain traversable with diagnostics, while unreadable catalogs stop that branch. Descendants absent from reachable catalogs cannot be discovered. +`list_agents` resolves the caller’s live root and traverses its catalogs without a cursor, refining activity through the live Agent registry. Unknown modes remain traversable with diagnostics; unreadable catalogs stop that branch. Sessions absent from reachable catalogs cannot be discovered. ### Source map | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `send_message` and `interrupt_agent` registration | -| [`src/list-agents.ts`](src/list-agents.ts) | `list_agents` registration: parent, sibling, and child discovery and status refinement | +| [`src/list-agents.ts`](src/list-agents.ts) | Whole-Team discovery and activity projection | @@ -134,11 +134,11 @@ Append-only; each result follows the reusable request prefix. #### What the model sees -`message delivered to agent ` on acceptance; canonical output carries the accepted `messageId`. A target outside the caller’s supported parent, child, and sibling routes, or rejected admission, produces an errored result. +`message delivered to agent ` on acceptance; canonical output carries the accepted `messageId`. A target outside the caller’s Team or rejected admission produces an errored result. #### Token effect -One short acknowledgement per call; the target’s response never returns through this call. Children use parent and sibling names from `list_agents` for follow-up messages. +One short acknowledgement per call; replies arrive as separately addressed messages. Use names from `list_agents` for follow-ups across the Team. #### KV Cache effect @@ -148,11 +148,11 @@ Append-only; newly visible content follows the reusable request prefix and does #### What the model sees -One line per descendant in stable order: ` [] parent= depth= —