diff --git a/.agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml b/.agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml index 0cc877effb..40f4c554ef 100644 --- a/.agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-21-cross-session-references.md -2026-07-21-cross-session-references.md: d9aaaca442ba939b4221351e0df3601294674587 -2026-07-21-cross-session-references.zh.md: 17c3f91225f5f5ae2c2b1a639cc9d3c1794a490b +2026-07-21-cross-session-references.md: 774a948345e3d45adbba47ef6a7edd3e6f0740b2 +2026-07-21-cross-session-references.zh.md: fd864203d17954167646edf3b1a62946f8fa3f23 diff --git a/.agents/notes/implemented/feature/2026-07-21-cross-session-references.md b/.agents/notes/implemented/feature/2026-07-21-cross-session-references.md index d9aaaca442..774a948345 100644 --- a/.agents/notes/implemented/feature/2026-07-21-cross-session-references.md +++ b/.agents/notes/implemented/feature/2026-07-21-cross-session-references.md @@ -10,7 +10,7 @@ Web users need to bring relevant work from another conversation into one new mes ## Decision -`@deepseek-ai/dsh-session-reference` is one context consumer service at `ctx.sessionReferenceResolver`. Hosts normalize their protocol into `SessionReferenceInput[]` and call `prepare()` before delivery. The service returns detached readable content plus an optional identified, frozen `UserMessage` snapshot; core agent packages do not parse session URIs or read another log. +`@deepseek-ai/dsh-session-reference` is one context consumer service at `ctx.sessionReferenceResolver`. Its outer `agent/pre-step` listener parses canonical mentions in accepted direct user messages and calls `prepare()` without adding reference behavior to a host gateway. The service returns detached readable content plus an optional identified, frozen `UserMessage` snapshot; core agent packages do not parse session URIs or read another log. `dsh-session:` is the canonical host-independent identifier. JSON string encoding precedes base64url so quotes, slashes, backslashes, Unicode, newlines, and every other JavaScript string value round-trip without delimiter ambiguity. Web receives that URI inside the Host-produced `@[label](uri)` mention and keeps it behind an atomic session chip; text-only clients may use the same inline mention. Explicit Markdown mentions reject malformed URIs. Bare text becomes a reference only for a non-empty base64url-shaped payload, whose decode must still be canonical; empty or punctuation-only uses remain ordinary discussion text. @@ -18,7 +18,7 @@ The service uses `ctx.sessionQuery.readSurface(sessionId)`, which loads one live ## Snapshot and projection -Preparation deduplicates in first-appearance order, rejects the target id, enforces a configurable limit with a hard maximum of three references, and performs all reads in parallel. It returns no partially prepared context: any read, cancellation, validation, or budget error rejects the operation before `followup()` or `steer()`. Cancellation races in-flight discovery and exact reads, so a host settles promptly even when a persistence backend cannot interrupt its pending operation; any late backend settlement is observed but cannot enqueue the message. A source is read before enqueue, so later source messages, compaction, deletion, or persistence replacement cannot change the target session. +Preparation deduplicates in first-appearance order, rejects the target id, enforces a configurable limit with a hard maximum of three references, and performs all reads in parallel. It returns no partial context: any read, cancellation, validation, or budget error ends the turn before the accepted messages enter model-visible history. Cancellation races in-flight discovery and exact reads, so the listener settles promptly even when a persistence backend cannot interrupt its pending operation. A queued message captures each source when it reaches `agent/pre-step`; later source messages, compaction, deletion, or persistence replacement cannot change the context recorded in the target session. Projection retains direct-user messages and steering, completed assistant text, and checkpoint user messages carrying the canonical source exported by `dsh-compaction`. That marker is part of the compaction capability contract rather than a backend package name. Reference snapshots remain separate sourced `user/message` events, so projection excludes them as injected context and never recursively propagates an earlier snapshot. Projection also excludes shadowed pre-compaction nodes, tools and results, reasoning, other plugin user messages, log-only records, and incomplete assistant chunks. Repeated compaction therefore exposes only the latest folded checkpoint lineage still on the current surface plus its retained tail; there is no raw/current switch and no shadow recovery. @@ -26,15 +26,15 @@ One aggregated context is serialized as JSON beneath a fixed untrusted-backgroun ## Message ownership -Web owns the snapshot/direct-message transaction without extending the generic inbox record. Before delivery it installs a one-shot outer `agent/pre-step` listener keyed by the prepared direct message id. When a pre-step enters with a claimed batch containing that id, the listener inserts the frozen snapshot immediately before that direct message; discarding the direct message removes the listener without writing the snapshot. Moving the queued message to steering preserves its identity and listener, so both placements reach the same pre-step boundary in the same order. The [separate-context decision](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md) owns this generic delivery boundary. +The service's outer `agent/pre-step` listener calls downstream listeners first and processes only an `enter` decision. It parses each accepted direct user message, preserves that message's id while replacing canonical mentions with readable labels, and inserts the frozen snapshot immediately before that message. Queue edits and queue-to-steer relocation need no reference-specific state because the final claimed messages are the input to preparation. The [separate-context decision](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md) owns this context ordering. -Reference preparation is not a new steering protocol and does not create a turn by itself. Ordinary queued delivery and queue-to-steer relocation share the message-id-scoped pre-step listener, retaining snapshot order without a second context path. +Reference preparation is not a new delivery protocol and does not create a turn by itself. A preparation failure terminates the already accepted turn through the agent loop's existing plugin-failure path. ## Host adapters The unified Web `@` source combines session candidates with Host-backed file discovery. Session candidate lookup matches case-insensitive substrings of the session id, cwd, or latest folded title, displays that title, and falls back to the session id when a title observation is absent or fails. Lookup follows the request's cancellation signal, and session id, cwd, and mention labels escape external control characters while the canonical URI retains the original id. -Web exposes discovery through the `sessionReferenceResolver/candidates` Remote method and preparation through `session.prompt`, as detailed in [Web file and session references](2026-07-27-web-file-and-session-references.md). Session picks are atomic chips backed by the Host-produced canonical mention. The composer retains text and chips until preparation and enqueue succeed, restoring them unchanged after failure; replay associates the separate session-reference context with its neighboring direct message and renders a compact source summary instead of exposing the snapshot JSON. +Web exposes file and session discovery through generated Remote methods on their owning services, as detailed in [Web file and session references](2026-07-27-web-file-and-session-references.md). Session picks are atomic chips backed by the Host-produced canonical mention. Ordinary `session.prompt` delivery carries that mention without a reference-specific API Proxy route. Replay associates the separate session-reference context with its neighboring direct message and renders a compact source summary instead of exposing the snapshot JSON. The [automation-only ACP transport](../simplification/2026-07-23-acp-automation-only-protocol.md) deliberately does not mount session-query or session-reference services. @@ -47,15 +47,15 @@ Each of at most three references is independently capped at 65,536 UTF-8 bytes b - **Wait for SQLite FTS5** — rejected because snapshot correctness requires exact id reads and canonical surface folding, not content search. FTS improves discovery only. - **Put mention syntax in agent delivery methods** — rejected because it would make the core protocol parse one host's presentation syntax and prevent typed non-text hosts from sharing the semantic layer. - **Implement references separately in each host** — rejected because projection, security warning, retention, and persistence would drift across hosts. -- **Attach context to `SendOptions` and the direct prompt's inbox record** — rejected because generic delivery would own a domain transaction through admission, steering, cancellation, and observation. A domain-specific admission wrapper and the existing next-step inbox preserve the required pairing without enlarging every direct prompt. +- **Attach context to `SendOptions` and the direct prompt's inbox record** — rejected because generic delivery would own a domain transaction through admission, steering, cancellation, and observation. The domain listener can prepare the final claimed message without enlarging every direct prompt. - **Bake the prefix host-side before `followup()`** — rejected because `agent/pre-step` must inspect and rewrite only the direct prompt. Keeping the snapshot as a separate sourced message preserves that boundary and lets Web hide background bytes from the direct user bubble. - **Replay the raw source log or restore shadowed events** — rejected because compact defines the current model surface and may intentionally retire sensitive or expensive history. - **Resume or fork the source** — rejected because the feature supplies read-only background for one target message, not identity or lifecycle continuity. -- **Inject at request time by rereading the source** — rejected because the reference would become nondeterministic, cancellation races could alter its bytes, and target replay would depend on external mutable state. +- **Reread the source after the model step enters** — rejected because target replay would depend on external mutable state instead of the logged snapshot. ## Verification -Unit and integration coverage pins URI round-trips and text-boundary punctuation, explicit malformed references, id/cwd/title candidate matching and ranking, failed title-observation fallback, candidate cancellation, control-character escaping, projection exclusions, non-recursive snapshot projection, backend-independent compact checkpoints, tag-safe framing, deduplication, self-reference, count limits, all-or-nothing reads, prompt cancellation against a non-settling storage read, independent per-source byte retention, frozen message ownership, message-id-scoped pre-step insertion, discard cleanup, queue-to-steer preservation, title isolation, missing capability, Web wire preparation, and failure-preserving Web submission. A keyless Web snapshot pins the assembled reference selection path. +Unit and integration coverage pins URI round-trips and text-boundary punctuation, explicit malformed references, id/cwd/title candidate matching and ranking, failed title-observation fallback, candidate cancellation, control-character escaping, projection exclusions, non-recursive snapshot projection, backend-independent compact checkpoints, tag-safe framing, deduplication, self-reference, count limits, all-or-nothing reads, cancellation against a non-settling storage read, independent per-source byte retention, frozen message ownership, pre-step parsing and insertion, downstream rejection, node-owned replay association, title isolation, and the generated Remote discovery faces. A keyless Web snapshot pins the assembled reference selection path. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md b/.agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md index 17c3f91225..fd864203d1 100644 --- a/.agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md +++ b/.agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md @@ -10,7 +10,7 @@ Web 用户需要把另一场对话中的相关工作带入一条新消息,但 ## 决策 -`@deepseek-ai/dsh-session-reference` 是注册在 `ctx.sessionReferenceResolver` 上的单一上下文消费服务。宿主先把各自的协议规范化为 `SessionReferenceInput[]`,并在交付前调用 `prepare()`。该服务返回分离的可读内容和一份可选的、带标识且冻结的 `UserMessage` 快照;核心 agent 包既不解析会话 URI,也不读取其他日志。 +`@deepseek-ai/dsh-session-reference` 是注册在 `ctx.sessionReferenceResolver` 上的单一上下文消费服务。它的外层 `agent/pre-step` 监听器会解析已接受直接用户消息中的规范 mention,并调用 `prepare()`,宿主网关无需添加引用行为。该服务返回分离的可读内容和一份可选的、带标识且冻结的 `UserMessage` 快照;核心 agent 包既不解析会话 URI,也不读取其他日志。 `dsh-session:` 是与宿主无关的规范标识符。系统先执行 JSON 字符串编码,再执行 base64url 编码,因此引号、正斜杠、反斜杠、Unicode、换行符以及其他任意 JavaScript 字符串值都能无损往返,不会因分隔符产生歧义。Web 接收由 Host 生成、包含该 URI 的 `@[label](uri)` 提及标记,并把它封装为原子 session chip;纯文本客户端可以使用同一种行内提及标记。显式 Markdown 提及标记会拒绝格式错误的 URI。裸文本只有在负载非空且形状符合 base64url 时才会成为引用,而且解码过程仍须通过规范性校验;空负载或只含标点符号的用法仍按普通讨论文本处理。 @@ -18,7 +18,7 @@ Web 用户需要把另一场对话中的相关工作带入一条新消息,但 ## 快照与投影 -准备过程按首次出现的顺序去重、拒绝目标会话自身的 id,并且执行可配置的数量限制,但引用硬上限为三个,所有读取均并行执行。该过程不会返回部分完成的上下文:任何读取、取消、校验或预算错误都会在调用 `followup()` 或 `steer()` 前拒绝本次操作。取消会与进行中的候选发现和精确读取竞速,因此即使持久化后端无法中断待处理操作,宿主也能及时结束等待;后端迟到的完成结果仍会被观察,但不能让消息入队。源会话在入队前完成读取,因此源会话后续新增消息、执行压缩、被删除或替换持久化内容,都无法改变目标会话中的快照。 +准备过程按首次出现的顺序去重、拒绝目标会话自身的 id,并且执行可配置的数量限制,但引用硬上限为三个,所有读取均并行执行。该过程不会返回部分上下文:任何读取、取消、校验或预算错误都会在已接受消息进入面向模型的历史之前结束该轮次。取消会与进行中的候选发现和精确读取竞速,因此即使持久化后端无法中断待处理操作,监听器也能及时结束等待。queued 消息到达 `agent/pre-step` 时会捕获每个源;此后源会话新增消息、执行压缩、被删除或替换持久化内容,都无法改变目标会话中记录的上下文。 投影会保留直接用户消息与 steering(中途引导)、已完成的 assistant 文本,以及携带由 `dsh-compaction` 导出的规范来源标记的检查点用户消息。该标记属于压缩能力约定的一部分,而非某个后端包名称。引用快照始终是独立且带来源的 `user/message` 事件,因此投影会把它们作为注入上下文排除,绝不递归传播早先的快照。投影还会排除压缩前已被遮蔽的节点、工具及其结果、推理(reasoning)、其他插件用户消息、仅用于日志的记录,以及尚未完成的 assistant 分片。因此,重复压缩只会暴露当前表层仍保留的最新折叠检查点谱系及其尾部消息;系统不提供 raw/current 开关,也不恢复被遮蔽的内容。 @@ -26,15 +26,15 @@ Web 用户需要把另一场对话中的相关工作带入一条新消息,但 ## 消息所有权 -Web 负责快照/直接消息事务,不扩展通用收件箱记录。交付前,它会安装一个以已准备直接消息 id 为键的一次性外层 `agent/pre-step` 监听器。当 pre-step 以包含该 id 的已领取批次进入时,监听器会把冻结快照紧邻插入到该直接消息之前;丢弃直接消息会移除监听器且不写入快照。把 queued 消息移到 steering 时会保留其身份和监听器,因此两种放置方式都以相同顺序抵达同一 pre-step 边界。这一通用交付边界由[上下文分离决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md)规定。 +该服务的外层 `agent/pre-step` 监听器会先调用下游监听器,并且只处理 `enter` 决策。它会解析每条已接受的直接用户消息,在把规范 mention 替换为可读标签时保留消息 id,并把冻结快照插入到该消息紧前。最终领取的消息是准备过程的输入,因此队列编辑和从 queue 移动到 steer 不需要引用专用状态。[上下文分离决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md)规定了这一上下文顺序。 -引用准备过程不是新的 steering 协议,本身也不会创建轮次。普通 queued 交付与 queue-to-steer 移动共用以消息 id 为范围的 pre-step 监听器,无需第二条上下文路径即可保持快照顺序。 +引用准备过程不是新的投递协议,本身也不会创建轮次。准备失败会通过 agent loop 的现有插件失败路径终止已经接受的轮次。 ## 宿主适配器 统一的 Web `@` source 把会话候选与 Host 支持的文件发现组合在一起。会话候选查询会对 session id、cwd 或最新折叠后的标题执行不区分大小写的子串匹配,显示该标题,并在没有标题观察结果或标题观察失败时回退到 session id。查询遵循请求的取消信号;session id、cwd 和提及标签中的外部控制字符会被转义,但规范 URI 仍保留原始 id。 -Web 通过 `sessionReferenceResolver/candidates` Remote 方法暴露发现流程、通过 `session.prompt` 暴露准备流程,详见 [Web 文件与会话引用](2026-07-27-web-file-and-session-references.md)。session 选择项是由 Host 生成的规范提及标记支撑的原子 chip。composer 会保留文本和 chip,直到准备与入队均成功;失败后会原样恢复。回放会把独立的 session-reference 上下文与相邻直接消息关联起来,并渲染精简来源摘要,而不暴露快照 JSON。 +Web 通过所属服务上的生成 Remote 方法提供文件与会话发现,详见 [Web 文件与会话引用](2026-07-27-web-file-and-session-references.md)。session 选择项是由 Host 生成的规范 mention 支撑的原子 chip。普通 `session.prompt` 投递会携带该 mention,无需引用专用 API Proxy 路由。回放会把独立的 session-reference 上下文与相邻直接消息关联起来,并渲染精简来源摘要,而不暴露快照 JSON。 [仅面向自动化的 ACP(Agent Client Protocol)传输层](../simplification/2026-07-23-acp-automation-only-protocol.md)有意不挂载会话查询或会话引用服务。 @@ -47,15 +47,15 @@ Web 通过 `sessionReferenceResolver/candidates` Remote 方法暴露发现流程 - **等待 SQLite FTS5**:不予采纳,因为快照正确性依赖按准确 id 读取和规范表层折叠,而不是内容搜索。FTS 只改进候选发现。 - **把提及标记语法放入 agent 投递方法**:不予采纳,因为这会迫使核心协议解析某个宿主的展示语法,并阻止带类型的非文本宿主复用同一语义层。 - **在每个宿主中分别实现引用**:不予采纳,因为投影、安全警告、保留策略和持久化会在不同宿主之间逐渐偏离。 -- **把上下文附加到 `SendOptions` 和直接提示词的收件箱记录**:不予采纳,因为通用投递将不得不负责贯穿准入、steering、取消和观察的领域事务。领域专用的准入包装层和现有 next-step inbox 可以保持所需配对,而无需扩大每条直接提示词。 +- **把上下文附加到 `SendOptions` 和直接提示词的收件箱记录**:不予采纳,因为通用投递将不得不负责贯穿准入、steering、取消和观察的领域事务。领域监听器可以准备最终领取的消息,无需扩大每条直接提示词。 - **在调用 `followup()` 前由宿主合并前缀**:不予采纳,因为 `agent/pre-step` 必须只检查和改写直接提示词。将快照保留为独立的带来源消息,可以维持该边界,并让 Web 从直接用户气泡中隐藏背景字节。 - **回放原始源日志或恢复被遮蔽的事件**:不予采纳,因为压缩定义了当前模型表层,并且可能有意淘汰敏感或开销高昂的历史内容。 - **恢复或 fork 源会话**:不予采纳,因为本功能只为一条目标消息提供只读背景,不提供身份或生命周期连续性。 -- **在请求时重新读取源会话并注入**:不予采纳,因为这会让引用变得不确定,取消竞态可能改变其字节内容,目标回放也会依赖可变的外部状态。 +- **模型步骤进入后重新读取源会话**:不予采纳,因为目标回放会依赖可变的外部状态,而不是已记录的快照。 ## 验证 -单元与集成测试覆盖 URI 无损往返与文本边界标点、显式格式错误的引用、按 id/cwd/标题进行候选匹配与排序、标题观察失败时的回退、候选查询取消、控制字符转义、投影排除规则、快照的非递归投影、与后端无关的压缩检查点、标签安全封套、去重、自引用、数量限制、读取的全有或全无、存储读取不结束时取消提示词、逐源独立字节保留、冻结消息所有权、按消息 id 限定的 pre-step 插入、丢弃清理、queue-to-steer 关联保留、标题隔离、能力缺失、Web 线路准备,以及失败时保留输入的 Web 提交。一个无密钥 Web 快照会固定组装后的引用选择路径。 +单元与集成测试覆盖 URI 无损往返与文本边界标点、显式格式错误的引用、按 id/cwd/标题进行候选匹配与排序、标题观察失败时的回退、候选查询取消、控制字符转义、投影排除规则、快照的非递归投影、与后端无关的压缩检查点、标签安全封套、去重、自引用、数量限制、读取的全有或全无、存储读取不结束时的取消、逐源独立字节保留、冻结消息所有权、pre-step 解析和插入、下游拒绝、节点负责的回放关联、标题隔离,以及生成的 Remote 发现接口。一个无密钥 Web 快照会固定组装后的引用选择路径。 ## 后果 diff --git a/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.i18n.yaml b/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.i18n.yaml index fcd057ebd7..46f6c117eb 100644 --- a/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.md -2026-07-27-web-file-and-session-references.md: 5032525d6362b1afa0da387c2dd3d5f2d961139f -2026-07-27-web-file-and-session-references.zh.md: fce6ec0d35b7712d2e966932144b468f88b67862 +2026-07-27-web-file-and-session-references.md: ad8e5c53832a567bd38d1d1e560122cb8b630daa +2026-07-27-web-file-and-session-references.zh.md: acb016866efc42ef3ea9f661cb10ee1459cf1a6b diff --git a/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.md b/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.md index 5032525d63..ad8e5c5383 100644 --- a/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.md +++ b/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.md @@ -6,7 +6,7 @@ English | [中文](2026-07-27-web-file-and-session-references.zh.md) ## Problem -The Web composer had a reusable slash/reference trigger pipeline, but its `@` source was inert subagent-label text. Web needed Host-backed workspace-path discovery and structured cross-session snapshots without scanning the Host filesystem in the browser, binding session identity to a display label, or clearing a draft before Host-side snapshot preparation succeeded. +The Web composer had a reusable slash/reference trigger pipeline, but its `@` source was inert subagent-label text. Web needed Host-backed workspace-path discovery and structured cross-session snapshots without scanning the Host filesystem in the browser or binding session identity to a display label. ## Decision @@ -14,19 +14,19 @@ Web exposes one combined `@file` and `@session` menu through `@deepseek-ai/dsh-c The file capability follows the three-package seam: `@deepseek-ai/dsh-file-reference` owns `ctx.fileReferences`, the shared `@path` token grammar, candidate shape, and stable model guidance; `@deepseek-ai/dsh-file-reference-local` owns bounded per-agent Host-filesystem indexes, invalidation, and scoped prompt installation; `dsh-client-ui-reference` consumes the generated Remote namespaces and shared grammar. A file pick remains path-only prompt text and a directory pick retriggers completion below its trailing slash. -A session pick is an atomic composer reference. Its visible label is presentation, while its hidden value and clipboard form are the canonical `@[label](dsh-session:…)` mention produced by the Host. `session.prompt` parses those mentions and calls `ctx.sessionReferenceResolver.prepare()` before delivery. Delivery binds the prepared context to the exact message id through a one-shot outer `agent/pre-step` listener that inserts the frozen snapshot immediately before that message on an enter decision; ordinary discard or agent disposal releases the listener, and queue-to-steer relocation preserves the pairing. Invalid mentions, cancellation, missing capability, source-read failure, and budget failure deliver nothing. +A session pick is an atomic composer reference. Its visible label is presentation, while its hidden value and clipboard form are the canonical `@[label](dsh-session:…)` mention produced by the Host. Ordinary `session.prompt` delivery carries that mention unchanged. The session-reference service parses accepted direct user messages at `agent/pre-step`, captures every source, replaces the canonical mention with readable text while preserving the direct message id, and inserts the frozen snapshot immediately before that message. The API Proxy contains no reference-specific route, dependency, or error code. -The input machine keeps ordinary draft text and atomic references until the default sink reports Host acceptance. Serialization or Remote failure returns the same draft to editing. On success the logged prompt envelope remains the replay authority: the browser renders adjacent metadata-confirmed session references as separate reference chips and preserves that projection when following text is adjacent without whitespace, plus a compact session-source summary instead of the snapshot JSON baked into model content. +The input machine keeps ordinary draft text and atomic references until the default sink reports Host acceptance. Serialization or prompt transport failure returns the same draft to editing. After acceptance, reference preparation belongs to the agent turn; a malformed mention, failed source read, cancellation, or budget failure terminates that turn. The logged prompt remains the replay authority. The concrete user and steering chat-node definition associates labels from an immediately preceding session-reference context, so the renderer receives the association from its own node data and shows a compact source summary instead of snapshot JSON. ## Reference transaction ```text type @ → parallel file/session Remote calls → pick path text or canonical session chip - → serialize draft → Host parses and prepares all sessions → enqueue once - ↘ any pre-enqueue failure: retain the unchanged editable draft + → serialize draft → ordinary session.prompt enqueue + → agent/pre-step parses mentions → capture sources → context + readable prompt ``` -File lookup is advisory and cancellable; selection itself performs no read. Session preparation is authoritative and all-or-nothing because the source snapshot must be fixed before the target inbox accepts the message. +File lookup is advisory and cancellable; selection itself performs no read. Session preparation is all-or-nothing for one accepted model step. A queued message captures each source when the message is claimed, so queue edits and queue-to-steer relocation use the same path without gateway coordination. ## Alternatives considered @@ -38,12 +38,12 @@ File lookup is advisory and cancellable; selection itself performs no read. Sess **Represent sessions as plain `@label` text.** Rejected because labels are neither stable nor unique and cannot identify the source snapshot. Canonical Host-produced mentions preserve opaque session identity while keeping a readable display. -**Clear the composer before the RPC settles.** Rejected because a failed preparation would lose the only editable copy of the request and visually claim acceptance that never occurred. +**Clear the composer before prompt admission settles.** Rejected because a transport or admission failure would lose the only editable copy of the request and visually claim acceptance that never occurred. ## Verification -Package tests pin shared file grammar and ranking, cache invalidation and lifecycle cleanup, parallel Web lookup, quoted paths, independent candidate failure, cancellation, grouped headings that do not alter option indexes, file/directory continuation, canonical session chips, adjacent-reference and adjacent-text reference projection, codec round-trip, the owning services' Remote faces, all-or-nothing prompt preparation, and draft retention across serialization and Remote failures. The keyless assembled Web snapshot renders the available reference sections, selects a file, then selects a session reference through the real client composition. +Package tests pin shared file grammar and ranking, cache invalidation and lifecycle cleanup, parallel Web lookup, quoted paths, independent candidate failure, cancellation, grouped headings that do not alter option indexes, file/directory continuation, canonical session chips, adjacent-reference and adjacent-text reference projection, codec round-trip, generated Remote type inference, pre-step preparation, downstream rejection, and chat-node-owned label association. The keyless assembled Web snapshot renders the available reference sections, selects a file, then selects a session reference through the real client composition. ## Consequences -Web now uses the shared `@file` discovery seam and structured session-reference identity, while Host services remain the authority for filesystem and session access. The new file-reference seam adds two packages whose discovery methods are unary Remote contracts on the owning services, keeps browser bundles free of Node APIs, and permits another provider to align completion with a remote filesystem. Candidate lookup failures remain quiet menu degradation; submission failures remain explicit and recoverable. File references cost only path text plus stable conditional guidance, whereas session references retain the bounded snapshot cost and trust framing owned by `dsh-session-reference`. +Web now uses the shared `@file` discovery seam and structured session-reference identity, while Host services remain the authority for filesystem and session access. File and session discovery are unary Remote contracts on the owning services, so generated client types replace handwritten RPC interfaces and browser bundles remain free of Node APIs. Candidate lookup failures remain quiet menu degradation. Reference preparation failures occur after prompt acceptance and end the agent turn. File references cost only path text plus stable conditional guidance, whereas session references retain the bounded snapshot cost and trust framing owned by `dsh-session-reference`. diff --git a/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.zh.md b/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.zh.md index fce6ec0d35..acb016866e 100644 --- a/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.zh.md +++ b/.agents/notes/implemented/feature/2026-07-27-web-file-and-session-references.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -Web 输入框已有可复用的斜杠命令/引用触发流水线,但它的 `@` source 只是不会产生实际作用的 subagent 标签文本。Web 需要由宿主提供工作区路径发现和结构化跨会话快照,同时避免在浏览器中扫描宿主文件系统、把会话身份绑定到显示标签,或者在宿主侧快照准备成功前清除草稿。 +Web 输入框已有可复用的斜杠命令/引用触发流水线,但它的 `@` source 只是不会产生实际作用的 subagent 标签文本。Web 需要由宿主提供工作区路径发现和结构化跨会话快照,同时避免在浏览器中扫描宿主文件系统或把会话身份绑定到显示标签。 ## 决策 @@ -14,19 +14,19 @@ Web 通过 `@deepseek-ai/dsh-client-ui-reference` 暴露一个合并的 `@file` 文件功能遵循由三个包构成的 seam:`@deepseek-ai/dsh-file-reference` 拥有 `ctx.fileReferences`、共享 `@path` token 语法、候选形状和稳定的模型指引;`@deepseek-ai/dsh-file-reference-local` 拥有每个 agent(智能体)有界的宿主文件系统索引、失效处理和作用域内的提示词安装;`dsh-client-ui-reference` 消费生成的 Remote 命名空间与共享语法。选择文件后仍只会把路径文本写入提示词,选择目录则会在其尾部斜杠后重新触发补全。 -选择会话会创建一个原子的输入框引用。可见标签只用于呈现,隐藏值和剪贴板形式则是宿主生成的规范 `@[label](dsh-session:…)` 提及标记。`session.prompt` 会解析这些提及标记,并在递送前调用 `ctx.sessionReferenceResolver.prepare()`。递送通过一次性的外层 `agent/pre-step` 监听器把准备后的上下文绑定到精确消息 id:进入决策时把冻结快照插入到该消息紧前,普通丢弃或 agent(智能体)销毁会释放监听器,队列转 steering 的迁移保持这一配对。无效提及标记、取消、功能缺失、读取源会话失败和预算失败都不会递送消息。 +选择会话会创建一个原子的输入框引用。可见标签只用于呈现,隐藏值和剪贴板形式则是宿主生成的规范 `@[label](dsh-session:…)` mention。普通 `session.prompt` 投递会原样携带该 mention。session-reference 服务会在 `agent/pre-step` 解析已接受的直接用户消息,捕获每个源,在保留直接消息 id 的同时把规范 mention 替换为可读文本,并把冻结快照插入到该消息紧前。API Proxy 不包含引用专用路由、依赖或错误码。 -输入状态机在默认 sink 报告宿主已接受前,会保留普通草稿文本和原子引用。序列化或 Remote 调用失败后,同一草稿会回到可编辑状态。成功后,日志中的提示词封套仍是回放的权威来源:浏览器会把相邻且经元数据确认的会话引用分别渲染为独立的引用 chip,即使后续文本无空白紧邻,也会保持该投影;同时显示精简的会话来源摘要,而不会显示嵌入模型内容中的快照 JSON。 +输入状态机在默认 sink 报告宿主已接受前,会保留普通草稿文本和原子引用。序列化或提示词传输失败后,同一草稿会回到可编辑状态。接受后,引用准备属于 agent 轮次;格式错误的 mention、源读取失败、取消或预算失败会终止该轮次。已记录的提示词仍是回放权威。具体的 user 和 steering chat-node 定义会关联紧邻前一条 session-reference 上下文中的标签,因此渲染器会从自身节点数据接收关联信息,并显示精简的来源摘要,而不是快照 JSON。 ## 引用事务 ```text type @ → parallel file/session Remote calls → pick path text or canonical session chip - → serialize draft → Host parses and prepares all sessions → enqueue once - ↘ any pre-enqueue failure: retain the unchanged editable draft + → serialize draft → ordinary session.prompt enqueue + → agent/pre-step parses mentions → capture sources → context + readable prompt ``` -文件查询仅供参考且可取消;选择操作本身不会读取文件。会话准备具有权威性,并且必须全有或全无,因为目标收件箱接受消息前必须固定源快照。 +文件查询仅供参考且可取消;选择操作本身不会读取文件。会话准备针对一个已接受的模型步骤保持全有或全无。queued 消息被领取时会捕获每个源,因此队列编辑和从 queue 移动到 steer 使用同一路径,无需网关协调。 ## 备选方案 @@ -38,12 +38,12 @@ type @ → parallel file/session Remote calls → pick path text or canonical se **用普通 `@label` 文本表示会话。** 不予采纳,因为标签既不稳定也不唯一,无法标识源快照。宿主生成的规范提及标记既能保留不透明会话身份,也能保持显示内容易读。 -**RPC 完成前清空输入框。** 不予采纳,因为准备失败会丢失请求唯一可编辑的副本,并在视觉上错误表示一个从未成功的接受操作。 +**提示词准入结算前清空输入框。** 不予采纳,因为传输或准入失败会丢失请求唯一可编辑的副本,并在视觉上错误表示一个从未成功的接受操作。 ## 验证 -包(package)测试固定共享文件语法和排序、缓存失效及生命周期清理、Web 并行查询、带引号的路径、候选项独立失败、取消、不改变候选项索引的分组标题、文件/目录继续补全、规范会话 chip、相邻引用及相邻文本条件下的引用投影、codec 无损往返、所属服务的 Remote 调用面、全有或全无的提示词准备,以及在序列化和 Remote 调用失败时保留草稿。无密钥的装配 Web 快照会渲染可用的引用分组,并通过真实客户端组合依次选择文件和会话引用。 +包(package)测试固定共享文件语法和排序、缓存失效及生命周期清理、Web 并行查询、带引号的路径、候选项独立失败、取消、不改变候选项索引的分组标题、文件/目录继续补全、规范会话 chip、相邻引用及相邻文本条件下的引用投影、codec 无损往返、生成的 Remote 类型推断、pre-step 准备、下游拒绝,以及 chat node 自有的标签关联。无密钥的装配 Web 快照会渲染可用的引用分组,并通过真实客户端组合依次选择文件和会话引用。 ## 后果 -Web 现在使用共享的 `@file` 发现 seam 和结构化会话引用身份,宿主服务仍然是文件系统与会话访问的权威来源。新的文件引用 seam 增加了两个包,其发现方法是所属服务上的一元 Remote 契约;浏览器 bundle 中不包含 Node API,并允许其他提供方让补全与远程文件系统对齐。候选查询失败仍会让菜单静默降级;提交失败仍会显式报告且可恢复。文件引用只产生路径文本和稳定的条件式指引成本,而会话引用仍保留 `dsh-session-reference` 所拥有的有界快照开销与信任限定文本。 +Web 现在使用共享的 `@file` 发现 seam 和结构化会话引用身份,宿主服务仍然是文件系统与会话访问的权威来源。文件和会话发现都是所属服务上的一元 Remote 契约,因此生成的客户端类型会替代手写 RPC 接口,浏览器 bundle 中也不包含 Node API。候选查询失败仍会让菜单静默降级。引用准备失败发生在提示词已接受之后,并会结束 agent 轮次。文件引用只产生路径文本和稳定的条件式指引成本,而会话引用仍保留 `dsh-session-reference` 所拥有的有界快照开销与信任限定文本。 diff --git a/docs/event-producer-consumer.i18n.yaml b/docs/event-producer-consumer.i18n.yaml index 4a61e2d00c..e271745350 100644 --- a/docs/event-producer-consumer.i18n.yaml +++ b/docs/event-producer-consumer.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/event-producer-consumer.md -event-producer-consumer.md: f088251b46c66588bd932141df448b7312ac5c52 -event-producer-consumer.zh.md: ef2b5a45465f1a6ed985cc04496573b300fec606 +event-producer-consumer.md: 34b7d8d7cbee89753516485e35a5b4611b1b9077 +event-producer-consumer.zh.md: 3216c76a7a33c473e4ed67cfbc2c4ba7fb442c9f diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index f088251b46..34b7d8d7cb 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -10,12 +10,12 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:183`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:13`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `apiproxy` | | `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:159`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | | `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `apiproxy`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`session-telemetry`](../packages/session/session-telemetry) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:197`](../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) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:205`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `apiproxy`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | +| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:205`](../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/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:186`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), `apiproxy`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/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-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill) | | `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:244`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:260`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) | | `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:217`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index ef2b5a4546..3216c76a7a 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -12,12 +12,12 @@ | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:183`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:13`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `apiproxy` | | `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:159`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | | `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `apiproxy`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`session-telemetry`](../packages/session/session-telemetry) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:197`](../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) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:205`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `apiproxy`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | +| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:205`](../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/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:186`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), `apiproxy`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/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-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill) | | `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:244`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:260`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) | | `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:217`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index 826a05243c..00dcfcd1a6 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/module-graph.md -module-graph.md: 2eb7c748ee0bcf6eb63d200841e35f2606958a24 -module-graph.zh.md: 35a5615914711da1f52e2ecfb938c2e134f6afcb +module-graph.md: ce9cd0f0e7bdf5be6ada822cb6ba4e975c6a2f35 +module-graph.zh.md: 631c2c535e987d2699c0cbd453ae7d8626599c8a diff --git a/docs/module-graph.md b/docs/module-graph.md index 2eb7c748ee..ce9cd0f0e7 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -140,6 +140,7 @@ flowchart TD pkg_client_ui_permission_presets["client-ui-permission-presets"] pkg_client_ui_plan["client-ui-plan"] pkg_client_ui_primitives["client-ui-primitives"] + pkg_client_ui_reference["client-ui-reference"] pkg_client_ui_settings["client-ui-settings"] pkg_client_ui_settings_general["client-ui-settings-general"] pkg_client_ui_settings_models["client-ui-settings-models"] @@ -171,6 +172,8 @@ flowchart TD end subgraph group_context["packages/context"] pkg_agent_instructions["agent-instructions"] + pkg_file_reference["file-reference"] + pkg_file_reference_local["file-reference-local"] pkg_session_reference["session-reference"] pkg_time_context["time-context"] pkg_tmux_context["tmux-context"] @@ -513,6 +516,9 @@ flowchart TD pkg_web_search_deepseek --> pkg_web pkg_spill_local --> pkg_invariants pkg_spill_local --> pkg_spill + pkg_file_reference --> pkg_agent + pkg_file_reference --> pkg_invariants + pkg_file_reference --> pkg_typert_protocol pkg_time_context --> pkg_agent pkg_time_context --> pkg_invariants pkg_time_context --> pkg_session @@ -834,6 +840,11 @@ flowchart TD pkg_agent_instructions --> pkg_llm pkg_agent_instructions --> pkg_session pkg_agent_instructions --> pkg_tools + pkg_file_reference_local --> pkg_agent + pkg_file_reference_local --> pkg_file_reference + pkg_file_reference_local --> pkg_invariants + pkg_file_reference_local --> pkg_system_prompt + pkg_file_reference_local --> pkg_tools pkg_session_reference --> pkg_agent pkg_session_reference --> pkg_compaction pkg_session_reference --> pkg_invariants @@ -841,6 +852,7 @@ flowchart TD pkg_session_reference --> pkg_output_retention pkg_session_reference --> pkg_session pkg_session_reference --> pkg_session_query + pkg_session_reference --> pkg_typert_protocol pkg_cordis_host_runner --> pkg_agent pkg_cordis_host_runner --> pkg_brand pkg_cordis_host_runner --> pkg_invariants @@ -1004,6 +1016,7 @@ flowchart TD pkg_api_remotes --> pkg_commands pkg_api_remotes --> pkg_cordis_host_runner pkg_api_remotes --> pkg_credentials + pkg_api_remotes --> pkg_file_reference pkg_api_remotes --> pkg_goal pkg_api_remotes --> pkg_host_plugin_inventory pkg_api_remotes --> pkg_invariants @@ -1011,6 +1024,7 @@ flowchart TD pkg_api_remotes --> pkg_message_feedback pkg_api_remotes --> pkg_session pkg_api_remotes --> pkg_session_persistence + pkg_api_remotes --> pkg_session_reference pkg_api_remotes --> pkg_settings pkg_api_remotes --> pkg_typert_registry pkg_web_app --> pkg_invariants @@ -1261,6 +1275,15 @@ flowchart TD pkg_client_ui_layout --> pkg_client_ui_slots pkg_client_ui_layout --> pkg_client_ui_theme pkg_client_ui_layout --> pkg_invariants + pkg_client_ui_reference --> pkg_api_remotes + pkg_client_ui_reference --> pkg_client_locale + pkg_client_ui_reference --> pkg_client_runtime + pkg_client_ui_reference --> pkg_client_ui_input_trigger + pkg_client_ui_reference --> pkg_client_ui_slots + pkg_client_ui_reference --> pkg_file_reference + pkg_client_ui_reference --> pkg_invariants + pkg_client_ui_reference --> pkg_session_reference + pkg_client_ui_reference --> pkg_typert_protocol pkg_client_ui_settings_general --> pkg_api_remotes pkg_client_ui_settings_general --> pkg_client_connection pkg_client_ui_settings_general --> pkg_client_locale @@ -1340,7 +1363,6 @@ flowchart TD pkg_client_ui_subagent --> pkg_client_locale pkg_client_ui_subagent --> pkg_client_runtime pkg_client_ui_subagent --> pkg_client_ui_conversation - pkg_client_ui_subagent --> pkg_client_ui_input_trigger pkg_client_ui_subagent --> pkg_client_ui_primitives pkg_client_ui_subagent --> pkg_client_ui_slots pkg_client_ui_subagent --> pkg_invariants @@ -1500,6 +1522,7 @@ flowchart TD | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`spill`](../packages/spill/spill) | +| [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-protocol`](../packages/typert/protocol) | | [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | | [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | @@ -1560,7 +1583,8 @@ flowchart TD | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | -| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | +| [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`typert-protocol`](../packages/typert/protocol) | | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | `extensions` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | | [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) | | [`tool-call-timeout-policy`](../packages/guard/timeout-policy) | `guard` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | @@ -1588,7 +1612,7 @@ flowchart TD | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | -| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) | +| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-reference`](../packages/context/session-reference), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) | | [`web-app`](../packages/bundle/web-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) | | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | @@ -1624,6 +1648,7 @@ flowchart TD | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`client-ui-reference`](../packages/client/ui-reference) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-slots`](../packages/client/ui-slots), [`file-reference`](../packages/context/file-reference), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-reference`](../packages/context/session-reference), [`typert-protocol`](../packages/typert/protocol) | | [`client-ui-settings-general`](../packages/client/ui-settings-general) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`cordis-client-runner`](../packages/extensions/cordis-client-runner) | `extensions` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) | @@ -1633,7 +1658,7 @@ flowchart TD | [`client-ui-jobs`](../packages/client/ui-jobs) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-message-feedback`](../packages/client/ui-message-feedback) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback), [`typert-protocol`](../packages/typert/protocol) | | [`client-ui-plan`](../packages/client/ui-plan) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`plan-mode`](../packages/plan/plan-mode) | -| [`client-ui-subagent`](../packages/client/ui-subagent) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) | +| [`client-ui-subagent`](../packages/client/ui-subagent) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) | | [`client-ui-tool`](../packages/client/ui-tool) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-workflow-run`](../packages/client/ui-workflow-run) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 35a5615914..631c2c535e 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -142,6 +142,7 @@ flowchart TD pkg_client_ui_permission_presets["client-ui-permission-presets"] pkg_client_ui_plan["client-ui-plan"] pkg_client_ui_primitives["client-ui-primitives"] + pkg_client_ui_reference["client-ui-reference"] pkg_client_ui_settings["client-ui-settings"] pkg_client_ui_settings_general["client-ui-settings-general"] pkg_client_ui_settings_models["client-ui-settings-models"] @@ -173,6 +174,8 @@ flowchart TD end subgraph group_context["packages/context"] pkg_agent_instructions["agent-instructions"] + pkg_file_reference["file-reference"] + pkg_file_reference_local["file-reference-local"] pkg_session_reference["session-reference"] pkg_time_context["time-context"] pkg_tmux_context["tmux-context"] @@ -515,6 +518,9 @@ flowchart TD pkg_web_search_deepseek --> pkg_web pkg_spill_local --> pkg_invariants pkg_spill_local --> pkg_spill + pkg_file_reference --> pkg_agent + pkg_file_reference --> pkg_invariants + pkg_file_reference --> pkg_typert_protocol pkg_time_context --> pkg_agent pkg_time_context --> pkg_invariants pkg_time_context --> pkg_session @@ -836,6 +842,11 @@ flowchart TD pkg_agent_instructions --> pkg_llm pkg_agent_instructions --> pkg_session pkg_agent_instructions --> pkg_tools + pkg_file_reference_local --> pkg_agent + pkg_file_reference_local --> pkg_file_reference + pkg_file_reference_local --> pkg_invariants + pkg_file_reference_local --> pkg_system_prompt + pkg_file_reference_local --> pkg_tools pkg_session_reference --> pkg_agent pkg_session_reference --> pkg_compaction pkg_session_reference --> pkg_invariants @@ -843,6 +854,7 @@ flowchart TD pkg_session_reference --> pkg_output_retention pkg_session_reference --> pkg_session pkg_session_reference --> pkg_session_query + pkg_session_reference --> pkg_typert_protocol pkg_cordis_host_runner --> pkg_agent pkg_cordis_host_runner --> pkg_brand pkg_cordis_host_runner --> pkg_invariants @@ -1006,6 +1018,7 @@ flowchart TD pkg_api_remotes --> pkg_commands pkg_api_remotes --> pkg_cordis_host_runner pkg_api_remotes --> pkg_credentials + pkg_api_remotes --> pkg_file_reference pkg_api_remotes --> pkg_goal pkg_api_remotes --> pkg_host_plugin_inventory pkg_api_remotes --> pkg_invariants @@ -1013,6 +1026,7 @@ flowchart TD pkg_api_remotes --> pkg_message_feedback pkg_api_remotes --> pkg_session pkg_api_remotes --> pkg_session_persistence + pkg_api_remotes --> pkg_session_reference pkg_api_remotes --> pkg_settings pkg_api_remotes --> pkg_typert_registry pkg_web_app --> pkg_invariants @@ -1263,6 +1277,15 @@ flowchart TD pkg_client_ui_layout --> pkg_client_ui_slots pkg_client_ui_layout --> pkg_client_ui_theme pkg_client_ui_layout --> pkg_invariants + pkg_client_ui_reference --> pkg_api_remotes + pkg_client_ui_reference --> pkg_client_locale + pkg_client_ui_reference --> pkg_client_runtime + pkg_client_ui_reference --> pkg_client_ui_input_trigger + pkg_client_ui_reference --> pkg_client_ui_slots + pkg_client_ui_reference --> pkg_file_reference + pkg_client_ui_reference --> pkg_invariants + pkg_client_ui_reference --> pkg_session_reference + pkg_client_ui_reference --> pkg_typert_protocol pkg_client_ui_settings_general --> pkg_api_remotes pkg_client_ui_settings_general --> pkg_client_connection pkg_client_ui_settings_general --> pkg_client_locale @@ -1342,7 +1365,6 @@ flowchart TD pkg_client_ui_subagent --> pkg_client_locale pkg_client_ui_subagent --> pkg_client_runtime pkg_client_ui_subagent --> pkg_client_ui_conversation - pkg_client_ui_subagent --> pkg_client_ui_input_trigger pkg_client_ui_subagent --> pkg_client_ui_primitives pkg_client_ui_subagent --> pkg_client_ui_slots pkg_client_ui_subagent --> pkg_invariants @@ -1502,6 +1524,7 @@ flowchart TD | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`spill`](../packages/spill/spill) | +| [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-protocol`](../packages/typert/protocol) | | [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | | [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | @@ -1562,7 +1585,8 @@ flowchart TD | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | -| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | +| [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`typert-protocol`](../packages/typert/protocol) | | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | `extensions` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | | [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) | | [`tool-call-timeout-policy`](../packages/guard/timeout-policy) | `guard` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | @@ -1590,7 +1614,7 @@ flowchart TD | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | -| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) | +| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-reference`](../packages/context/session-reference), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) | | [`web-app`](../packages/bundle/web-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) | | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | @@ -1626,6 +1650,7 @@ flowchart TD | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`client-ui-reference`](../packages/client/ui-reference) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-slots`](../packages/client/ui-slots), [`file-reference`](../packages/context/file-reference), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-reference`](../packages/context/session-reference), [`typert-protocol`](../packages/typert/protocol) | | [`client-ui-settings-general`](../packages/client/ui-settings-general) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`cordis-client-runner`](../packages/extensions/cordis-client-runner) | `extensions` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) | @@ -1635,7 +1660,7 @@ flowchart TD | [`client-ui-jobs`](../packages/client/ui-jobs) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-message-feedback`](../packages/client/ui-message-feedback) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback), [`typert-protocol`](../packages/typert/protocol) | | [`client-ui-plan`](../packages/client/ui-plan) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`plan-mode`](../packages/plan/plan-mode) | -| [`client-ui-subagent`](../packages/client/ui-subagent) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) | +| [`client-ui-subagent`](../packages/client/ui-subagent) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) | | [`client-ui-tool`](../packages/client/ui-tool) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) | | [`client-ui-workflow-run`](../packages/client/ui-workflow-run) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) | diff --git a/docs/subsystems/session-reference.i18n.yaml b/docs/subsystems/session-reference.i18n.yaml index 535685927d..d354125b2a 100644 --- a/docs/subsystems/session-reference.i18n.yaml +++ b/docs/subsystems/session-reference.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session-reference.md -session-reference.md: 2380584e8bdc70ac1fee28849c8d09f69d55fd9d -session-reference.zh.md: 974723e95d6f7f68d45ddb8efd65db3fa9470fc4 +session-reference.md: e3ef2c00b7ddbeed9fa3f9d074df436d8c0cc7b5 +session-reference.zh.md: 74063c1525a1f7f67663e9b11148e18902b22748 diff --git a/docs/subsystems/session-reference.md b/docs/subsystems/session-reference.md index 2380584e8b..e3ef2c00b7 100644 --- a/docs/subsystems/session-reference.md +++ b/docs/subsystems/session-reference.md @@ -158,11 +158,11 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con @Remote('candidates') async remoteExportCandidates( agent: Agent, query: string, signal: AbortSignal, ): Promise /** - * Snapshot all references before enqueue and return one aggregated durable context. + * Snapshot all references for one accepted direct message and return one aggregated durable context. * @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 signal - optional cancellation boundary for host request teardown. + * @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 diff --git a/docs/subsystems/session-reference.zh.md b/docs/subsystems/session-reference.zh.md index 974723e95d..74063c1525 100644 --- a/docs/subsystems/session-reference.zh.md +++ b/docs/subsystems/session-reference.zh.md @@ -158,11 +158,11 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con @Remote('candidates') async remoteExportCandidates( agent: Agent, query: string, signal: AbortSignal, ): Promise /** - * Snapshot all references before enqueue and return one aggregated durable context. + * Snapshot all references for one accepted direct message and return one aggregated durable context. * @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 signal - optional cancellation boundary for host request teardown. + * @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 diff --git a/packages/client/connection/src/client/fixture.ts b/packages/client/connection/src/client/fixture.ts index f10329f0f2..5c0ca574bc 100644 --- a/packages/client/connection/src/client/fixture.ts +++ b/packages/client/connection/src/client/fixture.ts @@ -3133,7 +3133,7 @@ export class FixtureApiClient extends AbstractApiClient { case 'session.selectModel': return this.api.sessions.selectModel(request) case 'session.rename': return this.api.sessions.rename(request) case 'session.fork': return this.api.sessions.fork(request) - case 'session.prompt': return this.api.sessions.prompt(request, signal) + case 'session.prompt': return this.api.sessions.prompt(request) case 'session.attachment': return this.api.sessions.attachment(request) case 'session.updateQueue': return this.api.sessions.updateQueue(request) case 'session.cancel': return this.api.sessions.cancel(request) diff --git a/packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx b/packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx index 0a52d552a4..f3343a183f 100644 --- a/packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx +++ b/packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx @@ -1,5 +1,4 @@ import { memo, useMemo } from 'react' -import { sessionRecallLabels } from '@deepseek-ai/dsh-client-runtime/client' import { JsonBlock } from '@deepseek-ai/dsh-client-ui-primitives' import type { ChatNodeOwnerProps, ChatViewSlotProps } from '../contract/slots.ts' import type { ChatNode } from '../contract/chat-nodes.ts' @@ -22,19 +21,6 @@ export const ChatNodeSeat = memo(function ChatNodeSeat({ loadImage, fileMentions, useSession, renderSlot, t, }: ChatNodeSeatProps) { const node = useSession(snapshot => snapshot.chat.nodes.get(nodeKey)) - const referenceLabelsJson = useSession((snapshot) => { - const index = snapshot.chat.order.indexOf(nodeKey) - if (index <= 0) return '' - const previousKey = snapshot.chat.order[index - 1] - const previous = previousKey === undefined ? undefined : snapshot.chat.nodes.get(previousKey) - if (previous?.kind !== 'context') return '' - const labels = sessionRecallLabels((previous as ChatNode<'context'>).data.source) - return labels.length === 0 ? '' : JSON.stringify(labels) - }) - const referenceLabels = useMemo( - () => referenceLabelsJson === '' ? [] : JSON.parse(referenceLabelsJson) as string[], - [referenceLabelsJson], - ) const routedNode = node as ChatNode | undefined const owner = useMemo(() => node === undefined ? null @@ -46,8 +32,7 @@ export const ChatNodeSeat = memo(function ChatNodeSeat({ forkAt, loadImage, fileMentions, - ...(referenceLabels.length === 0 ? {} : { referenceLabels }), - }, [node, selectedCallId, cwd, openFile, inspectCall, forkAt, loadImage, fileMentions, referenceLabels]) + }, [node, selectedCallId, cwd, openFile, inspectCall, forkAt, loadImage, fileMentions]) if (routedNode === undefined || owner === null) return null // Runtime dispatch owns the correlation: every Node's discriminant is the // keyed-slot entry passed alongside that same Node. TypeScript does not diff --git a/packages/client/ui-conversation/src/client/chat/MessageItem.tsx b/packages/client/ui-conversation/src/client/chat/MessageItem.tsx index a9c7cd6d31..87c48c3b87 100644 --- a/packages/client/ui-conversation/src/client/chat/MessageItem.tsx +++ b/packages/client/ui-conversation/src/client/chat/MessageItem.tsx @@ -258,14 +258,14 @@ export function PendingSteeringBubble({ content, loadImage, t }: { /** User and admitted-steering keyed Chat renderer. */ export const UserMessageNodeView = memo(function UserMessageNodeView({ - node, loadImage, referenceLabels, t, + node, loadImage, t, }: ChatNodeViewProps<'user' | 'steering'>) { const data = node.data return ( ( Promise fileMentions: (owner: TurnTailOwnerProps) => MarkdownFileMentions | undefined - /** Session-reference labels owned by the immediately preceding recall node. */ - referenceLabels?: readonly string[] | undefined } /** Full props of one registered keyed Chat business renderer. */ diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/message.ts b/packages/client/ui-conversation/src/client/conversation-nodes/message.ts index 085127f9c5..3f9f2b5ef1 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/message.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/message.ts @@ -3,19 +3,29 @@ import type { ContextMessageNode, ConversationNodeDefinition, SteeringMessageNode, UserMessageNode, } from '@deepseek-ai/dsh-client-runtime/client' import { - contextForm, contextProvenance, isAppendSurfaceEvent, isReplacementSurfaceEvent, + contextForm, contextProvenance, isAppendSurfaceEvent, isReplacementSurfaceEvent, sessionRecallLabels, } from '@deepseek-ai/dsh-client-runtime/client' import type { InboxState } from './inbox.ts' import { chatNode } from './common.ts' -type MessageNode = UserMessageNode | SteeringMessageNode | ContextMessageNode +interface ReferencedUserMessageNode extends UserMessageNode { + /** Labels cited by the immediately preceding session-reference context. */ + readonly referenceLabels?: readonly string[] +} + +interface ReferencedSteeringMessageNode extends SteeringMessageNode { + /** Labels cited by the immediately preceding session-reference context. */ + readonly referenceLabels?: readonly string[] +} + +type MessageNode = ReferencedUserMessageNode | ReferencedSteeringMessageNode | ContextMessageNode declare module '@deepseek-ai/dsh-client-ui-conversation/client' { interface ChatNodeDataMap { /** Ordinary turn-opening user message. */ - user: UserMessageNode + user: ReferencedUserMessageNode /** User message admitted into an active turn. */ - steering: SteeringMessageNode + steering: ReferencedSteeringMessageNode /** Non-user context injected into model history. */ context: ContextMessageNode } @@ -51,6 +61,11 @@ export const messageDefinition: ConversationNodeDefinition = { } } const claimed = reader.previous('inbox-next-step')?.state.claimed.has(String(event.data.id)) === true + const previous = reader.previous('input-message') + const labels = previous?.state.kind === 'context' && previous.state.seq + 1 === event.seq + ? sessionRecallLabels(previous.state.source) + : [] + const referenceLabels = labels.length === 0 ? {} : { referenceLabels: labels } return claimed ? { kind: 'steering', @@ -59,6 +74,7 @@ export const messageDefinition: ConversationNodeDefinition = { time: event.time, content: event.data.content, source: event.data.source, + ...referenceLabels, } : { kind: 'user', @@ -66,6 +82,7 @@ export const messageDefinition: ConversationNodeDefinition = { time: event.time, content: event.data.content, source: event.data.source, + ...referenceLabels, } }, update: context => context.state, diff --git a/packages/client/ui-conversation/tests/chat-branch-tails.client.spec.tsx b/packages/client/ui-conversation/tests/chat-branch-tails.client.spec.tsx index a6308b07e9..f1e3163c43 100644 --- a/packages/client/ui-conversation/tests/chat-branch-tails.client.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-branch-tails.client.spec.tsx @@ -61,9 +61,13 @@ function MessageItem({ node, t: translate, referenceLabels }: MessageItemProps) anchorSeq: node.seq, location: { kind: 'session' }, visibility: 'visible', - data: node.kind === 'model-retry' ? { attempts: [node], current: node } : node, + data: node.kind === 'model-retry' + ? { attempts: [node], current: node } + : (node.kind === 'user' || node.kind === 'steering') && referenceLabels !== undefined + ? { ...node, referenceLabels } + : node, } - const props = { node: viewNode, t: translate, referenceLabels } as ChatNodeViewProps + const props = { node: viewNode, t: translate } as ChatNodeViewProps switch (node.kind) { case 'user': case 'steering': diff --git a/packages/client/ui-conversation/tests/conversation-node-definitions.client.spec.ts b/packages/client/ui-conversation/tests/conversation-node-definitions.client.spec.ts index c6a33c2f76..a9de47c7f3 100644 --- a/packages/client/ui-conversation/tests/conversation-node-definitions.client.spec.ts +++ b/packages/client/ui-conversation/tests/conversation-node-definitions.client.spec.ts @@ -514,6 +514,31 @@ describe('built-in conversation node Definitions', () => { }) }) + it('associates session-reference labels inside the adjacent direct-message node', () => { + const referenceSource = { + kind: 'session-reference', + form: 'recall', + version: 1, + references: [ + { sessionId: 'source-a', label: 'Research' }, + { sessionId: 'source-b', label: 'Review' }, + ], + } + const value = assembler([ + at(1, 'user/message', { + ...textMessage('reference-context', 'snapshot'), + source: referenceSource, + }, { surfaceOp: 'append' }), + at(2, 'user/message', textMessage('citing-user', '@Research and @Review'), { surfaceOp: 'append' }), + at(4, 'user/message', textMessage('later-user', 'unrelated'), { surfaceOp: 'append' }), + ]) + + const current = snapshot(value) + const users = [...current.nodes.values()].filter(candidate => candidate.kind === 'user') + expect(users[0]?.data).toMatchObject({ referenceLabels: ['Research', 'Review'] }) + expect(users[1]?.data).not.toHaveProperty('referenceLabels') + }) + it('keeps replacement copies out of Chat business nodes', () => { const value = assembler([ at(1, 'turn/start', { turn: 1 }), diff --git a/packages/client/ui-reference/README.i18n.yaml b/packages/client/ui-reference/README.i18n.yaml index a1c62f669c..a2ccb405e1 100644 --- a/packages/client/ui-reference/README.i18n.yaml +++ b/packages/client/ui-reference/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-reference/README.md -README.md: c9ad377d2757a9dfea97c0660ca56cfb6beb6d20 -README.zh.md: 7e027abfc6237d7260fe00ff82af9a7103281804 +README.md: 12a8e69624c0d7fe28c10ae708466fdda4a8480d +README.zh.md: bfaee51af3947fe794afab3a99df194c543b0082 diff --git a/packages/client/ui-reference/README.md b/packages/client/ui-reference/README.md index c9ad377d27..12a8e69624 100644 --- a/packages/client/ui-reference/README.md +++ b/packages/client/ui-reference/README.md @@ -6,7 +6,7 @@ Unified Web `@file` and `@session` source. The browser starts the `fileReference File picks insert the natural text defined by the shared `@path` grammar. A file closes completion and adds a trailing space; a directory keeps the menu active at its trailing slash so the user can descend another level. Paths containing whitespace use `@"path with spaces"`, and a quote the user opened explicitly remains quoted. -Session picks insert an atomic composer chip whose hidden `ref` and clipboard representation are the canonical `@[label](dsh-session:…)` mention returned by the Host. The visible chip uses `@label`; serialization never reconstructs identity from that label. Ordinary send delegates the canonical mention to `session.prompt`, where Host-side session-reference preparation owns validation, snapshotting, and model context. +Session picks insert an atomic composer chip whose hidden `ref` and clipboard representation are the canonical `@[label](dsh-session:…)` mention returned by the Host. The visible chip uses `@label`; serialization never reconstructs identity from that label. Ordinary send carries the canonical mention through `session.prompt`; the session-reference service validates it and captures model context at `agent/pre-step`. The `/client` export is the plugin body (`apply`/`inject`) only; candidate encoding stays internal to the registration effect. @@ -20,6 +20,6 @@ Candidate browsing has no model effect. A selected file or session changes only ## Known Limitations and Deferred Work -- **Candidate failure is intentionally quiet** — one unavailable or failed Remote discovery call yields no rows for that domain, while prompt submission still reports session-reference preparation failures through the ordinary send path. +- **Candidate failure is intentionally quiet** — one unavailable or failed Remote discovery call yields no rows for that domain. A session-reference preparation failure occurs after prompt acceptance and terminates that agent turn. - **No browser-side file scan** — Web completion requires a mounted Host `ctx.fileReferences` provider; the browser cannot fall back to its own filesystem. - **Session search remains metadata-only** — discovery filters session id, cwd, and the latest log-backed title through `ctx.sessionReferenceResolver`; message bodies and full transcripts are not searched. diff --git a/packages/client/ui-reference/README.zh.md b/packages/client/ui-reference/README.zh.md index 7e027abfc6..bfaee51af3 100644 --- a/packages/client/ui-reference/README.zh.md +++ b/packages/client/ui-reference/README.zh.md @@ -6,7 +6,7 @@ 选择文件会插入共享 `@path` 语法所定义的自然文本。文件会关闭补全并追加一个尾随空格;目录则让菜单在尾部斜杠处保持活跃,用户可以继续进入下一层。包含空白的路径使用 `@"path with spaces"`,用户显式打开的引号会继续保留。 -选择会话会插入一个原子的输入框 chip,其隐藏 `ref` 与剪贴板表示均为宿主返回的规范 `@[label](dsh-session:…)` 提及标记。可见 chip 使用 `@label`;序列化永远不会根据该标签重建身份。普通发送会把规范提及标记交给 `session.prompt`,由宿主侧的会话引用准备负责校验、生成快照和模型上下文。 +选择会话会插入一个原子的输入框 chip,其隐藏 `ref` 与剪贴板表示均为宿主返回的规范 `@[label](dsh-session:…)` mention。可见 chip 使用 `@label`;序列化永远不会根据该标签重建身份。普通发送会通过 `session.prompt` 携带规范 mention,session-reference 服务会在 `agent/pre-step` 校验它并捕获模型上下文。 `/client` 只导出插件主体(`apply`/`inject`);候选编码保留在注册 effect 内部。 @@ -20,6 +20,6 @@ ## 已知限制与暂缓事项 -- **候选失败有意保持静默**:Remote 发现调用不可用或失败时,该领域不产生候选行;提示词提交仍会通过普通发送路径报告会话引用准备失败。 +- **候选失败有意保持静默**:Remote 发现调用不可用或失败时,该领域不产生候选行。会话引用准备失败发生在提示词接受后,并会终止该 agent 轮次。 - **浏览器侧不扫描文件**:Web 补全需要挂载宿主 `ctx.fileReferences` 提供方;浏览器无法回退到自身文件系统。 - **会话搜索仍仅使用元数据**:发现流程通过 `ctx.sessionReferenceResolver` 筛选 session id、cwd 和以日志为依据的最新标题;不搜索消息主体或完整 transcript(文本记录)。 diff --git a/packages/client/ui-reference/src/client/index.ts b/packages/client/ui-reference/src/client/index.ts index f838083f63..fb356e73a8 100644 --- a/packages/client/ui-reference/src/client/index.ts +++ b/packages/client/ui-reference/src/client/index.ts @@ -9,31 +9,15 @@ import type {} from '@deepseek-ai/dsh-api-remotes/client' // Type-only: pulls the locale plugin's Context merge (ctx.locale). import type {} from '@deepseek-ai/dsh-client-locale/client' -import type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client' +import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client' import type { ClientSessionContext, InputTriggerServiceContract, InputTriggerSource, } from '@deepseek-ai/dsh-client-ui-input-trigger/client' import { formatFileMention } from '@deepseek-ai/dsh-file-reference/grammar' import type { FileReferenceCandidate } from '@deepseek-ai/dsh-file-reference/types' import type { SessionReferenceMentionCandidate } from '@deepseek-ai/dsh-session-reference/types' -import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol' import { en, NS, zh, type ReferenceKey } from './locales.ts' -/** - * The two Remote calls this source needs. The generated face wraps every - * business result in {@link RemoteResult}: a carrier failure arrives as the - * `ok: false` branch rather than a rejection, so discovery reads one envelope - * per domain and either domain can fail without hiding the other. - */ -interface ReferenceRemotes { - readonly fileReferences: { - list: (agentId: SessionId, query: string, signal?: AbortSignal) => Promise> - } - readonly sessionReferenceResolver: { - candidates: (agentId: SessionId, query: string, signal?: AbortSignal) => Promise> - } -} - /** Required services: the trigger registry, the Remote namespaces, and the copy. */ export const inject = [ 'inputTriggers', 'locale', 'remote', 'remote.fileReferences', 'remote.sessionReferenceResolver', @@ -46,18 +30,17 @@ export const inject = [ export function apply(ctx: ClientContext): void { ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-reference: dictionaries') const t = ctx.locale.bind(NS) - const remote: ReferenceRemotes = ctx.remote const source: InputTriggerSource = { trigger: '@', name: 'reference', async candidates(session: ClientSessionContext, { query, quoted, signal }) { - const files = remote.fileReferences.list(session.sessionId, query, signal).then( + const files = ctx.remote.fileReferences.list(session.sessionId, query, signal).then( result => result.ok ? result.value : [], () => [], ) const sessions = quoted === true ? Promise.resolve([] as SessionReferenceMentionCandidate[]) - : remote.sessionReferenceResolver.candidates(session.sessionId, query, signal).then( + : ctx.remote.sessionReferenceResolver.candidates(session.sessionId, query, signal).then( result => result.ok ? result.value : [], () => [], ) diff --git a/packages/context/session-reference/README.i18n.yaml b/packages/context/session-reference/README.i18n.yaml index 5a56be0add..489b884d57 100644 --- a/packages/context/session-reference/README.i18n.yaml +++ b/packages/context/session-reference/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/context/session-reference/README.md -README.md: 706838bfd475c320b589e091467b2974c9cc43ab -README.zh.md: 71e5fdca0755ea481b19aec79ad1e5ec9cf067fb +README.md: 13dfcf2fa99a118efa03f4f183b6b426bf704c2b +README.zh.md: a81f8c62942ca9a9b3b10c1c109c1fb227971ec0 diff --git a/packages/context/session-reference/README.md b/packages/context/session-reference/README.md index 706838bfd4..13dfcf2fa9 100644 --- a/packages/context/session-reference/README.md +++ b/packages/context/session-reference/README.md @@ -7,14 +7,14 @@ English | [中文](README.zh.md) ## Public API - `listCandidates(agent, query?, limit?)` lists sessions other than `agent.id`, filters case-insensitively by id, cwd, or the latest log-backed title, and ranks same-cwd, cwd-less, then other-cwd records while preserving `listSessions()` creation order within each group. Each selected candidate uses that title as the mention label and falls back to the session id when the title is absent or unreadable; message bodies are not searched. The unary `sessionReferenceResolver/candidates` Remote method serves the same discovery under the configured candidate limit and attaches each candidate's canonical mention, so browser consumers call `ctx.remote.sessionReferenceResolver.candidates` without an API Proxy route. -- `prepare(agent, content, references, signal?)` preserves first-mention order, deduplicates ids, rejects self-reference and more than the configured distinct-source limit, reads every source in parallel, and returns detached content plus zero or one aggregated, identified `UserMessage` context. Any invalid reference, failed read, cancellation, or budget failure rejects before the host calls `followup()` or `steer()`. +- `prepare(agent, content, references, signal?)` preserves first-mention order, deduplicates ids, rejects self-reference and more than the configured distinct-source limit, reads every source in parallel, and returns detached content plus zero or one aggregated, identified `UserMessage` context. The service calls it for canonical mentions in direct user messages after downstream `agent/pre-step` listeners accept the step. - `encodeSessionReferenceUri()` and `decodeSessionReferenceUri()` implement `dsh-session:` so every JavaScript string id round-trips exactly. `formatSessionReferenceMention()` emits `@[label](uri)`, and `parseSessionReferenceText()` replaces Markdown mentions or bare canonical URIs with readable `@label` text while returning structured references. Explicit Markdown mentions reject every malformed URI; bare text is considered a reference only when a non-empty base64url-shaped payload follows the scheme, and a matching noncanonical candidate still fails. Empty or punctuation-only scheme mentions remain ordinary discussion text. ## Snapshot semantics -Preparation calls `ctx.sessionQuery.readSurface()` once per distinct source and never rereads it after enqueue. It projects only direct-user `user/message`, assistant text, and `user/message` checkpoints carrying the canonical `dsh-compaction` source marker from the folded current surface. Separately sourced session-reference messages are injected context and are excluded, preventing recursive snapshot propagation. Shadowed pre-compaction events, tools, reasoning, other plugin-generated user messages except marked compact checkpoints, and unfinished assistant chunks are also excluded. A compacted source therefore contributes its latest checkpoint plus retained later conversation, not restored shadowed text. +Preparation calls `ctx.sessionQuery.readSurface()` once per distinct source when the target message reaches `agent/pre-step`. A queued message therefore captures the source state at model-step entry, and the resulting context is immutable after that point. Projection keeps only direct-user `user/message`, assistant text, and `user/message` checkpoints carrying the canonical `dsh-compaction` source marker from the folded current surface. Separately sourced session-reference messages are injected context and are excluded, preventing recursive snapshot propagation. Shadowed pre-compaction events, tools, reasoning, other plugin-generated user messages except marked compact checkpoints, and unfinished assistant chunks are also excluded. A compacted source therefore contributes its latest checkpoint plus retained later conversation, not restored shadowed text. -The context source is `{ kind: 'session-reference', version: 1, references }`; each reference records its source id and label, capture seq, compact presence, retained/omitted message counts, omitted UTF-8 bytes, and truncation state. The Web host installs a one-shot `agent/pre-step` listener keyed by the prepared direct message id before delivery. An `enter` decision containing that exact id receives the snapshot immediately before the direct message; an ordinary discard removes the listener, and queue-to-steer relocation preserves the association. The target log therefore records a sourced context `user/message` followed by the readable direct `user/message`. Later source mutation, compaction, or deletion cannot change target replay. +The context source is `{ kind: 'session-reference', version: 1, references }`; each reference records its source id and label, capture seq, compact presence, retained/omitted message counts, omitted UTF-8 bytes, and truncation state. The service's outer `agent/pre-step` listener post-processes accepted direct user messages, preserves their message ids, and inserts each snapshot immediately before the message that cited it. Queue edits and queue-to-steer relocation need no reference-specific handling because parsing occurs after the final inbox claim. Invalid mentions, failed reads, cancellation, and budget failures end that turn before its messages enter model-visible history. The target log records a sourced context `user/message` followed by the readable direct `user/message`; source mutation after capture cannot change target replay. ## Configuration diff --git a/packages/context/session-reference/README.zh.md b/packages/context/session-reference/README.zh.md index 71e5fdca07..a81f8c6294 100644 --- a/packages/context/session-reference/README.zh.md +++ b/packages/context/session-reference/README.zh.md @@ -7,14 +7,14 @@ ## 公开 API - `listCandidates(agent, query?, limit?)` 会列出 `agent.id` 之外的会话,按 id、cwd 或以日志为依据的最新标题进行不区分大小写的筛选,再按同 cwd、无 cwd、其他 cwd 记录排序,同时保持每组内的 `listSessions()` 创建顺序。每个已选候选会话都使用该标题作为 mention label;标题不存在或无法读取时回退到会话 id。不搜索消息主体。一元 `sessionReferenceResolver/candidates` Remote 方法在配置的候选上限内提供同一发现能力,并为每个候选附上规范 mention,浏览器消费方直接调用 `ctx.remote.sessionReferenceResolver.candidates`,无需 API Proxy 路由。 -- `prepare(agent, content, references, signal?)` 会保留首次 mention 顺序、对 id 去重,并拒绝自引用或超过已配置不同源上限的情况。它会并行读取所有源,返回与输入脱离的内容,外加零个或一个聚合且带标识的 `UserMessage` 上下文。任何无效引用、读取失败、取消或预算失败,都会使准备操作在宿主调用 `followup()` 或 `steer()` 之前失败。 +- `prepare(agent, content, references, signal?)` 会保留首次 mention 顺序、对 id 去重,并拒绝自引用或超过已配置不同源上限的情况。它会并行读取所有源,返回与输入脱离的内容,外加零个或一个聚合且带标识的 `UserMessage` 上下文。下游 `agent/pre-step` 监听器接受步骤后,该服务会针对直接用户消息中的规范 mention 调用此方法。 - `encodeSessionReferenceUri()` 与 `decodeSessionReferenceUri()` 实现 `dsh-session:`,因此每个 JavaScript 字符串 id 都能精确往返。`formatSessionReferenceMention()` 发出 `@[label](uri)`,`parseSessionReferenceText()` 将 Markdown mention 或裸规范 URI 替换为可读的 `@label` 文本,并返回结构化引用。解析器会拒绝显式 Markdown mention 中任何格式错误的 URI;只当 scheme 后跟非空、符合 base64url 形状的 payload 时,裸文本才被视为引用,匹配但非规范的候选项仍会失败。空 scheme mention 或只含标点符号的 scheme mention 仍是普通讨论文本。 ## 快照语义 -准备阶段会对每个不同源调用一次 `ctx.sessionQuery.readSurface()`,入队后绝不重读。它仅投影折叠后当前表层中的用户直接发出的 `user/message`、assistant 文本,以及 `user/message` 检查点;这类检查点携带规范 `dsh-compaction` 源标记。带独立来源的 session-reference 消息属于注入上下文,会被排除以防止快照递归传播。已遮蔽的压缩(compaction)前事件、工具、推理(reasoning)、除已标记 compact 检查点外的其他插件生成 user 消息,以及未完成的 assistant 分片也都会被排除。因此,已压缩源只会提供最新检查点及其后保留的会话内容,不会还原已遮蔽的文本。 +目标消息到达 `agent/pre-step` 时,准备阶段会对每个不同源调用一次 `ctx.sessionQuery.readSurface()`。因此,queued 消息在进入模型步骤时捕获源状态,此后生成的上下文保持不变。它仅投影折叠后当前表层中的用户直接发出的 `user/message`、assistant 文本,以及 `user/message` 检查点;这类检查点携带规范 `dsh-compaction` 源标记。带独立来源的 session-reference 消息属于注入上下文,会被排除以防止快照递归传播。已遮蔽的压缩(compaction)前事件、工具、推理(reasoning)、除已标记 compact 检查点外的其他插件生成 user 消息,以及未完成的 assistant 分片也都会被排除。因此,已压缩源只会提供最新检查点及其后保留的会话内容,不会还原已遮蔽的文本。 -上下文源为 `{ kind: 'session-reference', version: 1, references }`;每条引用会记录其源 id 与 label、捕获 seq、是否存在 compact、已保留/已省略消息数、已省略 UTF-8 字节数与截断状态。Web 宿主会在交付前安装一个以已准备直接消息 id 为键的一次性 `agent/pre-step` 监听器。包含该确切 id 的 `enter` 决策会在直接消息之前紧邻插入快照;普通丢弃会移除监听器,而从 queue 移动到 steer 会保留两者的关联。目标日志因此会先记录一条带来源信息的上下文 `user/message`,再记录可读的直接 `user/message`。后续源变更、压缩或删除都无法改变目标回放。 +上下文源为 `{ kind: 'session-reference', version: 1, references }`;每条引用会记录其源 id 与 label、捕获 seq、是否存在 compact、已保留/已省略消息数、已省略 UTF-8 字节数与截断状态。该服务的外层 `agent/pre-step` 监听器会处理已接受的直接用户消息,保留其消息 id,并把每份快照插入到引用它的消息紧前。解析发生在最终领取收件箱消息之后,因此队列编辑和从 queue 移动到 steer 不需要引用专用处理。无效 mention、读取失败、取消和预算失败会在消息进入面向模型的历史之前结束该轮次。目标日志会先记录一条带来源信息的上下文 `user/message`,再记录可读的直接 `user/message`;捕获后的源变更无法改变目标回放。 ## 配置 diff --git a/packages/context/session-reference/src/index.ts b/packages/context/session-reference/src/index.ts index b77ba8ce16..8110f4ead6 100644 --- a/packages/context/session-reference/src/index.ts +++ b/packages/context/session-reference/src/index.ts @@ -7,9 +7,9 @@ import { Context } from '@deepseek-ai/cordis' import z from '@deepseek-ai/schemastery' -import type { Agent } from '@deepseek-ai/dsh-agent' +import type { Agent, PreStepDecision } from '@deepseek-ai/dsh-agent' import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol' -import { createUserMessage } from '@deepseek-ai/dsh-llm' +import { createUserMessage, freezeMessage } from '@deepseek-ai/dsh-llm' import type { ContentBlock, UserMessage } from '@deepseek-ai/dsh-llm' import type { SessionId } from '@deepseek-ai/dsh-session' import type { SessionSurfaceSnapshot, SessionTitleObservationResult } from '@deepseek-ai/dsh-session-query' @@ -26,7 +26,7 @@ import type { PreparedReferencedMessage, SessionReferenceCandidate, SessionReferenceInput, SessionReferenceMentionCandidate, SessionReferenceSource, } from './types.ts' -import { formatSessionReferenceMention } from './uri.ts' +import { formatSessionReferenceMention, parseSessionReferenceText } from './uri.ts' export type * from './types.ts' export type { Config, SessionReferenceErrorCode } from './config.ts' @@ -103,6 +103,46 @@ export class SessionReferenceResolver extends TypertRemoteService { 'SESSION_REFERENCE_INVALID_CONFIG', ) } + ctx.on('agent/pre-step', async ({ agent, signal }, next): Promise => { + const decision = await next() + if (decision.kind === 'reject') return decision + return { + kind: 'enter', + messages: await this.prepareDirectMessages(agent, decision.messages, signal), + } + }, { prepend: true }) + } + + /** + * Replace canonical mentions in direct user messages and place each prepared + * snapshot immediately before the message that cited it. + * @param agent - agent entering the model step. + * @param messages - messages accepted by downstream pre-step listeners. + * @param signal - active turn cancellation. + * @returns messages with session-reference context inserted in citation order. + */ + private async prepareDirectMessages( + agent: Agent, + messages: readonly UserMessage[], + signal: AbortSignal, + ): Promise { + const prepared = await Promise.all(messages.map(async (message): Promise => { + if (message.source.kind !== 'user') return [message] + const references: SessionReferenceInput[] = [] + const content = message.content.map((block): ContentBlock => { + if (block.type !== 'text') return block + const parsed = parseSessionReferenceText(block.text) + references.push(...parsed.references) + return { type: 'text', text: parsed.text } + }) + if (references.length === 0) return [message] + const resolved = await this.prepare(agent, content, references, signal) + const direct = freezeMessage({ ...message, content: resolved.content }) + return resolved.additionalContext === undefined + ? [direct] + : [resolved.additionalContext, direct] + })) + return prepared.flat() } /** @@ -186,11 +226,11 @@ export class SessionReferenceResolver extends TypertRemoteService { } /** - * Snapshot all references before enqueue and return one aggregated durable context. + * Snapshot all references for one accepted direct message and return one aggregated durable context. * @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 signal - optional cancellation boundary for host request teardown. + * @param signal - optional cancellation boundary for the active turn. * @returns detached content and optional referenced-session context. */ async prepare( diff --git a/packages/context/session-reference/tests/session-reference.spec.ts b/packages/context/session-reference/tests/session-reference.spec.ts index d08b7da413..357e8bb0a6 100644 --- a/packages/context/session-reference/tests/session-reference.spec.ts +++ b/packages/context/session-reference/tests/session-reference.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import type { Agent } from '@deepseek-ai/dsh-agent' +import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent' import { CompactionId, compactCheckpointSource } from '@deepseek-ai/dsh-compaction' import { createUserMessage, CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm' import SessionStore, { Session, SessionId } from '@deepseek-ai/dsh-session' @@ -301,6 +301,79 @@ describe('session reference discovery and preparation', () => { }]) }) + it('prepares direct mentions at pre-step and keeps ordinary and plugin messages unchanged', async () => { + const ctx = await harness() + const target = ctx.sessions.create(SessionId('target')) + const source = ctx.sessions.create(SessionId('source')) + source.append('user/message', createUserMessage({ + content: [{ type: 'text', text: 'source fact' }], + source: { kind: 'user' }, + }), { surfaceOp: 'append' }) + const agent = fakeAgent(target) + const direct = createUserMessage({ + content: [{ + type: 'text', + text: `compare ${formatSessionReferenceMention({ sessionId: source.id, label: 'Research' })} now`, + }], + source: { kind: 'user' }, + }) + const ordinary = createUserMessage({ + content: [{ type: 'text', text: 'ordinary prompt' }], + source: { kind: 'user' }, + }) + const plugin = createUserMessage({ + content: [{ type: 'text', text: formatSessionReferenceMention({ sessionId: source.id, label: 'Ignored' }) }], + source: { kind: 'plugin', plugin: 'test' }, + }) + const signal = new AbortController().signal + + const decision = await agentEvents(ctx, agent).waterfall( + 'agent/pre-step', + { messages: [direct, ordinary, plugin], turn: 1, step: 1, signal }, + () => Promise.resolve({ kind: 'enter' as const, messages: [direct, ordinary, plugin] }), + ) + + expect(decision.kind).toBe('enter') + if (decision.kind !== 'enter') throw new Error('expected entered pre-step') + expect(decision.messages).toHaveLength(4) + expect(decision.messages[0]?.source).toMatchObject({ + kind: 'session-reference', + references: [{ sessionId: source.id, label: 'Research' }], + }) + expect(decision.messages[1]).toMatchObject({ + id: direct.id, + content: [{ type: 'text', text: 'compare @Research now' }], + }) + expect(decision.messages[1]).not.toBe(direct) + expect(decision.messages[2]).toBe(ordinary) + expect(decision.messages[3]).toBe(plugin) + }) + + it('does not prepare a rejected pre-step and rejects malformed direct mentions', async () => { + const ctx = await harness() + const target = ctx.sessions.create(SessionId('target')) + const agent = fakeAgent(target) + const malformed = createUserMessage({ + content: [{ type: 'text', text: '@[bad](dsh-session:not-canonical)' }], + source: { kind: 'user' }, + }) + const readSurface = vi.spyOn(ctx.sessionQuery, 'readSurface') + const signal = new AbortController().signal + + await expect(agentEvents(ctx, agent).waterfall( + 'agent/pre-step', + { messages: [malformed], turn: 1, step: 1, signal }, + () => Promise.resolve({ kind: 'reject' as const }), + )).resolves.toEqual({ kind: 'reject' }) + expect(readSurface).not.toHaveBeenCalled() + + await expect(agentEvents(ctx, agent).waterfall( + 'agent/pre-step', + { messages: [malformed], turn: 1, step: 1, signal }, + () => Promise.resolve({ kind: 'enter' as const, messages: [malformed] }), + )).rejects.toThrow(/invalid session reference URI/) + }) + it('keeps metadata matches when one title observation fails and cancels a stalled title batch', async () => { const ctx = await harness() const target = ctx.sessions.create(SessionId('target')) diff --git a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts index 4e76ca745c..dbd3731614 100644 --- a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts +++ b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts @@ -216,7 +216,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [ }, ], ownerProps: [ - '/** Stable owner currency delivered to one keyed Chat business renderer. */\nexport interface ChatNodeOwnerProps {\n /** Selected Tool call, when the shared details store names one. */\n selectedCallId?: CallId | undefined\n /** Session workspace root; Tool summaries display paths relative to it. */\n cwd?: string | undefined\n openFile: (path: string) => void\n inspectCall: (callId: CallId) => void\n forkAt: (seq: number) => void\n /** Resolve a session-authorized historical image for inline display. */\n loadImage: (attachment: ImageAttachmentRef) => Promise\n fileMentions: (owner: TurnTailOwnerProps) => MarkdownFileMentions | undefined\n /** Session-reference labels owned by the immediately preceding recall node. */\n referenceLabels?: readonly string[] | undefined\n}', + '/** Stable owner currency delivered to one keyed Chat business renderer. */\nexport interface ChatNodeOwnerProps {\n /** Selected Tool call, when the shared details store names one. */\n selectedCallId?: CallId | undefined\n /** Session workspace root; Tool summaries display paths relative to it. */\n cwd?: string | undefined\n openFile: (path: string) => void\n inspectCall: (callId: CallId) => void\n forkAt: (seq: number) => void\n /** Resolve a session-authorized historical image for inline display. */\n loadImage: (attachment: ImageAttachmentRef) => Promise\n fileMentions: (owner: TurnTailOwnerProps) => MarkdownFileMentions | undefined\n}', ], ownerPropsReferences: [ 'ImageAttachmentRef', diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index 29f47d5ecd..ad7d0dbb62 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -1289,8 +1289,8 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, { signature: 'async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise', - description: 'Snapshot all references before enqueue and return one aggregated durable context.', - parameters: [{ name: 'agent', description: 'target agent; references to it are rejected.' }, { name: 'content', description: 'already host-normalized readable message content.' }, { name: 'references', description: 'structured source sessions in mention order.' }, { name: 'signal', description: 'optional cancellation boundary for host request teardown.' }], + description: 'Snapshot all references for one accepted direct message and return one aggregated durable context.', + parameters: [{ name: 'agent', description: 'target agent; references to it are rejected.' }, { name: 'content', description: 'already host-normalized readable message content.' }, { name: 'references', description: 'structured source sessions in mention order.' }, { name: 'signal', description: 'optional cancellation boundary for the active turn.' }], returns: 'detached content and optional referenced-session context.', }, ], diff --git a/packages/host/apiproxy/README.i18n.yaml b/packages/host/apiproxy/README.i18n.yaml index 22fb19c837..5826f75f75 100644 --- a/packages/host/apiproxy/README.i18n.yaml +++ b/packages/host/apiproxy/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/host/apiproxy/README.md -README.md: 69f10f1ed22d95e7c4601754957218df374a66cc -README.zh.md: 40179d51f8024105beafdc7c49e6eb8e70c7a224 +README.md: 607cd4e4176631b64daf4a298b5d86a75ccdce68 +README.zh.md: cdfb5aa65b3ad5b00596487aae6c99e2f9d4e433 diff --git a/packages/host/apiproxy/README.md b/packages/host/apiproxy/README.md index 69f10f1ed2..607cd4e417 100644 --- a/packages/host/apiproxy/README.md +++ b/packages/host/apiproxy/README.md @@ -38,8 +38,6 @@ Session model selection is a session-domain contract. `session.models` returns t `session.prompt` and `subagent.prompt` accept optional request-local `clientTimeZone` provenance. When present, the Host validates and canonicalizes `UTC` or an IANA Area/Location before Agent entry, rejects invalid input with `invalid-time-zone`, and records the canonical value on that exact `user-rpc` message beside its `rpcId`. The value is not Session, connection, create, resume, or fork state; non-browser callers may omit it. -`session.prompt` parses canonical session mentions from normalized text blocks and asks `ctx.sessionReferenceResolver` to prepare every snapshot before enqueue. Parsing, cancellation, validation, reads, and budget enforcement are one admission transaction: failure enqueues no message, while success registers the separately sourced context against the readable prompt's exact message id for insertion immediately before it at pre-step entry. Ordinary discard removes the registration, and moving a queued prompt to steering preserves the association. - Pending queued input is a live control-plane contract, not conversation history. The gateway derives the complete `next-turn` queue from durable `agent/inbox/spliced` mutations and broadcasts authoritative `session/queue` snapshots after each change and on reconnect; pending `next-step` steering stays outside this Web projection. Within `next-step`, user-origin messages carry the `steering` placement while injected context (approval notices, task completion, attached snapshots) carries `context` and is not surfaced until claimed. The message-local `agent/inbox/inserted`, `claimed`, and `discarded` notifications remain available to lifecycle observers but do not build the queue view. `session.updateQueue` addresses one `MessageId`; edit and remove mutate the attached Agent through `Inbox.splice()`. A claim's pure deletion splice wins races before pre-step admission, so a later operation returns `queue-item-not-found`. `session.cancel` aborts only the active turn and preserves pending inbox work; after cancellation reaches quiescence and the closing turn flushes, AgentLoop claims the next waking message in FIFO order, and the browser never resends or promotes it. Queue operations never resume a cold session, and the client never infers retirement from turn or status events. Background jobs ride the same live-push posture. When `ctx.jobs` is composed, the gateway subscribes to its change feed and broadcasts a whole `session/jobs` snapshot after every registry commit that alters what a session can see — registration, the stopping transition, settlement, and owner-disposal removal — plus a subscription baseline for each session that already has tasks (an absent baseline is the empty set; a change that empties a set still sends `[]`). A change carrying an owner reads through that exact `Agent`, so a push stays correct while its scope tears down; the baseline reads `ctx.agents.get(sessionId)`, which yields only unowned tasks for a session with no live Agent and never resumes a cold one. An unowned change fans out to every subscribed session, because unowned tasks are visible to every caller. The wire `JobView` drops `ownerSession`, `reported`, and `outputLimitBytes`: the frame's own `sessionId` carries the first, and the other two are internal notice and model-presentation policy. A composition without the registry emits no such frames. diff --git a/packages/host/apiproxy/README.zh.md b/packages/host/apiproxy/README.zh.md index 40179d51f8..cdfb5aa65b 100644 --- a/packages/host/apiproxy/README.zh.md +++ b/packages/host/apiproxy/README.zh.md @@ -38,8 +38,6 @@ Settings 分节中的 `reasoningEffort` 在 agent-default-model 插件配置中 `session.prompt` 和 `subagent.prompt` 接受可选的请求本地 `clientTimeZone` 来源信息。若提供该值,Host 会在进入 Agent 前校验 `UTC` 或 IANA Area/Location 并将其规范化;无效输入以 `invalid-time-zone` 拒绝,规范值则与 `rpcId` 一起记录在这条确切的 `user-rpc` 消息上。该值不属于 Session、连接、create、resume 或 fork 状态;非浏览器调用方可以省略它。 -`session.prompt` 从规范化文本块中解析规范会话提及标记,并要求 `ctx.sessionReferenceResolver` 在入队前准备每个快照。解析、取消、校验、读取和预算约束共同构成一个准入事务:失败时不会有消息入队;成功时,会以可读提示词的确切消息 id 注册独立来源上下文,等待 pre-step 进入时紧邻插入到它之前。普通丢弃会移除该注册,把 queued 提示词移动到 steering(中途引导)时仍会保留两者的关联。 - 待处理的 queued 输入属于实时控制平面约定,而非对话历史。网关根据持久 `agent/inbox/spliced` 变更派生完整的 `next-turn` 队列,并在每次变更后及重连时广播权威 `session/queue` 快照;待处理的 `next-step` steering(中途引导)不进入此 Web 投影。在 `next-step` 内,用户来源的消息携带 `steering` placement,而注入上下文(审批通知、任务完成、附加快照)携带 `context`,领取前不对外呈现。面向单条消息的 `agent/inbox/inserted`、`claimed` 与 `discarded` 通知仍供生命周期观察方使用,但不用于构建队列视图。`session.updateQueue` 通过 `MessageId` 寻址单个项;编辑和移除经已挂载 Agent 的 `Inbox.splice()` 修改队列。认领操作的纯删除 splice 会在 pre-step 准入前赢得竞态,因此之后的操作返回 `queue-item-not-found`。`session.cancel` 仅中止活动轮次并保留待处理 inbox 工作;取消达到完全停稳且结束中的轮次完成 flush 后,AgentLoop 按 FIFO 顺序认领下一条可唤醒消息,浏览器绝不重发或提升它。队列操作绝不恢复冷会话,客户端也绝不根据轮次或状态事件推断某项已退出队列。 后台任务沿用同一种实时推送姿态。当组合中有 `ctx.jobs` 时,网关订阅它的变更订阅,并在注册表每一次改变某个会话可见内容的提交后——注册、转入 stopping、结算,以及 owner 销毁时的移除——广播一份完整的 `session/jobs` 快照,另外为每个已经有任务的会话发送订阅 baseline(没有 baseline 即表示空集;把集合清空的那次变更仍然发送 `[]`)。带 owner 的变更通过那个确切的 `Agent` 读取,因此推送在其 scope 拆除期间依然正确;baseline 读 `ctx.agents.get(sessionId)`,对没有活体 Agent 的会话只得到无主任务,且绝不恢复冷会话。无主变更向每一个已订阅会话扇出,因为无主任务对所有调用方可见。线路上的 `JobView` 丢弃 `ownerSession`、`reported` 和 `outputLimitBytes`:第一个由帧自身的 `sessionId` 携带,另外两个分别是内部通知位和模型呈现策略。没有该注册表的组合不发出这类帧。 diff --git a/packages/host/apiproxy/package.json b/packages/host/apiproxy/package.json index 8c8bd1e05b..946e27ded3 100644 --- a/packages/host/apiproxy/package.json +++ b/packages/host/apiproxy/package.json @@ -58,7 +58,6 @@ "@deepseek-ai/dsh-native-command": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-persistence": "workspace:^", - "@deepseek-ai/dsh-session-reference": "workspace:^", "@deepseek-ai/dsh-session-projection": "workspace:^", "@deepseek-ai/dsh-session-projection-cache": "workspace:^", "@deepseek-ai/dsh-session-query": "workspace:^", @@ -89,6 +88,7 @@ "@deepseek-ai/dsh-storage": "workspace:^", "@deepseek-ai/dsh-storage-domain": "workspace:^", "@deepseek-ai/dsh-typert-protocol": "workspace:^", - "@deepseek-ai/dsh-typert-registry": "workspace:^" + "@deepseek-ai/dsh-typert-registry": "workspace:^", + "@deepseek-ai/cordis": "workspace:^" } } diff --git a/packages/host/apiproxy/src/api-proxy.ts b/packages/host/apiproxy/src/api-proxy.ts index 8a8b3a8506..c675b60907 100644 --- a/packages/host/apiproxy/src/api-proxy.ts +++ b/packages/host/apiproxy/src/api-proxy.ts @@ -8,7 +8,7 @@ import { mkdir, stat } from 'node:fs/promises' import { dirname } from 'node:path' import type { Context } from '@deepseek-ai/cordis' import { installModelSelection } from '@deepseek-ai/dsh-agent' -import type { Agent, ModelSelection, ModelSelectionRef, AgentOptions, AgentStatus, PreStepDecision } from '@deepseek-ai/dsh-agent' +import type { Agent, ModelSelection, ModelSelectionRef, AgentOptions, AgentStatus } from '@deepseek-ai/dsh-agent' import type {} from '@deepseek-ai/dsh-agent-presets/types' import { AttachmentError } from '@deepseek-ai/dsh-attachment' import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment' @@ -18,10 +18,6 @@ import type { ContentBlock, MessageSource } from '@deepseek-ai/dsh-llm' import { isAppendSurfaceEvent, isJsonValue } from '@deepseek-ai/dsh-session' import type { JsonValue, Session, SessionEvent, SessionEventMap, SessionHeader, SessionId, UserMessage } from '@deepseek-ai/dsh-session' import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence' -import { - parseSessionReferenceText, - type SessionReferenceInput, -} from '@deepseek-ai/dsh-session-reference' import { SessionQueryError, type SessionSearchCursor } from '@deepseek-ai/dsh-session-query' import { SubagentError } from '@deepseek-ai/dsh-subagent' import type { SubagentListEntry as CatalogSubagentListEntry } from '@deepseek-ai/dsh-subagent' @@ -86,7 +82,7 @@ import type { SettingsDescriptor, SettingsNamespace, SettingsPathOp } from '@dee import { credentialRef } from '@deepseek-ai/dsh-credentials' // Value edge: the rename impl narrows the title service's validation failure; the import also resolves `ctx.get('sessionTitle')`. import { SessionTitleInvalidError } from '@deepseek-ai/dsh-session-title' -import type { CallId, MessageId } from '@deepseek-ai/dsh-llm/brand' +import type { CallId } from '@deepseek-ai/dsh-llm/brand' import type { ScopeKey } from '@deepseek-ai/dsh-scope' import type { ApprovalOutcome, ApprovalRequestId } from '@deepseek-ai/dsh-user-approval' // Side-effect type import: resolves the `approval/request` waterfall and @@ -165,21 +161,6 @@ async function durablePromptContent(ctx: Context, content: readonly PromptConten return blocks } -/** Remove canonical session mentions from text blocks and retain their structured identities. */ -function parseReferencedContent(content: readonly PromptContentPart[]): { - content: PromptContentPart[] - references: SessionReferenceInput[] -} { - const references: SessionReferenceInput[] = [] - const normalized = content.map((part): PromptContentPart => { - if (part.type !== 'text') return part - const parsed = parseSessionReferenceText(part.text) - references.push(...parsed.references) - return { type: 'text', text: parsed.text } - }) - return { content: normalized, references } -} - /** Search durable content for an image reference, including nested tool results. */ function imageBlockIn(content: unknown, match: (ref: ImageAttachmentRef) => boolean): ImageAttachmentRef | undefined { if (!Array.isArray(content)) return undefined @@ -1085,72 +1066,6 @@ function changedWorkspaceView(workspaceId: string, value: unknown): WorkspaceVie } } -/** One ApiProxy instance's pending reference-prompt admission listeners. */ -interface PreparedPromptOwnership { - readonly relocating: Set - readonly cleanups: Map void> -} - -/** Deliver a prepared prompt and inject its snapshot immediately before that exact message enters. */ -function deliverPrompt( - ctx: Context, - agent: Agent, - mode: 'queue' | 'steer', - message: UserMessage, - additionalContext: UserMessage | undefined, - ownership: PreparedPromptOwnership, -): void { - if (additionalContext === undefined) { - if (mode === 'steer') agent.steer(message) - else agent.followup(message) - return - } - let cleanedUp = false - let detachPreStep = (): void => {} - let detachDiscard = (): void => {} - let detachDisposed = (): void => {} - const cleanup = (): void => { - /* v8 ignore next -- all settlement paths share this idempotent release. */ - if (cleanedUp) return - cleanedUp = true - ownership.cleanups.delete(message.id) - detachPreStep() - detachDiscard() - detachDisposed() - } - ownership.cleanups.set(message.id, cleanup) - // An agent retired with the prepared prompt still pending must not leave - // these listeners on the Host root context for the process lifetime. - detachDisposed = ctx.on('agent/disposed', ({ agent: subject }) => { - if (subject === agent) cleanup() - }) - detachPreStep = ctx.on('agent/pre-step', async ({ agent: subject, messages }, next): Promise => { - if (subject !== agent || !messages.some(candidate => candidate.id === message.id)) return next() - cleanup() - const decision = await next() - if (decision.kind !== 'enter') return decision - const promptIndex = decision.messages.findIndex(candidate => candidate.id === message.id) - if (promptIndex < 0) return decision - return { - kind: 'enter', - messages: decision.messages.toSpliced(promptIndex, 0, additionalContext), - } - }, { prepend: true }) - detachDiscard = ctx.on('agent/inbox/discarded', ({ agent: subject, message: discarded }) => { - if (subject !== agent || discarded.id !== message.id || ownership.relocating.has(message.id)) return - const remainsPending = [...agent.inbox.nextTurn, ...agent.inbox.nextStep] - .some(candidate => candidate.id === message.id) - if (!remainsPending) cleanup() - }) - try { - if (mode === 'steer') agent.steer(message) - else agent.followup(message) - } catch (error: unknown) { - cleanup() - throw error - } -} - /** * Implement ApiProxy over a composed host context. * @param ctx - a context with the Host spine and Workspace registry mounted. @@ -1185,10 +1100,6 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro const pendingApprovals = new Map() const muxQueues = new Set>>() const imageAdmissionChains = new WeakMap>() - const preparedPromptOwnership: PreparedPromptOwnership = { - relocating: new Set(), - cleanups: new Map(), - } /** Serialize image admission with model selection for one agent. */ function serializeImageAdmission(agent: Agent, operation: () => Promise): Promise { @@ -2487,7 +2398,7 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro return ok(request, { sessionId: childId }) }, - async prompt(request, signal) { + async prompt(request) { const { sessionId, mode, content, clientTimeZone } = request.payload const canonicalTimeZone = clientTimeZone === undefined ? undefined @@ -2502,32 +2413,15 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro const resolved = await turnAgentFor<{ accepted: true }>(request, sessionId) if ('refused' in resolved) return resolved.refused const agent = resolved.agent - let parsed: ReturnType - try { - parsed = parseReferencedContent(content) - } catch (error: unknown) { - return err(request, { - code: 'reference-invalid', - message: 'invalid session reference', - details: { reason: String(error) }, - }) - } // Request identity and optional browser zone ride the exact durable user message. const source: MessageSource = { kind: 'user', rpcId: request.rpcId, ...(canonicalTimeZone === undefined ? {} : { clientTimeZone: canonicalTimeZone }), } - const hasImage = parsed.content.some(part => part.type === 'image') + const hasImage = content.some(part => part.type === 'image') const admit = async (): Promise> => { try { - if (signal?.aborted === true) { - return err(request, { - code: 'cancelled', - message: 'prompt submission was aborted', - details: {}, - }) - } if (hasImage) { const current = selectionFor(agent).current const modelInfo = await ctx.llm.resolveModelInfo(current.provider, current.model) @@ -2539,45 +2433,10 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro }) } } - let durable = await durablePromptContent(ctx, parsed.content) - let additionalContext: UserMessage | undefined - if (parsed.references.length > 0) { - const sessionReferences = ctx.get('sessionReferenceResolver') - if (sessionReferences === undefined) { - return err(request, { - code: 'reference-unavailable', - message: 'session reference capability unavailable', - details: { kind: 'session' }, - }) - } - try { - const prepared = await sessionReferences.prepare(agent, durable, parsed.references, signal) - durable = prepared.content - additionalContext = prepared.additionalContext - } catch (error: unknown) { - if (signal !== undefined && isAborted(signal)) { - return err(request, { - code: 'cancelled', - message: 'session reference preparation was aborted', - details: {}, - }) - } - return err(request, { - code: 'reference-failed', - message: 'session reference preparation failed', - details: { reason: String(error) }, - }) - } - } - if (signal !== undefined && isAborted(signal)) { - return err(request, { - code: 'cancelled', - message: 'prompt submission was aborted', - details: {}, - }) - } + const durable = await durablePromptContent(ctx, content) const message: UserMessage = createUserMessage({ content: durable, source }) - deliverPrompt(ctx, agent, mode, message, additionalContext, preparedPromptOwnership) + if (mode === 'steer') agent.steer(message) + else agent.followup(message) } catch (error: unknown) { if (error instanceof AttachmentError) { return err(request, { @@ -2690,16 +2549,8 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro if (action.kind === 'edit') { agent.inbox.replace(itemId, freezeMessage({ ...message, content: action.content })) } else { - if (action.kind === 'steer') preparedPromptOwnership.relocating.add(itemId) - try { - agent.inbox.remove(itemId) - if (action.kind === 'steer') agent.steer(message) - } catch (error: unknown) { - preparedPromptOwnership.cleanups.get(itemId)?.() - throw error - } finally { - preparedPromptOwnership.relocating.delete(itemId) - } + agent.inbox.remove(itemId) + if (action.kind === 'steer') agent.steer(message) } return Promise.resolve(ok(request, { accepted: true as const })) }, diff --git a/packages/host/apiproxy/src/api/rpc.schema.ts b/packages/host/apiproxy/src/api/rpc.schema.ts index 7c2d80fcef..03cfdd1e15 100644 --- a/packages/host/apiproxy/src/api/rpc.schema.ts +++ b/packages/host/apiproxy/src/api/rpc.schema.ts @@ -53,9 +53,6 @@ export const rpcErrorSchema: z.ZodType = z.discriminatedUnion('code', z.object({ code: z.literal('agent-preset-not-found'), message: z.string(), details: z.object({ agentPreset: z.string(), available: z.array(z.string()) }) }), z.object({ code: z.literal('agent-preset-invalid'), message: z.string(), details: z.object({ agentPreset: z.string(), reason: z.string() }) }), z.object({ code: z.literal('agent-busy'), message: z.string(), details: z.object({ reason: z.string() }) }), - z.object({ code: z.literal('reference-unavailable'), message: z.string(), details: z.object({ kind: z.union([z.literal('file'), z.literal('session')]) }) }), - z.object({ code: z.literal('reference-invalid'), message: z.string(), details: z.object({ reason: z.string() }) }), - z.object({ code: z.literal('reference-failed'), message: z.string(), details: z.object({ reason: z.string() }) }), z.object({ code: z.literal('attachment-error'), message: z.string(), details: z.object({ reason: z.string() }) }), z.object({ code: z.literal('queue-item-not-found'), message: z.string(), details: z.object({ itemId: z.string() }) }), z.object({ code: z.literal('steer-unavailable'), message: z.string(), details: z.object({ itemId: z.string() }) }), diff --git a/packages/host/apiproxy/src/api/rpc.ts b/packages/host/apiproxy/src/api/rpc.ts index 05431eca08..0b5506b6b6 100644 --- a/packages/host/apiproxy/src/api/rpc.ts +++ b/packages/host/apiproxy/src/api/rpc.ts @@ -51,9 +51,6 @@ export interface RpcErrorDetailsMap { 'agent-preset-not-found': { agentPreset: string; available: string[] } 'agent-preset-invalid': { agentPreset: string; reason: string } 'agent-busy': { reason: string } - 'reference-unavailable': { kind: 'file' | 'session' } - 'reference-invalid': { reason: string } - 'reference-failed': { reason: string } 'attachment-error': { reason: string } 'queue-item-not-found': { itemId: MessageId } 'steer-unavailable': { itemId: MessageId } diff --git a/packages/host/apiproxy/src/api/sessions.ts b/packages/host/apiproxy/src/api/sessions.ts index 24f91bf552..2e7c9b22f4 100644 --- a/packages/host/apiproxy/src/api/sessions.ts +++ b/packages/host/apiproxy/src/api/sessions.ts @@ -349,7 +349,7 @@ export interface SessionsApi { mode: 'queue' | 'steer' content: PromptContentPart[] clientTimeZone?: string - }>, signal?: AbortSignal): + }>): Promise> /** Reads one durable image after proving that this session's log references its id. */ diff --git a/packages/host/apiproxy/src/fetch/handler.ts b/packages/host/apiproxy/src/fetch/handler.ts index d33e8dc55b..697171ec53 100644 --- a/packages/host/apiproxy/src/fetch/handler.ts +++ b/packages/host/apiproxy/src/fetch/handler.ts @@ -96,7 +96,7 @@ const UNARY_ROUTES: UnaryRoutes = { 'session.selectModel': { schema: sessionSelectModelRequestSchema, invoke: (api, r) => api.sessions.selectModel(r) }, 'session.rename': { schema: sessionRenameRequestSchema, invoke: (api, r) => api.sessions.rename(r) }, 'session.fork': { schema: sessionForkRequestSchema, invoke: (api, r) => api.sessions.fork(r) }, - 'session.prompt': { schema: sessionPromptRequestSchema, invoke: (api, r, signal) => api.sessions.prompt(r, signal) }, + 'session.prompt': { schema: sessionPromptRequestSchema, invoke: (api, r) => api.sessions.prompt(r) }, 'session.attachment': { schema: sessionAttachmentRequestSchema, invoke: (api, r) => api.sessions.attachment(r) }, 'session.updateQueue': { schema: sessionUpdateQueueRequestSchema, invoke: (api, r) => api.sessions.updateQueue(r) }, 'session.cancel': { schema: sessionCancelRequestSchema, invoke: (api, r) => api.sessions.cancel(r) }, @@ -172,6 +172,9 @@ function fullResponse(narrow: RpcResponse): Response { * Wire<> widening back to the exact payload (undefined-valued properties and * absent ones are indistinguishable after JSON transport). */ +// K appears once in the signature but ties the UNARY_ROUTES[K] row lookup to its own +// schema/invoke pairing; a union parameter degrades the row to an uninvokable intersection. +// oxlint-disable-next-line typescript/no-unnecessary-type-parameters async function handleUnary( api: ApiProxy, method: K, message: ClientRequest, signal: AbortSignal, ): Promise { diff --git a/packages/host/apiproxy/tests/api-proxy-references.spec.ts b/packages/host/apiproxy/tests/api-proxy-references.spec.ts deleted file mode 100644 index 921440d7f2..0000000000 --- a/packages/host/apiproxy/tests/api-proxy-references.spec.ts +++ /dev/null @@ -1,412 +0,0 @@ -/** - * Referenced prompt coverage over the real ApiProxy: atomic snapshot - * preparation before enqueue and error/cancellation behavior. Discovery lives - * on the owning services' Remote faces, tested in their packages. - */ -import { Context } from '@deepseek-ai/cordis' -import { describe, expect, it, vi } from 'vitest' -import AgentRegistry, { agentEvents, Inbox } from '@deepseek-ai/dsh-agent' -import type { Agent } from '@deepseek-ai/dsh-agent' -import { createUserMessage } from '@deepseek-ai/dsh-llm' -import SessionStore from '@deepseek-ai/dsh-session' -import type { SessionId } from '@deepseek-ai/dsh-session' -import { formatSessionReferenceMention } from '@deepseek-ai/dsh-session-reference' -import SystemPrompt from '@deepseek-ai/dsh-system-prompt' -import ToolRegistry from '@deepseek-ai/dsh-tools' -import UserQuestionService from '@deepseek-ai/dsh-user-questions' -import type { RpcRequest, RpcResponse } from '../src/api/rpc.ts' -import { RpcId } from '../src/api/rpc.ts' -import { createApiProxy } from '../src/api-proxy.ts' - -const DEFAULTS = { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' } -let nextRpc = 1 - -function request

