Merge branch 'worktree/composer-image-preupload' into worktree/pr-6426-review-970262

# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/src/client/input/facade.ts
#	packages/client/ui-conversation/src/client/input/hub.ts
This commit is contained in:
Yichen Jiang committed 2026-10-21 15:15:46 +08:00
commit faf62f4c1a
574 files changed
+10024 -3845

No files matched your search

@@ -9,8 +9,8 @@
en: 6bb727a659ba1503
zh: d2ebf5c8164a807e
/agent-note-current-profile-plugin-management-shares-cli-transactions/decision:
en: 863b4369c39f3933
zh: 7dd44a91ccaaef8f
en: d2e13562b5aef903
zh: 07face30189c07fc
/agent-note-current-profile-plugin-management-shares-cli-transactions/alternatives-considered:
en: 30c609de89084351
zh: 27f2747ad9d9b752
@@ -16,7 +16,7 @@ Configuration watches use Chokidar write stabilization by default. Its ordinary
Profile files remain the persisted state: entry toggles edit only `disabled` in the last override matching the entry id and any module-name assertion, appending when none matches, and bundle toggles edit the ordered string list. Dependency updates do not reactivate retained disabled bundles. A service removal first applies the composition without the bundle and waits for old fibers to finish before deleting the dependency. Saved configuration, pnpm completion and runtime activation have separate outcomes; a failed removal preserves the actual partial state and a diagnostic path, while a failed or cancelled installation restores the profile files it snapshotted.
This extends the [profile bundle composition decision](2026-08-05-profile-plugin-bundles.md). Profiles without HMR keep their process composition, and Desktop package management remains shell-owned. Web controls and explicitly enabled agent tools call the same service. Management operations return results to callers without adding messages to live Agents. The agent tool is enabled in Creator mode and disabled by default in the base bundle and other shipped presets. The browser-only worker preview has no host package installer; its module-proxy table refuses `execa` calls explicitly while retaining the management module for inventory discovery.
This extends the [profile bundle composition decision](2026-08-05-profile-plugin-bundles.md). Profiles without HMR keep their process composition, and Desktop package management remains shell-owned. Web controls and explicitly enabled agent tools call the same service. Management operations return results to callers without adding messages to live Agents. The agent tool is enabled in Standard and Creator modes and disabled by default in the base bundle and other shipped presets. The browser-only worker preview has no host package installer; its module-proxy table refuses `execa` calls explicitly while retaining the management module for inventory discovery.
CLI calls inherit the terminal and authentication environment; service calls retain the subprocess credential scrub and bounded diagnostics. Management records carry error codes and parameters for locale-owned Web presentation. Reconciliation compares entry identity, fiber identity, configuration and diagnostics before and after updating: unchanged inactive entries remain warnings, while newly affected failures reject the operation. Explicit enablement targets must activate.
@@ -16,7 +16,7 @@ Web 和 Agent 控件需要修改运行中的 profile,同时避免另建包安
profile 文件保持为持久状态:条目开关只修改最后一条符合条目 id 及模块名称断言的覆盖项中的 `disabled`,没有匹配项时追加,组合包开关修改有序字符串列表。更新依赖不会重新激活保留的已停用组合包。service 删除组合包时,先应用去掉该组合包的配置,等待旧 fiber 完成卸载后再删除依赖。已保存配置、pnpm 完成状态与运行时激活分别报告;失败的删除保留实际的部分状态与诊断路径,失败或被取消的安装则恢复它快照的 profile 文件。
这扩展了[profile 组合包决策](2026-08-05-profile-plugin-bundles.zh.md)。startup profile 保留进程组合,Desktop 包管理仍由 shell 持有。Web 控件与显式启用的 Agent 工具调用同一 service。管理操作向调用方返回结果,不向存活 Agent 添加消息。创造模式启用该 Agent 工具;base 组合包和其他内置预设默认禁用。纯浏览器 worker 预览没有宿主包安装器;其模块代理表明确拒绝 `execa` 调用,同时保留管理模块用于清单发现。
这扩展了[profile 组合包决策](2026-08-05-profile-plugin-bundles.zh.md)。startup profile 保留进程组合,Desktop 包管理仍由 shell 持有。Web 控件与显式启用的 Agent 工具调用同一 service。管理操作向调用方返回结果,不向存活 Agent 添加消息。标准模式和创造模式启用该 Agent 工具;base 组合包和其他内置预设默认禁用。纯浏览器 worker 预览没有宿主包安装器;其模块代理表明确拒绝 `execa` 调用,同时保留管理模块用于清单发现。
CLI 调用继承终端和认证环境;service 调用保留子进程凭据清理与有界诊断。管理结果提供错误码和参数,由 Web 词典呈现文案。重载前后比较 entry、fiber、配置与诊断:未变化的已有故障保留为警告,本次影响到的新故障使操作失败。显式启用的目标必须成功激活。
@@ -9,8 +9,8 @@
en: 44615a7a0939054e
zh: b8efa721321ba0d7
/agent-note-creator-mode-installs-persistent-plugin-bundles/decision:
en: c0d74db1b2b16576
zh: b8713a6aadf007f9
en: 38ecabc170c418c7
zh: d380f53fa508328e
/agent-note-creator-mode-installs-persistent-plugin-bundles/alternatives-considered:
en: af527d8aabdfd59c
zh: 34388da992d2ab56
@@ -12,6 +12,8 @@ Agents need to install capabilities and use them during the same conversation. G
Creator mode enables the existing `plugin_manager` tool. Agents author packages and Loader YAML patches as workspace files, then install them through `install_bundle`. An MCP connection is a configuration-only bundle inserting the installed `dsh-mcp-client`; a UI bundle includes a Host entry and a Client artifact. Profile locking, package installation, enablement and HMR remain owned by the existing manager. The entire management tool requires `danger-full-access` or approval for one call: profile changes can load code with host permissions and affect other sessions. Each execution, including inventory reads, uses the shared sandbox escalation helper and approval service before accessing the manager. Under lower sandbox modes, `ask` requests approval and `never` denies; a full-access session needs no additional approval. A grant does not change session permissions, but its profile changes persist. This keeps shell-equivalent Host access explicit without requiring a permanent session-wide elevation. Human-operated Web and CLI controls retain their existing behavior.
The default Standard mode enables the same tool under the same profile condition and approval gate. Without it, Standard sessions did not install plugins directly and spent extra turns and tokens working around the missing tool; the change also moves Standard and Creator toward one capability set. A full-access Standard session can therefore install Host code without a separate prompt, as its shell already can. PTC and Minimal keep the tool disabled.
The model sees two read-only Cordis inspection tools. Generated-code define/run/stop/undefine and dynamic self-inspection tool APIs are absent. Existing runtime and Client consumers retain their services; historical session cards remain readable. This supersedes only the model-facing mutation workflow in the [self-referential toolset reference](../../../../packages/extensions/cordis-host-runner/README.md); its runtime ownership and sandbox rationale remain independently relevant. The [profile transaction decision](2026-09-14-current-profile-plugin-management.md) continues to govern locking, package installation, and partial failures.
Visual creation requests default to an installed Client plugin rendered in the current Web page unless the user names another destination. The development skill supplies a minimal package and effect-owned Client registration. Discovery ends when the required APIs are known; a working first version is installed before optional visual refinement. Verification uses the connected page where available. Browser authentication or operating-system setup is not a prerequisite for plugin installation, and a mock preview cannot establish an in-app result.
@@ -12,6 +12,8 @@ agent 需要安装能力并在同一段对话中使用。生成代码工具在
创造模式启用现有的 `plugin_manager` 工具。agent 将包和 Loader YAML patch 写入工作区文件,再通过 `install_bundle` 安装。MCP 连接是插入已安装 `dsh-mcp-client` 的纯配置组合包;UI 组合包包含 Host 入口和 Client 产物。profile 锁、包安装、启停和 HMR 继续由现有管理器负责。 整个管理工具要求 `danger-full-access` 或单次调用的批准:profile 变更能以宿主权限加载代码,并影响其他会话。每次执行(包括查询列表)都会先使用共享沙箱提权函数和审批服务,再访问管理器。在较低沙箱模式下,`ask` 请求审批,`never` 拒绝;完整权限会话无需额外审批。批准不改变会话权限,但本次操作的 profile 变更会持久化。这让与 shell 等价的宿主访问需要明确授权,同时不必永久提升整个会话的权限。由用户直接操作的 Web 和 CLI 控件保持原有行为。
默认的标准模式以相同的 profile 条件和审批门槛启用同一工具。缺少该工具时,标准模式会话不会直接安装插件,而要额外花费轮次和 token 绕过;此改动也让标准模式和创造模式朝同一能力集收敛。因此,完整权限的标准模式会话无需单独审批即可安装 Host 代码,与其 shell 已有的能力一致。PTC 和 Minimal 仍禁用该工具。
模型可见两个只读 Cordis 检查工具,不再提供生成代码的 define/run/stop/undefine 和动态自省工具 API。现有运行时和 Client 消费者保留其服务;历史会话卡片仍然可读。这仅取代[自引用工具集参考](../../../../packages/extensions/cordis-host-runner/README.zh.md)中的模型侧变更流程;其运行时所有权和沙箱依据仍有独立价值。[profile 事务决策](2026-09-14-current-profile-plugin-management.zh.md)继续规定锁、包安装和部分失败行为。
除非用户指定其他目标,视觉创建请求默认通过已安装的 Client 插件显示在当前 Web 页面。开发 skill 提供最小包和由 effect 管理的 Client 注册示例。已知所需 API 后结束探查,在可选视觉优化之前先安装能工作的初版。有条件时使用已连接页面验证。浏览器认证或操作系统设置不是安装插件的前提,mock 预览不能证明应用内结果。
@@ -9,8 +9,8 @@
en: f96bf1ebc0719fb0
zh: e9a1144f0e76572d
/agent-note-background-hover-and-screenshot-coordinates/decision:
en: 0cdd295d5f1872ed
zh: fe03ec9f7df94a4a
en: 0f45528ea6c5988c
zh: 9bde9df417ddb1ce
/agent-note-background-hover-and-screenshot-coordinates/alternatives-considered:
en: 5fc4beaf79761c50
zh: 4948333b0020be28
@@ -12,7 +12,7 @@ Hover-only controls can sometimes be revealed without moving the user's pointer.
Native hover first uses a same-window element or ancestor advertising both `AXShowAlternateUI` and `AXShowDefaultUI`. Entering its revealed descendants preserves the parent UI; leaving restores it. An unavailable route falls back to the real system pointer. An AX action failure propagates because received effects may be uncertain. The result records the route and whether the system pointer moved. System movement requires the selected foreground app and AppKit's mouse hit test, which excludes input-transparent overlays. No route grants [cross-call ownership](2026-10-13-cua-continuous-hover.md).
Model-facing coordinate input defaults to pixels in the returned screenshot. `relative: true` accepts integer thousandths from 0 to 1000 for click, hover, scroll and drag; it does not apply to AX indices. The Host owns the mapping to native window points. An application-wide observation refreshes its inventory; a scoped observation preserves other windows' image mappings. Window resizing invalidates the old image, while movement preserves local coordinates. Relative endpoints remain inside the window and do not depend on screenshot resolution.
Model-facing coordinate input defaults to pixels in the returned screenshot. `relative: true` accepts integer thousandths from 0 to 999 for click, hover, scroll and drag; it does not apply to AX indices. The Host owns the mapping to native window points. An application-wide observation refreshes its inventory; a scoped observation preserves other windows' image mappings. Window resizing invalidates the old image, while movement preserves local coordinates. Relative endpoints remain inside the window and do not depend on screenshot resolution.
`LlmAdapter.imageRequestTarget` exposes the same dimensions and byte target used by image serialization. DeepSeek and pi-ai share their existing projection functions with this method. Other adapters preserve normalized images unless they declare resizing. Screenshot bytes returned to programs match the request image; its durable source is admitted and stored once. The native PNG metadata is checked against decoded source dimensions before any input mapping is published.
@@ -12,7 +12,7 @@ Status: implemented
原生悬停优先寻找同一窗口内同时声明 `AXShowAlternateUI` 和 `AXShowDefaultUI` 的元素或祖先。进入其展开区域的子控件时保持父控件展开,离开后恢复。没有可用路径时回退到真实系统鼠标。AX 动作失败直接返回,因为已产生的效果可能不确定。结果记录实际路径及是否移动系统鼠标。系统移动要求所选应用处于前台,并使用 AppKit 的鼠标命中查询排除不接收输入的覆盖窗口。所有路径都遵守[跨调用不分配所有权](2026-10-13-cua-continuous-hover.zh.md)的决定。
面向模型的坐标默认使用返回截图中的像素。click、hover、scroll 和 drag 传入 `relative: true` 时接受 0~1000 的千分比整数,不适用于 AX 索引。Host 负责换算原生窗口 point。应用级观察刷新窗口清单,单窗口观察保留其他窗口的图片映射。窗口尺寸变化使旧图片失效,移动窗口保留局部坐标。相对坐标的端点保持在窗口内,不随截图分辨率改变。
面向模型的坐标默认使用返回截图中的像素。click、hover、scroll 和 drag 传入 `relative: true` 时接受 0~999 的千分比整数,不适用于 AX 索引。Host 负责换算原生窗口 point。应用级观察刷新窗口清单,单窗口观察保留其他窗口的图片映射。窗口尺寸变化使旧图片失效,移动窗口保留局部坐标。相对坐标的端点保持在窗口内,不随截图分辨率改变。
`LlmAdapter.imageRequestTarget` 提供与图片序列化相同的尺寸和字节目标。DeepSeek 和 pi-ai 在该方法中复用已有投影函数。其他适配器除非声明缩放,否则保留归一化图片。返回程序的截图字节与模型请求图片一致,持久原图只接入并保存一次。发布输入映射前,原生 PNG 元数据必须与解码后的原图尺寸一致。
@@ -0,0 +1,22 @@
# Bilingual-pair consistency record for 2026-08-26-web-rich-text-quotes.md (docs/i18n/README.md): per heading
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-26-web-rich-text-quotes.md
/agent-note-durable-rich-text-quotes-in-the-web-composer:
en: 49522a0a81db6981
zh: 92e81872377e96e9
/agent-note-durable-rich-text-quotes-in-the-web-composer/problem:
en: 7bd295c086ad9905
zh: 704dc665acfaa5ec
/agent-note-durable-rich-text-quotes-in-the-web-composer/decision:
en: d27134e46ab55ea0
zh: 3f1da063b661a1b3
/agent-note-durable-rich-text-quotes-in-the-web-composer/alternatives-considered:
en: 1999da9f900c9be3
zh: cf6aad990a180851
/agent-note-durable-rich-text-quotes-in-the-web-composer/verification:
en: b320c262d9c08ee4
zh: 6047f864213a2b4c
/agent-note-durable-rich-text-quotes-in-the-web-composer/consequences:
en: bb6f4338aad0ec41
zh: 93a4396b619492b1
@@ -0,0 +1,37 @@
# Agent Note: Durable rich-text quotes in the Web composer
Status: implemented
English | [中文](2026-08-26-web-rich-text-quotes.zh.md)
## Problem
Selected transcript excerpts need atomic editing and reload-safe drafts without depending on the availability of their source messages.
## Decision
`ui-conversation` owns an inline `QuoteNode` beside reference nodes in the existing editor runtime. Its clipboard projection is canonical Markdown, while its detect projection is one atomic character. The existing semantic `DraftSnapshot` persists quote text and clipboard-coordinate spans beside references; raw Lexical JSON and runtime node identities remain outside browser storage. Session switching, Workspace transfer, and failed-send restoration use this same snapshot. Quote controls receive runtime-only locale labels.
`ui-chat` accepts one nonblank browser selection within displayed user content, including pending steering and local submission echoes or finalized Assistant text and reasoning. Streaming and interrupted Assistant output, tools, injected context, and Markdown controls are excluded. Selection extraction follows visible line and block breaks and inserts through the current Session input actions.
Text interaction belongs to the shared primitive and the Chat-owned list Slot. Quote registers only gesture admission and insertion. Local absolute placement keeps actions in the content coordinate system; outside presses and Escape close the same area-owned interaction for every consumer. Dismissal and selection clearing are separate operations so a failed insertion preserves the excerpt. Context-menu consumers cancel their originating browser event synchronously; rendering a Slot cannot retroactively cancel it.
The [text-derived blockquote decision](../simplification/2026-10-13-text-derived-message-quotes.md) owns sent-message recognition and presentation. The [UserText evaluator](../architecture/2026-10-10-user-text-json-nodes.md) renders derived quote nodes through the existing matcher registry.
Quotes display a compact single-line preview; clipped excerpts expose the complete text in a shared inline HoverCard. Their text has no source-navigation action. The editable draft alone exposes whole-node removal. Only selected text survives message persistence: no source Session, message, or selection coordinates are retained.
## Alternatives considered
**Persist Lexical JSON or introduce another draft format.** Editor serialization would become a product storage obligation, while a second draft format would duplicate restoration and transfer ownership. The existing semantic snapshot already carries reference spans and extends to quotes.
**Retain source navigation.** Availability checks would require source coordinates, subscriptions, history pagination, focus routing, and failure states. A future navigation feature must define those durable coordinates and target-owned actions before adding them.
**Store UserText nodes or callbacks.** Text nodes are derived presentation, and callbacks are runtime state. Both can change independently of the logged input and semantic draft.
## Verification
Composer tests cover atomic removal and undo, semantic storage round trips, Markdown serialization, and failed-send restoration. UserText tests cover text recognition, literal excerpt rendering, literal references, and credential spelling. The keyless Web scenario exercises selection, clipped previews, draft switching and reload, persistence, and ordinary Markdown submission through the shipped composition.
## Consequences
The draft and Session retain enough data for atomic editing and replay without retaining source-history dependencies. UserText remains a display projection; its JSON nodes never replace authoritative content. Browser-local storage retains unsent selected text under the existing draft policy.
@@ -0,0 +1,37 @@
# Agent Note:Web composer 中的持久富文本 quote
Status: implemented
[English](2026-08-26-web-rich-text-quotes.md) | 中文
## 问题
所选 transcript 摘录需要原子编辑与可跨刷新恢复的草稿,不依赖来源消息的可用性。
## 决策
`ui-conversation` 在现有编辑器 runtime 中于 reference 节点旁拥有行内 `QuoteNode`。其剪贴板投影是规范 Markdown,检测投影则是一个原子字符。现有语义 `DraftSnapshot` 在 reference 旁持久化 quote 文本与剪贴板坐标范围;原始 Lexical JSON 与 runtime 节点身份不进入浏览器存储。Session 切换、Workspace 转移和发送失败恢复使用同一个 snapshot。Quote 控件接收仅用于 runtime 的 locale 标签。
`ui-chat` 接受已展示的用户内容(包括待接纳 steering 和本地发送回显)、或最终完成的 Assistant 文本与 reasoning 中的一个非空浏览器选区。流式及中断的 Assistant 输出、tool、注入上下文和 Markdown 控件被排除。选区提取遵循可见的行与 block 换行,通过当前 Session 的 input actions 插入。
文本交互属于共享基础能力与 Chat 拥有的 list Slot,quote 只注册手势接管与插入操作。本地绝对定位让操作处于内容坐标系;外部按下与 Escape 对所有使用方关闭同一个区域拥有的交互。关闭展示与清除选区是独立操作,因此插入失败时保留摘录。右键菜单使用方同步取消来源浏览器事件;渲染 Slot 无法事后取消事件。
[文本派生 blockquote 决策](../simplification/2026-10-13-text-derived-message-quotes.zh.md)拥有发送后的消息识别与展示。[UserText 计算器](../architecture/2026-10-10-user-text-json-nodes.zh.md)通过现有 matcher registry 渲染派生的 quote 节点。
Quote 显示紧凑的单行预览;截断的摘录通过共享行内 HoverCard 展示完整文本。文本不提供来源导航操作,只有可编辑草稿提供整节点删除。消息持久化只保留所选文本:不保留来源 Session、消息或选区坐标。
## 考虑过的替代方案
**持久化 Lexical JSON 或引入另一套草稿格式。** 编辑器序列化会成为产品存储义务,第二套草稿格式则会重复拥有恢复与转移逻辑。现有语义 snapshot 已携带 reference 范围,也可扩展到 quote。
**保留来源导航。** 可用性检查需要来源坐标、订阅、历史分页、焦点路由和失败状态。未来若添加导航功能,必须先定义其持久坐标与目标拥有的操作。
**存储 UserText 节点或 callback。** 文本节点是派生的展示,callback 是 runtime 状态;二者都可以独立于记录的输入和语义草稿变化。
## 验证
Composer 测试覆盖原子删除与撤销、语义存储往返、Markdown 序列化和发送失败恢复。UserText 测试覆盖文本识别、字面摘录展示、字面 reference 与凭据原文。无密钥 Web 场景通过已发布组合验证选区、截断预览、草稿切换与刷新、持久化和普通 Markdown 提交。
## 后果
草稿与 Session 保留足以支持原子编辑和 replay 的数据,不保留对来源历史的依赖。UserText 仍是展示投影;其 JSON 节点不会替代权威 content。浏览器本地存储按现有草稿策略保留未发送的所选文本。
@@ -0,0 +1,22 @@
# Bilingual-pair consistency record for 2026-10-13-text-derived-message-quotes.md (docs/i18n/README.md): per heading
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-10-13-text-derived-message-quotes.md
/agent-note-recognize-message-quotes-from-markdown-text:
en: a2abd7f693937d51
zh: f9e4bb817536dbbe
/agent-note-recognize-message-quotes-from-markdown-text/problem:
en: d71ebbacbd9bd8a3
zh: 45c1f7fd1d797f61
/agent-note-recognize-message-quotes-from-markdown-text/decision:
en: 0abb09d5ba396f69
zh: dd067b0ca5fd376f
/agent-note-recognize-message-quotes-from-markdown-text/alternatives-considered:
en: 05c6009796927583
zh: 9f08229767c07b31
/agent-note-recognize-message-quotes-from-markdown-text/verification:
en: 790598fd0f3f0c67
zh: 0399e0e322ffca40
/agent-note-recognize-message-quotes-from-markdown-text/consequences:
en: 21f96c04f6edcc1d
zh: 8ea3f42a8e981592
@@ -0,0 +1,29 @@
# Agent Note: Recognize message quotes from Markdown text
Status: implemented
English | [中文](2026-10-13-text-derived-message-quotes.zh.md)
## Problem
Quote-only content indexes duplicate information in Markdown text and require coordinated prompt, source, echo, and queue metadata. The same visible blockquote can acquire different presentation and direct-input behavior depending on how it was created.
## Decision
Messages contain ordinary Markdown text. The shared blockquote matcher recognizes consecutive explicitly marked lines for UserText presentation. Hand-written and composer-created blockquotes have identical sent-message behavior. Quote previews display decoded text without running UserText rules over their contents. Session-reference and skill processing applies normally to quoted text; recognized Session labels retain the same presentation as the surrounding quote text.
The [rich-text quote feature](../feature/2026-08-26-web-rich-text-quotes.md) retains atomic draft nodes and semantic restoration spans. This decision supersedes only its durable quote-index distinction. UserText nodes and recognition ranges remain derived values.
## Alternatives considered
**Keep quote-only indexes.** Indexes preserve a distinction between typed and inserted quotes, but that distinction is unnecessary for sent messages and requires metadata coordination across every producer and renderer.
**Remove atomic draft spans.** Plain text cannot restore whole-node editing, undo, Session switching, or failed-send restoration. Draft spans remain browser-owned editing state.
## Verification
Matcher tests cover original offsets, explicit line markers, blank excerpts, and literal nested quotes. Composer tests assert ordinary text submission. The shipped Web scenario checks text-only persistence and quote display after reload.
## Consequences
Messages need no quote fields or content indexes. Typed blockquotes also display as quotes without changing direct-input processing. Recognition deliberately covers explicit quote lines rather than Markdown lazy continuation or fenced-code parsing. Source navigation still requires a separate durable-coordinate design.
@@ -0,0 +1,29 @@
# Agent Note: 从 Markdown 文本识别消息 quote
Status: implemented
[English](2026-10-13-text-derived-message-quotes.md) | 中文
## 问题
纯 quote content 索引重复了 Markdown 文本中的信息,并要求 prompt、source、回显与队列协同携带元数据。同样可见的 blockquote 会因创建方式不同而获得不同的展示与直接输入行为。
## 决策
消息包含普通 Markdown 文本。共享 blockquote 匹配器识别连续的显式标记行,用于 UserText 展示。手写与输入框创建的 blockquote 在发送后具有相同行为。Quote 预览直接展示还原后的文本,不对正文再次运行 UserText 规则。Session-reference 与 skill 处理照常作用于引用文本;已识别的 Session 标签与周围引用文字保持相同样式。
[富文本 quote 功能](../feature/2026-08-26-web-rich-text-quotes.zh.md)保留原子草稿节点与语义恢复范围。本决策仅取代其持久 quote 索引区分。UserText 节点与识别范围仍是派生值。
## 考虑过的替代方案
**保留纯 quote 索引。** 索引保留手写与插入 quote 的区别,但发送后的消息无需这一区分,且要求每个生产方与渲染器协同处理元数据。
**移除原子草稿范围。** 纯文本无法恢复整节点编辑、撤销、Session 切换或发送失败恢复。草稿范围仍是浏览器拥有的编辑状态。
## 验证
匹配器测试覆盖原始偏移、显式行标记、空白摘录与字面的嵌套 quote。Composer 测试断言普通文本提交。已发布 Web 场景检查仅文本的持久化与刷新后的 quote 展示。
## 后果
消息无需 quote 字段或 content 索引。手写 blockquote 也显示为 quote,不改变直接输入处理。识别有意限定为显式引用行,不涵盖 Markdown 懒惰续行或代码围栏解析。来源导航仍需独立设计持久坐标。
+1 -1
View File
@@ -55,7 +55,7 @@ This is not a one-way shortening pass. Add or restore prose when code, types, an
- **Postmortems:** retain the incident sequence, evidence, causal chain, impact, and prevention. Remove repeated persuasion or implementation detail that does not establish causality.
- **Skills and agent instructions:** state behavioral guardrails and explicit scope limitations such as “guidance, not a script/checklist.” Keep the workflow concise and link its source of truth.
- **Examples and configuration comments:** explain access limits, non-obvious wiring or load order, security stance, replay behavior, exceptions, and likely misuse. Do not narrate entries that the configuration already shows.
- **Prompts and visible strings:** treat wording as behavior. Client UI copy belongs in typed locale dictionaries and reaches Cordis-free primitives as explicit localized props; inspect text, accessibility names, tooltips, placeholders, and format templates together, then run `verify-client-ui-i18n`. Update the owning runnable snapshot for model-visible text and repository-required GUI evidence. If the authorized scope has no owning scenario, leave the wording unchanged and report the deferral; do not silently fold it into a prose-only edit.
- **Prompts and visible strings:** treat wording as behavior. Client UI copy belongs in typed locale dictionaries and reaches Cordis-free primitives as explicit localized props; inspect text, accessibility names, tooltips, placeholders, and format templates together, then run `verify-client-ui-i18n`. English UI copy, including plugin `meta.title` and `meta.description`, uses sentence case: capitalize only the first word, proper nouns, and product names such as `Chrome DevTools`, `Git`, or `MCP` (`Git worktrees`, not `Git Worktrees`). Chinese copy keeps its own conventions. Update the owning runnable snapshot for model-visible text and repository-required GUI evidence. If the authorized scope has no owning scenario, leave the wording unchanged and report the deferral; do not silently fold it into a prose-only edit.
- **Diagnostics:** name the failing subject or path, violated rule, and correction when it is non-obvious. Remove internal execution narration.
Preserve searchable mechanism names and meaningful modal, temporal, or negative emphasis. Normalize decorative emphasis only.
+1 -1
View File
@@ -155,7 +155,7 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and
- **Tests describe behavior, not correctness.** Change obsolete behavior with its tests; explain why in the PR.
- **Create Agent Notes only for durable decision rationale;** mechanical/local edits are exempt, including local UI changes ([scope](.agents/notes/README.md#when-to-write-one)). Archived notes are frozen: never edit or treat them as current authority ([archive policy](.agents/notes/README.md#archiving-and-deletion)).
- **Official plugin artwork:** use [dsh-official-plugin-artwork](.agents/skills/dsh-official-plugin-artwork/SKILL.md) for official and official experimental plugin icons; obtain designer approval before landing new glyphs.
- **Client UI copy is locale-owned.** Route product text through typed dictionaries and `t` or localized primitive props; `verify-client-ui-i18n` rejects hardcoded copy ([decision](.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md)).
- **Client UI copy is locale-owned.** Route product text through typed dictionaries and `t` or localized primitive props; `verify-client-ui-i18n` rejects hardcoded copy ([decision](.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md)). English copy, including plugin titles, uses sentence case ([rule](.agents/skills/dsh-prose-standard/SKILL.md#required-coverage-by-prose-location)).
- **Testing policy** — [docs/testing.md](docs/testing.md). Every non-trivial model- or product-user-visible change updates a keyless recorded-session snapshot; [snapshot ownership](snapshots/AGENTS.md) reserves the top-level tree for session-driven cases and keeps other expected output owner-local. Fixtures replay on macOS/Linux; fix fixtures, not normalizers.
- **Design each tool's UI presentation up front.** Host presenters stay pure; Web cards derive from raw events and persisted result metadata ([cookbook](docs/cookbook/adding-a-tool.md)).
- **Plan unit, e2e, and snapshot coverage** for capability seams, lifecycle paths, and transcript output; include missing snapshot-harness support in the same change.
+1
View File
@@ -78,6 +78,7 @@ External packages installed for runtime use or embedded in distributed Host or b
| [`ajv`](https://github.com/ajv-validator/ajv) | MIT |
| [`anser`](https://github.com/IonicaBizau/anser) | MIT |
| [`big.js`](https://github.com/MikeMcl/big.js) | MIT |
| [`birpc`](https://github.com/antfu-collective/birpc) | MIT |
| [`buffer`](https://github.com/feross/buffer) | MIT |
| [`chokidar`](https://github.com/paulmillr/chokidar) | MIT |
| [`chrome-devtools-frontend`](https://github.com/ChromeDevTools/devtools-frontend) | BSD-3-Clause |
+3 -2
View File
@@ -5,7 +5,7 @@ import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-skill'
import type {} from '@deepseek-ai/dsh-tools'
import { unsupportedInbox } from '@deepseek-ai/dsh-agent-loop-testkit'
import { unsupportedModel, unsupportedInbox } from '@deepseek-ai/dsh-agent-loop-testkit'
const overlayPath = process.argv[2]
if (overlayPath === undefined) throw new Error('dsh-badge snapshot requires an overlay path')
@@ -31,6 +31,7 @@ try {
id: agentId,
options: {},
session,
model: unsupportedModel(),
inbox: unsupportedInbox(),
status: 'idle',
send: () => {},
@@ -43,7 +44,7 @@ try {
}
const decision = await agentEvents(ctx, agent).waterfall(
'agent/pre-step',
{ messages: [], turn: 1, step: 1, signal: new AbortController().signal },
{ modelSelection: { provider: 'mock', model: 'mock' }, messages: [], turn: 1, step: 1, signal: new AbortController().signal },
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
)
const catalog = decision.kind === 'enter'
@@ -17,6 +17,7 @@
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"}}}
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"model/selection","data":{"provider":"cli-mock","model":"cli-mock","reasoningEffort":"off"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"cli-smoke-call"},"toolCallId":"cli-smoke-call","content":[{"type":"text","text":"CLI_TOOL_ROUND_TRIP"}],"isError":false,"id":"{{sessionId}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
+1 -1
View File
@@ -300,7 +300,7 @@ describe('the shipped Web composition', () => {
// depend on ripgrep being present on the machine.
expect(toolNames(ctx, handle.agent).filter(name => name !== 'glob' && name !== 'grep')).toEqual([
'ask_user_question', 'bash', 'create_goal', 'edit', 'exit_plan_mode',
'get_goal', 'interrupt_agent', 'job_kill', 'job_list', 'job_output', 'list_agents', 'list_subagent_models', 'present', 'read', 'read_image',
'get_goal', 'interrupt_agent', 'job_kill', 'job_list', 'job_output', 'list_agents', 'list_subagent_models', 'plugin_manager', 'present', 'read', 'read_image',
'schedule_create', 'schedule_delete', 'schedule_list', 'schedule_update',
'send_message', 'skill',
'subagent', 'subagent_fork', 'todo_write', 'update_goal', 'web_fetch', 'web_search',
+3
View File
@@ -72,5 +72,8 @@
"@deepseek-ai/dsh-experimental-cot-translation-bundle": "workspace:*",
"@deepseek-ai/dsh-experimental-worktree": "workspace:*",
"@deepseek-ai/dsh-experimental-plugin-market-bundle": "workspace:*"
},
"dependencies": {
"@deepseek-ai/dsh-message-quotes": "workspace:*"
}
}
+8 -8
View File
@@ -85,8 +85,8 @@ describe('web e2e: Agent Teams panel', () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-agent-team-panel'))
const projectionReads = vi.spyOn(scaffold.ctx.sessionController, 'projections')
onTestFinished(() => { projectionReads.mockRestore() })
await page.locator('[data-team-action]').getByRole('button', { name: /Agent Team/iu }).click()
const action = page.getByRole('dialog', { name: 'Agent Team', exact: true })
await page.locator('[data-team-action]').getByRole('button', { name: 'Agent team', exact: true }).click()
const action = page.getByRole('dialog', { name: 'Agent team', exact: true })
await action.getByText('No shared tasks yet. Create them through the conversation.').waitFor()
await action.getByText('lead').waitFor()
expect(projectionReads).not.toHaveBeenCalled()
@@ -119,8 +119,8 @@ describe('web e2e: Agent Teams panel', () => {
it('keeps the panel inside the viewport and closes on outside click or Escape', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-agent-team-panel-keyboard'))
const panel = page.getByRole('dialog', { name: 'Agent Team', exact: true })
const trigger = page.locator('[data-team-action]').getByRole('button', { name: /Agent Team/iu })
const panel = page.getByRole('dialog', { name: 'Agent team', exact: true })
const trigger = page.locator('[data-team-action]').getByRole('button', { name: 'Agent team', exact: true })
const viewport = page.viewportSize()!
await trigger.click()
await panel.getByText('lead', { exact: true }).waitFor()
@@ -162,8 +162,8 @@ describe('web e2e: Agent Teams panel', () => {
})
it('keeps the hovered panel open when the pointer returns directly to its trigger', async () => {
const trigger = page.locator('[data-team-action]').getByRole('button', { name: 'Agent Team', exact: true })
const panel = page.getByRole('dialog', { name: 'Agent Team', exact: true })
const trigger = page.locator('[data-team-action]').getByRole('button', { name: 'Agent team', exact: true })
const panel = page.getByRole('dialog', { name: 'Agent team', exact: true })
await trigger.hover()
await panel.waitFor()
await panel.hover()
@@ -182,8 +182,8 @@ describe('web e2e: Agent Teams panel', () => {
writeScopes: [],
})
const viewport = page.viewportSize()!
const trigger = page.locator('[data-team-action]').getByRole('button', { name: 'Agent Team', exact: true })
const panel = page.getByRole('dialog', { name: 'Agent Team', exact: true })
const trigger = page.locator('[data-team-action]').getByRole('button', { name: 'Agent team', exact: true })
const panel = page.getByRole('dialog', { name: 'Agent team', exact: true })
const card = panel.locator('article').filter({ hasText: 'Responsive task' })
await trigger.click()
await card.waitFor()
+11
View File
@@ -112,6 +112,17 @@ describe.each(MODE === 'record' ? ['button'] as const : ['button', 'keyboard'] a
await panel.waitFor({ timeout: MODE === 'record' ? 180_000 : 60_000 })
const scroll = panel.locator('[data-approval-scroll]')
await expect.poll(() => scroll.getByText(/tok/).count(), { timeout: 15_000 }).toBeGreaterThan(0)
const defaults = await panel.evaluate((root: HTMLElement) => {
const before = root.style.textAlign
root.style.textAlign = 'center'
try {
return {
alignment: getComputedStyle(root.querySelector('[data-approval-scroll]')!).textAlign,
wrapping: getComputedStyle(root.querySelector('button')!.parentElement!).flexWrap,
}
} finally { root.style.textAlign = before }
})
expect(defaults).toEqual({ alignment: 'center', wrapping: 'nowrap' })
if (MODE !== 'record') {
const snapshot = await captureStableAria(page, '[data-approval-key]', scaffold.workspaceCwd)
+7 -1
View File
@@ -26,6 +26,10 @@ it('keeps settings and expanded plugin dialogs clear of the Windows caption acro
await openSettings(page, 'zh')
const settings = page.getByRole('dialog', { name: '设置', exact: true })
const settingsMask = settings.locator('..').locator(':scope > [aria-hidden="true"]')
const permissionText = settings.getByText('权限', { exact: true }).locator('..')
expect(await permissionText.evaluate(el => getComputedStyle(el).paddingRight)).toBe('48px')
const cubes = settings.getByRole('button', { name: /浅色|深色|跟随系统/ })
expect(await cubes.first().evaluate(el => getComputedStyle(el).flexBasis)).toBe('180px')
for (const fullscreen of [false, true, false]) {
await page.evaluate(value => document.documentElement.toggleAttribute('data-fullscreen', value), fullscreen)
const margin = fullscreen ? 24 : 60
@@ -86,8 +90,10 @@ it('keeps settings and expanded plugin dialogs clear of the Windows caption acro
}, platform)
if (await reopenSidebar.isVisible()) await reopenSidebar.click()
await openSettings(page, 'zh')
await expect.poll(async () => (await settings.boundingBox())?.y).toBe(platform === 'darwin' ? 68 : 24)
await expect.poll(async () => (await settings.boundingBox())?.y).toBe(platform === 'darwin' ? 68 : 0)
expect((await settingsMask.boundingBox())?.y).toBe(0)
expect(await permissionText.evaluate(el => getComputedStyle(el).paddingRight)).toBe(platform === 'darwin' ? '48px' : '16px')
expect(await cubes.first().evaluate(el => getComputedStyle(el).flexBasis)).toBe(platform === 'darwin' ? '180px' : '0px')
await page.keyboard.press('Escape')
await settings.waitFor({ state: 'hidden' })
}
+18 -9
View File
@@ -59,13 +59,20 @@ it.skipIf(process.platform !== 'darwin')('keeps system requests pending until fr
callId: ToolCallId('cua-app-approval'), name: 'run_code',
arguments: { code: 'return await tools.cua({ method: "getApp", params: { selector: "Fixture" } })',
description: 'Request application access' } })
const approval = page.locator('[data-question-key]')
const approval = page.locator('[data-approval-key]')
await Promise.race([approval.waitFor(), grant.then((result) => {
throw new Error(`Application selection completed before consent: ${JSON.stringify(result)}`)
})])
expect(await approval.textContent()).toContain('Fixture')
await approval.getByRole('radio', { name: 'Always allow', exact: true }).click()
await approval.getByRole('button', { name: 'Submit', exact: true }).click()
expect(await approval.getByText('Fixture', { exact: true }).count()).toBe(1)
expect(await approval.getByRole('textbox').count()).toBe(0)
expect(await approval.locator('[data-approval-scroll]').evaluate(element => getComputedStyle(element).textAlign)).toBe('left')
expect(await approval.getByRole('button', { name: 'Always allow', exact: true })
.evaluate(button => getComputedStyle(button.parentElement!).flexWrap)).toBe('wrap')
await expect.poll(() => approval.locator('img').evaluate((image: HTMLImageElement) => image.naturalWidth)).toBe(1)
expect(await approval.getByText('/Applications/Fixture.app', { exact: true }).count()).toBe(0)
await compareOrRefreshGolden(fileURLToPath(new URL('./expected/cua-permissions/application-approval.expected.md', import.meta.url)),
await captureStableAria(page, '[data-approval-key]', scaffold.workspaceCwd), webSnapshotMode())
await approval.getByRole('button', { name: 'Always allow', exact: true }).click()
expect((await grant).isError).toBe(false)
await approval.waitFor({ state: 'hidden' })
await page.getByRole('button', { name: 'Plugins', exact: true }).click()
@@ -73,15 +80,15 @@ it.skipIf(process.platform !== 'darwin')('keeps system requests pending until fr
expect(await card.getByRole('button', { name: /Accessibility permission|Screen Recording permission/ }).count()).toBe(0)
await page.setViewportSize({ width: 1680, height: 600 })
const plugins = page.locator('[data-plugin-panel]')
const openDetails = page.getByRole('button', { name: 'View Computer Use', exact: true })
const openDetails = page.getByRole('button', { name: 'View Computer use', exact: true })
await openDetails.scrollIntoViewIfNeeded()
expect(await plugins.evaluate(element => element.scrollTop)).toBeGreaterThan(0)
await openDetails.click()
const panel = page.locator('[data-cua-settings]')
const accessibility = panel.getByRole('group', { name: 'Accessibility', exact: true })
await accessibility.getByRole('button', { name: 'Request permission', exact: true }).waitFor()
expect(await panel.getByRole('region', { name: 'Authorize Computer Use', exact: true }).getByRole('group').count()).toBe(4)
expect(await panel.getByRole('region', { name: 'Manage applications Computer Use can access', exact: true }).count()).toBe(1)
expect(await panel.getByRole('region', { name: 'Authorize computer use', exact: true }).getByRole('group').count()).toBe(4)
expect(await panel.getByRole('region', { name: 'Manage applications computer use can access', exact: true }).count()).toBe(1)
await expect.poll(() => panel.locator('li img').evaluate((image: HTMLImageElement) => image.naturalWidth)).toBe(1)
expect(await panel.getByText('/fixture/app', { exact: true }).count()).toBe(0)
expect(await accessibility.getByText('Visual Studio Code.app', { exact: true }).count()).toBe(1)
@@ -146,7 +153,9 @@ it.skipIf(process.platform !== 'darwin')('keeps system requests pending until fr
{ method: 'openPermissionSettings', params: { permission: 'accessibility' } },
])
expect(events.every(event => ['permissionStatus', 'requestPermission', 'openPermissionSettings', 'applicationIcons', 'getApp'].includes(event.method))).toBe(true)
await plugins.locator('[data-detail-document] h1').waitFor()
const preview = plugins.locator('[data-detail-document] img')
await preview.waitFor()
await preview.evaluate(async (image: HTMLImageElement) => { await image.decode() })
const trace = await page.evaluateHandle(() => {
const errors: string[] = []
const stable = [...document.querySelectorAll('[data-cua-settings] [role="alert"]')].map(node => node.textContent?.trim())
@@ -177,7 +186,7 @@ it.skipIf(process.platform !== 'darwin')('keeps system requests pending until fr
return { errors, layouts, documents, stop: () => { observer.disconnect(); cancelAnimationFrame(frame) } }
})
try {
const toggle = page.locator('[data-plugin-panel]').getByRole('switch', { name: 'Enable Computer Use', exact: true })
const toggle = page.locator('[data-plugin-panel]').getByRole('switch', { name: 'Enable Computer use', exact: true })
for (const width of [1680, 390]) {
await page.setViewportSize({ width, height: 1000 })
if (width === 390) await page.locator('[data-sidebar-collapsed]').waitFor({ state: 'attached' })
@@ -95,6 +95,8 @@ describe.skipIf(webSnapshotMode() === 'record')('web e2e: DeepSeek Messages sett
onTestFailed(() => saveFailureShot(page, 'web-e2e-deepseek-messages-default'))
await page.keyboard.press('Escape')
await scaffold.ctx.agentDefaultModel.saveSelection({ provider: 'deepseek-official', model: 'deepseek-v4-flash' })
const patchPath = join(scaffold.harnessHome, 'profiles', 'scaffold', 'cordis.patch.yml')
expect(await readFile(patchPath, 'utf8')).toContain('model: deepseek-v4-flash')
await page.reload({ waitUntil: 'load' })
const input = page.locator('[data-composer-input]').first()
const trigger = page.getByRole('button', { name: '请选择模型', exact: true })
@@ -109,8 +111,8 @@ describe.skipIf(webSnapshotMode() === 'record')('web e2e: DeepSeek Messages sett
await expect.poll(() => scaffold.ctx.agentDefaultModel.currentSelection()).toMatchObject({
provider: 'deepseek-official', model: 'deepseek-flash',
})
const settings = await readFile(join(scaffold.harnessHome, 'profiles', 'scaffold', 'cordis.patch.yml'), 'utf8')
expect(settings).toContain('provider: deepseek-official')
const settings = await readFile(patchPath, 'utf8')
expect(settings).not.toContain('id: agent-default-model')
expect(tripwire.pageErrors).toEqual([])
}, 60_000)
})
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -0,0 +1,8 @@
- text: Waiting for approval
- group "Approval details":
- strong: Fixture
- text: Allow computer use to launch, read, capture and control this application?
- paragraph: Session access lasts until DSH restarts or computer use is disabled.
- button "Reject"
- button "Allow for this session"
- button "Always allow"
@@ -1,8 +1,8 @@
- region "Authorize Computer Use":
- heading "Authorize Computer Use" [level=3]
- region "Authorize computer use":
- heading "Authorize computer use" [level=3]
- group "Default mode for new sessions":
- strong: Default mode for new sessions
- paragraph: Computer Use is available only in PTC mode.
- paragraph: Computer use is available only in PTC mode.
- button "Set to PTC"
- group "PTC execution mode":
- strong: PTC execution mode
@@ -17,8 +17,8 @@
- paragraph: In System Settings, allow Visual Studio Code.app to capture application windows.
- button "Request permission"
- paragraph: Manage permissions in System Settings for the application named by macOS.
- region "Manage applications Computer Use can access":
- heading "Manage applications Computer Use can access" [level=3]
- region "Manage applications computer use can access":
- heading "Manage applications computer use can access" [level=3]
- heading "Always-allowed applications" [level=4]
- list:
- listitem:
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "Settings":
- text: Settings
- navigation:
- text: Settings
- button "General"
- button "Models"
- button "Built-in plugins"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,6 +1,6 @@
- dialog "Settings":
- text: Settings
- navigation:
- text: Settings
- button "General"
- button "Models"
- button "Built-in plugins"
@@ -1,6 +1,6 @@
- dialog "Settings":
- text: Settings
- navigation:
- text: Settings
- button "General"
- button "Models"
- button "Built-in plugins"
@@ -1,6 +1,6 @@
- dialog "设置":
- text: 设置
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
@@ -1,9 +1,9 @@
- bundle: Developer Tools
- bundle: Developer tools
description: Inspect and debug raw session data, grouped chat messages, and the NodeJS backend.
- component: NodeJS Inspector
- component: NodeJS inspector
description: Press Ctrl/Cmd+Shift+. to open Chrome DevTools in the bottom panel for development and debugging.
- component: Session Log
- component: Session log
description: Enable analysis of the current session's raw data and grouped chat messages in the session sidebar.
- session sidebar: Session Log
- session sidebar: Session log
description: Analyze raw logs and grouped chat messages for the current session in the sidebar.
- NodeJS bottom: NodeJS Inspector
- NodeJS bottom: NodeJS inspector
@@ -72,7 +72,7 @@
- region "Approval area":
- heading "Approval area" [level=3]
- list:
- listitem: Allow once Enter
- listitem: Allow Enter
- listitem: Reject Esc
- contentinfo:
- button "Restore all defaults" [disabled]
@@ -72,7 +72,7 @@
- region "Approval area":
- heading "Approval area" [level=3]
- list:
- listitem: Allow once Enter
- listitem: Allow Enter
- listitem: Reject Esc
- contentinfo:
- button "Restore all defaults" [disabled]
@@ -72,7 +72,7 @@
- region "审批区域":
- heading "审批区域" [level=3]
- list:
- listitem: 允许一次 Enter
- listitem: 允许 Enter
- listitem: 拒绝 Esc
- contentinfo:
- button "恢复全部默认" [disabled]
+1 -1
View File
@@ -35,7 +35,7 @@ it('opens bottom DevTools after the Developer Tools bundle is enabled on an alre
await page.getByRole('navigation', { name: 'Global panels' }).getByRole('button', { name: 'Plugins', exact: true }).click()
const plugins = page.locator('[data-plugin-panel]')
const toggle = plugins.getByRole('switch', { name: 'Enable Developer Tools', exact: true })
const toggle = plugins.getByRole('switch', { name: 'Enable Developer tools', exact: true })
await toggle.waitFor()
expect(await toggle.getAttribute('aria-checked')).toBe('false')
const panel = page.locator('[data-inspector-panel]')
+4 -4
View File
@@ -923,14 +923,14 @@ describe('web e2e: plugin manager', () => {
expect(await child.getByText('为智能体提供成员协调、消息通信和共享任务管理工具。', { exact: true }).count()).toBe(1)
try {
await setLanguage('en')
await panel.getByRole('heading', { name: 'Agent Teams', exact: true }).waitFor()
await panel.getByRole('heading', { name: 'Agent teams', exact: true }).waitFor()
expect(await packageName.textContent()).toBe('@deepseek-ai/dsh-experimental-agent-team-profile')
expect(await panel.getByText('Enable team collaboration, team tools, the member roster, and the shared task board.').count()).toBe(1)
await child.getByText('Team Tools', { exact: true }).waitFor()
await child.getByText('Team tools', { exact: true }).waitFor()
expect(await child.getByText('Give agents tools to coordinate members, exchange messages, and manage shared tasks.', { exact: true }).count()).toBe(1)
await panel.getByRole('button', { name: 'Back to plugins' }).click()
await panel.getByRole('button', { name: 'View Agent Teams', exact: true }).waitFor()
expect(await panel.getByRole('switch', { name: 'Enable Agent Teams', exact: true }).count()).toBe(1)
await panel.getByRole('button', { name: 'View Agent teams', exact: true }).waitFor()
expect(await panel.getByRole('switch', { name: 'Enable Agent teams', exact: true }).count()).toBe(1)
// Schedule ships in the delivered composition, so no bundle card carries it in either language.
expect(await panel.getByRole('button', { name: 'View Automation tasks', exact: true }).count()).toBe(0)
// The official configuration pages follow the language too, from their own dictionary.
+1 -1
View File
@@ -776,7 +776,7 @@ async function openSessionInspector(page: Page): Promise<void> {
// The showcase opens with a file tab in the right sidebar; a new tab shows the guide entries.
await page.getByRole('button', { name: 'New tab', exact: true }).click()
await page.locator('[data-sidebar-right-guide-entry="session-inspector-log"]').click({ timeout: 30_000 })
await page.getByRole('table', { name: 'Raw Log', exact: true }).waitFor({ timeout: 15_000 })
await page.getByRole('table', { name: 'Raw log', exact: true }).waitFor({ timeout: 15_000 })
expect(await setBundle(page, INSPECTOR_BUNDLE, false)).toBe('applied')
}
+35 -1
View File
@@ -12,7 +12,7 @@ import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import type { Browser, Locator, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { afterAll, beforeAll, describe, expect, it, onTestFailed, onTestFinished } from 'vitest'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import type { SessionId } from '@deepseek-ai/dsh-session/types'
import { TIMED_WAIT_PARAMETER } from '@deepseek-ai/dsh-user-questions'
@@ -700,3 +700,37 @@ describe.skipIf(MODE === 'record')('web e2e: late-answered question transcript',
])
})
})
it.each(['another-feature', 'cua-app-access'])('answers %s approval without a specialized composer', async (id) => {
const resources: { scaffold?: WebScaffold; browser?: Browser; waiting?: Promise<unknown> } = {}
const controller = new AbortController()
onTestFinished(async () => {
controller.abort()
await resources.waiting
await resources.browser?.close()
await resources.scaffold?.close()
})
const scaffold = await launchWebScaffold()
resources.scaffold = scaffold
const browser = await chromium.launch()
resources.browser = browser
const page = await newEnglishPage(browser)
const tripwire = watchConsole(page)
onTestFailed(() => saveFailureShot(page, `web-approval-fallback-${id}`))
await page.goto(scaffold.authenticatedUrl)
await connectFreshWorkspace(page, scaffold.workspaceCwd)
const agent = scaffold.ctx.agents.list()[0]!
const result = scaffold.ctx.userQuestions.ask({ agent, signal: controller.signal, questions: [{
id, question: 'Allow fixture access?', detail: 'Only this request.',
options: [{ label: 'Allow' }, { label: 'Deny' }],
intent: { kind: 'approval', approve: 'Allow', reject: 'Deny' },
}] })
resources.waiting = Promise.allSettled([result])
const question = page.locator('[data-question-key]')
await question.getByRole('radio', { name: 'Deny', exact: true }).click()
await question.getByRole('button', { name: 'Submit', exact: true }).click()
expect(await result).toEqual({ answers: [{ id, selected: ['Deny'] }] })
await question.waitFor({ state: 'hidden' })
expect(tripwire.pageErrors).toEqual([])
})
+646
View File
@@ -0,0 +1,646 @@
/** Keyless real-Web coverage for quote selection, persistence, and uniform reference presentation. */
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import type { Browser, Locator, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import type { ReplayOverrideDoc } from '@deepseek-ai/dsh-llm-replay'
import { SessionId } from '@deepseek-ai/dsh-session'
import { formatSessionReferenceMention } from '@deepseek-ai/dsh-session-reference'
import { formatQuoteText } from '@deepseek-ai/dsh-client-ui-primitives'
import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, launchWebScaffold,
parseSeedFixture, renderSeedFixture, seedSession, selectedSessionFixture, watchConsole, webSnapshotMode,
type WebScaffold,
} from './scaffold.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/rich-text-quotes', import.meta.url))
const BORROWED = fileURLToPath(new URL('../../../snapshots/web/seeded-history/session.jsonl', import.meta.url))
const UI_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/rich-text-quotes/ui.expected.md', import.meta.url))
const MODE = webSnapshotMode()
const SESSION_ID = 'rich-text-quotes-web-e2e'
const OTHER_ID = 'rich-text-quotes-other'
const LEGACY_DRAFT_KEY = `dsh.conversation.${SESSION_ID}`
const CURRENT_DRAFT_KEY = `dsh.conversation.${SESSION_ID}`
const LEGACY_DRAFT = JSON.stringify({ draft: 'Imported plain draft', view: 'chat', viewRequest: null })
const SOURCE_TEXT = 'Durable source phrase selected from an earlier message and preserved as structured quote metadata.'
const THINKING_TEXT = 'Settled reasoning source'
const LITERAL_CREDENTIAL = 'sk-1234567890abcdefghijklmnopqrstuvwxyz'
const SESSION_MENTION = formatSessionReferenceMention({ sessionId: SessionId(OTHER_ID), label: 'Quote source' })
const DIRECT_SYNTAX_TEXT = [
`/quoted-demo ${SESSION_MENTION} ${LITERAL_CREDENTIAL} **literal Markdown**`,
...Array.from({ length: 80 }, (_, index) => `Quote line ${String(index + 1)} contains preserved text.`),
'End of long quote.',
].join('\n')
const SELECTED_SYNTAX_TEXT = DIRECT_SYNTAX_TEXT.replaceAll('\n', ' ')
const SENT_SYNTAX_TEXT = SELECTED_SYNTAX_TEXT.replace(SESSION_MENTION, '@Quote source')
const REPLY = 'Quote received.'
async function quoteAppearance(quote: Locator): Promise<Record<string, string>> {
return quote.evaluate((element) => {
const style = getComputedStyle(element)
return {
alignItems: style.alignItems,
color: style.color,
display: style.display,
fontSize: style.fontSize,
gap: style.gap,
lineHeight: style.lineHeight,
margin: style.margin,
padding: style.padding,
}
})
}
const REPLAY: ReplayOverrideDoc = [{
kind: 'chunks',
chunks: [
{ type: 'block-start', index: 0, blockType: 'text' },
{ type: 'text-delta', index: 0, text: REPLY },
{ type: 'block-end', index: 0, block: { type: 'text', text: REPLY } },
{ type: 'usage', usage: { inputTokens: 128, outputTokens: 8 } },
{ type: 'finish', reason: { kind: 'stop' } },
],
}]
function quoteHistoryFixture(raw: string): string {
const { headerLine } = parseSeedFixture(raw)
let seq = 0
let time = 0
const events: Array<Record<string, unknown> & { seq: number; time: number }> = []
const at = (event: Record<string, unknown>): number => {
const assigned = seq++
events.push({ ...event, seq: assigned, time: time++ })
return assigned
}
at({ type: 'turn/start', data: { turn: 1 } })
at({
type: 'user/message',
data: {
id: '{{message:1}}',
role: 'user',
content: [{ type: 'text', text: SOURCE_TEXT }],
source: { kind: 'user', rpcId: '{{rpc:1}}' },
},
surfaceOp: 'append',
})
for (let ordinal = 2; ordinal <= 51; ordinal++) {
at({
type: 'user/message',
data: {
id: `{{message:${String(ordinal)}}}`,
role: 'user',
content: [{
type: 'text',
text: ordinal === 2 ? formatQuoteText(DIRECT_SYNTAX_TEXT) : `Filler message ${String(ordinal)}`,
}],
source: { kind: 'user', rpcId: `{{rpc:${String(ordinal)}}}` },
},
surfaceOp: 'append',
})
}
at({
type: 'user/message',
data: {
id: '{{message:52}}',
role: 'user',
content: [
{ type: 'text', text: `> ${SOURCE_TEXT}` },
{ type: 'text', text: '\n\nUse that excerpt.' },
],
source: {
kind: 'user',
rpcId: '{{rpc:52}}',
},
},
surfaceOp: 'append',
})
at({
type: 'session/title',
data: {
title: 'Quote history',
messageSeqs: Array.from({ length: 52 }, (_value, index) => index + 1),
source: { kind: 'fallback' },
},
})
at({ type: 'step/start', data: { turn: 1, step: 1 } })
at({
type: 'assistant/message',
data: {
turn: 1,
step: 1,
message: {
id: '{{message:53}}',
role: 'assistant',
content: [
{ type: 'reasoning', text: THINKING_TEXT },
{ type: 'text', text: 'Quote fixture ready.' },
],
source: { kind: 'model', provider: 'fixture', model: 'fixture' },
},
stream: [
{ type: 'chunk', time: 0, chunk: { type: 'block-start', index: 0, blockType: 'reasoning' } },
{ type: 'reasoning-chunks', time0: 0, index: 0, dt: [], texts: [THINKING_TEXT] },
{ type: 'chunk', time: 0, chunk: { type: 'block-end', index: 0, block: { type: 'reasoning', text: THINKING_TEXT } } },
{ type: 'chunk', time: 0, chunk: { type: 'block-start', index: 1, blockType: 'text' } },
{ type: 'text-chunks', time0: 0, index: 1, dt: [], texts: ['Quote fixture ready.'] },
{ type: 'chunk', time: 0, chunk: { type: 'block-end', index: 1, block: { type: 'text', text: 'Quote fixture ready.' } } },
{ type: 'chunk', time: 0, chunk: { type: 'finish', reason: { kind: 'stop' } } },
],
},
surfaceOp: 'append',
})
at({ type: 'step/end', data: { turn: 1, step: 1 } })
at({ type: 'turn/end', data: { turn: 1, reason: { kind: 'completed' } } })
return renderSeedFixture(headerLine, events)
}
function otherSessionFixture(raw: string): string {
const { headerLine } = parseSeedFixture(raw)
let seq = 0
const events: Array<Record<string, unknown> & { seq: number; time: number }> = []
const at = (event: Record<string, unknown>): void => {
events.push({ ...event, seq, time: seq })
seq += 1
}
at({ type: 'turn/start', data: { turn: 1 } })
at({
type: 'user/message', surfaceOp: 'append',
data: {
id: '{{message:1}}', role: 'user',
content: [{ type: 'text', text: 'Other session.' }],
source: { kind: 'user', rpcId: '{{rpc:1}}' },
},
})
at({ type: 'session/title', data: { title: 'Other session', messageSeqs: [1], source: { kind: 'fallback' } } })
at({ type: 'turn/end', data: { turn: 1, reason: { kind: 'completed' } } })
return renderSeedFixture(headerLine, events)
}
async function selectAllText(locator: Locator): Promise<void> {
await locator.evaluate((element) => {
const range = document.createRange()
range.selectNodeContents(element)
const selection = window.getSelection()
selection?.removeAllRanges()
selection?.addRange(range)
document.dispatchEvent(new Event('selectionchange'))
})
}
async function expectEllipsizedQuote(box: Locator, text: Locator, container: Locator): Promise<number> {
const [quoteBounds, containerBounds, presentation] = await Promise.all([
box.boundingBox(),
container.boundingBox(),
text.evaluate((element) => {
const style = getComputedStyle(element)
return {
clientWidth: element.clientWidth,
scrollWidth: element.scrollWidth,
overflow: style.overflow,
textOverflow: style.textOverflow,
whiteSpace: style.whiteSpace,
}
}),
])
expect(quoteBounds).not.toBeNull()
expect(containerBounds).not.toBeNull()
const expectedMaximum = Math.min(containerBounds!.width * 0.8, 300)
expect(quoteBounds!.width).toBeLessThanOrEqual(expectedMaximum + 1)
expect(presentation.overflow).toBe('hidden')
expect(presentation.textOverflow).toBe('ellipsis')
expect(presentation.whiteSpace).toBe('nowrap')
expect(presentation.scrollWidth).toBeGreaterThan(presentation.clientWidth)
return quoteBounds!.width
}
async function openQuoteSession(page: Page): Promise<void> {
await page.locator(`[data-row-key="session:${SESSION_ID}"]`).click()
}
async function expectScrollableQuote(page: Page, quote: Locator, text: string): Promise<void> {
await quote.hover()
const content = page.locator('[data-quote-popover]')
await expect.poll(() => content.textContent()).toBe(text)
const panel = content.locator('..')
await expect.poll(async () => {
const bounds = await panel.boundingBox()
return bounds !== null && bounds.y >= 8 && bounds.y + bounds.height <= page.viewportSize()!.height - 8
}).toBe(true)
await panel.hover()
await page.mouse.wheel(0, 10_000)
await expect.poll(() => panel.evaluate(element => ({
scrolled: element.scrollTop > 0,
atEnd: element.scrollTop + element.clientHeight >= element.scrollHeight - 1,
}))).toEqual({ scrolled: true, atEnd: true })
expect(await content.evaluate((element) => {
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT)
let last: Node | null = null
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) last = node
if (last === null) return false
const length = last.textContent?.length ?? 0
const range = document.createRange()
range.setStart(last, length - 'End of long quote.'.length)
range.setEnd(last, length)
const end = range.getBoundingClientRect()
const panelBounds = element.parentElement!.getBoundingClientRect()
return end.top >= panelBounds.top && end.bottom <= panelBounds.bottom
})).toBe(true)
await page.mouse.move(0, 0)
await expect.poll(() => content.count()).toBe(0)
}
async function crossQuoteGap(page: Page, quote: Locator, text: string, placement: 'above' | 'below'): Promise<void> {
await quote.hover()
const content = page.locator('[data-quote-popover]')
await expect.poll(() => content.count()).toBe(1)
await expect.poll(() => content.textContent()).toBe(text)
const anchor = (await quote.locator('..').boundingBox())!
const panel = (await content.locator('..').boundingBox())!
expect(panel.width).toBeLessThan(300)
const above = panel.y < anchor.y
expect(above).toBe(placement === 'above')
const x = Math.max(anchor.x, panel.x) + Math.min(anchor.width, panel.width) / 2
const y = above ? (panel.y + panel.height + anchor.y) / 2 : (anchor.y + anchor.height + panel.y) / 2
expect(await page.evaluate(point =>
Boolean(document.elementFromPoint(point.x, point.y)?.closest('[data-hovercard-bridge]')), { x, y })).toBe(true)
await page.mouse.move(x, y, { steps: 8 })
await page.evaluate(() => new Promise<void>((resolve) => { requestAnimationFrame(() => { requestAnimationFrame(() => { resolve() }) }) }))
await expect.poll(() => content.count()).toBe(1)
expect(await content.textContent()).toBe(text)
await content.hover()
const body = (await content.boundingBox())!
await page.mouse.move(body.x + 12, body.y + 15)
await page.mouse.down()
await page.mouse.move(body.x + 60, body.y + 15, { steps: 5 })
await page.mouse.up()
expect(await page.evaluate(() => window.getSelection()?.toString().trim())).not.toBe('')
expect(await content.count()).toBe(1)
await page.keyboard.press('Escape')
await expect.poll(() => content.count()).toBe(0)
}
describe.skipIf(MODE === 'record')('web e2e: rich-text quotes', () => {
let scaffold: WebScaffold
let browser: Browser
let page: Page
let replayDir: string
let tripwire: ReturnType<typeof watchConsole>
beforeAll(async () => {
replayDir = await mkdtemp(join(tmpdir(), 'dsh-rich-text-quotes-replay-'))
const replayOverride = join(replayDir, 'replay.override.json')
await writeFile(replayOverride, JSON.stringify(REPLAY))
scaffold = await launchWebScaffold({
replayFixture: join(replayDir, 'override-only.jsonl'),
replayOverride,
})
const skillDir = join(scaffold.workspaceCwd, '.agents', 'skills', 'quoted-demo')
await mkdir(skillDir, { recursive: true })
await writeFile(join(skillDir, 'SKILL.md'), [
'---',
'name: quoted-demo',
'description: Example user-invoked skill',
'disable-model-invocation: true',
'---',
'',
'An example skill instruction.',
'',
].join('\n'))
const borrowed = await readFile(await selectedSessionFixture(BORROWED), 'utf8')
const createdAt = Date.now() - 60_000
await seedSession(scaffold, quoteHistoryFixture(borrowed), SESSION_ID, undefined, { createdAt })
await seedSession(scaffold, otherSessionFixture(borrowed), OTHER_ID, undefined, { createdAt: createdAt + 1_000 })
browser = await chromium.launch()
page = await newEnglishPage(browser)
await page.addInitScript(({ key, value }) => {
if (localStorage.getItem(key) === null) localStorage.setItem(key, value)
}, { key: LEGACY_DRAFT_KEY, value: LEGACY_DRAFT })
tripwire = watchConsole(page)
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
const group = page.getByRole('treeitem', { name: /Ungrouped/ })
await group.waitFor({ timeout: 15_000 })
if (await group.getAttribute('aria-expanded') !== 'true') await group.click()
await openQuoteSession(page)
}, 120_000)
afterAll(async () => {
const failures: unknown[] = []
await browser?.close().catch((error: unknown) => failures.push(error))
await scaffold?.close().catch((error: unknown) => failures.push(error))
if (replayDir !== undefined) {
await rm(replayDir, { recursive: true, force: true })
.catch((error: unknown) => failures.push(error))
}
if (failures.length === 1) throw failures[0]
if (failures.length > 1) throw new AggregateError(failures, 'rich-text-quotes e2e cleanup failed')
})
it('renders and restores rich quotes with uniform styling after reference recognition', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-rich-text-quotes'))
const input = page.locator('[data-composer-input]').first()
await expect.poll(() => input.textContent()).toBe('Imported plain draft')
await input.click()
await input.press('ControlOrMeta+A')
await input.press('Backspace')
await expect.poll(() => input.textContent()).toBe('')
const sentQuote = page.locator('[data-message-bubble] [data-quote-preview]').filter({ hasText: SOURCE_TEXT }).first()
await sentQuote.waitFor({ timeout: 15_000 })
await expect.poll(() => sentQuote.getAttribute('title')).toBeNull()
await expect.poll(() => sentQuote.getAttribute('role')).toBeNull()
await expect.poll(() => sentQuote.getAttribute('tabindex')).toBeNull()
await expect.poll(() => sentQuote.locator('xpath=ancestor::*[@data-message-bubble][1]').textContent())
.toBe(`${SOURCE_TEXT} Use that excerpt.`)
const sentText = sentQuote.locator('[data-quote-preview-text]')
const sentWidth = await expectEllipsizedQuote(
sentQuote,
sentText,
sentQuote.locator('xpath=ancestor::*[@data-message-bubble][1]'),
)
const sentAppearance = await quoteAppearance(sentQuote)
expect(sentAppearance.gap).toBe('4px')
expect(sentAppearance.margin).toBe('0px')
await sentQuote.hover()
const quotePopover = page.locator('[data-quote-popover]')
await expect.poll(() => quotePopover.textContent()).toBe(SOURCE_TEXT)
const hoveredSentBounds = await sentQuote.boundingBox()
expect(hoveredSentBounds).not.toBeNull()
expect(hoveredSentBounds!.width).toBeCloseTo(sentWidth, 0)
await quotePopover.hover()
// Exceed the pointer grace to prove the panel itself keeps the popover open.
await page.waitForTimeout(300)
await expect.poll(() => quotePopover.count()).toBe(1)
await page.mouse.move(0, 0)
await expect.poll(() => quotePopover.count()).toBe(0)
const sourceBlocks = page.locator('[data-text-interaction-source]').filter({ hasText: SOURCE_TEXT })
const olderSource = page.getByRole('button', { name: 'Load earlier' })
if (await sourceBlocks.count() < 2 && await olderSource.isVisible()) await olderSource.click()
await expect.poll(() => sourceBlocks.count(), { timeout: 15_000 }).toBeGreaterThanOrEqual(2)
const source = sourceBlocks.filter({ hasNot: page.locator('[data-quote-preview]') }).first()
await source.waitFor()
const quoteAction = page.getByRole('button', { name: 'Quote text', exact: true })
await source.scrollIntoViewIfNeeded()
await source.dispatchEvent('pointerdown', { pointerId: 1, pointerType: 'mouse', buttons: 1 })
await selectAllText(source)
await expect.poll(() => quoteAction.count()).toBe(0)
await source.dispatchEvent('pointerup', { pointerId: 1, pointerType: 'mouse', buttons: 0 })
await expect.poll(() => quoteAction.count()).toBe(1)
expect(await quoteAction.evaluate(element => element.closest('[data-chat-flow]') !== null)).toBe(true)
expect(await quoteAction.evaluate(element => getComputedStyle(element.closest('[data-text-interaction-surface]')!).position)).toBe('absolute')
await page.keyboard.press('Escape')
await expect.poll(() => quoteAction.count()).toBe(0)
await page.evaluate(() => window.dispatchEvent(new Event('resize')))
await expect.poll(() => quoteAction.count()).toBe(0)
await source.dispatchEvent('pointerdown', { pointerId: 1, pointerType: 'mouse', button: 0, buttons: 1 })
await selectAllText(source)
await source.dispatchEvent('pointerup', { pointerId: 1, pointerType: 'mouse', button: 0, buttons: 0 })
await expect.poll(() => quoteAction.count()).toBe(1)
await input.click()
await source.scrollIntoViewIfNeeded()
await expect.poll(() => quoteAction.count()).toBe(0)
await source.dispatchEvent('pointerdown', { pointerId: 1, pointerType: 'mouse', button: 0, buttons: 1 })
await selectAllText(source)
await source.dispatchEvent('pointerup', { pointerId: 1, pointerType: 'mouse', button: 0, buttons: 0 })
await expect.poll(() => quoteAction.count()).toBe(1)
await page.locator('[data-conversation-scroll]').evaluate((element) => {
element.scrollTop = element.scrollHeight
element.dispatchEvent(new Event('scroll'))
})
await expect.poll(() => quoteAction.isVisible()).toBe(false)
expect(await page.locator('[data-text-interaction-surface] button').count()).toBe(1)
await source.scrollIntoViewIfNeeded()
await expect.poll(() => quoteAction.isVisible()).toBe(true)
await quoteAction.click()
const draftQuote = page.locator('[data-composer-quote]')
const draftPreview = draftQuote.locator('[data-quote-preview]')
const draftPreviewText = draftPreview.locator('[data-quote-preview-text]')
await expect.poll(() => draftQuote.count()).toBe(1)
await expect.poll(() => draftQuote.textContent()).toContain(SOURCE_TEXT)
const draftText = draftPreviewText
await expect.poll(() => draftPreview.getAttribute('role')).toBeNull()
await expect.poll(() => draftPreview.getAttribute('tabindex')).toBeNull()
const draftWidth = await expectEllipsizedQuote(
draftPreview,
draftPreviewText,
input,
)
expect(await quoteAppearance(draftPreview)).toEqual(sentAppearance)
await draftText.hover()
await expect.poll(() => quotePopover.textContent()).toBe(SOURCE_TEXT)
const hoveredDraftBounds = await draftPreview.boundingBox()
expect(hoveredDraftBounds).not.toBeNull()
expect(hoveredDraftBounds!.width).toBeCloseTo(draftWidth, 0)
await page.mouse.move(0, 0)
await expect.poll(() => quotePopover.count()).toBe(0)
await input.press('Backspace')
await expect.poll(() => draftQuote.count()).toBe(0)
await expect.poll(() => input.textContent()).toBe('')
await input.press('ControlOrMeta+z')
await expect.poll(() => draftQuote.count()).toBe(1)
const removeQuote = draftQuote.getByRole('button', { name: 'Remove quote' })
const removeIcons = removeQuote.locator('svg')
await expect.poll(() => removeIcons.first().evaluate(element => getComputedStyle(element).opacity)).toBe('1')
await expect.poll(() => removeIcons.last().evaluate(element => getComputedStyle(element).opacity)).toBe('0')
await input.click()
await page.keyboard.press('ControlOrMeta+A')
await expect.poll(() => page.evaluate(() => window.getSelection()?.toString() ?? '')).toContain(SOURCE_TEXT)
await draftQuote.hover()
await expect.poll(() => removeIcons.first().evaluate(element => getComputedStyle(element).opacity)).toBe('0')
await expect.poll(() => removeIcons.last().evaluate(element => getComputedStyle(element).opacity)).toBe('1')
await removeQuote.focus()
await expect.poll(() => removeIcons.last().evaluate(element => getComputedStyle(element).opacity)).toBe('1')
await expectEllipsizedQuote(draftPreview, draftPreviewText, input)
await removeQuote.click()
await expect.poll(() => draftQuote.count()).toBe(0)
await expect.poll(() => input.textContent()).toBe('')
await expect.poll(() => page.evaluate(() => window.getSelection()?.isCollapsed ?? true)).toBe(true)
await page.keyboard.press('ControlOrMeta+z')
await expect.poll(() => draftQuote.count()).toBe(1)
await input.click()
await page.keyboard.press('ControlOrMeta+A')
await page.keyboard.press('ArrowRight')
await page.keyboard.press('Backspace')
await page.keyboard.press('Backspace')
await expect.poll(() => draftQuote.count()).toBe(0)
await page.keyboard.press('ControlOrMeta+z')
await expect.poll(() => draftQuote.count()).toBe(1)
await page.keyboard.press('ControlOrMeta+A')
await page.keyboard.press('ArrowLeft')
await page.keyboard.press('Delete')
await expect.poll(() => draftQuote.count()).toBe(0)
await page.keyboard.press('ControlOrMeta+z')
await expect.poll(() => draftQuote.count()).toBe(1)
await page.locator(`[data-row-key="session:${OTHER_ID}"]`).click()
await openQuoteSession(page)
await expect.poll(() => draftQuote.count()).toBe(1)
await page.reload({ waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
const restoredQuote = page.locator('[data-composer-quote]')
await expect.poll(() => restoredQuote.count(), { timeout: 15_000 }).toBe(1)
const restoredPreview = restoredQuote.locator('[data-quote-preview]')
const restoredText = restoredQuote.locator('[data-quote-preview-text]')
await expect.poll(() => restoredPreview.getAttribute('role')).toBeNull()
await restoredText.click()
await restoredQuote.getByRole('button', { name: 'Remove quote' }).click()
await expect.poll(() => input.textContent()).toBe('')
const process = page.locator('[data-turn-process]').first()
if (await process.getAttribute('aria-expanded') === 'false') await process.click()
await page.getByRole('button', { name: 'Analysis completed', exact: true }).click()
const think = page.getByRole('button', { name: /Think/u })
await think.click()
const reasoningSource = page.locator('[data-text-interaction-source]').filter({ hasText: THINKING_TEXT })
await reasoningSource.waitFor()
await selectAllText(reasoningSource)
await expect.poll(() => quoteAction.isVisible()).toBe(true)
const beforeComposerScroll = await reasoningSource.evaluate((element) => {
const scroller = element.closest<HTMLElement>('[data-conversation-scroll]')!
const composer = scroller.querySelector<HTMLElement>('[data-composer-seat]')!
const previous = scroller.scrollTop
scroller.scrollTop -= composer.getBoundingClientRect().top - element.getBoundingClientRect().top + 16
return previous
})
await expect.poll(() => reasoningSource.evaluate((element) => {
const composer = element.closest('[data-conversation-scroll]')!.querySelector('[data-composer-seat]')!
return element.getBoundingClientRect().top >= composer.getBoundingClientRect().top
})).toBe(true)
await expect.poll(() => quoteAction.isVisible()).toBe(false)
expect(await page.locator('[data-text-interaction-surface] button').count()).toBe(1)
await page.locator('[data-conversation-scroll]').evaluate((element, top) => { element.scrollTop = top }, beforeComposerScroll)
await expect.poll(() => quoteAction.isVisible()).toBe(true)
await quoteAction.click()
await expect.poll(() => draftQuote.textContent()).toContain(THINKING_TEXT)
await think.click()
await expect.poll(() => reasoningSource.isVisible()).toBe(false)
const reasoningQuote = draftQuote.locator('[data-quote-preview-text]')
await expect.poll(() => draftQuote.locator('[data-quote-preview]').getAttribute('role')).toBeNull()
await reasoningQuote.click()
await expect.poll(() => reasoningSource.isVisible()).toBe(false)
await expect.poll(() => think.getAttribute('aria-expanded')).toBe('false')
const backToBottom = page.getByRole('button', { name: 'Back to bottom', exact: true })
if (await backToBottom.isVisible()) await backToBottom.click()
await expect.poll(() => backToBottom.isVisible()).toBe(false)
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
await draftQuote.getByRole('button', { name: 'Remove quote' }).click()
const loadEarlier = page.getByRole('button', { name: 'Load earlier' })
if (await loadEarlier.isVisible()) await loadEarlier.click()
await page.getByText('Filler message 3', { exact: true }).waitFor({ timeout: 15_000 })
await page.locator('[data-conversation-scroll]').evaluate((element) => {
element.scrollTop = 0
element.dispatchEvent(new Event('scroll'))
})
const syntaxSource = page.locator('[data-text-interaction-source]').filter({ hasText: DIRECT_SYNTAX_TEXT }).first()
await syntaxSource.waitFor({ timeout: 15_000 })
await syntaxSource.scrollIntoViewIfNeeded()
await selectAllText(syntaxSource)
await quoteAction.click()
const viewport = page.viewportSize()!
try {
await expectScrollableQuote(page, draftPreview, SELECTED_SYNTAX_TEXT)
await page.setViewportSize({ width: 420, height: 420 })
await expectScrollableQuote(page, draftPreview, SELECTED_SYNTAX_TEXT)
} finally {
await page.setViewportSize(viewport)
}
const beforeOffset = scaffold.ctx.agents.get(SessionId(SESSION_ID))!.session.seq
const settled = scaffold.whenTurnSettled()
await page.getByRole('button', { name: 'Send message' }).click()
const settledSessionId = await settled
await page.getByText(REPLY, { exact: true }).waitFor({ timeout: 20_000 })
const longSentQuote = page.locator('[data-message-bubble] [data-quote-preview]').filter({ hasText: SENT_SYNTAX_TEXT }).last()
try {
await expectScrollableQuote(page, longSentQuote, SENT_SYNTAX_TEXT)
await page.setViewportSize({ width: 420, height: 420 })
await expectScrollableQuote(page, longSentQuote, SENT_SYNTAX_TEXT)
} finally {
await page.setViewportSize(viewport)
}
expect(await longSentQuote.textContent()).toContain(LITERAL_CREDENTIAL)
const sentSource = longSentQuote.locator('[data-quote-preview-text]')
expect(await sentSource.textContent()).toBe(SENT_SYNTAX_TEXT)
expect(await sentSource.locator('*').count()).toBe(0)
const textAppearance = await quoteAppearance(sentSource)
const quoteStyle = await quoteAppearance(longSentQuote)
expect(textAppearance.color).toBe(quoteStyle.color)
expect(textAppearance.fontSize).toBe(quoteStyle.fontSize)
expect(textAppearance.lineHeight).toBe(quoteStyle.lineHeight)
expect(await longSentQuote.locator('[data-secret], [data-ref-chip], strong').count()).toBe(0)
const events = scaffold.ctx.agents.get(settledSessionId)!.session.snapshotEvents(beforeOffset)
expect(events.some(event => event.type === 'user/message'
&& event.data.source.kind === 'skill-invocation')).toBe(true)
expect(events.some(event => event.type === 'user/message'
&& event.data.source.kind === 'session-reference')).toBe(true)
const submitted = events.find(event => event.type === 'user/message'
&& event.data.source.kind === 'user')
expect(submitted).toMatchObject({
data: {
content: [{ type: 'text', text: formatQuoteText(SENT_SYNTAX_TEXT) }],
},
})
expect(submitted).not.toHaveProperty('data.source.quotes')
await expect.poll(() => input.textContent()).toBe('')
const persisted = await page.evaluate((key): unknown => JSON.parse(localStorage.getItem(key)!), CURRENT_DRAFT_KEY)
expect(persisted).toMatchObject({ draft: { text: '', references: [] } })
await page.reload({ waitUntil: 'load' })
await page.getByText(REPLY, { exact: true }).waitFor({ timeout: 20_000 })
await expectScrollableQuote(page, longSentQuote, SENT_SYNTAX_TEXT)
const literalQuote = await captureStableAria(
page, `[data-message-bubble]:has([data-quote-preview-text]):has-text("${LITERAL_CREDENTIAL}") >> nth=-1`, scaffold.workspaceCwd,
)
await compareOrRefreshGolden(UI_EXPECTED, `${snapshot}\n\n## Literal quote body\n\n${literalQuote}`, MODE)
await expect.poll(() => input.textContent()).toBe('')
expect(await page.locator('[data-slot-error]').count()).toBe(0)
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])
await assertFixtureInventory(SNAPSHOT_DIR, ['ui.expected.md'])
}, 120_000)
it('keeps adjacent quote previews exclusive while crossing their hover gaps', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-quote-hover-gap'))
const texts = ['First excerpt', 'Second excerpt'].map(line => Array.from({ length: 6 }, () => line).join('\n'))
const quoted = texts.map(formatQuoteText)
const separator = '\nx\n'
const draft = {
text: quoted[0]! + separator + quoted[1]!, references: [],
quotes: texts.map((text, index) => ({
text, offset: index === 0 ? 0 : quoted[0]!.length + separator.length, length: quoted[index]!.length,
})),
}
const viewport = page.viewportSize()!
try {
await page.evaluate(({ key, draft }) => {
localStorage.setItem(key, JSON.stringify({ draft, view: 'chat', viewRequest: null }))
}, { key: CURRENT_DRAFT_KEY, draft })
await page.reload({ waitUntil: 'load' })
const draftQuotes = page.locator('[data-composer-quote] [data-quote-preview]')
await expect.poll(() => draftQuotes.count()).toBe(2)
await crossQuoteGap(page, draftQuotes.last(), texts[1]!, 'above')
const settled = scaffold.whenTurnSettled()
await page.getByRole('button', { name: 'Send message' }).click()
await settled
await page.setViewportSize({ width: 600, height: 1000 })
const sentQuotes = page.locator('[data-message-bubble] [data-quote-preview]').filter({ hasText: 'First excerpt' })
await sentQuotes.last().waitFor()
await sentQuotes.last().evaluate((element) => { element.scrollIntoView({ block: 'center' }) })
await crossQuoteGap(page, sentQuotes.last(), texts[0]!, 'below')
expect(tripwire.pageErrors).toEqual([])
} finally {
await page.keyboard.press('Escape')
await page.setViewportSize(viewport)
}
})
})
+130
View File
@@ -0,0 +1,130 @@
// UI-only phone geometry for the Automation tasks page: no text field it owns
// sits under the 16px touch step, so the page never reads wider than the screen
// — iOS Safari zooms the visual viewport for a focused field below it.
import type { Browser, Locator, Page } from 'playwright'
import { chromium } from 'playwright'
import { expect, it } from 'vitest'
import { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-schedule'
import { launchWebScaffold, watchConsole } from './scaffold.ts'
const PHONE_TASK_SESSION = SessionId('schedule-phone-web-e2e')
const PHONE_TASK_TITLE = 'Review overdue deployment'
const PHONE_PAGE_WIDTH = 390
/** The page's box against the screen, plus the font steps of its text fields. */
interface ViewFacts {
width: number
documentWidth: number
smallestField: number
name: string
search: string
instruction: string
}
/** Open the Automation tasks page and return its panel. */
async function openTaskManager(page: Page, url: string): Promise<Locator> {
await page.goto(url, { waitUntil: 'load' })
await page.getByRole('button', { name: '自动化任务', exact: true }).click()
const panel = page.getByTestId('task-manager-page')
await panel.getByRole('heading', { name: '自动化任务', exact: true }).waitFor({ timeout: 30_000 })
await page.evaluate(() => document.fonts.ready)
return panel
}
/** Read the facts above from the open view. */
async function viewFacts(page: Page): Promise<ViewFacts> {
return page.evaluate(() => {
const panel = document.querySelector('[data-testid="task-manager-page"]')
const fields = Array.from(panel?.querySelectorAll('input, textarea, select') ?? [])
const steps = fields.map(element => Number.parseFloat(getComputedStyle(element).fontSize))
const step = (selector: string): string => {
const element = document.querySelector(selector)
return element === null ? 'absent' : getComputedStyle(element).fontSize
}
return {
width: window.innerWidth,
documentWidth: document.documentElement.scrollWidth,
smallestField: steps.length === 0 ? -1 : Math.min(...steps),
name: step('[class*="_editName"]'),
search: step('[class*="_searchField"] input'),
instruction: step('[class*="_instruction"]'),
}
})
}
it('keeps the automation page on screen with its text fields at the touch step', async () => {
const scaffold = await launchWebScaffold()
const url = scaffold.authenticatedUrl
try {
await scaffold.ctx.schedule.create(PHONE_TASK_SESSION, {
prompt: 'Check the deployment log', title: PHONE_TASK_TITLE, at: '2099-08-25T12:08:00.000Z',
})
const browser: Browser = await chromium.launch()
try {
// A coarse pointer is the touch device, so the phone context takes the
// 16px step.
const phone = await browser.newContext({
viewport: { width: PHONE_PAGE_WIDTH, height: 844 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true, locale: 'zh-CN',
})
const phonePage = await phone.newPage()
const phoneConsole = watchConsole(phonePage)
const phonePanel = await openTaskManager(phonePage, url)
await expect.poll(() => viewFacts(phonePage)).toEqual({
width: PHONE_PAGE_WIDTH,
documentWidth: PHONE_PAGE_WIDTH,
smallestField: 16,
name: 'absent',
search: '16px',
instruction: 'absent',
})
// The detail keeps the floor too, and the name control keeps the 20px step
// the floor must not flatten.
await phonePanel.getByRole('button', { name: PHONE_TASK_TITLE, exact: true }).click()
await phonePanel.locator('[class*="_detailScroll"]').waitFor({ timeout: 10_000 })
await expect.poll(() => viewFacts(phonePage)).toEqual({
width: PHONE_PAGE_WIDTH,
documentWidth: PHONE_PAGE_WIDTH,
smallestField: 16,
name: '20px',
search: '16px',
instruction: '16px',
})
await phone.close()
// A fine pointer keeps the compact steps: the floor is pointer-scoped.
const desktop = await browser.newContext({ viewport: { width: 1024, height: 800 }, locale: 'zh-CN' })
const desktopPage = await desktop.newPage()
const desktopConsole = watchConsole(desktopPage)
const desktopPanel = await openTaskManager(desktopPage, url)
await expect.poll(() => viewFacts(desktopPage)).toEqual({
width: 1024,
documentWidth: 1024,
smallestField: 14,
name: 'absent',
search: '14px',
instruction: 'absent',
})
await desktopPanel.getByRole('button', { name: PHONE_TASK_TITLE, exact: true }).click()
await desktopPanel.locator('[class*="_detailScroll"]').waitFor({ timeout: 10_000 })
await expect.poll(() => viewFacts(desktopPage)).toEqual({
width: 1024,
documentWidth: 1024,
smallestField: 14,
name: '20px',
search: '14px',
instruction: '14px',
})
await desktop.close()
expect(phoneConsole.pageErrors).toEqual([])
expect(phoneConsole.warnings).toEqual([])
expect(desktopConsole.pageErrors).toEqual([])
expect(desktopConsole.warnings).toEqual([])
} finally {
await browser.close()
}
} finally {
await scaffold.close()
}
}, 180_000)
+7 -7
View File
@@ -46,12 +46,12 @@ it.skipIf(webSnapshotMode() === 'record')('inspects a replayed Session log and C
await page.locator('[data-sidebar-right-guide-entry="session-inspector-log"]').click()
const mode = page.getByRole('combobox', { name: 'Presentation', exact: true })
expect(await mode.inputValue()).toBe('session-log')
const raw = page.getByRole('table', { name: 'Raw Log', exact: true })
const raw = page.getByRole('table', { name: 'Raw log', exact: true })
await raw.getByRole('button', { name: 'Filter types', exact: true }).click()
await page.getByRole('combobox', { name: 'Type keyword', exact: true }).fill('user/message')
await page.getByRole('option', { name: 'user/message', exact: true }).click()
await raw.getByRole('button', { name: 'user/message', exact: true }).first().click()
const panel = page.locator('section[aria-label="Raw Log"]')
const panel = page.locator('section[aria-label="Raw log"]')
const details = panel.getByRole('separator').locator('..').locator('ul').first()
expect(await details.innerText()).toContain(prompts[0])
expect(await details.locator('details:not([open])').count()).toBe(0)
@@ -64,14 +64,14 @@ it.skipIf(webSnapshotMode() === 'record')('inspects a replayed Session log and C
const replacements = [...new Set(times)].filter(value => /^\d{2}:\d{2}:\d{2}\.\d{3}$/.test(value))
.map(value => [value, '{{time}}'] as const)
await compareOrRefreshGolden(join(directory, 'log.expected.md'),
await captureStableAria(page, 'section[aria-label="Raw Log"] table', scaffold.workspaceCwd, { replacements }), webSnapshotMode())
await captureStableAria(page, 'section[aria-label="Raw log"] table', scaffold.workspaceCwd, { replacements }), webSnapshotMode())
await mode.selectOption('chat-node')
const groups = page.getByRole('table', { name: 'Chat Group', exact: true })
const groups = page.getByRole('table', { name: 'Chat group', exact: true })
expect(await groups.getByRole('columnheader', { name: 'Time (UTC)' }).count()).toBe(0)
await groups.getByRole('button', { name: 'assistant-step', exact: true }).first().click()
await page.getByRole('navigation', { name: 'Raw data navigation', exact: true }).waitFor()
const groupPanel = page.locator('section[aria-label="Chat Group"]')
await groupPanel.locator('summary').filter({ hasText: 'Node Data' }).click()
const groupPanel = page.locator('section[aria-label="Chat group"]')
await groupPanel.locator('summary').filter({ hasText: 'Node data' }).click()
const blocks = groupPanel.getByText('blocks:', { exact: true }).locator('..')
await blocks.click()
await blocks.locator('..').getByText('Other properties:', { exact: true }).waitFor()
@@ -104,7 +104,7 @@ it.skipIf(webSnapshotMode() === 'record')('highlights nearby Chat content when t
await page.getByText('DONE', { exact: true }).waitFor()
await page.getByRole('button', { name: 'Open right sidebar', exact: true }).click()
await page.locator('[data-sidebar-right-guide-entry="session-inspector-log"]').click()
const table = page.getByRole('table', { name: 'Raw Log', exact: true })
const table = page.getByRole('table', { name: 'Raw log', exact: true })
await table.getByRole('button', { name: 'Filter types', exact: true }).click()
await page.getByRole('combobox', { name: 'Type keyword', exact: true }).fill('tool/call')
await page.getByRole('button', { name: 'Apply filter', exact: true }).click()
+105 -2
View File
@@ -84,6 +84,17 @@ describe('web e2e: settings modal and General preferences', () => {
const dialog = page.getByRole('dialog', { name: '设置' })
await dialog.waitFor({ timeout: 10_000 })
expect(await trigger.getAttribute('aria-expanded')).toBe('false')
const panelBox = await dialog.boundingBox()
const optionsBox = await dialog.locator('[class*="_options"]').boundingBox()
const firstSectionBox = await dialog.getByRole('navigation').getByRole('button').first().boundingBox()
expect(optionsBox!.y - panelBox!.y).toBe(54)
expect(firstSectionBox!.y - panelBox!.y).toBe(64)
const lastSection = dialog.getByRole('navigation').getByRole('button').last()
await lastSection.focus()
await page.keyboard.press('Tab')
expect(await dialog.getByRole('button', { name: '打开配置文件' }).evaluate(el => el === document.activeElement)).toBe(true)
await page.keyboard.press('Tab')
expect(await dialog.getByRole('button', { name: '关闭', exact: true }).evaluate(el => el === document.activeElement)).toBe(true)
// General is active by default; Permission, Language and Appearance are functional.
expect(await dialog.getByRole('button', { name: '通用设置' }).getAttribute('aria-current')).toBe('true')
await dialog.getByRole('button', { name: '工作区内修改' }).waitFor({ timeout: 10_000 })
@@ -210,7 +221,7 @@ describe('web e2e: settings modal and General preferences', () => {
await page.keyboard.press('Escape')
await expect.poll(() => page.getByRole('dialog', { name: '设置' }).count(), { timeout: 5_000 }).toBe(0)
expect(await trigger.getAttribute('aria-expanded')).toBe('false')
// Close path 2: the header close button (focus lands there on open).
// Close path 2: the header close button.
await openSettings(page, 'zh')
await page.getByRole('dialog', { name: '设置' }).getByRole('button', { name: '关闭' }).click()
await expect.poll(() => page.getByRole('dialog', { name: '设置' }).count(), { timeout: 5_000 }).toBe(0)
@@ -227,7 +238,7 @@ describe('web e2e: settings modal and General preferences', () => {
const dialog = overflowPage.getByRole('dialog', { name: '设置', exact: true })
const navigation = dialog.getByRole('navigation')
const list = navigation.locator('[class*="_navList"]')
const title = navigation.getByText('设置', { exact: true })
const title = dialog.getByText('设置', { exact: true })
const options = dialog.locator('[class*="_options"]')
const last = navigation.getByRole('button').last()
await last.waitFor()
@@ -261,6 +272,98 @@ describe('web e2e: settings modal and General preferences', () => {
expect(console.warnings).toEqual([])
})
it('fills a phone viewport and turns the rail into the horizontal section strip', async () => {
const phone = await browser.newPage({ viewport: { width: 390, height: 844 }, locale: ZH_BROWSER_LOCALE })
onTestFinished(() => phone.close())
onTestFailed(() => saveSettingsFailureShot(phone, 'web-e2e-settings-phone'))
const console = watchConsole(phone)
await phone.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await openSettings(phone, 'zh')
const dialog = phone.getByRole('dialog', { name: '设置', exact: true })
await dialog.waitFor({ timeout: 10_000 })
await dialog.getByRole('button', { name: '通用设置', exact: true }).waitFor({ timeout: 10_000 })
// The panel is the viewport: no centered card, no side margins, no rail.
await expect.poll(() => dialog.boundingBox()).toEqual({ x: 0, y: 0, width: 390, height: 844 })
// The rail reads left to right on one line instead of stacking.
const navigation = dialog.getByRole('navigation')
const list = navigation.locator('[class*="_navList"]')
const cells = navigation.getByRole('button')
const first = await cells.first().boundingBox()
const second = await cells.nth(1).boundingBox()
expect(first).not.toBeNull()
expect(second).not.toBeNull()
expect(second!.x).toBeGreaterThanOrEqual(first!.x + first!.width - 1)
expect(Math.abs(second!.y - first!.y)).toBeLessThan(1)
expect(await list.evaluate(el => getComputedStyle(el).overflowX)).toBe('auto')
// The shipped sections overflow the strip, which scrolls sideways; the
// section column starts below it and keeps the panel's width.
const strip = await list.boundingBox()
const options = dialog.locator('[class*="_options"]')
const optionsBox = await options.boundingBox()
expect(strip).not.toBeNull()
expect(optionsBox).not.toBeNull()
expect(await list.evaluate(el => el.scrollWidth)).toBeGreaterThan(await list.evaluate(el => el.clientWidth))
expect(optionsBox!.y).toBeGreaterThanOrEqual(strip!.y + strip!.height - 1)
expect(Math.round(optionsBox!.width)).toBe(390)
expect(await phone.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390)
// Tapping a cell swaps the page, and a General row keeps its label and
// control on one line rather than stacking.
await cells.nth(1).click()
await expect.poll(() => cells.nth(1).getAttribute('aria-current')).toBe('true')
await expect.poll(() => dialog.getByText('权限', { exact: true }).count()).toBe(0)
await cells.first().click()
await expect.poll(() => dialog.getByText('权限', { exact: true }).count(), { timeout: 5_000 }).toBe(1)
const permissionRow = dialog.getByText('权限', { exact: true }).locator('..')
const permissionBox = await permissionRow.boundingBox()
const controlBox = await dialog.getByRole('button', { name: '工作区内修改' }).boundingBox()
expect(controlBox).not.toBeNull()
expect(permissionBox).not.toBeNull()
expect(controlBox!.y).toBeLessThan(permissionBox!.y + permissionBox!.height)
// The Appearance cubes share one row instead of wrapping to two.
const cubes = dialog.getByRole('button', { name: /浅色|深色|跟随系统/ })
const cubeBoxes = await cubes.evaluateAll(nodes => nodes.map(node => node.getBoundingClientRect().top))
expect(new Set(cubeBoxes.map(top => Math.round(top))).size).toBe(1)
await cells.first().focus()
for (let index = 1; index < await cells.count(); index += 1) {
await phone.keyboard.press('Tab')
}
const last = cells.last()
expect(await last.evaluate(el => el === document.activeElement)).toBe(true)
const lastBox = await last.boundingBox()
// Scroll offsets can round while glyph widths remain fractional.
expect(Math.round(lastBox!.x)).toBeGreaterThanOrEqual(0)
expect(Math.round(lastBox!.x + lastBox!.width)).toBeLessThanOrEqual(390)
await last.click()
await expect.poll(() => last.getAttribute('aria-current')).toBe('true')
await phone.keyboard.press('Escape')
await dialog.waitFor({ state: 'hidden' })
await openSettings(phone, 'zh')
const active = dialog.locator('[aria-current="true"]')
expect(await active.textContent()).toBe('通用设置')
expect(await active.evaluate(el => el === document.activeElement)).toBe(true)
const close = dialog.getByRole('button', { name: '关闭', exact: true })
const closeBox = await close.boundingBox()
expect(closeBox!.width).toBe(28)
expect(closeBox!.x).toBeGreaterThanOrEqual(0)
expect(closeBox!.y).toBeGreaterThanOrEqual(0)
expect(closeBox!.x + closeBox!.width).toBeLessThanOrEqual(390)
expect(closeBox!.y + closeBox!.height).toBeLessThanOrEqual(844)
await close.click()
await dialog.waitFor({ state: 'hidden' })
const trigger = phone.getByRole('button', { name: '账号菜单', exact: true })
await expect.poll(() => trigger.evaluate(el => el === document.activeElement)).toBe(true)
expect(console.pageErrors).toEqual([])
expect(console.warnings).toEqual([])
})
it('stores Permission as the default for future sessions without changing an existing session', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-settings-permission'))
const existing = scaffold.ctx.sessions.create(SessionId('settings-permission-before'))
+5 -5
View File
@@ -42,7 +42,7 @@ it('toggles bottom DevTools from the page and iframe without a selected Session'
const close = panel.getByRole('button', { name: 'Collapse', exact: true })
expect(await close.textContent()).toBe('')
expect(await close.locator('svg').count()).toBe(1)
const divider = panel.getByRole('separator', { name: 'Resize NodeJS Inspector panel' })
const divider = panel.getByRole('separator', { name: 'Resize NodeJS inspector panel' })
const drag = async (deltaY: number): Promise<void> => {
const box = (await divider.boundingBox())!
const x = box.x + box.width / 2
@@ -93,10 +93,10 @@ it.each([
openSidebar: '打开右侧边栏', sidebarDescription: '在侧边栏分析当前会话原始日志和聊天分组数据',
},
{
locale: 'en-US', file: 'en', plugins: 'Plugins', open: 'View Developer Tools', bundle: 'Developer Tools',
locale: 'en-US', file: 'en', plugins: 'Plugins', open: 'View Developer tools', bundle: 'Developer tools',
description: 'Inspect and debug raw session data, grouped chat messages, and the NodeJS backend.',
inspector: 'NodeJS Inspector', inspectorDescription: 'Press Ctrl/Cmd+Shift+. to open Chrome DevTools in the bottom panel for development and debugging.',
session: 'Session Log', sessionDescription: "Enable analysis of the current session's raw data and grouped chat messages in the session sidebar.",
inspector: 'NodeJS inspector', inspectorDescription: 'Press Ctrl/Cmd+Shift+. to open Chrome DevTools in the bottom panel for development and debugging.',
session: 'Session log', sessionDescription: "Enable analysis of the current session's raw data and grouped chat messages in the session sidebar.",
openSidebar: 'Open right sidebar', sidebarDescription: 'Analyze raw logs and grouped chat messages for the current session in the sidebar.',
},
])('shows the Inspector bundle and component copy in $locale', async (copy) => {
@@ -150,7 +150,7 @@ it.each([
` description: ${await sessionGuide.getByText(copy.sidebarDescription, { exact: true }).innerText()}`,
`- NodeJS bottom: ${await bottomTitle.innerText()}`)
await sessionGuide.click()
await page.getByRole('table', { name: copy.locale === 'zh-CN' ? '原始数据' : 'Raw Log', exact: true }).waitFor()
await page.getByRole('table', { name: copy.locale === 'zh-CN' ? '原始数据' : 'Raw log', exact: true }).waitFor()
await compareOrRefreshGolden(fileURLToPath(new URL(`./expected/shared-inspector/metadata-${copy.file}.expected.md`, import.meta.url)),
snapshot.join('\n'), webSnapshotMode())
})
@@ -484,6 +484,7 @@ const EXPECTED_TOOLS = [
'job_output',
'list_agents',
'list_subagent_models',
'plugin_manager',
'present',
'read',
'read_image',
@@ -1,4 +1,4 @@
- dialog "Agent Team":
- dialog "Agent team":
- heading "Members" [level=3]
- button "lead Current chat Inactive" [disabled]
- heading "Shared tasks 2" [level=3]
@@ -1,4 +1,4 @@
- dialog "Agent Team":
- dialog "Agent team":
- heading "Members" [level=3]
- button "lead Current chat Inactive" [disabled]
- heading "Shared tasks 1" [level=3]
+4 -4
View File
@@ -70,9 +70,9 @@ it('persists the Git Worktrees switch and removes its capability while retaining
}
let panel = await openPlugins()
let card = panel.locator(`[data-plugin-group="more"] [data-plugin-package="${BUNDLE}"]`)
await card.getByRole('button', { name: 'View Git Worktrees', exact: true }).waitFor()
await card.getByRole('button', { name: 'View Git worktrees', exact: true }).waitFor()
expect(await card.getByText('Experimental', { exact: true }).count()).toBe(1)
let toggle = card.getByRole('switch', { name: 'Enable Git Worktrees', exact: true })
let toggle = card.getByRole('switch', { name: 'Enable Git worktrees', exact: true })
expect(await toggle.getAttribute('aria-checked')).toBe('false')
expect(await selectedBundles()).not.toContain(BUNDLE)
expect(rows()).toEqual([])
@@ -92,7 +92,7 @@ it('persists the Git Worktrees switch and removes its capability while retaining
await page.reload({ waitUntil: 'load' })
panel = await openPlugins()
card = panel.locator(`[data-plugin-group="more"] [data-plugin-package="${BUNDLE}"]`)
toggle = card.getByRole('switch', { name: 'Enable Git Worktrees', exact: true })
toggle = card.getByRole('switch', { name: 'Enable Git worktrees', exact: true })
await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('true')
expect(tools()).toContain('create_worktree')
await toggle.click()
@@ -108,7 +108,7 @@ it('persists the Git Worktrees switch and removes its capability while retaining
await page.reload({ waitUntil: 'load' })
panel = await openPlugins()
await expect.poll(() => panel.getByRole('switch', { name: 'Enable Git Worktrees', exact: true })
await expect.poll(() => panel.getByRole('switch', { name: 'Enable Git worktrees', exact: true })
.getAttribute('aria-checked')).toBe('false')
expect(tools()).not.toContain('create_worktree')
expect(await selectedBundles()).not.toContain(BUNDLE)
+2
View File
@@ -23,6 +23,7 @@
// this client-registered project must not also hold them (one program
// cannot see both sides of the cordis Context merges).
"exclude": [
"tests/rich-text-quotes.e2e.ts",
"tests/hover-input.e2e.ts",
"tests/collapse-timing.e2e.ts",
"tests/notice-wake-tail.e2e.ts",
@@ -89,6 +90,7 @@
"tests/cua-optin.e2e.ts",
"tests/cua-permissions.e2e.ts",
"tests/schedule-header.e2e.ts",
"tests/schedule-phone.e2e.ts",
"tests/worktree-opt-in.e2e.ts",
"tests/session-inspector.e2e.ts",
"tests/shared-inspector.e2e.ts",
@@ -30,7 +30,6 @@ import * as SessionTurnOutlinePlugin from '@deepseek-ai/dsh-session-turn-outline
import TokenMeter from '@deepseek-ai/dsh-token-meter'
// These Host-only adapters have no public Node export and are compiled into the benchmark worker.
import { SessionHistoryController } from '../../packages/api/session-controller/src/history.ts'
import { modelSelectionProjection } from '../../packages/api/session-controller/src/model-selection-projection.ts'
import { assertBuiltBenchmarkRuntime } from '../support/built-worker.ts'
import { SYNTHETIC_SESSION_ID } from './session-open.constants.ts'
@@ -131,7 +130,6 @@ function memoryDelta(
async function installProjectionSet(ctx: Context): Promise<void> {
// Mirrors projection owners mounted by the base and web-app bundles without timing profile boot.
await ctx.sessionProjections.register(agentPresetProjectionDefinition)
await ctx.sessionProjections.register(modelSelectionProjection)
await ctx.plugin(SessionTitleService, {
fallbackMaxWords: 5,
fallbackMaxBytes: 40,
+4 -4
View File
@@ -24,11 +24,11 @@
en: e89802f066e4955d
zh: ea2fb4bef94e086d
/deepseek-harness-architecture/turn-flow:
en: e6402b28c0ed15fa
zh: 6c07be9047b42056
en: 4151c0b69f563f4e
zh: 32c62b6326446c10
/deepseek-harness-architecture/session-log:
en: f1729d805c20df36
zh: 6b71b6039663e2e7
en: 8a7f19b95d426c99
zh: d5bb0538404e20ea
/deepseek-harness-architecture/capability-seams:
en: a5bc20c3f2a1b370
zh: 91773a281410b2f4
+3 -3
View File
@@ -83,7 +83,7 @@ A **step** is one model request plus the tools it calls. A **turn** is zero or m
```text
turn/start
claim next-step input plus one queued message
assemble prompt sections + tool schemas
snapshot Session model selection; assemble prompt sections + tool schemas
-> agent/pre-step; project runtime context at fallback
reject | enter(messages, startsRequestSeries?)
reject, or a first enter rewritten empty -> close the turn with no step
@@ -107,7 +107,7 @@ turn/end
One inbox feeds the driver; injected context waits for a waking message. AgentLoop’s durable `inbox` projection exposes pending input without live Agents.
`agent/pre-step` decides the accepted input. Listeners may rewrite or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, `agent/request` and `prepareCall()` resolve the actual route before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The first admitted step reserves the system head before user messages even for an empty prompt (no wire message). The prompt travels only as `system/message` history: an empty rendering clears all active system nodes, leaving no old prompt model-visible; capable routes append non-empty updates after cached history, including supported tool updates; incapable routes and new request series consolidate non-empty prompt text at the first system node, with logged empty replacements for non-empty later system nodes ([decision](../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md); [decision rule](../packages/core/agent-loop/README.md#understand-the-implementation)).
The loop snapshots Session model selection before assembly and shares it through `AssembleContext.modelSelection` and `agent/pre-step.modelSelection`; retries retain that selection, and omitted reasoning effort uses adapter defaults. `agent/pre-step` decides the accepted input. Listeners may rewrite or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, the deprecated `agent/request` hook adjusts controls without changing the provider, model, or effort selection, and `prepareCall()` binds the adapter before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The system prompt travels as `system/message` history: empty text clears it, capable routes append updates, and other routes consolidate it at the first system node. [Agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) owns prompt admission and replacement details.
The loop sends immutable requests with live cancellation and reuses freeze evidence for frozen identities; [agent-loop](../packages/core/agent-loop/README.md) owns construction and cancellation causes.
@@ -123,7 +123,7 @@ Session consumers know only the current logical format. Header-only `stat` and `
**Model-visible means logged.** Every model request must be reconstructable from the log. New model-visible inputs require session events. Plugins changing existing message content register [pure message projections](subsystems/session.md#plugin-owned-message-projections); detached readers supply the same definitions explicitly. Tool changes are capability-independent; [Session tool history](../packages/core/session/README.md) supplies provider declarations.
**Projection seam.** `dsh-session-projection` owns `ctx.sessionProjections`: registered units fold committed events incrementally, and carriers batch cropped client views with `snapshot()`. Domain services await registration during activation, retain private handles, and expose business methods for other plugins. Releasing registration removes global participation; retained handles continue folding Session events. `AgentRegistry` owns turn and step state and exposes `turnBoundaryOf(session)` ([registration lifecycle](../packages/session/session-projection/README.md)).
**Projection seam.** `dsh-session-projection` owns `ctx.sessionProjections`: registered units fold committed events incrementally, and carriers batch cropped client views with `snapshot()`. Domain services await registration during activation, retain private handles, and expose business methods for other plugins. Releasing registration removes global participation; retained handles continue folding Session events. `AgentRegistry` owns turn and step state and exposes `turnBoundaryOf(session)` ([registration lifecycle](../packages/session/session-projection/README.md)). `AgentLoop` owns `modelSelection` and constructs each Agent’s `model` instance alongside its inbox.
[Working directories](subsystems/working-directory.md) supply user context and execution paths without changing original project identity, sandbox write roots, or existing process directories.
+3 -3
View File
@@ -87,7 +87,7 @@ AgentLoop 在启动已排队工作前等待串行 `agent/created`,初始化失
```text
turn/start
claim next-step input plus one queued message
assemble prompt sections + tool schemas
snapshot Session model selection; assemble prompt sections + tool schemas
-> agent/pre-step; project runtime context at fallback
reject | enter(messages, startsRequestSeries?)
reject, or a first enter rewritten empty -> close the turn with no step
@@ -111,7 +111,7 @@ turn/end
输入通过同一个 inbox 到达驱动器;注入的上下文等待一条唤醒消息。AgentLoop 的持久 `inbox` 投影使待处理输入在没有活跃 Agent 时仍可读取。
`agent/pre-step` 决定接纳的输入。监听器可以改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,`agent/request` 和 `prepareCall()` 先解析实际路由,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。首次接纳的步骤在用户消息之前预留系统头节点,即使提示词为空(不产生协议消息)。提示词仅通过 `system/message` 历史传递:空渲染文本清除所有生效的系统节点,模型不再看到旧提示词;具备能力的路由可在缓存前缀之后追加非空更新,包括同时发生的受支持工具更新;不具备能力的路由与新请求序列将非空提示词文本归并到首个系统节点,并为非空的后续系统节点记录空内容替换([决策](../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md);[决策规则](../packages/core/agent-loop/README.zh.md#understand-the-implementation))。
循环在组装前快照 Session 模型选择,并通过 `AssembleContext.modelSelection` 与 `agent/pre-step.modelSelection` 传递该选择;重试保留该选择,省略推理强度时使用适配器默认值。`agent/pre-step` 决定接纳的输入。监听器可以改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,已弃用的 `agent/request` hook 在不改变提供方、模型或推理强度选择的前提下调整控制参数,`prepareCall()` 绑定适配器,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。系统提示词通过 `system/message` 历史传递:空文本清除提示词,具备能力的路由追加更新,其他路由将其归并到首个系统节点。[Agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation) 说明提示词准入与替换的细节。
循环发送不可变请求,同时保持取消有效,仅对完全冻结的对象复用冻结证据;[agent-loop](../packages/core/agent-loop/README.zh.md) 负责请求构建与取消原因。
@@ -127,7 +127,7 @@ Session 消费方只了解当前逻辑格式。仅 header 的 `stat` 与 `list`
**模型可见即已记录。** 每个模型请求都必须能从日志重建。新增模型可见输入需要会话事件。修改现有消息内容的插件注册[纯消息投影](subsystems/session.zh.md#plugin-owned-message-projections),独立读取器显式传入相同的处理器。 工具变更不依赖能力;[Session 工具历史](../packages/core/session/README.zh.md)提供提供方声明。
**投影 seam。** `dsh-session-projection` 提供 `ctx.sessionProjections`:已注册单元增量折叠已提交事件,载体通过 `snapshot()` 批量取得裁剪后的客户端视图。领域服务在激活时等待注册完成,在内部保留句柄,并向其他插件提供业务方法。释放注册会移除全局参与关系;已有句柄仍可继续折叠 Session 事件。`AgentRegistry` 持有轮次和步骤状态,并提供 `turnBoundaryOf(session)`([注册生命周期](../packages/session/session-projection/README.zh.md))。
**投影 seam。** `dsh-session-projection` 提供 `ctx.sessionProjections`:已注册单元增量折叠已提交事件,载体通过 `snapshot()` 批量取得裁剪后的客户端视图。领域服务在激活时等待注册完成,在内部保留句柄,并向其他插件提供业务方法。释放注册会移除全局参与关系;已有句柄仍可继续折叠 Session 事件。`AgentRegistry` 持有轮次和步骤状态,并提供 `turnBoundaryOf(session)`([注册生命周期](../packages/session/session-projection/README.zh.md))。 `AgentLoop` 持有 `modelSelection`,并与 inbox 一起构建每个 Agent 的 `model` 实例。
[工作目录](subsystems/working-directory.zh.md) 提供用户上下文和执行路径,不改变原始项目标识、沙箱写入根目录或已有进程目录。
+5 -4
View File
@@ -16,7 +16,7 @@ Each package entry labels its data with three identifiers: `inject` lists the se
- `inject`: `agents` · `llm` · `sessionPersistence` · `sessions`
- `refs`: `Stream` (`@agentclientprotocol/sdk`)
- `source`: [`packages/acp/acp/src/index.ts:75`](../packages/acp/acp/src/index.ts)
- `source`: [`packages/acp/acp/src/index.ts:74`](../packages/acp/acp/src/index.ts)
```ts config-catalog
/** Plugin config: the provider/model selection used for each ACP-created agent. */
@@ -96,7 +96,7 @@ export interface Config {
- `inject`: `agents` · `sessions` · `llm` · `tools` · `systemPrompt` · `sessionProjections`
- `refs`: [`AgentOptions`](subsystems/core.md) · [`SessionId`](subsystems/core.md) · `Volatile` (`@deepseek-ai/cosmokit`)
- `source`: [`packages/core/agent-loop/src/index.ts:250`](../packages/core/agent-loop/src/index.ts)
- `source`: [`packages/core/agent-loop/src/index.ts:253`](../packages/core/agent-loop/src/index.ts)
```ts config-catalog
/** Agent-loop plugin configuration. */
@@ -106,7 +106,7 @@ export interface Config {
* omission defaults to {@link DEFAULT_MAX_PARALLEL_TOOL_CALLS}.
*/
maxParallelToolCalls: Volatile<number>
/** Agents created or resumed at plugin startup. */
/** Agents created or resumed at plugin startup; a resumed agent records its configured route as the Session's model selection. */
agents: (AgentOptions & {
/** Stable config label used in logs and as the fresh combined-id prefix. */
id: string
@@ -2604,7 +2604,7 @@ export interface Config {
maxOutputBytes?: number
/** V8 old-generation heap limit in MiB; native allocations are excluded. */
maxOldGenerationSizeMb?: number
/** Maximum control frame, outstanding argument and queued control-output bytes. */
/** Maximum control message, outstanding binding-argument and queued control-output bytes. */
maxMessageBytes?: number
/** Maximum simultaneous host binding calls accepted from a program. */
maxPendingCalls?: number
@@ -4785,6 +4785,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
| `@deepseek-ai/dsh-llm` | — | [`packages/llm/llm/src/index.ts`](../packages/llm/llm/src/index.ts) |
| `@deepseek-ai/dsh-lsp` | — | [`packages/lsp/lsp/src/index.ts`](../packages/lsp/lsp/src/index.ts) |
| `@deepseek-ai/dsh-mcp-resources` | `tools` | [`packages/mcp/mcp-resources/src/index.ts`](../packages/mcp/mcp-resources/src/index.ts) |
| `@deepseek-ai/dsh-message-quotes` | — | [`packages/session/message-quotes/src/index.ts`](../packages/session/message-quotes/src/index.ts) |
| `@deepseek-ai/dsh-otel` | — | [`packages/telemetry/otel/src/index.ts`](../packages/telemetry/otel/src/index.ts) |
| `@deepseek-ai/dsh-sandbox-ssh` | `ssh` | [`packages/ssh/sandbox-ssh/src/index.ts`](../packages/ssh/sandbox-ssh/src/index.ts) |
| `@deepseek-ai/dsh-session` | — | [`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts) |
+5 -4
View File
@@ -18,7 +18,7 @@
- `inject`: `agents` · `llm` · `sessionPersistence` · `sessions`
- `refs`: `Stream` (`@agentclientprotocol/sdk`)
- `source`: [`packages/acp/acp/src/index.ts:75`](../packages/acp/acp/src/index.ts)
- `source`: [`packages/acp/acp/src/index.ts:74`](../packages/acp/acp/src/index.ts)
```ts config-catalog
/** Plugin config: the provider/model selection used for each ACP-created agent. */
@@ -98,7 +98,7 @@ export interface Config {
- `inject`: `agents` · `sessions` · `llm` · `tools` · `systemPrompt` · `sessionProjections`
- `refs`: [`AgentOptions`](subsystems/core.zh.md) · [`SessionId`](subsystems/core.zh.md) · `Volatile` (`@deepseek-ai/cosmokit`)
- `source`: [`packages/core/agent-loop/src/index.ts:250`](../packages/core/agent-loop/src/index.ts)
- `source`: [`packages/core/agent-loop/src/index.ts:253`](../packages/core/agent-loop/src/index.ts)
```ts config-catalog
/** Agent-loop plugin configuration. */
@@ -108,7 +108,7 @@ export interface Config {
* omission defaults to {@link DEFAULT_MAX_PARALLEL_TOOL_CALLS}.
*/
maxParallelToolCalls: Volatile<number>
/** Agents created or resumed at plugin startup. */
/** Agents created or resumed at plugin startup; a resumed agent records its configured route as the Session's model selection. */
agents: (AgentOptions & {
/** Stable config label used in logs and as the fresh combined-id prefix. */
id: string
@@ -2606,7 +2606,7 @@ export interface Config {
maxOutputBytes?: number
/** V8 old-generation heap limit in MiB; native allocations are excluded. */
maxOldGenerationSizeMb?: number
/** Maximum control frame, outstanding argument and queued control-output bytes. */
/** Maximum control message, outstanding binding-argument and queued control-output bytes. */
maxMessageBytes?: number
/** Maximum simultaneous host binding calls accepted from a program. */
maxPendingCalls?: number
@@ -4787,6 +4787,7 @@ export interface Config {
| `@deepseek-ai/dsh-llm` | — | [`packages/llm/llm/src/index.ts`](../packages/llm/llm/src/index.ts) |
| `@deepseek-ai/dsh-lsp` | — | [`packages/lsp/lsp/src/index.ts`](../packages/lsp/lsp/src/index.ts) |
| `@deepseek-ai/dsh-mcp-resources` | `tools` | [`packages/mcp/mcp-resources/src/index.ts`](../packages/mcp/mcp-resources/src/index.ts) |
| `@deepseek-ai/dsh-message-quotes` | — | [`packages/session/message-quotes/src/index.ts`](../packages/session/message-quotes/src/index.ts) |
| `@deepseek-ai/dsh-otel` | — | [`packages/telemetry/otel/src/index.ts`](../packages/telemetry/otel/src/index.ts) |
| `@deepseek-ai/dsh-sandbox-ssh` | `ssh` | [`packages/ssh/sandbox-ssh/src/index.ts`](../packages/ssh/sandbox-ssh/src/index.ts) |
| `@deepseek-ai/dsh-session` | — | [`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts) |
+2 -2
View File
@@ -21,8 +21,8 @@
en: 0894bd9f35430753
zh: 2d28090e71134edd
/cookbook-adding-a-workspace-package/5-add-optional-plugin-display-metadata:
en: ed501a72ab8ba6e7
zh: 8ad775b735887a10
en: cb799f5d561dd917
zh: 7739af36ef8ffb3f
/cookbook-adding-a-workspace-package/6-verify:
en: 80c7fb90b1175aae
zh: 2ba37962dbc57299
+1 -1
View File
@@ -136,7 +136,7 @@ Merge these entries into `package.json`, retaining existing runtime exports and
}
```
Keep language files together in `locale/`, with `en.json` as the discovery entry. Fields are optional; present values must be non-empty strings. Missing files or fields permit fallback, while malformed JSON or invalid fields produce a per-plugin diagnostic.
Keep language files together in `locale/`, with `en.json` as the discovery entry. Fields are optional; present values must be non-empty strings. English `meta.title` and `meta.description` use sentence case: capitalize only the first word, proper nouns, and product names, as in `Git worktrees` ([rule](../../.agents/skills/dsh-prose-standard/SKILL.md#required-coverage-by-prose-location)). Missing files or fields permit fallback, while malformed JSON or invalid fields produce a per-plugin diagnostic.
Fields fall back independently before view-specific name formatting, using the existing locale language chain first:
+1 -1
View File
@@ -136,7 +136,7 @@ Append-only, prefix-stable, replacing, or independent behavior, including the ex
}
```
语言文件统一放在 `locale/`,并保留 `en.json` 作为发现入口。字段可选,填写时必须是非空字符串。文件或字段缺失时允许回退,JSON 损坏或字段无效时显示单插件诊断。
语言文件统一放在 `locale/`,并保留 `en.json` 作为发现入口。字段可选,填写时必须是非空字符串。英文 `meta.title` 和 `meta.description` 使用 sentence case:只大写首个单词、专有名词和产品名,例如 `Git worktrees`([rule](../../.agents/skills/dsh-prose-standard/SKILL.md#required-coverage-by-prose-location))。文件或字段缺失时允许回退,JSON 损坏或字段无效时显示单插件诊断。
各字段先独立回退,再按页面规则格式化技术名称;回退优先经过现有 locale 语言链:
+2 -2
View File
@@ -21,5 +21,5 @@
en: f0a28d07c0bb4d1b
zh: 840962f512cb4031
/cookbook-extension-plugin-shapes/the-feature-mechanism-map:
en: fa917d46c071fad2
zh: 4062805237e2c847
en: af09d0fbb5f39df9
zh: 189166baa84868bc
+1 -1
View File
@@ -103,7 +103,7 @@ Every product feature maps to a listener on a documented extension point — the
| Product feature | Plugin mechanism |
|---|---|
| Hook system (user + project level) | listeners on `agent/created`, `agent/pre-step`, `agent/request`, `tools/pre-execute`, `tools/post-execute`, and `agent/turn-stopping`; the waterfalls return typed decisions, while `agent/turn-stopping` may steer another step; the `dsh-hooks-claude-code` / `dsh-hooks-codex` bridges map hook config files onto these extension points |
| Hook system (user + project level) | listeners on `agent/created`, `agent/pre-step`, `tools/pre-execute`, `tools/post-execute`, and `agent/turn-stopping`; the waterfalls return typed decisions, while `agent/turn-stopping` may steer another step; the `dsh-hooks-claude-code` / `dsh-hooks-codex` bridges map hook config files onto these extension points |
| `/goal` | `ctx.goals` owns durable state, `dsh-goal-round-driver` schedules same-session rounds through the public `Agent`, and separate command/tool producers expose human/model control |
| `/loop` | on the `turn/end` session event, `followup()` the next iteration; or force-continue |
| Dynamic workflow | `ctx.workflowEngine` + the PTC workflow engine + the `workflow` tool; structured in-process children enforce output with scoped prompt/tool registrations, a monotonic tool guard, final `tools/result` commit (including enclosing `run_code`), and the structured-output execution's monotonic `concludeTurn()` marker |
+1 -1
View File
@@ -107,7 +107,7 @@ export function apply(ctx: Context) {
| 产品功能 | 插件机制 |
|---|---|
| 钩子系统(用户级 + 项目级) | `agent/created`、`agent/pre-step`、`agent/request`、`tools/pre-execute`、`tools/post-execute` 和 `agent/turn-stopping` 上的监听器;waterfall 返回类型化决策,`agent/turn-stopping` 则可通过 steering(中途引导)触发下一步;`dsh-hooks-claude-code` / `dsh-hooks-codex` 桥接器将钩子配置文件映射到这些扩展点上 |
| 钩子系统(用户级 + 项目级) | `agent/created`、`agent/pre-step`、`tools/pre-execute`、`tools/post-execute` 和 `agent/turn-stopping` 上的监听器;waterfall 返回类型化决策,`agent/turn-stopping` 则可通过 steering(中途引导)触发下一步;`dsh-hooks-claude-code` / `dsh-hooks-codex` 桥接器将钩子配置文件映射到这些扩展点上 |
| `/goal` | `ctx.goals` 管理持久状态,`dsh-goal-round-driver` 通过公共 `Agent` 调度同会话 Round,独立的命令/工具生产方分别提供人类/模型控制 |
| `/loop` | 在 `turn/end` 会话事件上 `followup()` 下一次迭代;或强制继续 |
| 动态工作流 | `ctx.workflowEngine` + PTC 工作流引擎 + `workflow` 工具;结构化的进程内子任务通过作用域化的提示词/工具注册、单调工具守卫、最终 `tools/result` 提交(包括外层 `run_code`)和结构化输出执行的单调 `concludeTurn()` 标记来强制输出 |
+2 -2
View File
@@ -12,5 +12,5 @@
en: 24799f579b04b2d4
zh: 97d4d82dc3a02c75
/4-events/waterfall-transform-or-short-circuit:
en: 8309aea415b395c3
zh: 480ee4a163543dd0
en: a0027cc61e674138
zh: c96dd1e7d559b748
+1 -1
View File
@@ -137,7 +137,7 @@ Walk through the second line: listener 1 runs first, calls `next()`, which invok
The discipline that follows: **a waterfall listener that only observes or annotates must call `next()`**; returning without it is a deliberate short-circuit. Forgetting `next()` in a logging listener silently swallows the default behavior for everyone downstream. It is a standing rule of this repository ([waterfall semantics](../cordis-primer.md#cordis-waterfall-semantics)).
The harness uses waterfalls for decisions that cooperating plugins may wrap or answer: [`agent/request`](../subsystems/core.md#agentrequest--waterfall) lets a plugin replace the model-call config, and [`approval/request`](../subsystems/approval.md#approvalrequest--waterfall) lets a policy answer instead of the user.
The harness uses waterfalls for decisions that cooperating plugins may wrap or answer: [`agent/pre-step`](../subsystems/core.md#agentpre-step--waterfall) lets a plugin reject a proposed step or replace its input, and [`approval/request`](../subsystems/approval.md#approvalrequest--waterfall) lets a policy answer instead of the user.
Next: [Configuration](05-config.md) — plugin options from `cordis.yml`.
+1 -1
View File
@@ -137,7 +137,7 @@ HELLO
由此得到一项纪律:**只负责观察或标注的 waterfall 监听器必须调用 `next()`**;不调用就直接返回代表有意短路。如果日志监听器忘记调用 `next()`,会悄无声息地吞掉所有下游的默认行为。这是本仓库的常设规则([waterfall 语义](../cordis-primer.zh.md#cordis-waterfall-semantics))。
harness 使用 waterfall 处理协作插件可以包装或回答的决策:[`agent/request`](../subsystems/core.zh.md#agentrequest--waterfall) 允许插件替换模型调用配置,[`approval/request`](../subsystems/approval.zh.md#approvalrequest--waterfall) 允许策略代替用户作答。
harness 使用 waterfall 处理协作插件可以包装或回答的决策:[`agent/pre-step`](../subsystems/core.zh.md#agentpre-step--waterfall) 允许插件拒绝拟议步骤或替换其输入,[`approval/request`](../subsystems/approval.zh.md#approvalrequest--waterfall) 允许策略代替用户作答。
下一章:[配置](05-config.zh.md):来自 `cordis.yml` 的插件选项。
+20 -20
View File
@@ -8,25 +8,24 @@ This matrix shows which packages dispatch each harness-owned event and which pac
<!-- BEGIN GENERATED event-producer-consumer:events -->
| Event | Mode | Declared in | Dispatchers | Listeners |
| --- | --- | --- | --- | --- |
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:196`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:199`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
| `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` |
| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:363`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` |
| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:270`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:393`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) |
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) |
| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:307`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) |
| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:288`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) |
| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:337`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`compaction-basic`](../packages/compaction/compaction-basic), [`webhook`](../packages/experimental/webhook) |
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:627`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:607`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:634`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:613`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:620`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` |
| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) |
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) |
| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) |
| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) |
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:600`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:580`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:607`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:586`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:593`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:54`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
@@ -74,7 +73,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:142`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:158`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code) |
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`working-directory`](../packages/session/working-directory) |
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | `browser-use-runtime`, [`working-directory`](../packages/session/working-directory) |
| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:209`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:165`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `claude-code-mods`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` |
@@ -82,7 +81,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:154`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:191`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) |
| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:199`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) |
| `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:157`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes`, `server` |
| `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:174`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes`, `server` |
| `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:39`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `remote-access`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` |
| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) |
| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) |
@@ -99,6 +98,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
<!-- BEGIN GENERATED event-producer-consumer:undeclared -->
| Event string | Dispatchers | Listeners |
| --- | --- | --- |
| `agent/request` | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic) |
| `internal/config` | [`config-editor`](../packages/boot/config-editor) (`waterfall`) | [`llm-pi-ai`](../packages/llm/llm-pi-ai) |
| `internal/dispatch` | - | [`terminal-bash`](../packages/terminal/terminal-bash) |
| `internal/plugin` | - | `computer-use-cua-driver-native`, `inspector`, `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), [`mcp-client`](../packages/mcp/mcp-client), `modules` |
+20 -20
View File
@@ -10,25 +10,24 @@
<!-- BEGIN GENERATED event-producer-consumer:events -->
| Event | Mode | Declared in | Dispatchers | Listeners |
| --- | --- | --- | --- | --- |
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:196`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:199`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
| `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` |
| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:363`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` |
| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:270`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:393`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) |
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) |
| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:307`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) |
| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:288`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) |
| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:337`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`compaction-basic`](../packages/compaction/compaction-basic), [`webhook`](../packages/experimental/webhook) |
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:627`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:607`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:634`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:613`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:620`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` |
| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) |
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) |
| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) |
| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) |
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:600`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:580`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:607`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:586`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:593`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:54`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
@@ -76,7 +75,7 @@
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:142`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:158`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code) |
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`working-directory`](../packages/session/working-directory) |
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | `browser-use-runtime`, [`working-directory`](../packages/session/working-directory) |
| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:209`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:165`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `claude-code-mods`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` |
@@ -84,7 +83,7 @@
| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:154`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:191`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) |
| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:199`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) |
| `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:157`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes`, `server` |
| `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:174`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes`, `server` |
| `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:39`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `remote-access`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` |
| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) |
| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) |
@@ -101,6 +100,7 @@
<!-- BEGIN GENERATED event-producer-consumer:undeclared -->
| Event string | Dispatchers | Listeners |
| --- | --- | --- |
| `agent/request` | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic) |
| `internal/config` | [`config-editor`](../packages/boot/config-editor) (`waterfall`) | [`llm-pi-ai`](../packages/llm/llm-pi-ai) |
| `internal/dispatch` | - | [`terminal-bash`](../packages/terminal/terminal-bash) |
| `internal/plugin` | - | `computer-use-cua-driver-native`, `inspector`, `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), [`mcp-client`](../packages/mcp/mcp-client), `modules` |
+2
View File
@@ -374,6 +374,7 @@ flowchart TD
pkg_sdk_protocol["sdk-protocol"]
end
subgraph group_session["packages/session"]
pkg_message_quotes["message-quotes"]
pkg_session_checkpoint_policy["session-checkpoint-policy"]
pkg_session_format["session-format"]
pkg_session_format_catalog["session-format-catalog"]
@@ -1563,6 +1564,7 @@ flowchart TD
| [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | — |
| [`host-open-in-app`](../packages/host/open-in-app) | `host` | — |
| [`host-webserver`](../packages/host/webserver) | `host` | — |
| [`message-quotes`](../packages/session/message-quotes) | `session` | — |
| [`session-format`](../packages/session/session-format) | `session` | — |
| [`session-format-v0-to-v1`](../packages/session/session-format-v0-to-v1) | `session` | — |
| [`session-format-v1-to-v2`](../packages/session/session-format-v1-to-v2) | `session` | — |
+2
View File
@@ -376,6 +376,7 @@ flowchart TD
pkg_sdk_protocol["sdk-protocol"]
end
subgraph group_session["packages/session"]
pkg_message_quotes["message-quotes"]
pkg_session_checkpoint_policy["session-checkpoint-policy"]
pkg_session_format["session-format"]
pkg_session_format_catalog["session-format-catalog"]
@@ -1565,6 +1566,7 @@ flowchart TD
| [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | — |
| [`host-open-in-app`](../packages/host/open-in-app) | `host` | — |
| [`host-webserver`](../packages/host/webserver) | `host` | — |
| [`message-quotes`](../packages/session/message-quotes) | `session` | — |
| [`session-format`](../packages/session/session-format) | `session` | — |
| [`session-format-v0-to-v1`](../packages/session/session-format-v0-to-v1) | `session` | — |
| [`session-format-v1-to-v2`](../packages/session/session-format-v1-to-v2) | `session` | — |
+18 -18
View File
@@ -21,8 +21,8 @@
en: 9188862058c6986b
zh: 9188862058c6986b
/session-persistence-event-catalog/events/agent/agent-inbox-spliced-log-only:
en: 8309b4babad90744
zh: b344ac640d79947e
en: 481727894895b43b
zh: 098bc9788bf88968
/session-persistence-event-catalog/events/agent-preset:
en: 49891ca9be5ac123
zh: 49891ca9be5ac123
@@ -141,8 +141,8 @@
en: 9edf4ef31e60e880
zh: 9edf4ef31e60e880
/session-persistence-event-catalog/events/model/model-selection-log-only:
en: 06589ec99fb2bb40
zh: 3b78f0418d44609b
en: c2a60ec7d297ed79
zh: a2f6b2d3c733ce96
/session-persistence-event-catalog/events/permission:
en: 19917bc5438e60af
zh: 19917bc5438e60af
@@ -1074,8 +1074,8 @@
en: a3087ab7bebe9371
zh: abf6afc2f9f5b692
/session-persistence-event-catalog/resolved-persistence-types/inboxtarget:
en: 938113c1b01efad1
zh: d6829dbe979e6819
en: b3b7b8a3414bcb6b
zh: 06be5b8e56bacedf
/session-persistence-event-catalog/resolved-persistence-types/jobregistereddata:
en: f604eebb973fe2cf
zh: 0c0aba1e17ecf66e
@@ -1143,11 +1143,11 @@
en: 8bd12543fcdcda5f
zh: d4c6b17083948856
/session-persistence-event-catalog/resolved-persistence-types/modelmessagesource:
en: 3b677f021d7dd4d3
zh: 2a875742b22879a2
en: 5f9154ca34235ab9
zh: a833105910ebfa82
/session-persistence-event-catalog/resolved-persistence-types/modelselection:
en: a756bce4b580d325
zh: 84ab7988df86487d
en: 6e301c0ea35d9e0e
zh: 6fbf1d318db9a4e0
/session-persistence-event-catalog/resolved-persistence-types/oneshotscheduledispatchchange:
en: a3300babe57b6b67
zh: 4aaf06a149b399c3
@@ -1347,8 +1347,8 @@
en: 2786fef560851bb4
zh: 5d787475356b2606
/session-persistence-event-catalog/resolved-persistence-types/string:
en: 10d206dcc422b065
zh: 8e65d17727283dcd
en: a9d7dc010ada133d
zh: 9bd682adee72d04e
/session-persistence-event-catalog/resolved-persistence-types/string~2:
en: 0089620bf4638263
zh: 1922bef1713094bb
@@ -1524,8 +1524,8 @@
en: 8a9199b1784c70f5
zh: 76c98bc74686fda0
/session-persistence-event-catalog/resolved-persistence-types/inserted-outcome-removedcount-start:
en: 452ff7ab39ffb3a9
zh: 33af56489f0a45bc
en: 346d884a7a74e48d
zh: 646ac1021f9ca920
/session-persistence-event-catalog/resolved-persistence-types/interrupted-message-step-stream:
en: ec10cd8e9c202a81
zh: e08f859bcb13f0d4
@@ -1833,8 +1833,8 @@
en: 809a23c25ded8497
zh: e9cd23e281f8d31f
/session-persistence-event-catalog/resolved-persistence-types/kind-user:
en: ecd4a562cf440158
zh: 9bd3ec23c094a14e
en: 81620af099525ee9
zh: 812db2fdf9e5d309
/session-persistence-event-catalog/resolved-persistence-types/kind-user-approval:
en: a52d2dfbaf844127
zh: 4c5306ca40f20873
@@ -1857,8 +1857,8 @@
en: 993991e8f05665bb
zh: a03931839e647bac
/session-persistence-event-catalog/resolved-persistence-types/kind-user-question-reply:
en: 77a8fc6a2f3a9319
zh: 4dc1de1358b51a8d
en: 246940ea354e5493
zh: 9f3f462ec3bc2000
/session-persistence-event-catalog/resolved-persistence-types/kind-webhook-form-notice:
en: 0b74295f7c5c8f11
zh: 5f1213cec87f6be0
+11 -11
View File
@@ -185,7 +185,7 @@ Sources: [`packages/core/session/src/types.ts:432`](../packages/core/session/src
}
```
Source: [`packages/core/agent/src/types.ts:84`](../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts)
### `agent-preset/*`
@@ -694,13 +694,13 @@ Source: [`packages/llm/llm-retry/src/types.ts:11`](../packages/llm/llm-retry/src
```ts persistence-catalog
/**
* Complete validated model selection requested for subsequent prompt
* assembly. Log-only: it never enters derived model history.
* Complete model selection that applies from the next model step until a
* later selection. Log-only: it never enters derived model history.
*/
'model/selection': ModelSelection
```
Source: [`packages/api/session-controller/src/types.ts:37`](../packages/api/session-controller/src/types.ts)
Source: [`packages/core/agent/src/types.ts:121`](../packages/core/agent/src/types.ts)
### `permission/*`
@@ -3788,7 +3788,7 @@ Array of [`ImageOffloadTarget`](#persistence-type-sha256-1cde1bedde79c5e8a56aee9
SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c`
Sources: [`packages/core/agent/src/types.ts:39`](../packages/core/agent/src/types.ts)
Sources: [`packages/core/agent/src/types.ts:40`](../packages/core/agent/src/types.ts)
One of:
@@ -4331,13 +4331,13 @@ Sources: [`packages/llm/llm/src/message.ts:24`](../packages/llm/llm/src/message.
<a id="persistence-type-modelselection"></a>
<a id="persistence-type-packagesapisession-controllersrctypestsmodelselection"></a>
<a id="persistence-type-packagescoreagentsrctypestsmodelselection"></a>
### `ModelSelection`
SHA-256: `f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b`
Sources: [`packages/api/session-controller/src/types.ts:99`](../packages/api/session-controller/src/types.ts)
Sources: [`packages/core/agent/src/types.ts:79`](../packages/core/agent/src/types.ts)
| Property | Presence | Type |
|---|---|---|
@@ -5583,7 +5583,7 @@ Array of `number`.
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
Sources: [`packages/api/session-controller/src/types.ts:407`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
Sources: [`packages/api/session-controller/src/types.ts:380`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
`string`
@@ -6377,7 +6377,7 @@ Sources: [`packages/core/session/src/types.ts:428`](../packages/core/session/src
SHA-256: `fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42`
Sources: [`packages/core/agent/src/types.ts:84`](../packages/core/agent/src/types.ts)
Sources: [`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts)
| Property | Presence | Type |
|---|---|---|
@@ -7554,7 +7554,7 @@ Sources: [`packages/core/tools/src/index.ts:35`](../packages/core/tools/src/inde
SHA-256: `6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def`
Sources: [`packages/api/session-controller/src/types.ts:416`](../packages/api/session-controller/src/types.ts)
Sources: [`packages/api/session-controller/src/types.ts:389`](../packages/api/session-controller/src/types.ts)
| Property | Presence | Type |
|---|---|---|
@@ -7647,7 +7647,7 @@ SHA-256: `5654685d7557ec747e74887237065e626a5bdfa426ca43f7fbb0b6fee9b0916c`
SHA-256: `c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005`
Sources: [`packages/interaction/user-questions/src/types.ts:117`](../packages/interaction/user-questions/src/types.ts)
Sources: [`packages/interaction/user-questions/src/types.ts:134`](../packages/interaction/user-questions/src/types.ts)
| Property | Presence | Type |
|---|---|---|
+11 -11
View File
@@ -187,7 +187,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
}
```
来源:[`packages/core/agent/src/types.ts:84`](../packages/core/agent/src/types.ts)
来源:[`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts)
### `agent-preset/*`
@@ -696,13 +696,13 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
```ts persistence-catalog
/**
* Complete validated model selection requested for subsequent prompt
* assembly. Log-only: it never enters derived model history.
* Complete model selection that applies from the next model step until a
* later selection. Log-only: it never enters derived model history.
*/
'model/selection': ModelSelection
```
来源:[`packages/api/session-controller/src/types.ts:37`](../packages/api/session-controller/src/types.ts)
来源:[`packages/core/agent/src/types.ts:121`](../packages/core/agent/src/types.ts)
### `permission/*`
@@ -3790,7 +3790,7 @@ SHA-256: `9e2c06d8653585b33d72d5c1a80da79c8a24a88c284409bca15cc0f0ead5ebfb`
SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c`
来源:[`packages/core/agent/src/types.ts:39`](../packages/core/agent/src/types.ts)
来源:[`packages/core/agent/src/types.ts:40`](../packages/core/agent/src/types.ts)
以下类型之一:
@@ -4333,13 +4333,13 @@ SHA-256: `719c51f67074f97034a6c94297fe1f4b5d3c8d392c57586dab732aa5398c7a91`
<a id="persistence-type-modelselection"></a>
<a id="persistence-type-packagesapisession-controllersrctypestsmodelselection"></a>
<a id="persistence-type-packagescoreagentsrctypestsmodelselection"></a>
### `ModelSelection`
SHA-256: `f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b`
来源:[`packages/api/session-controller/src/types.ts:99`](../packages/api/session-controller/src/types.ts)
来源:[`packages/core/agent/src/types.ts:79`](../packages/core/agent/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
@@ -5585,7 +5585,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
来源:[`packages/api/session-controller/src/types.ts:407`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
来源:[`packages/api/session-controller/src/types.ts:380`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
`string`
@@ -6379,7 +6379,7 @@ SHA-256: `17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3`
SHA-256: `fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42`
来源:[`packages/core/agent/src/types.ts:84`](../packages/core/agent/src/types.ts)
来源:[`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
@@ -7556,7 +7556,7 @@ SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543`
SHA-256: `6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def`
来源:[`packages/api/session-controller/src/types.ts:416`](../packages/api/session-controller/src/types.ts)
来源:[`packages/api/session-controller/src/types.ts:389`](../packages/api/session-controller/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
@@ -7649,7 +7649,7 @@ SHA-256: `5654685d7557ec747e74887237065e626a5bdfa426ca43f7fbb0b6fee9b0916c`
SHA-256: `c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005`
来源:[`packages/interaction/user-questions/src/types.ts:117`](../packages/interaction/user-questions/src/types.ts)
来源:[`packages/interaction/user-questions/src/types.ts:134`](../packages/interaction/user-questions/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
+6 -6
View File
@@ -253,7 +253,7 @@
{"digest":"4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731","names":["packages/llm/llm/src/message.ts#ToolResultMessage"],"sources":["packages/llm/llm/src/message.ts:173"]},
{"digest":"4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea","names":[],"sources":[]},
{"digest":"4bb451330ff699f36b45539ceb0eb2606ead72084e2c5bb637c36cc80e2252db","names":[],"sources":["packages/core/session/src/types.ts:194"]},
{"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:407","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/agent-team/src/types.ts:21","packages/experimental/agent-team/src/types.ts:33","packages/experimental/agent-team/src/types.ts:9","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/workflow/workflow/src/types.ts:13"]},
{"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:380","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/agent-team/src/types.ts:21","packages/experimental/agent-team/src/types.ts:33","packages/experimental/agent-team/src/types.ts:9","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/workflow/workflow/src/types.ts:13"]},
{"digest":"4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159","names":[],"sources":[]},
{"digest":"4e44c9118152f6829634bfd508e47669ab0497caffa380c94b4be91f9390d35d","names":[],"sources":[]},
{"digest":"4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c","names":["packages/llm/llm/src/types.ts#TextBlock"],"sources":["packages/llm/llm/src/types.ts:62","packages/web/web-search-deepseek/src/provider.ts:66"]},
@@ -335,7 +335,7 @@
{"digest":"6ccb6bc423fb3952458abf9c1b1c3284208b7e660344b612cc55da773d61afe4","names":[],"sources":[]},
{"digest":"6cda0d6d3b4336d83998fe580628426df81c2afbab5d768b8813b56d569120e8","names":[],"sources":[]},
{"digest":"6cf082d0fb6034dea61636787ff0ddf43b6e59aad71e644435a479bc9b83ad65","names":[],"sources":[]},
{"digest":"6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def","names":[],"sources":["packages/api/session-controller/src/types.ts:416"]},
{"digest":"6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def","names":[],"sources":["packages/api/session-controller/src/types.ts:389"]},
{"digest":"6e52dd55a1d48954ed5464ec46fe34d9eb65f709baabc9561e7a279910b0c406","names":[],"sources":["packages/preset/agent-preset-registry/src/session.ts:31"]},
{"digest":"6ea608bdc8a7a63d3d23b84c49721325094db88d136482e43a820945295eee3e","names":[],"sources":[]},
{"digest":"6ebcc2dbec9f2dabca9f668ae6d39e877899b180740570f453409db339221df3","names":[],"sources":[]},
@@ -430,7 +430,7 @@
{"digest":"95c0e88fcf0bafb2087f912ad9e2369de6ae6530cc0c4c2772c96205b57f7f60","names":[],"sources":[]},
{"digest":"9645cf93769a6dfb227bd400bd6cb9fa7bab9140f4a851e4ae92514ade6e3114","names":[],"sources":[]},
{"digest":"96bfb916a727cd4445a594df264c523fc039749a31bfed924b8cc7bb308a5bb0","names":[],"sources":[]},
{"digest":"96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c","names":["packages/core/agent/src/types.ts#InboxTarget"],"sources":["packages/core/agent/src/types.ts:39"]},
{"digest":"96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c","names":["packages/core/agent/src/types.ts#InboxTarget"],"sources":["packages/core/agent/src/types.ts:40"]},
{"digest":"97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c","names":[],"sources":[]},
{"digest":"9890113e97dd3541787bfe6704f2b223d3a890b5325ca0acae2b0398909b413e","names":[],"sources":["packages/sandbox/sandbox-policy/src/session-mode.ts:33"]},
{"digest":"99135d8a3937977955eaac5f8f04f930dcd1562f8450a54a51cde9b921dd59a6","names":[],"sources":[]},
@@ -554,7 +554,7 @@
{"digest":"c2f49004d9ae38a964e80850c87bc6849d6a872091a1d43e99eedf00c641e2a3","names":[],"sources":[]},
{"digest":"c43b119ce579b3e9983a0d3adc4ba402ebb9ba773d89738a2dab01ecf5813943","names":[],"sources":[]},
{"digest":"c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49","names":[],"sources":[]},
{"digest":"c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005","names":[],"sources":["packages/interaction/user-questions/src/types.ts:117"]},
{"digest":"c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005","names":[],"sources":["packages/interaction/user-questions/src/types.ts:134"]},
{"digest":"c5687b1313206a6ff334066047b6a0681611467dc4df8c5a8e71d0b6df39311f","names":[],"sources":[]},
{"digest":"c589e938cc0e4989df8b82abef6339de621f0dc4fe62c230d145b94996af7f92","names":[],"sources":[]},
{"digest":"c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6","names":["packages/util/values/src/index.ts#JsonValue"],"sources":["packages/util/values/src/index.ts:4"]},
@@ -640,7 +640,7 @@
{"digest":"ef226a90ddac6cd8e631a3a337a3a3e27f9494be92befb7c718b404681d75e21","names":[],"sources":[]},
{"digest":"ef4f00923b9edbae15c7ef05320280bf61147ca17146cf59690359bfd5e5da45","names":[],"sources":[]},
{"digest":"f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c","names":[],"sources":[]},
{"digest":"f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b","names":["packages/api/session-controller/src/types.ts#ModelSelection"],"sources":["packages/api/session-controller/src/types.ts:99"]},
{"digest":"f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b","names":["packages/core/agent/src/types.ts#ModelSelection"],"sources":["packages/core/agent/src/types.ts:79"]},
{"digest":"f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402","names":["packages/core/session/src/types.ts#TurnEndReason"],"sources":["packages/core/session/src/types.ts:233"]},
{"digest":"f1996af2602e6f62b5ab179ca5c4af00d8753938b74cc6ef102de09e99e74a7e","names":[],"sources":[]},
{"digest":"f19bee84bf3d20904553cdcd57465232bc1c2a194f54c4d195292ba0487223a6","names":["packages/attachment/attachment/src/types.ts#FileAttachmentRef"],"sources":["packages/attachment/attachment/src/types.ts:39"]},
@@ -666,7 +666,7 @@
{"digest":"f90eb4ab2d3897bc20c521a038e3119d581f791ec974b1a0004f63a8ab9e232d","names":[],"sources":["packages/core/session/src/types.ts:289","packages/deliverables/workspace-changes/src/types.ts:106"]},
{"digest":"f97f47b0eb87905ed77cc27c5e4ed75ef805cb372c98905253709de1aab9b63c","names":[],"sources":[]},
{"digest":"f9cfc89e8fe085b4fcc9cd32b74b6f103608c54d7d8cd97b47c01de34f252ba3","names":[],"sources":[]},
{"digest":"fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42","names":[],"sources":["packages/core/agent/src/types.ts:84"]},
{"digest":"fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42","names":[],"sources":["packages/core/agent/src/types.ts:110"]},
{"digest":"fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be","names":["packages/llm/llm/src/message.ts#AssistantMessage"],"sources":["packages/llm/llm/src/message.ts:167"]},
{"digest":"fab2bb09bc5e3b9db0685e8f5500b029af1a2b33e70bc703c1d5d0f596fbc1fd","names":["packages/sandbox/sandbox/src/index.ts#SandboxMode"],"sources":["packages/sandbox/sandbox/src/index.ts:30"]},
{"digest":"fab35c4520883c0d69e42d079c9f543fc5ac7a2d15b537b5572fcd99059c7018","names":[],"sources":[]},
+2 -2
View File
@@ -12,8 +12,8 @@
en: ab09a38b4e1f0883
zh: 81e2d14c4036ab6c
/core/the-agent-handle:
en: c21787347d8e2b14
zh: 25bc9cb60ee1520a
en: 6ee15fec81bc0686
zh: b6f3fcd48823ea75
/core/initiating-agent:
en: 6c8c47ac78de2dde
zh: c9d527a25a85d1d9
+74 -35
View File
@@ -61,10 +61,12 @@ Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types
interface Agent {
/** Session-backed Agent identity. */
readonly id: SessionId
/** The provider route and model this agent's requests use. */
/** Route fallback and output controls; live accessors apply until the Session records a selection or request. */
readonly options: AgentOptions
/** The live session this agent drives; its log is the durable source of truth. */
readonly session: Session
/** Agent-owned access to durable model selection for future steps. */
readonly model: Model
/** Agent-owned access to durable pending work. */
readonly inbox: Inbox
/** The current lifecycle state, mirrored on every `agent/status` transition. */
@@ -193,20 +195,86 @@ type AssistantStreamFrame =
`running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `followup()` returns no handle: its `MessageId` identifies durable inbox insertion, claim, and discard facts, not a later assistant output or turn ending. `whenIdle()` observes the whole agent, so callers may call a receipt-to-idle interval a run only when they explicitly own that interval ([decision](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md)).
```ts type-equiv
/** Merge-extensible agent creation options. Persona belongs to system-prompt sections. */
/**
* Merge-extensible agent options. Route fields may be live accessors until the
* Session records a selection or request; step preparation captures their value.
* Persona belongs to system-prompt sections.
*/
interface AgentOptions {
/** Provider route (must have a registered adapter at call time). */
provider?: string
/** Model id interpreted by the selected provider adapter. */
model?: string
/** Adapter-owned reasoning effort for the selected provider/model route. */
reasoningEffort?: ReasoningEffortId
reasoningEffort?: ReasoningEffortId | undefined
/** Maximum output tokens for each conversation-model request. */
maxTokens?: number
}
```
Dispatch requires `provider` and `model` after `agent/request`. An explicit `reasoningEffort` seeds the first request on that route; exact-model resolution validates it, while omission allows the adapter default to materialize. When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. An agent-scoped `deployment:persona-prefix` prompt section may shadow the global default persona.
Step preparation requires `provider` and `model` before prompt assembly. The deprecated `agent/request` hook may change request controls but must preserve that route. An explicit `reasoningEffort` seeds the first request on that route; exact-model resolution validates it, while omission allows the adapter default to materialize. When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. An agent-scoped `deployment:persona-prefix` prompt section may shadow the global default persona.
`AgentLoop` registers `modelSelection` on `ctx.sessionProjections` and exposes selection operations through each `agent.model` instance. `model/selection` records a complete choice without entering model history. A matching request header clears `pending`; the latest request remains `lastUsed`. Adapter-defaulted effort is omitted so later requests resolve the default again. `next` is null only before either a choice or a request is recorded; in that case `AgentOptions` supplies the route. The [Agent model API](../../packages/core/agent/README.md) describes selection precedence and resume policy.
```ts type-equiv
/** Agent-owned access to the model choice for future steps. */
interface Model {
/**
* Resolve the choice for the next step to begin preparation. Recorded choices
* take precedence over the last request and then the Agent's live options.
* This read neither starts a step nor changes an already-prepared step.
* @returns a detached choice, or undefined when no source names both provider and model.
*/
current(): Readonly<ModelSelection> | undefined
/**
* Replace the next step's complete selection. Omitted effort follows adapter
* defaults without inheriting a previous effort or resolving defaults here.
* An equal recorded choice is a no-op; an options-only fallback is
* always recorded. Callers own model availability and capability validation.
* The append uses the Session's existing persistence and failure guarantees.
* @param selection - complete provider, model, and optional explicit effort.
*/
select(selection: Readonly<ModelSelection>): void
}
```
```ts type-equiv
/** Complete provider, model, and optional reasoning effort chosen for one Session's model steps. */
interface ModelSelection {
/** Registered provider route. */
provider: string
/** Provider-owned model id. */
model: string
/** Adapter-owned reasoning effort, or provider/default behavior when absent. */
reasoningEffort?: ReasoningEffortId
}
```
```ts type-equiv
/**
* Durable model-selection state for one Session. An effort the adapter
* defaulted on a recorded request is omitted, so later requests resolve the
* default again instead of pinning it as a choice.
*/
interface ModelSelectionProjectionState {
/** Selection used by the latest recorded model request. */
readonly lastUsed: ModelSelection | null
/** Later recorded selection not yet used by a matching model request. */
readonly pending: ModelSelection | null
}
```
```ts type-equiv
/** Client view of the durable model-selection fold. */
interface ModelSelectionProjection {
/** Selection used by the latest recorded model request. */
readonly lastUsed: ModelSelection | null
/** Selection the next model step uses; null while the Agent's initial options supply the route. */
readonly next: ModelSelection | null
}
```
The inbox is the delivery vocabulary — two ordered pending-message lists the agent owns as a durable projection:
@@ -976,46 +1044,17 @@ Reject a proposed step or replace the messages that enter it. Calling `next()` p
* @param payload.turn - the turn that will own the step.
* @param payload.step - the step proposed by the loop.
* @param payload.signal - the current turn's cancellation signal.
* @param payload.modelSelection - captured step selection, retained across retries; omitted effort uses adapter defaults.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode waterfall
*/
'agent/pre-step'(this: Scoped<Agent>, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise<PreStepDecision>): Promise<PreStepDecision>
'agent/pre-step'(this: Scoped<Agent>, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal; modelSelection: Readonly<ModelSelection> }, next: () => Promise<PreStepDecision>): Promise<PreStepDecision>
```
Types: [Scoped](scope.md) · [UserMessage](session.md)
Source: [`packages/core/agent/src/runtime-types.ts`](../../packages/core/agent/src/runtime-types.ts)
<a id="agentrequest--waterfall"></a>
#### `agent/request` — waterfall
Replace the frozen call configuration. `await next()` yields the config the machine would use (agent options on the first request, the logged header afterwards); return a replacement to switch. On step admission, this runs after assembly and `step/start`, before the system prompt and accepted user batch are committed. Cancellation here or during subsequent `prepareCall()` resolution commits neither. The prepared call capability governs prompt admission. Model-visible content must use logged channels; this waterfall cannot mutate messages.
```ts cordis-catalog
/**
* Replace the frozen call configuration. `await next()` yields the config
* the machine would use (agent options on the first request, the logged
* header afterwards); return a replacement to switch. On step admission,
* this runs after assembly and `step/start`, before the system prompt and
* accepted user batch are committed. Cancellation here or during subsequent
* `prepareCall()` resolution commits neither. The prepared call capability
* governs prompt admission. Model-visible content must use logged channels;
* this waterfall cannot mutate messages.
* @param payload.agent - the agent making the model call.
* @param payload.turn - the open turn number.
* @param payload.step - the step whose request this is.
* @param payload.signal - the current turn's explicit abort signal.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode waterfall
*/
'agent/request'(this: Scoped<Agent>, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise<LlmCallConfig>): Promise<LlmCallConfig>
```
Types: [LlmCallConfig](llm-streaming.md) · [Scoped](scope.md)
Source: [`packages/core/agent/src/runtime-types.ts`](../../packages/core/agent/src/runtime-types.ts)
<a id="agentrequest-error--waterfall"></a>
#### `agent/request-error` — waterfall
+74 -35
View File
@@ -65,10 +65,12 @@ interface AgentHandle {
interface Agent {
/** Session-backed Agent identity. */
readonly id: SessionId
/** The provider route and model this agent's requests use. */
/** Route fallback and output controls; live accessors apply until the Session records a selection or request. */
readonly options: AgentOptions
/** The live session this agent drives; its log is the durable source of truth. */
readonly session: Session
/** Agent-owned access to durable model selection for future steps. */
readonly model: Model
/** Agent-owned access to durable pending work. */
readonly inbox: Inbox
/** The current lifecycle state, mirrored on every `agent/status` transition. */
@@ -197,20 +199,86 @@ type AssistantStreamFrame =
`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。dispose 会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`followup()` 不返回句柄:其 `MessageId` 标识的是持久的 inbox 插入、认领与丢弃事实,而非之后的助手输出或轮次结束。`whenIdle()` 观察的是整个 agent,因此只有当调用方明确拥有从回执到空闲的这段区间时,才能把它称为一次 run([决策](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.zh.md))。
```ts type-equiv
/** Merge-extensible agent creation options. Persona belongs to system-prompt sections. */
/**
* Merge-extensible agent options. Route fields may be live accessors until the
* Session records a selection or request; step preparation captures their value.
* Persona belongs to system-prompt sections.
*/
interface AgentOptions {
/** Provider route (must have a registered adapter at call time). */
provider?: string
/** Model id interpreted by the selected provider adapter. */
model?: string
/** Adapter-owned reasoning effort for the selected provider/model route. */
reasoningEffort?: ReasoningEffortId
reasoningEffort?: ReasoningEffortId | undefined
/** Maximum output tokens for each conversation-model request. */
maxTokens?: number
}
```
在 `agent/request` 之后,分发要求 `provider` 与 `model` 都存在。显式 `reasoningEffort` 会为该路由的首次请求提供初始值;确切模型解析会校验该值,省略时则允许填入适配器默认值。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。agent 作用域的 `deployment:persona-prefix` 提示词段落可以遮蔽全局默认 persona。
步骤准备在提示词组装前要求 `provider` 与 `model` 都存在。已弃用的 `agent/request` hook 可以更改请求控制参数,但必须保留该路由。显式 `reasoningEffort` 会为该路由的首次请求提供初始值;确切模型解析会校验该值,省略时则允许填入适配器默认值。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。agent 作用域的 `deployment:persona-prefix` 提示词段落可以遮蔽全局默认 persona。
`AgentLoop` 在 `ctx.sessionProjections` 上注册 `modelSelection`,并通过每个 `agent.model` 实例提供选择操作。`model/selection` 记录完整选择,但不进入模型历史。匹配的请求头会清除 `pending`,最近请求仍保留为 `lastUsed`。适配器默认的推理强度会被省略,使后续请求重新解析默认值。只有尚未记录选择或请求时,`next` 才为 null,此时由 `AgentOptions` 提供路由。[Agent 模型 API](../../packages/core/agent/README.zh.md)说明选择优先级与恢复策略。
```ts type-equiv
/** Agent-owned access to the model choice for future steps. */
interface Model {
/**
* Resolve the choice for the next step to begin preparation. Recorded choices
* take precedence over the last request and then the Agent's live options.
* This read neither starts a step nor changes an already-prepared step.
* @returns a detached choice, or undefined when no source names both provider and model.
*/
current(): Readonly<ModelSelection> | undefined
/**
* Replace the next step's complete selection. Omitted effort follows adapter
* defaults without inheriting a previous effort or resolving defaults here.
* An equal recorded choice is a no-op; an options-only fallback is
* always recorded. Callers own model availability and capability validation.
* The append uses the Session's existing persistence and failure guarantees.
* @param selection - complete provider, model, and optional explicit effort.
*/
select(selection: Readonly<ModelSelection>): void
}
```
```ts type-equiv
/** Complete provider, model, and optional reasoning effort chosen for one Session's model steps. */
interface ModelSelection {
/** Registered provider route. */
provider: string
/** Provider-owned model id. */
model: string
/** Adapter-owned reasoning effort, or provider/default behavior when absent. */
reasoningEffort?: ReasoningEffortId
}
```
```ts type-equiv
/**
* Durable model-selection state for one Session. An effort the adapter
* defaulted on a recorded request is omitted, so later requests resolve the
* default again instead of pinning it as a choice.
*/
interface ModelSelectionProjectionState {
/** Selection used by the latest recorded model request. */
readonly lastUsed: ModelSelection | null
/** Later recorded selection not yet used by a matching model request. */
readonly pending: ModelSelection | null
}
```
```ts type-equiv
/** Client view of the durable model-selection fold. */
interface ModelSelectionProjection {
/** Selection used by the latest recorded model request. */
readonly lastUsed: ModelSelection | null
/** Selection the next model step uses; null while the Agent's initial options supply the route. */
readonly next: ModelSelection | null
}
```
inbox 即投递词汇——agent 以持久投影形式拥有的两条有序待处理消息列表:
@@ -988,46 +1056,17 @@ Reject a proposed step or replace the messages that enter it. Calling `next()` p
* @param payload.turn - the turn that will own the step.
* @param payload.step - the step proposed by the loop.
* @param payload.signal - the current turn's cancellation signal.
* @param payload.modelSelection - captured step selection, retained across retries; omitted effort uses adapter defaults.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode waterfall
*/
'agent/pre-step'(this: Scoped<Agent>, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise<PreStepDecision>): Promise<PreStepDecision>
'agent/pre-step'(this: Scoped<Agent>, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal; modelSelection: Readonly<ModelSelection> }, next: () => Promise<PreStepDecision>): Promise<PreStepDecision>
```
Types: [Scoped](scope.zh.md) · [UserMessage](session.zh.md)
Source: [`packages/core/agent/src/runtime-types.ts`](../../packages/core/agent/src/runtime-types.ts)
<a id="agentrequest--waterfall"></a>
#### `agent/request` — waterfall
Replace the frozen call configuration. `await next()` yields the config the machine would use (agent options on the first request, the logged header afterwards); return a replacement to switch. On step admission, this runs after assembly and `step/start`, before the system prompt and accepted user batch are committed. Cancellation here or during subsequent `prepareCall()` resolution commits neither. The prepared call capability governs prompt admission. Model-visible content must use logged channels; this waterfall cannot mutate messages.
```ts cordis-catalog
/**
* Replace the frozen call configuration. `await next()` yields the config
* the machine would use (agent options on the first request, the logged
* header afterwards); return a replacement to switch. On step admission,
* this runs after assembly and `step/start`, before the system prompt and
* accepted user batch are committed. Cancellation here or during subsequent
* `prepareCall()` resolution commits neither. The prepared call capability
* governs prompt admission. Model-visible content must use logged channels;
* this waterfall cannot mutate messages.
* @param payload.agent - the agent making the model call.
* @param payload.turn - the open turn number.
* @param payload.step - the step whose request this is.
* @param payload.signal - the current turn's explicit abort signal.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode waterfall
*/
'agent/request'(this: Scoped<Agent>, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise<LlmCallConfig>): Promise<LlmCallConfig>
```
Types: [LlmCallConfig](llm-streaming.zh.md) · [Scoped](scope.zh.md)
Source: [`packages/core/agent/src/runtime-types.ts`](../../packages/core/agent/src/runtime-types.ts)
<a id="agentrequest-error--waterfall"></a>
#### `agent/request-error` — waterfall
+2 -2
View File
@@ -39,8 +39,8 @@
en: 2b0831aaae8e1cdf
zh: f669d93d1b9d6025
/llm-streaming/the-model-request/the-request-envelope-llmcallconfig-and-the-logged-header:
en: f2e6932e211f179b
zh: 75112586a6f3f4aa
en: b0eb4620c6953646
zh: fabf51c7245a4855
/llm-streaming/official-deepseek-request-extensions:
en: 42bc50394b070e96
zh: 007caa743ec3c790
+1 -1
View File
@@ -760,7 +760,7 @@ interface LlmDiscoveredModel {
The loop builds each request from logged state. `EpochHeader` records call config, marks the fields supplied by adapter defaults, and records the authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. The rendered prompt is derived history — the `system/message` at surface node 0, plus any later system node an `in-history` route appended — so the header and the derived history together make the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md).
`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus the fields supplied by adapter defaults under the turn signal. On step admission, this waterfall and preparation run after assembly and `step/start` but before the system prompt and accepted user batch are committed; cancellation during either commits neither. The prepared capability governs prompt reconciliation, and the call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests.
The deprecated `agent/request` hook remains available to existing listeners. New code selects models and reasoning effort through [Session model selection](../../packages/core/agent/README.md#use-this-package). The hook receives a frozen call-config seed based on the step's captured model selection and may replace controls such as sampling. A replacement must preserve provider, model, and the optional explicit reasoning effort; record a new Session selection before the next step to change them. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus the fields supplied by adapter defaults under the turn signal. On step admission, this waterfall and preparation run after assembly and `step/start` but before the system prompt and accepted user batch are committed; cancellation during either commits neither. The prepared capability governs prompt reconciliation, and the call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests.
On the wire, a loop-built request is the derived history alone: the rendered prompt travels as the leading `system`-role message (surface node 0, a `system/message` event) and, when the prepared call declares `systemPromptUpdate: 'in-history'`, a non-empty changed prompt may follow the cached history as a later `system`-role message that the model reads as the effective prompt; the request's `system` field is unset — `GenerateOptions.system` serves direct one-shot callers such as title providers. An empty rendering leaves no system messages in derived history, even when earlier requests retained several prompt versions. The logged request ends with the newest `user/message` on a turn's first step and the previous step's tool results on later steps.
+1 -1
View File
@@ -766,7 +766,7 @@ interface LlmDiscoveredModel {
循环从已记录状态构建每个请求。`EpochHeader` 记录调用配置,标记由适配器默认值提供的字段,并通过完整的 `request/header` 快照记录权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。渲染后的提示词是派生历史——surface 第 0 号节点上的 `system/message`,加上 `in-history` 路由追加的任何后续系统节点——因此请求头与派生历史共同使请求可由会话日志重建。见 [session.md](session.zh.md#the-request-header-event-requestheader) 与[可重建性 Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md)。
`agent/request` 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall(瀑布式事件)开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置以及由适配器默认值提供的字段。步骤准入时,该 waterfall 与准备过程在组装和 `step/start` 之后、系统提示词与已接纳用户批次提交之前运行;在任一阶段取消都不会提交这两者。已准备调用的能力决定提示词协调,调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。
已弃用的 `agent/request` hook 仍可供现有监听器使用。新代码通过 [Session 模型选择](../../packages/core/agent/README.zh.md#use-this-package) 设置模型与推理强度。该 hook 收到基于步骤已捕获模型选择的冻结调用配置初值,可以替换采样等控制参数。替换值必须保留提供方、模型及可选的显式推理强度;要改变这些选择,需在下一个步骤之前记录新的 Session 选择。waterfall(瀑布式事件)开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置以及由适配器默认值提供的字段。步骤准入时,该 waterfall 与准备过程在组装和 `step/start` 之后、系统提示词与已接纳用户批次提交之前运行;在任一阶段取消都不会提交这两者。已准备调用的能力决定提示词协调,调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。
在协议中,循环构建的请求只有派生历史:渲染后的提示词作为开头的 `system` 角色消息(surface 第 0 号节点,即一个 `system/message` 事件)传输,并且当已准备调用声明 `systemPromptUpdate: 'in-history'` 时,变化后的非空提示词可以作为后续的 `system` 角色消息跟在已缓存历史之后,由模型读作有效提示词;请求的 `system` 字段不设置——`GenerateOptions.system` 服务于标题提供方等直接单次调用方。空渲染文本使派生历史不包含任何系统消息,即使先前请求保留了多个提示词版本。已记录的请求会以最新的 `user/message`(轮次首步)或上一步的工具结果(后续步骤)结尾。
+4 -3
View File
@@ -205,20 +205,21 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con
/**
* Snapshot all references for one accepted direct message and return one aggregated durable context.
* Automatic budgets use the last assembled route, or agent options before any assembly.
* Automatic budgets use the context capacity of `route`.
* Missing model capacity or adapter uses 64 KiB; other metadata lookup failures and cancellation reject preparation.
* Truncated previews include omission facts and a full-snapshot spill locator, or an explicit unavailable notice.
* Cancellation prevents context publication, including when storage completes after cancellation.
* @param agent - target agent; references to it are rejected.
* @param content - already host-normalized readable message content.
* @param references - structured source sessions in mention order.
* @param route - provider and model whose context capacity sizes the previews.
* @param signal - optional cancellation boundary for the active turn.
* @returns detached content and optional referenced-session context.
*/
async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise<PreparedReferencedMessage>
async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], route: Readonly<Pick<LlmCallConfig, 'provider' | 'model'>>, signal?: AbortSignal, ): Promise<PreparedReferencedMessage>
```
Types: [Agent](core.md) · [ContentBlock](llm-streaming.md)
Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) · [LlmCallConfig](llm-streaming.md)
Source: [`packages/context/session-reference/src/index.ts`](../../packages/context/session-reference/src/index.ts)
<!-- END GENERATED cordis-surface -->
+4 -3
View File
@@ -205,20 +205,21 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con
/**
* Snapshot all references for one accepted direct message and return one aggregated durable context.
* Automatic budgets use the last assembled route, or agent options before any assembly.
* Automatic budgets use the context capacity of `route`.
* Missing model capacity or adapter uses 64 KiB; other metadata lookup failures and cancellation reject preparation.
* Truncated previews include omission facts and a full-snapshot spill locator, or an explicit unavailable notice.
* Cancellation prevents context publication, including when storage completes after cancellation.
* @param agent - target agent; references to it are rejected.
* @param content - already host-normalized readable message content.
* @param references - structured source sessions in mention order.
* @param route - provider and model whose context capacity sizes the previews.
* @param signal - optional cancellation boundary for the active turn.
* @returns detached content and optional referenced-session context.
*/
async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise<PreparedReferencedMessage>
async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], route: Readonly<Pick<LlmCallConfig, 'provider' | 'model'>>, signal?: AbortSignal, ): Promise<PreparedReferencedMessage>
```
Types: [Agent](core.zh.md) · [ContentBlock](llm-streaming.zh.md)
Types: [Agent](core.zh.md) · [ContentBlock](llm-streaming.zh.md) · [LlmCallConfig](llm-streaming.zh.md)
Source: [`packages/context/session-reference/src/index.ts`](../../packages/context/session-reference/src/index.ts)
<!-- END GENERATED cordis-surface -->
+1
View File
@@ -137,6 +137,7 @@ Factory: conversation.content [ConversationContent]
├─ conversation.session [ConversationSession / DefaultConversationViews]
│ └─ conversation.view
│ ├─ id=chat [ChatView]
│ │ ├─ conversation.chat.text-interaction [list]
│ │ └─ conversation.chat.flow [ChatFlow]
│ │ └─ conversation.chat.node [ChatNodeSeat dispatch]
│ │ ├─ key=user,steering [UserMessageNodeView]
+1
View File
@@ -139,6 +139,7 @@ Factory: conversation.content [ConversationContent]
├─ conversation.session [ConversationSession / DefaultConversationViews]
│ └─ conversation.view
│ ├─ id=chat [ChatView]
│ │ ├─ conversation.chat.text-interaction [list]
│ │ └─ conversation.chat.flow [ChatFlow]
│ │ └─ conversation.chat.node [ChatNodeSeat dispatch]
│ │ ├─ key=user,steering [UserMessageNodeView]
Loaded 100 of 574 files, more files were not shown because too many files have changed in this diff. Show more