(payload: P): RpcRequest

{ - return { rpcId: RpcId(`reference-${String(nextRpc++)}`), payload } -} - -function expectOk(response: RpcResponse): T { - expect(response.result.ok).toBe(true) - if (!response.result.ok) throw new Error('unreachable') - return response.result.value -} - -function expectErr(response: RpcResponse): { code: string; message: string } { - expect(response.result.ok).toBe(false) - if (response.result.ok) throw new Error('unreachable') - return response.result.error -} - -async function harness(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt, { persona: '' }) - await ctx.plugin(ToolRegistry) - await ctx.plugin(UserQuestionService) - await ctx.plugin(AgentRegistry) - ctx.provide('workspace', { list: () => [] } as never) - return ctx -} - -function stubAgent(ctx: Context, status: Agent['status'] = 'idle') { - const session = ctx.sessions.create(undefined, { meta: { cwd: '/project' } }) - const followup = vi.fn() - const steer = vi.fn() - const inject = vi.fn() - const inbox = new Inbox(session, { - inserted(message) { - agentEvents(ctx, agent).emit('agent/inbox/inserted', { message }) - }, - discarded(message) { - agentEvents(ctx, agent).emit('agent/inbox/discarded', { message }) - }, - claimed(message, turn) { - agentEvents(ctx, agent).emit('agent/inbox/claimed', { message, turn }) - }, - }) - const agent = { - id: session.id, - session, - inbox, - status, - ctx, - followup, - steer, - inject, - cancel: vi.fn(), - } as unknown as Agent & { - followup: typeof followup - steer: typeof steer - inject: typeof inject - } - followup.mockImplementation((message) => { inbox.append('next-turn', message) }) - steer.mockImplementation((message) => { inbox.append('next-step', message) }) - inject.mockImplementation((message) => { inbox.append('next-step', message) }) - ctx.agents.register(agent) - return agent -} - -describe('referenced prompt preparation', () => { - it('normalizes the visible mention and waits for all context preparation before enqueue', async () => { - const ctx = await harness() - const agent = stubAgent(ctx) - const earlier = createUserMessage({ - source: { kind: 'user' }, - content: [{ type: 'text', text: 'earlier queued prompt' }], - }) - agent.followup(earlier) - const source = 'source-session' as SessionId - const mention = formatSessionReferenceMention({ sessionId: source, label: 'Research' }) - let finish!: () => void - const context = createUserMessage({ - source: { - kind: 'session-reference' as const, - form: 'recall' as const, - version: 1 as const, - references: [{ - sessionId: source, - label: 'Research', - capturedThroughSeq: null, - compacted: false, - originalMessages: 1, - retainedMessages: 1, - omittedMessages: 0, - omittedBytes: 0, - truncated: false, - inputIndex: 0, - }], - }, - content: [{ type: 'text' as const, text: 'snapshot' }], - }) - const prepare = vi.fn(() => new Promise<{ - content: { type: 'text'; text: string }[] - additionalContext: typeof context - }>((resolve) => { - finish = () => { - resolve({ - content: [{ type: 'text', text: 'compare @Research now' }], - additionalContext: context, - }) - } - })) - ctx.provide('sessionReferenceResolver', { prepare } as never) - const api = createApiProxy(ctx, DEFAULTS) - const signal = new AbortController().signal - const pending = api.sessions.prompt(request({ - sessionId: agent.id, - content: [{ type: 'text' as const, text: `compare ${mention} now` }], - mode: 'queue' as const, - }), signal) - await vi.waitFor(() => { expect(prepare).toHaveBeenCalledOnce() }) - expect(agent.followup).toHaveBeenCalledTimes(1) - expect(prepare).toHaveBeenCalledWith( - agent, - [{ type: 'text', text: 'compare @Research now' }], - [{ sessionId: source, label: 'Research' }], - signal, - ) - finish() - expect(expectOk(await pending)).toEqual({ accepted: true }) - expect(agent.followup).toHaveBeenCalledTimes(2) - const sent = agent.followup.mock.calls[1]?.[0] - expect(sent).toMatchObject({ - content: [{ type: 'text', text: 'compare @Research now' }], - source: { kind: 'user' }, - }) - if (sent === undefined) throw new Error('expected queued prompt') - const firstBatch = agent.inbox.claim('next-turn', 1) - const firstDecision = await agentEvents(ctx, agent).waterfall( - 'agent/pre-step', - { messages: firstBatch, turn: 1, step: 1, signal }, - () => Promise.resolve({ kind: 'enter' as const, messages: firstBatch }), - ) - expect(firstDecision).toEqual({ kind: 'enter', messages: [earlier] }) - const referencedBatch = agent.inbox.claim('next-turn', 2) - const decision = await agentEvents(ctx, agent).waterfall( - 'agent/pre-step', - { messages: referencedBatch, turn: 2, step: 1, signal }, - () => Promise.resolve({ kind: 'enter' as const, messages: referencedBatch }), - ) - expect(decision).toEqual({ kind: 'enter', messages: [context, sent] }) - const replay = await agentEvents(ctx, agent).waterfall( - 'agent/pre-step', - { messages: referencedBatch, turn: 2, step: 1, signal }, - () => Promise.resolve({ kind: 'enter' as const, messages: referencedBatch }), - ) - expect(replay).toEqual({ kind: 'enter', messages: referencedBatch }) - }) - - it('inserts prepared session context immediately before steering at admission', async () => { - const ctx = await harness() - const agent = stubAgent(ctx) - const source = 'source-session' as SessionId - const context = createUserMessage({ - source: { - kind: 'session-reference' as const, - form: 'recall' as const, - version: 1 as const, - references: [{ - sessionId: source, - label: 'Research', - capturedThroughSeq: null, - compacted: false, - originalMessages: 1, - retainedMessages: 1, - omittedMessages: 0, - omittedBytes: 0, - truncated: false, - inputIndex: 0, - }], - }, - content: [{ type: 'text' as const, text: 'snapshot' }], - }) - ctx.provide('sessionReferenceResolver', { - prepare: () => Promise.resolve({ - content: [{ type: 'text' as const, text: 'continue @Research' }], - additionalContext: context, - }), - } as never) - const api = createApiProxy(ctx, DEFAULTS) - const response = await api.sessions.prompt(request({ - sessionId: agent.id, - content: [{ - type: 'text' as const, - text: `continue ${formatSessionReferenceMention({ sessionId: source, label: 'Research' })}`, - }], - mode: 'steer' as const, - })) - expect(expectOk(response)).toEqual({ accepted: true }) - const steered = agent.steer.mock.calls[0]?.[0] - expect(steered?.content).toEqual([{ type: 'text', text: 'continue @Research' }]) - expect(steered?.source.kind).toBe('user') - if (steered === undefined) throw new Error('expected steering prompt') - const signal = new AbortController().signal - const batch = agent.inbox.claim('next-step', 1) - const decision = await agentEvents(ctx, agent).waterfall( - 'agent/pre-step', - { messages: batch, turn: 1, step: 1, signal }, - () => Promise.resolve({ kind: 'enter' as const, messages: batch }), - ) - expect(decision).toEqual({ kind: 'enter', messages: [context, steered] }) - expect(agent.inject).not.toHaveBeenCalled() - }) - - it('releases the admission listeners when the agent is disposed with the prompt pending', async () => { - const ctx = await harness() - const agent = stubAgent(ctx) - const source = 'source-session' as SessionId - const context = createUserMessage({ - source: { kind: 'plugin' as const, plugin: 'session-reference' }, - content: [{ type: 'text' as const, text: 'snapshot' }], - }) - ctx.provide('sessionReferenceResolver', { - prepare: () => Promise.resolve({ - content: [{ type: 'text' as const, text: 'continue @Research' }], - additionalContext: context, - }), - } as never) - const api = createApiProxy(ctx, DEFAULTS) - expectOk(await api.sessions.prompt(request({ - sessionId: agent.id, - content: [{ - type: 'text' as const, - text: `continue ${formatSessionReferenceMention({ sessionId: source, label: 'Research' })}`, - }], - mode: 'queue' as const, - }))) - const queued = agent.followup.mock.calls[0]?.[0] - if (queued === undefined) throw new Error('expected queued prompt') - - agentEvents(ctx, agent).emit('agent/disposed', { agent }) - - const signal = new AbortController().signal - const batch = agent.inbox.claim('next-turn', 1) - const decision = await agentEvents(ctx, agent).waterfall( - 'agent/pre-step', - { messages: batch, turn: 1, step: 1, signal }, - () => Promise.resolve({ kind: 'enter' as const, messages: batch }), - ) - expect(decision).toEqual({ kind: 'enter', messages: [queued] }) - }) - - it('keeps prepared context paired when a queued prompt moves to steering', async () => { - const ctx = await harness() - const agent = stubAgent(ctx, 'running') - const source = 'source-session' as SessionId - const context = createUserMessage({ - source: { - kind: 'session-reference' as const, - form: 'recall' as const, - version: 1 as const, - references: [{ - sessionId: source, - label: 'Research', - capturedThroughSeq: null, - compacted: false, - originalMessages: 1, - retainedMessages: 1, - omittedMessages: 0, - omittedBytes: 0, - truncated: false, - inputIndex: 0, - }], - }, - content: [{ type: 'text' as const, text: 'snapshot' }], - }) - ctx.provide('sessionReferenceResolver', { - prepare: () => Promise.resolve({ - content: [{ type: 'text' as const, text: 'continue @Research' }], - additionalContext: context, - }), - } as never) - const api = createApiProxy(ctx, DEFAULTS) - expect(expectOk(await api.sessions.prompt(request({ - sessionId: agent.id, - content: [{ - type: 'text' as const, - text: formatSessionReferenceMention({ sessionId: source, label: 'Research' }), - }], - mode: 'queue' as const, - })))).toEqual({ accepted: true }) - const queued = agent.inbox.nextTurn[0] - if (queued === undefined) throw new Error('expected queued reference prompt') - - expect(expectOk(await api.sessions.updateQueue(request({ - sessionId: agent.id, - itemId: queued.id, - action: { kind: 'steer' as const }, - })))).toEqual({ accepted: true }) - expect(agent.inbox.nextTurn).toEqual([]) - expect(agent.inbox.nextStep).toEqual([queued]) - - const signal = new AbortController().signal - const batch = agent.inbox.claim('next-step', 1) - const decision = await agentEvents(ctx, agent).waterfall( - 'agent/pre-step', - { messages: batch, turn: 1, step: 1, signal }, - () => Promise.resolve({ kind: 'enter' as const, messages: batch }), - ) - expect(decision).toEqual({ kind: 'enter', messages: [context, queued] }) - }) - - it.each(['queue', 'steer'] as const)( - 'does not deliver a %s prompt when preparation resolves after cancellation', - async (mode) => { - const ctx = await harness() - const agent = stubAgent(ctx) - const controller = new AbortController() - const source = 'source-session' as SessionId - const mention = formatSessionReferenceMention({ sessionId: source, label: 'Research' }) - ctx.provide('sessionReferenceResolver', { - prepare: async () => { - controller.abort() - return { - content: [{ type: 'text' as const, text: '@Research' }], - additionalContext: { - source: { - kind: 'session-reference' as const, - form: 'recall' as const, - version: 1 as const, - references: [{ - sessionId: source, - label: 'Research', - capturedThroughSeq: null, - compacted: false, - originalMessages: 1, - retainedMessages: 1, - omittedMessages: 0, - omittedBytes: 0, - truncated: false, - inputIndex: 0, - }], - }, - content: [{ type: 'text' as const, text: 'snapshot' }], - }, - } - }, - } as never) - const api = createApiProxy(ctx, DEFAULTS) - - const response = await api.sessions.prompt(request({ - sessionId: agent.id, - content: [{ type: 'text' as const, text: mention }], - mode, - }), controller.signal) - - expect(expectErr(response).code).toBe('cancelled') - expect(agent.followup).not.toHaveBeenCalled() - expect(agent.steer).not.toHaveBeenCalled() - expect(agent.inject).not.toHaveBeenCalled() - }, - ) - - it('rejects malformed mentions and preparation failures without enqueueing any prompt', async () => { - const ctx = await harness() - const agent = stubAgent(ctx) - const prepare = vi.fn(() => Promise.reject(new Error('snapshot unavailable'))) - ctx.provide('sessionReferenceResolver', { prepare } as never) - const api = createApiProxy(ctx, DEFAULTS) - - const malformed = await api.sessions.prompt(request({ - sessionId: agent.id, - content: [{ type: 'text' as const, text: '@[bad](dsh-session:not-canonical)' }], - mode: 'queue' as const, - })) - expect(expectErr(malformed).code).toBe('reference-invalid') - expect(prepare).not.toHaveBeenCalled() - expect(agent.followup).not.toHaveBeenCalled() - - const mention = formatSessionReferenceMention({ - sessionId: 'source-session' as SessionId, - label: 'Research', - }) - const failed = await api.sessions.prompt(request({ - sessionId: agent.id, - content: [{ type: 'text' as const, text: mention }], - mode: 'queue' as const, - })) - expect(expectErr(failed).code).toBe('reference-failed') - expect(agent.followup).not.toHaveBeenCalled() - expect(agent.steer).not.toHaveBeenCalled() - }) -}) diff --git a/packages/host/apiproxy/tests/rpc-schemas.spec.ts b/packages/host/apiproxy/tests/rpc-schemas.spec.ts index 8902715c6b..66ae52f783 100644 --- a/packages/host/apiproxy/tests/rpc-schemas.spec.ts +++ b/packages/host/apiproxy/tests/rpc-schemas.spec.ts @@ -73,9 +73,6 @@ describe('rpcErrorSchema', () => { details: { provider: 'p', model: 'm' }, }).code).toBe('model-unavailable') expect(rpcErrorSchema.parse({ code: 'agent-busy', message: 'm', details: { reason: 'r' } }).code).toBe('agent-busy') - expect(rpcErrorSchema.parse({ code: 'reference-unavailable', message: 'm', details: { kind: 'session' } }).code).toBe('reference-unavailable') - expect(rpcErrorSchema.parse({ code: 'reference-invalid', message: 'm', details: { reason: 'r' } }).code).toBe('reference-invalid') - expect(rpcErrorSchema.parse({ code: 'reference-failed', message: 'm', details: { reason: 'r' } }).code).toBe('reference-failed') expect(rpcErrorSchema.parse({ code: 'queue-item-not-found', message: 'm', details: { itemId: 'i' } }).code).toBe('queue-item-not-found') expect(rpcErrorSchema.parse({ code: 'command-error', message: 'm', details: {} }).code).toBe('command-error') expect(rpcErrorSchema.parse({ code: 'unknown-command', message: 'm', details: {} }).code).toBe('unknown-command') @@ -87,8 +84,6 @@ describe('rpcErrorSchema', () => { it('rejects a known code with missing details', () => { expect(() => rpcErrorSchema.parse({ code: 'agent-busy', message: 'm', details: {} })).toThrow() - expect(() => rpcErrorSchema.parse({ code: 'reference-unavailable', message: 'm', details: { kind: 'disk' } })).toThrow() - expect(() => rpcErrorSchema.parse({ code: 'reference-failed', message: 'm', details: {} })).toThrow() expect(() => rpcErrorSchema.parse({ code: 'title-invalid', message: 'm', details: {} })).toThrow() expect(() => rpcErrorSchema.parse({ code: 'command-error', message: 'm' })).toThrow() expect(() => rpcErrorSchema.parse({ code: 'nope', message: 'm', details: {} })).toThrow() diff --git a/packages/host/apiproxy/tsconfig.json b/packages/host/apiproxy/tsconfig.json index 6abd799c0e..3ed38b3d77 100644 --- a/packages/host/apiproxy/tsconfig.json +++ b/packages/host/apiproxy/tsconfig.json @@ -62,9 +62,6 @@ { "path": "../../session-query/session-query" }, - { - "path": "../../context/session-reference" - }, { "path": "../../session/session-title" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31610647f4..7fa0b63991 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4862,9 +4862,6 @@ importers: '@deepseek-ai/dsh-session-query': specifier: workspace:^ version: link:../../session-query/session-query - '@deepseek-ai/dsh-session-reference': - specifier: workspace:^ - version: link:../../context/session-reference '@deepseek-ai/dsh-session-title': specifier: workspace:^ version: link:../../session/session-title