diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index dff7978387..fe32a219de 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.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/architecture.md -architecture.md: 506283bfc651d38fe4bccd50fb8a86143d2b41d8 -architecture.zh.md: ea48af8131ebbdc70a45e403dfddc78dfcfb6f47 +architecture.md: 771d7489ee338db56362a6ccc133b1ebf8cdc7c0 +architecture.zh.md: ca7c4fe2a463e01a8e14e63a53f13aea45cbfa16 diff --git a/docs/architecture.md b/docs/architecture.md index 506283bfc6..771d7489ee 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -183,10 +183,11 @@ New behavior attaches to a documented extension point; a loop change updates thi | Intercept a request, tool, or turn | use its `agent/*` or `tools/*` event; `agent/turn-stopping` is the stop boundary | | Add model-facing context | call `agent.inject()` to queue sourced context for the next admitted request | | Add UI or editor integration | drive `ctx.agents` and render from `session/event` | +| Web Client Chat node | register a `ConversationNodeDefinition` + keyed renderer | | Add durable session state | extend `SessionEventMap`; render and replay from the log | | Add asynchronous session-title generation | register the sole `ctx.sessionTitle` provider | | Manage a same-session objective | use `ctx.goals`; continue through `Agent` and `agent/*` | | Fork a live session | call `ctx.sessions.fork(source, boundary?, childSessionId?)` | | Scope a registration to one agent | use its `agent.ctx` (see Agent Scope) | -The [extension cookbook](cookbook/extension-cookbook.md) maps features to capabilities; guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [vendored packages](cookbook/adding-a-vendored-package.md). +[Extension cookbook](cookbook/extension-cookbook.md) maps features to capabilities; guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), [Chat nodes](cookbook/adding-a-conversation-node.md), and [vendored packages](cookbook/adding-a-vendored-package.md). diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index ea48af8131..ca7c4fe2a4 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -183,10 +183,11 @@ idle inject: | 拦截请求、工具或轮次 | 使用相应的 `agent/*` 或 `tools/*` 事件;`agent/turn-stopping` 是停止边界 | | 添加模型可见上下文 | 调用 `agent.inject()`,将带来源的上下文排入下一次获准请求 | | 添加 UI 或编辑器集成 | 驱动 `ctx.agents` 并从 `session/event` 渲染 | +| Web Client Chat 节点 | 注册 `ConversationNodeDefinition` + keyed renderer | | 添加持久会话状态 | 扩展 `SessionEventMap`;从日志渲染和回放 | | 添加异步会话标题生成 | 注册唯一的 `ctx.sessionTitle` 提供方 | | 管理同会话目标 | 使用 `ctx.goals`;通过 `Agent` 和 `agent/*` 续跑 | | fork 活跃会话 | 调用 `ctx.sessions.fork(source, boundary?, childSessionId?)` | | 将注册项限定到单个 agent | 使用其 `agent.ctx`(参见 Agent 作用域) | -[扩展实操手册(cookbook)](cookbook/extension-cookbook.md)将功能映射到能力;指南涵盖[包](cookbook/adding-a-package.md)、[工具](cookbook/adding-a-tool.md)、[LLM 适配器](cookbook/adding-an-llm-adapter.md)和 [vendored 包](cookbook/adding-a-vendored-package.md)。 +[扩展实操手册](cookbook/extension-cookbook.md)将功能映射到能力;指南涵盖[包](cookbook/adding-a-package.md)、[工具](cookbook/adding-a-tool.md)、[LLM 适配器](cookbook/adding-an-llm-adapter.md)、[Chat 节点](cookbook/adding-a-conversation-node.md)和 [vendored 包](cookbook/adding-a-vendored-package.md)。 diff --git a/docs/cookbook/adding-a-conversation-node.i18n.yaml b/docs/cookbook/adding-a-conversation-node.i18n.yaml new file mode 100644 index 0000000000..aa268e9461 --- /dev/null +++ b/docs/cookbook/adding-a-conversation-node.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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/cookbook/adding-a-conversation-node.md +adding-a-conversation-node.md: ea4ec73eb109af6b0e4c7cf50fc8692942c75dd4 +adding-a-conversation-node.zh.md: 4b9a8049e2f1d060ec4bc3334036559b989ea562 diff --git a/docs/cookbook/adding-a-conversation-node.md b/docs/cookbook/adding-a-conversation-node.md new file mode 100644 index 0000000000..ea4ec73eb1 --- /dev/null +++ b/docs/cookbook/adding-a-conversation-node.md @@ -0,0 +1,232 @@ +# Add a Web Client conversation node + +English | [中文](adding-a-conversation-node.zh.md) + +This tutorial adds one business-owned row to the Web Client Chat view. The finished plugin correlates a durable Session event family into one Context, incrementally builds business State, publishes typed Step data, and renders a keyed Chat Node without scanning the Session window or other rendered nodes. It assumes the Host already records the events and the client plugin is composed into the Web bundle; external Host-side UIs and additional view targets such as Trajectory are outside this tutorial. + +The [Conversation Node assembly decision](../../.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md) owns the rationale and complete engine model. This guide covers the implementation path. + +## 1. Design a replayable event family + +Choose one stable business id before writing the Definition. Every event that contributes to the same Node must carry that id or derive it independently from its own payload; the client must never assign an update to “the latest unfinished” Context. + +For a review job, the event contract could be: + +| Event | Role | Required durable facts | +|---|---|---| +| `review/start` | unique start | `reviewId`, Turn/Step coordinates, title | +| `review/progress` | update | the same `reviewId`, coordinates, replayable progress | +| `review/end` | update | the same `reviewId`, coordinates, final summary | + +Use the producer-owned branded id type across the process boundary. Put the `SessionEventMap` merge and payload types on the producer's type-only export, then import that export for side effects from the client package. Each `(kind, id)` may have at most one start event. A single-event business can use the event's stable identity, such as `event.seq`, as its Definition-local id. + +Incremental events are supported. Prefer whole-value checkpoints when the producer can emit them cheaply, because they remain useful when the start is outside the loaded window. Each delta must carry the stable id and produce deterministic State when replayed in ascending log `seq`; it must not depend on live-only memory. If the current history window contains only updates, the assembler keeps a pending Context and builds no State until an older page supplies the start. If the product must render before the start is loaded, a terminal or checkpoint event must carry enough whole fallback state for the Definition to build that result directly; do not recover it by scanning unrelated events. + +## 2. Implement the Definition and typed Chat payload + +The example keeps the producer declarations and client contribution in one block so the complete relationship is visible. In a package family, keep the branded id and `SessionEventMap` declaration with the event producer, and keep the Definition, Chat data merge, and renderer in the client plugin. + +```ts ignore-check +import { createElement } from 'react' +import type { Branded } from '@deepseek-ai/dsh-brand' +import type { + ClientContext, ConversationLocation, ConversationNodeContext, + ConversationNodeDefinition, +} from '@deepseek-ai/dsh-client-runtime/client' +import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client' + +type ReviewId = Branded<'ReviewId'> + +interface ReviewStartData { + readonly reviewId: ReviewId + readonly turn: number + readonly step: number + readonly title: string +} + +interface ReviewProgressData { + readonly reviewId: ReviewId + readonly turn: number + readonly step: number + readonly completed: number +} + +interface ReviewEndData { + readonly reviewId: ReviewId + readonly turn: number + readonly step: number + readonly summary: string +} + +declare module '@deepseek-ai/dsh-session/types' { + interface SessionEventMap { + /** + * Opens one durable review job. + * @mode emit + * @param data - stable identity, location, and initial display state. + */ + 'review/start': ReviewStartData + /** + * Records replayable progress for one review job. + * @mode emit + * @param data - stable identity, location, and latest progress. + */ + 'review/progress': ReviewProgressData + /** + * Closes one review job with its final summary. + * @mode emit + * @param data - stable identity, location, and final display state. + */ + 'review/end': ReviewEndData + } +} + +interface ReviewChatData { + readonly title: string + readonly completed: number + readonly status: 'running' | 'completed' + readonly summary?: string +} + +declare module '@deepseek-ai/dsh-client-ui-conversation/client' { + interface ChatNodeDataMap { + 'review-job': ReviewChatData + } +} + +declare module '@deepseek-ai/dsh-client-runtime/client' { + interface ConversationStepDataMap { + 'review-job': ReviewChatData + } +} + +interface ReviewState extends ReviewChatData { + readonly turn: number + readonly step: number +} + +function locationOf(context: ConversationNodeContext): ConversationLocation { + return context.start?.location ?? context.matches[0]?.location ?? { kind: 'unresolved' } +} + +function viewData(state: ReviewState): ReviewChatData { + return { + title: state.title, + completed: state.completed, + status: state.status, + ...state.summary === undefined ? {} : { summary: state.summary }, + } +} + +const reviewDefinition: ConversationNodeDefinition = { + kind: 'review-job', + match: (event) => { + if (event.type === 'review/start') { + return { id: String(event.data.reviewId), role: 'start' } + } + if (event.type === 'review/progress' || event.type === 'review/end') { + return { id: String(event.data.reviewId), role: 'update' } + } + return null + }, + start: (_context, match) => { + if (match.event.type !== 'review/start') throw new Error('review-job requires review/start') + return { + turn: match.event.data.turn, + step: match.event.data.step, + title: match.event.data.title, + completed: 0, + status: 'running', + } + }, + update: (context, match) => { + if (match.event.type === 'review/progress') { + return { ...context.state, completed: match.event.data.completed } + } + if (match.event.type === 'review/end') { + return { ...context.state, completed: 100, status: 'completed', summary: match.event.data.summary } + } + return context.state + }, + publication: match => match.event.type === 'review/progress' + ? 'animation-frame' + : 'immediate', + buildLocationData: (context, scope) => { + if (scope !== 'step' || context.state === undefined) return null + return { + kind: 'step', + turn: context.state.turn, + step: context.state.step, + key: 'review-job', + value: viewData(context.state), + } + }, + buildViewNode: (context, target) => { + if (target !== 'chat' || context.state === undefined) return null + return { + key: context.key, + kind: 'review-job', + id: context.id, + target: 'chat', + anchorSeq: context.start?.event.seq ?? context.matches[0]?.event.seq ?? 0, + location: locationOf(context), + visibility: 'visible', + data: viewData(context.state), + } + }, +} + +function ReviewNodeView({ node }: ChatNodeViewProps<'review-job'>) { + const text = node.data.summary ?? `${node.data.title}: ${node.data.completed}%` + return createElement('p', null, text) +} + +export const inject = ['conversationEvents', 'slots'] + +export function apply(ctx: ClientContext): void { + ctx.conversationEvents.register(reviewDefinition) + ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ + name: 'conversation.chat.node', + key: 'review-job', + }, ReviewNodeView)) +} +``` + +`match(event)` is an identity extractor, not a fold: it receives only the current event and returns the Definition-local id and lifecycle role. After a match, the assembler locates the Context by `(kind, id)` and calls `start` once or `update` with the current State. Both functions return the State that the engine adopts; returning a new immutable value is preferred, but a function that mutates and returns the same object has the same adoption semantics. + +`buildLocationData(context, scope)` optionally publishes Definition-owned data onto an engine-owned Turn or Step. Use declaration merging to give each key a precise value type. Another Node in the same Location can consume that value through its constrained slot hook, such as `useTurnData(key)`, without receiving the Session or scanning `snapshot.chat.nodes`. + +`buildViewNode(context, target)` materializes the final target-specific Node. Preserve `context.key` as the React-facing identity, choose `anchorSeq` from durable ordering evidence, and return only renderer-ready data. Once a target Node has been published, keep returning the same key; use `visibility: 'hidden'` when it must temporarily leave the visible flow rather than withdrawing it with `null`. + +## 3. Query an earlier business Context only at start + +Some Definitions need the latest earlier State of another business kind. `start` receives a `ConversationContextReader`; call `reader.previous(kind)` there instead of accepting a Context collection or scanning events. The reader returns the nearest started Context before the current start `seq` as read-only data. + +The assembler records that dependency. If an older prepend later supplies a nearer predecessor, closes a previously unknown window gap, or revises the predecessor State, it reruns the dependent Context from `start` and replays its updates in ascending `seq`. The queried Definition remains responsible for writing useful State; the reader exposes no business-specific query methods and grants no mutation authority over another Context. + +## 4. Understand the three ingestion paths + +History may be requested from the tail backward one page at a time, but every accepted page is normalized into ascending `seq` before State replay. + +| Path | Engine work | Definition-visible behavior | +|---|---|---| +| Replace on open, resync, or gap repair | Rebuild the loaded window, match every event once per Definition, then replay each started Context | `start`, followed by its updates in ascending `seq`; pending update-only Contexts remain without State | +| Prepend one older page | Match only fresh older events, merge them into Contexts by `(kind, id)`, preserve existing keyed nodes, and replay only affected Contexts and dependencies | A newly found start activates its collected updates; a changed Location or predecessor may rerun the Context | +| Append one live event | Call each Definition's `match` once, look up the matched Context by key, and update only that Context | One `update` and one requested publication for a matching post-start event; no existing Context scan | + +With `D` registered Definitions, one incoming event performs `D` current-event matches and constant-time Context-key lookup after a match. Definition code must preserve that property: do not traverse the complete event window, every Context, `context.matches`, or the rendered Node collection on the normal append path. Use State for accumulated facts, Location data for same-Turn/Step sharing, and `reader.previous()` for indexed predecessor dependencies. + +`publication` controls when changed State is materialized. Use `immediate` for structural or terminal changes, `animation-frame` for high-frequency visible deltas, and `none` when the State change feeds only a later publication. The engine still applies every update in log order; cadence only coalesces view publication. + +## 5. Verify replay, pagination, and rendering + +Add focused tests that establish these outcomes: + +1. A complete window passed through replace produces the expected final State, Location data, Node payload, and `anchorSeq`. +2. An update-only tail stays pending; prepending the unique start produces the same result as a complete replace. +3. Initial history followed by live append produces the same result as replaying the combined window. +4. Prepending an older page adds earlier rows without replacing existing keyed Node values whose data did not change. +5. Repeated visible deltas preserve `context.key` and publish at most once per animation frame when requested. +6. The keyed renderer consumes `node.data` and constrained Location hooks only; it does not scan the Session event window, Contexts, or Chat Nodes. + +Use [`packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts`](../../packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts) for streaming and interruption, [`inbox.ts`](../../packages/client/ui-conversation/src/client/conversation-nodes/inbox.ts) plus [`message.ts`](../../packages/client/ui-conversation/src/client/conversation-nodes/message.ts) for predecessor queries, and [`packages/client/ui-deliverables`](../../packages/client/ui-deliverables) for a Definition that publishes Turn data without creating its own Node. diff --git a/docs/cookbook/adding-a-conversation-node.zh.md b/docs/cookbook/adding-a-conversation-node.zh.md new file mode 100644 index 0000000000..4b9a8049e2 --- /dev/null +++ b/docs/cookbook/adding-a-conversation-node.zh.md @@ -0,0 +1,232 @@ +# 添加 Web Client Conversation Node + +[English](adding-a-conversation-node.md) | 中文 + +本教程为 Web Client Chat 视图添加一行由业务自行拥有的内容。完成后的插件会把一个持久 Session 事件族关联成一个 Context,增量构造业务 State,发布类型化 Step 数据,再渲染 keyed Chat Node;整个过程不扫描 Session 窗口或其他已渲染节点。本教程假设 Host 已经记录这些事件,且该 Client 插件已组装进 Web bundle;Host 侧外部 UI 和 Trajectory 等额外视图目标不在本文范围内。 + +[Conversation Node 组装决策](../../.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md)记录完整的引擎模型和设计理由;本文只说明实现路径。 + +## 1. 设计可回放的事件族 + +编写 Definition 前先选定稳定的业务 id。构成同一个 Node 的每条事件都必须携带该 id,或只凭自身 payload 独立推导出该 id;Client 绝不能把 update 猜测为属于“最近一个未完成”的 Context。 + +以一个 review job 为例,事件约定可以是: + +| 事件 | 角色 | 必须持久化的事实 | +|---|---|---| +| `review/start` | 唯一 start | `reviewId`、Turn/Step 坐标、标题 | +| `review/progress` | update | 相同的 `reviewId`、坐标、可回放进度 | +| `review/end` | update | 相同的 `reviewId`、坐标、最终摘要 | + +跨进程边界使用生产方拥有的 branded id 类型。把 `SessionEventMap` 合并和 payload 类型放在生产方的纯类型导出中,再由 Client 包通过仅类型副作用导入该导出。每个 `(kind, id)` 最多只能有一条 start 事件。单事件业务可以把事件自身的稳定身份(例如 `event.seq`)作为 Definition 内部 id。 + +系统支持增量事件。如果生产方能以较低成本发出 whole-value checkpoint,应优先采用,因为 start 位于已加载窗口之外时它仍可直接使用。每条 delta 都必须携带稳定 id,并且按照日志 `seq` 升序回放时能够确定性地产生 State;它不能依赖只存在于实时内存中的状态。如果当前历史窗口只有 update,Assembler 会保留一个 pending Context,并在更早分页补齐 start 前不构造 State。如果产品必须在 start 尚未加载时渲染,terminal 或 checkpoint 事件就必须携带足够的完整 fallback 状态,让 Definition 能直接构造结果;不要通过扫描无关事件恢复它。 + +## 2. 实现 Definition 与类型化 Chat payload + +为了完整展示关联关系,下面把生产方声明和 Client 贡献写在同一个代码块里。实际的包族中,branded id 与 `SessionEventMap` 声明留在事件生产方,Definition、Chat data 合并与 renderer 留在 Client 插件。 + +```ts ignore-check +import { createElement } from 'react' +import type { Branded } from '@deepseek-ai/dsh-brand' +import type { + ClientContext, ConversationLocation, ConversationNodeContext, + ConversationNodeDefinition, +} from '@deepseek-ai/dsh-client-runtime/client' +import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client' + +type ReviewId = Branded<'ReviewId'> + +interface ReviewStartData { + readonly reviewId: ReviewId + readonly turn: number + readonly step: number + readonly title: string +} + +interface ReviewProgressData { + readonly reviewId: ReviewId + readonly turn: number + readonly step: number + readonly completed: number +} + +interface ReviewEndData { + readonly reviewId: ReviewId + readonly turn: number + readonly step: number + readonly summary: string +} + +declare module '@deepseek-ai/dsh-session/types' { + interface SessionEventMap { + /** + * Opens one durable review job. + * @mode emit + * @param data - stable identity, location, and initial display state. + */ + 'review/start': ReviewStartData + /** + * Records replayable progress for one review job. + * @mode emit + * @param data - stable identity, location, and latest progress. + */ + 'review/progress': ReviewProgressData + /** + * Closes one review job with its final summary. + * @mode emit + * @param data - stable identity, location, and final display state. + */ + 'review/end': ReviewEndData + } +} + +interface ReviewChatData { + readonly title: string + readonly completed: number + readonly status: 'running' | 'completed' + readonly summary?: string +} + +declare module '@deepseek-ai/dsh-client-ui-conversation/client' { + interface ChatNodeDataMap { + 'review-job': ReviewChatData + } +} + +declare module '@deepseek-ai/dsh-client-runtime/client' { + interface ConversationStepDataMap { + 'review-job': ReviewChatData + } +} + +interface ReviewState extends ReviewChatData { + readonly turn: number + readonly step: number +} + +function locationOf(context: ConversationNodeContext): ConversationLocation { + return context.start?.location ?? context.matches[0]?.location ?? { kind: 'unresolved' } +} + +function viewData(state: ReviewState): ReviewChatData { + return { + title: state.title, + completed: state.completed, + status: state.status, + ...state.summary === undefined ? {} : { summary: state.summary }, + } +} + +const reviewDefinition: ConversationNodeDefinition = { + kind: 'review-job', + match: (event) => { + if (event.type === 'review/start') { + return { id: String(event.data.reviewId), role: 'start' } + } + if (event.type === 'review/progress' || event.type === 'review/end') { + return { id: String(event.data.reviewId), role: 'update' } + } + return null + }, + start: (_context, match) => { + if (match.event.type !== 'review/start') throw new Error('review-job requires review/start') + return { + turn: match.event.data.turn, + step: match.event.data.step, + title: match.event.data.title, + completed: 0, + status: 'running', + } + }, + update: (context, match) => { + if (match.event.type === 'review/progress') { + return { ...context.state, completed: match.event.data.completed } + } + if (match.event.type === 'review/end') { + return { ...context.state, completed: 100, status: 'completed', summary: match.event.data.summary } + } + return context.state + }, + publication: match => match.event.type === 'review/progress' + ? 'animation-frame' + : 'immediate', + buildLocationData: (context, scope) => { + if (scope !== 'step' || context.state === undefined) return null + return { + kind: 'step', + turn: context.state.turn, + step: context.state.step, + key: 'review-job', + value: viewData(context.state), + } + }, + buildViewNode: (context, target) => { + if (target !== 'chat' || context.state === undefined) return null + return { + key: context.key, + kind: 'review-job', + id: context.id, + target: 'chat', + anchorSeq: context.start?.event.seq ?? context.matches[0]?.event.seq ?? 0, + location: locationOf(context), + visibility: 'visible', + data: viewData(context.state), + } + }, +} + +function ReviewNodeView({ node }: ChatNodeViewProps<'review-job'>) { + const text = node.data.summary ?? `${node.data.title}: ${node.data.completed}%` + return createElement('p', null, text) +} + +export const inject = ['conversationEvents', 'slots'] + +export function apply(ctx: ClientContext): void { + ctx.conversationEvents.register(reviewDefinition) + ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ + name: 'conversation.chat.node', + key: 'review-job', + }, ReviewNodeView)) +} +``` + +`match(event)` 是身份提取器,不是 fold:它只能收到当前事件,并返回 Definition 内部 id 与生命周期角色。命中后,Assembler 通过 `(kind, id)` 定位 Context,再调用一次 `start`,或把当前 State 交给 `update`。两个函数都必须返回引擎随后采用的 State;推荐返回新的 immutable value,但函数原地修改后返回同一对象时,采用语义也相同。 + +`buildLocationData(context, scope)` 可以把 Definition 拥有的数据发布到引擎拥有的 Turn 或 Step 上。通过 declaration merging 为每个 key 指定精确 value 类型。同一 Location 内的另一个 Node 可以使用受限 slot hook(例如 `useTurnData(key)`)读取该值,无须取得 Session,也无须扫描 `snapshot.chat.nodes`。 + +`buildViewNode(context, target)` 物化最终的目标专用 Node。把 `context.key` 保留为 React 侧身份,根据持久排序证据选择 `anchorSeq`,并且只返回 renderer 可以直接使用的数据。某个 target Node 一旦发布,就要继续返回同一个 key;需要暂时离开可见流时使用 `visibility: 'hidden'`,不要改为返回 `null` 撤回它。 + +## 3. 只在 start 时查询更早的业务 Context + +有些 Definition 需要另一个业务 kind 在当前位置之前的最新 State。`start` 会收到 `ConversationContextReader`;应在这里调用 `reader.previous(kind)`,不要接收 Context 集合或扫描事件。Reader 返回当前 start `seq` 之前最近一个已启动 Context 的只读数据。 + +Assembler 会记录这项依赖。如果后续 older prepend 带来了更近的前序 Context、补齐了原先未知的窗口缺口,或者前序 State 被修订,引擎会从 `start` 重新运行依赖方 Context,并按 `seq` 升序回放其 update。被查询的 Definition 仍负责把有用信息写入自身 State;Reader 不提供业务专用查询方法,也不授予修改其他 Context 的权限。 + +## 4. 理解三条摄入路径 + +历史可能从尾部开始一页一页向前请求,但每个已接收分页都会先按 `seq` 升序归一化,再进入 State 回放。 + +| 路径 | 引擎工作 | Definition 可观察到的行为 | +|---|---|---| +| open、resync 或 gap repair 时 replace | 重建已加载窗口,每条事件对每个 Definition 匹配一次,再回放每个已有 start 的 Context | 先执行 `start`,再按 `seq` 升序执行其 update;只有 update 的 pending Context 仍没有 State | +| prepend 一页更早历史 | 只匹配新增的更早事件,按 `(kind, id)` 合并进 Context,保留现有 keyed node,并只重放受影响的 Context 与依赖 | 新发现的 start 会激活已收集 update;Location 或前序依赖变化也可能重跑 Context | +| append 一条实时事件 | 每个 Definition 各调用一次 `match`,按 key 查找命中的 Context,只更新该 Context | 对 start 之后的匹配事件执行一次 `update` 并请求一次发布;不扫描已有 Context | + +注册 `D` 个 Definition 时,一条新事件会进行 `D` 次仅当前事件匹配;命中后的 Context key 查询是常数时间。Definition 代码必须维持这个性质:正常 append 热路径不得遍历完整事件窗口、所有 Context、`context.matches` 或已渲染 Node 集合。累计事实放进 State,同 Turn/Step 共享信息放进 Location data,有索引的前序依赖使用 `reader.previous()`。 + +`publication` 控制发生 State 变更后何时物化。结构或 terminal 变化使用 `immediate`,高频可见 delta 使用 `animation-frame`,只为后续发布积累 State 时使用 `none`。引擎仍会按日志顺序应用每条 update;该选项只合并视图发布频率。 + +## 5. 验证回放、分页与渲染 + +添加聚焦测试,证明以下结果: + +1. 完整窗口通过 replace 后产生预期的最终 State、Location data、Node payload 与 `anchorSeq`。 +2. 只有 update 的尾部窗口保持 pending;prepend 唯一 start 后,结果与完整 replace 相同。 +3. 初始历史后继续实时 append,与回放合并后的完整窗口得到相同结果。 +4. prepend 更早分页只增加更早的行;数据未变化的既有 keyed Node value 不被替换。 +5. 重复的可见 delta 保持 `context.key`,并在请求 `animation-frame` 时每帧最多发布一次。 +6. keyed renderer 只消费 `node.data` 与受限 Location hook,不扫描 Session 事件窗口、Context 或 Chat Node。 + +流式与中断处理可参考 [`packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts`](../../packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts),前序查询可参考 [`inbox.ts`](../../packages/client/ui-conversation/src/client/conversation-nodes/inbox.ts) 与 [`message.ts`](../../packages/client/ui-conversation/src/client/conversation-nodes/message.ts),只发布 Turn data 而不创建自有 Node 的例子见 [`packages/client/ui-deliverables`](../../packages/client/ui-deliverables)。 diff --git a/docs/cookbook/extension-cookbook.i18n.yaml b/docs/cookbook/extension-cookbook.i18n.yaml index 0509a67549..7eaa702a9d 100644 --- a/docs/cookbook/extension-cookbook.i18n.yaml +++ b/docs/cookbook/extension-cookbook.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/cookbook/extension-cookbook.md -extension-cookbook.md: 025a1b6ecd11593b5d6be9d0f64e57ac8b5e3139 -extension-cookbook.zh.md: f838a281fabdba473b72b27f7b14274d9aa97528 +extension-cookbook.md: 5d9312f2f5cf840100b12045bde1829b342e580d +extension-cookbook.zh.md: 29bb57c558a0ff9413619d0bb2e8bb5e6b70da56 diff --git a/docs/cookbook/extension-cookbook.md b/docs/cookbook/extension-cookbook.md index 025a1b6ecd..5d9312f2f5 100644 --- a/docs/cookbook/extension-cookbook.md +++ b/docs/cookbook/extension-cookbook.md @@ -34,7 +34,7 @@ This waterfall is the reorderable policy layer. Use `ctx.tools.guard()` when an ## A UI plugin -A UI plugin renders from the `session/event` feed (the assistant token stream as `assistant/chunk`, plus turn/step boundaries and tool activity), and drives input back in via `agent.followup()` / `agent.steer()`. +A UI plugin renders from the `session/event` feed (the assistant token stream as `assistant/chunk`, plus turn/step boundaries and tool activity), and drives input back in via `agent.followup()` / `agent.steer()`. A browser plugin contributing a business row to the built-in Web Client instead registers a `ConversationNodeDefinition` and keyed Chat renderer; follow the [Conversation Node guide](adding-a-conversation-node.md). ```ts import type { Context } from 'cordis' @@ -123,6 +123,7 @@ Every product feature maps to a listener on a documented extension point — the | Memory | section provider + tool | | Scheduled tasks (cron) | a plugin registers model-callable scheduling tools; timer fires → `followup(…, {source: {kind: 'cron', …}})` when idle / `inject()` notification when busy | | UI (GUI; CLI emits JSONL) | listen `session/event` (assistant chunks, boundaries, tool activity); input → `followup()` | +| Web Client Chat business node | register a `ConversationNodeDefinition` and `conversation.chat.node` keyed renderer | | Telemetry / replayable trace | `session/event` → JSONL; replay = `sessions.create(id, { seed })` | | Model adapters | `LlmAdapter` subclass via `registerAdapter` (`dsh-llm-deepseek`, `dsh-llm-pi-ai`) | | Plugin hot-reload | every registration is a `ctx.effect` → vendored HMR just works | diff --git a/docs/cookbook/extension-cookbook.zh.md b/docs/cookbook/extension-cookbook.zh.md index f838a281fa..29bb57c558 100644 --- a/docs/cookbook/extension-cookbook.zh.md +++ b/docs/cookbook/extension-cookbook.zh.md @@ -34,7 +34,7 @@ export function apply(ctx: Context) { ## UI 插件 -UI 插件从 `session/event` 事件流渲染(助手 token 流以 `assistant/chunk` 形式到达,加上轮次/步骤边界与工具活动),并通过 `agent.followup()` / `agent.steer()` 将输入驱动回去。 +UI 插件从 `session/event` 事件流渲染(助手 token 流以 `assistant/chunk` 形式到达,加上轮次/步骤边界与工具活动),并通过 `agent.followup()` / `agent.steer()` 将输入驱动回去。如果浏览器插件要向内建 Web Client 贡献业务行,则应注册 `ConversationNodeDefinition` 与 keyed Chat renderer;具体步骤见 [Conversation Node 指南](adding-a-conversation-node.md)。 ```ts import type { Context } from 'cordis' @@ -123,6 +123,7 @@ export function apply(ctx: Context) { | 记忆 | section 提供方 + 工具 | | 定时任务(cron) | 插件注册面向模型的调度工具;定时器触发 → 空闲时 `followup(…, {source: {kind: 'cron', …}})`/忙碌时 `inject()` 通知 | | UI(GUI;CLI(命令行界面)输出 JSONL) | 监听 `session/event`(助手分片、边界、工具活动);输入 → `followup()` | +| Web Client Chat 业务节点 | 注册 `ConversationNodeDefinition` 与 `conversation.chat.node` keyed renderer | | 遥测 / 可回放 trace | `session/event` → JSONL;回放 = `sessions.create(id, { seed })` | | 模型适配器 | 通过 `registerAdapter` 注册 `LlmAdapter` 子类(`dsh-llm-deepseek`、`dsh-llm-pi-ai`) | | 插件热重载 | 每个注册都是一个 `ctx.effect` → 随仓库提供的 HMR(热模块替换)直接生效 | diff --git a/docs/event-producer-consumer.i18n.yaml b/docs/event-producer-consumer.i18n.yaml index 812b490bef..89e1b03d35 100644 --- a/docs/event-producer-consumer.i18n.yaml +++ b/docs/event-producer-consumer.i18n.yaml @@ -3,4 +3,4 @@ # 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: 05ff62a391a0acb7db9abd2c9ce0c2082da52eaa -event-producer-consumer.zh.md: 2d4c805f9a5d3b0531ee59eebe9e6564347f0a00 +event-producer-consumer.zh.md: 7eafe5f2880cf9f0e6f2e7eb6c299730926f15ed diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 2d4c805f9a..7eafe5f288 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -10,20 +10,20 @@ | 事件 | 模式 | 声明位置 | 派发方 | 监听方 | | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:182`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | -| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:158`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`goal-session`](../packages/goal/goal-session) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:167`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`goal-session`](../packages/goal/goal-session), [`subagent`](../packages/subagent/subagent) | -| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:289`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `apiproxy`, [`goal-session`](../packages/goal/goal-session), [`session-telemetry`](../packages/session/session-telemetry) | -| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/types.ts:196`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-session`](../packages/goal/goal-session), [`subagent`](../packages/subagent/subagent) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/types.ts:204`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-session`](../packages/goal/goal-session), [`subagent`](../packages/subagent/subagent) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/types.ts:185`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-session`](../packages/goal/goal-session) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/types.ts:230`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) | -| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:243`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) | -| `agent/request-error` | `waterfall` | [`packages/core/agent/src/types.ts:259`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:216`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | -| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:177`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `apiproxy`, [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), `server` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/types.ts:277`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:159`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`goal-session`](../packages/goal/goal-session) | +| `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), [`goal-session`](../packages/goal/goal-session), [`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-session`](../packages/goal/goal-session), [`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-session`](../packages/goal/goal-session), [`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-session`](../packages/goal/goal-session), [`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-session`](../packages/goal/goal-session) | +| `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`) | [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) | +| `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`) | [`compact-basic`](../packages/compact/compact-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-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:178`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `apiproxy`, [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), `server` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:278`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/index.ts:30`](../packages/interaction/user-approval/src/index.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `apiproxy` | -| `commands/change` | `emit` | [`packages/interaction/commands/src/index.ts:172`](../packages/interaction/commands/src/index.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `apiproxy` | +| `commands/change` | `emit` | [`packages/interaction/commands/src/index.ts:134`](../packages/interaction/commands/src/index.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `apiproxy` | | `credentials/updated` | `emit` | [`packages/credentials/credentials/src/index.ts:67`](../packages/credentials/credentials/src/index.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) | | `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) | | `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:66`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) | @@ -46,12 +46,12 @@ | `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:29`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`system-prompt`](../packages/core/system-prompt) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:35`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | | `telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:43`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | -| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:191`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - | -| `tools/code-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:173`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | -| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:148`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`timeout-policy`](../packages/guard/timeout-policy) | -| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:160`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | -| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:137`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-tasks`](../packages/tasks/tool-tasks) | -| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:181`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`workspace-context`](../packages/context/workspace-context) | +| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:192`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - | +| `tools/code-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:174`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | +| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:149`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`timeout-policy`](../packages/guard/timeout-policy) | +| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:161`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | +| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:138`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-tasks`](../packages/tasks/tool-tasks) | +| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:182`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`workspace-context`](../packages/context/workspace-context) | | `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:81`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | | `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:70`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | | `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:91`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | @@ -59,9 +59,9 @@ | `workflow/phase` | `emit` | [`packages/workflow/workflow/src/index.ts:53`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:45`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | -## 包源码中出现的非 harness 或未声明事件字符串 +## Non-harness or undeclared event strings seen in package source -| 事件字符串 | 派发方 | 监听方 | +| Event string | Dispatchers | Listeners | | --- | --- | --- | | `commands/changed` | `runtime` (`emit`) | `ui-command` | | `connection/reset` | `runtime` (`emit`) | `ui-command`, `ui-models`, `ui-permission`, `ui-settings-general` | @@ -80,4 +80,4 @@ | `slots/changed` | `runtime` (`emit`) | - | | `theme/change` | `ui-theme` (`emit`) | `ui-layout`, `ui-theme` | -维护模式:英文源文件是生成内容,Cordis 事件声明及生产方/监听方的关系边由仓库的 TypeScript Program 解析;本中文文件作为经评审对侧通过双语配对维护。 +Maintenance mode: generated: Cordis event declarations and producer/listener edges are resolved from the repository TypeScript Program. diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index 24ddaad77c..61e55cd997 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.i18n.yaml @@ -3,4 +3,4 @@ # 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: a248ed4fcb8abc17ffc6982d2733b3c3c2a2a635 -module-graph.zh.md: 255253bcfa9cfad1ab85602dfa9f953a02dfc427 +module-graph.zh.md: 28185c255ffa18f3ebc02f177d20594af3356164 diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 255253bcfa..28185c255f 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -438,9 +438,6 @@ flowchart TD pkg_fs --> pkg_sandbox pkg_skill_badge --> pkg_invariants pkg_skill_badge --> pkg_skill - pkg_compact --> pkg_invariants - pkg_compact --> pkg_llm - pkg_compact --> pkg_session pkg_web_fetch_local --> pkg_invariants pkg_web_fetch_local --> pkg_timeout pkg_web_fetch_local --> pkg_web @@ -504,15 +501,11 @@ flowchart TD pkg_session_projection --> pkg_invariants pkg_session_projection --> pkg_session pkg_llm_retry --> pkg_agent + pkg_llm_retry --> pkg_brand pkg_llm_retry --> pkg_invariants pkg_llm_retry --> pkg_llm pkg_llm_retry --> pkg_session pkg_llm_retry --> pkg_timeout - pkg_token_meter --> pkg_compact - pkg_token_meter --> pkg_invariants - pkg_token_meter --> pkg_llm - pkg_token_meter --> pkg_session - pkg_token_meter --> pkg_session_projection pkg_agent_default_model --> pkg_agent pkg_agent_default_model --> pkg_invariants pkg_agent_default_model --> pkg_llm @@ -552,10 +545,6 @@ flowchart TD pkg_hook_protocol --> pkg_bash pkg_hook_protocol --> pkg_invariants pkg_hook_protocol --> pkg_session - pkg_llm_replay --> pkg_compact - pkg_llm_replay --> pkg_invariants - pkg_llm_replay --> pkg_llm - pkg_llm_replay --> pkg_session pkg_loader_smoke --> pkg_agent pkg_loader_smoke --> pkg_invariants pkg_loader_smoke --> pkg_llm @@ -676,14 +665,11 @@ flowchart TD pkg_fs_sandbox --> pkg_invariants pkg_fs_sandbox --> pkg_sandbox pkg_fs_sandbox --> pkg_sandbox_policy - pkg_command_compact --> pkg_commands - pkg_command_compact --> pkg_compact - pkg_command_compact --> pkg_invariants - pkg_compact_tool_result_prune --> pkg_compact - pkg_compact_tool_result_prune --> pkg_invariants - pkg_compact_tool_result_prune --> pkg_llm - pkg_compact_tool_result_prune --> pkg_session - pkg_compact_tool_result_prune --> pkg_token_meter + pkg_compact --> pkg_brand + pkg_compact --> pkg_commands + pkg_compact --> pkg_invariants + pkg_compact --> pkg_llm + pkg_compact --> pkg_session pkg_session_query --> pkg_brand pkg_session_query --> pkg_invariants pkg_session_query --> pkg_llm @@ -705,13 +691,6 @@ flowchart TD pkg_headless --> pkg_invariants pkg_headless --> pkg_llm pkg_headless --> pkg_session - pkg_client_ui_conversation --> pkg_client_locale - pkg_client_ui_conversation --> pkg_client_runtime - pkg_client_ui_conversation --> pkg_client_ui_primitives - pkg_client_ui_conversation --> pkg_client_ui_slash - pkg_client_ui_conversation --> pkg_client_ui_slots - pkg_client_ui_conversation --> pkg_invariants - pkg_client_ui_conversation --> pkg_token_meter pkg_command_feedback --> pkg_commands pkg_command_feedback --> pkg_invariants pkg_command_feedback --> pkg_session @@ -744,6 +723,11 @@ flowchart TD pkg_tasks_local --> pkg_invariants pkg_tasks_local --> pkg_tasks pkg_tasks_local --> pkg_timeout + pkg_token_meter --> pkg_compact + pkg_token_meter --> pkg_invariants + pkg_token_meter --> pkg_llm + pkg_token_meter --> pkg_session + pkg_token_meter --> pkg_session_projection pkg_agent_loop --> pkg_agent pkg_agent_loop --> pkg_invariants pkg_agent_loop --> pkg_llm @@ -792,13 +776,9 @@ flowchart TD pkg_tool_skill --> pkg_llm pkg_tool_skill --> pkg_skill pkg_tool_skill --> pkg_tools - pkg_compact_basic --> pkg_agent - pkg_compact_basic --> pkg_compact - pkg_compact_basic --> pkg_compact_tool_result_prune - pkg_compact_basic --> pkg_invariants - pkg_compact_basic --> pkg_llm - pkg_compact_basic --> pkg_session - pkg_compact_basic --> pkg_token_meter + pkg_command_compact --> pkg_commands + pkg_command_compact --> pkg_compact + pkg_command_compact --> pkg_invariants pkg_subagent --> pkg_agent pkg_subagent --> pkg_brand pkg_subagent --> pkg_invariants @@ -859,33 +839,10 @@ flowchart TD pkg_agent_loop_testkit --> pkg_session pkg_agent_loop_testkit --> pkg_system_prompt pkg_agent_loop_testkit --> pkg_tools - pkg_client_ui_command --> pkg_client_connection - pkg_client_ui_command --> pkg_client_locale - pkg_client_ui_command --> pkg_client_runtime - pkg_client_ui_command --> pkg_client_ui_conversation - pkg_client_ui_command --> pkg_client_ui_primitives - pkg_client_ui_command --> pkg_client_ui_slash - pkg_client_ui_command --> pkg_client_ui_slots - pkg_client_ui_command --> pkg_invariants - pkg_client_ui_deliverables --> pkg_client_locale - pkg_client_ui_deliverables --> pkg_client_runtime - pkg_client_ui_deliverables --> pkg_client_ui_conversation - pkg_client_ui_deliverables --> pkg_client_ui_slots - pkg_client_ui_deliverables --> pkg_invariants - pkg_client_ui_goal --> pkg_api_remotes - pkg_client_ui_goal --> pkg_client_locale - pkg_client_ui_goal --> pkg_client_runtime - pkg_client_ui_goal --> pkg_client_ui_conversation - pkg_client_ui_goal --> pkg_client_ui_primitives - pkg_client_ui_goal --> pkg_client_ui_slots - pkg_client_ui_goal --> pkg_goal - pkg_client_ui_goal --> pkg_invariants - pkg_client_ui_tool --> pkg_client_locale - pkg_client_ui_tool --> pkg_client_runtime - pkg_client_ui_tool --> pkg_client_ui_conversation - pkg_client_ui_tool --> pkg_client_ui_primitives - pkg_client_ui_tool --> pkg_client_ui_slots - pkg_client_ui_tool --> pkg_invariants + pkg_llm_replay --> pkg_compact + pkg_llm_replay --> pkg_invariants + pkg_llm_replay --> pkg_llm + pkg_llm_replay --> pkg_session pkg_session_reference --> pkg_agent pkg_session_reference --> pkg_compact pkg_session_reference --> pkg_invariants @@ -992,6 +949,11 @@ flowchart TD pkg_tool_pwsh --> pkg_system_prompt pkg_tool_pwsh --> pkg_tasks pkg_tool_pwsh --> pkg_tools + pkg_compact_tool_result_prune --> pkg_compact + pkg_compact_tool_result_prune --> pkg_invariants + pkg_compact_tool_result_prune --> pkg_llm + pkg_compact_tool_result_prune --> pkg_session + pkg_compact_tool_result_prune --> pkg_token_meter pkg_subagent_acp --> pkg_agent pkg_subagent_acp --> pkg_invariants pkg_subagent_acp --> pkg_llm @@ -1040,50 +1002,18 @@ flowchart TD pkg_web_app --> pkg_bash_env pkg_web_app --> pkg_invariants pkg_web_app --> pkg_system_prompt - pkg_client_ui_model --> pkg_client_connection - pkg_client_ui_model --> pkg_client_locale - pkg_client_ui_model --> pkg_client_runtime - pkg_client_ui_model --> pkg_client_ui_command - pkg_client_ui_model --> pkg_client_ui_conversation - pkg_client_ui_model --> pkg_client_ui_primitives - pkg_client_ui_model --> pkg_client_ui_slash - pkg_client_ui_model --> pkg_client_ui_slots - pkg_client_ui_model --> pkg_invariants - pkg_client_ui_permission --> pkg_client_connection - pkg_client_ui_permission --> pkg_client_locale - pkg_client_ui_permission --> pkg_client_runtime - pkg_client_ui_permission --> pkg_client_schema_form - pkg_client_ui_permission --> pkg_client_ui_command - pkg_client_ui_permission --> pkg_client_ui_primitives - pkg_client_ui_permission --> pkg_client_ui_slash - pkg_client_ui_permission --> pkg_client_ui_slots - pkg_client_ui_permission --> pkg_invariants - pkg_client_ui_permission --> pkg_permission - pkg_client_ui_plan --> pkg_client_connection - pkg_client_ui_plan --> pkg_client_locale - pkg_client_ui_plan --> pkg_client_runtime - pkg_client_ui_plan --> pkg_client_ui_conversation - pkg_client_ui_plan --> pkg_client_ui_primitives - pkg_client_ui_plan --> pkg_client_ui_slots - pkg_client_ui_plan --> pkg_invariants - pkg_client_ui_plan --> pkg_plan_mode - pkg_client_ui_skill --> pkg_client_connection - pkg_client_ui_skill --> pkg_client_locale - pkg_client_ui_skill --> pkg_client_runtime - pkg_client_ui_skill --> pkg_client_ui_primitives - pkg_client_ui_skill --> pkg_client_ui_slash - pkg_client_ui_skill --> pkg_client_ui_slots - pkg_client_ui_skill --> pkg_client_ui_tool - pkg_client_ui_skill --> pkg_invariants - 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_primitives - pkg_client_ui_subagent --> pkg_client_ui_slash - pkg_client_ui_subagent --> pkg_client_ui_slots - pkg_client_ui_subagent --> pkg_invariants - pkg_client_ui_subagent --> pkg_subagent - pkg_client_ui_subagent --> pkg_token_meter + pkg_client_ui_conversation --> pkg_agent + pkg_client_ui_conversation --> pkg_client_locale + pkg_client_ui_conversation --> pkg_client_runtime + pkg_client_ui_conversation --> pkg_client_ui_primitives + pkg_client_ui_conversation --> pkg_client_ui_slash + pkg_client_ui_conversation --> pkg_client_ui_slots + pkg_client_ui_conversation --> pkg_commands + pkg_client_ui_conversation --> pkg_compact + pkg_client_ui_conversation --> pkg_invariants + pkg_client_ui_conversation --> pkg_llm_retry + pkg_client_ui_conversation --> pkg_token_meter + pkg_client_ui_conversation --> pkg_tools pkg_sdk_protocol --> pkg_invariants pkg_sdk_protocol --> pkg_llm pkg_sdk_protocol --> pkg_session @@ -1107,6 +1037,14 @@ flowchart TD pkg_workflow_workerthread --> pkg_subagent pkg_workflow_workerthread --> pkg_tools pkg_workflow_workerthread --> pkg_workflow + pkg_compact_basic --> pkg_agent + pkg_compact_basic --> pkg_commands + pkg_compact_basic --> pkg_compact + pkg_compact_basic --> pkg_compact_tool_result_prune + pkg_compact_basic --> pkg_invariants + pkg_compact_basic --> pkg_llm + pkg_compact_basic --> pkg_session + pkg_compact_basic --> pkg_token_meter pkg_subagent_codex --> pkg_invariants pkg_subagent_codex --> pkg_llm pkg_subagent_codex --> pkg_sdk_protocol @@ -1122,6 +1060,50 @@ flowchart TD pkg_subagent_spawn --> pkg_invariants pkg_subagent_spawn --> pkg_subagent pkg_subagent_spawn --> pkg_subagent_inprocess + pkg_client_ui_command --> pkg_client_connection + pkg_client_ui_command --> pkg_client_locale + pkg_client_ui_command --> pkg_client_runtime + pkg_client_ui_command --> pkg_client_ui_conversation + pkg_client_ui_command --> pkg_client_ui_primitives + pkg_client_ui_command --> pkg_client_ui_slash + pkg_client_ui_command --> pkg_client_ui_slots + pkg_client_ui_command --> pkg_invariants + pkg_client_ui_deliverables --> pkg_client_locale + pkg_client_ui_deliverables --> pkg_client_runtime + pkg_client_ui_deliverables --> pkg_client_ui_conversation + pkg_client_ui_deliverables --> pkg_client_ui_slots + pkg_client_ui_deliverables --> pkg_invariants + pkg_client_ui_goal --> pkg_api_remotes + pkg_client_ui_goal --> pkg_client_locale + pkg_client_ui_goal --> pkg_client_runtime + pkg_client_ui_goal --> pkg_client_ui_conversation + pkg_client_ui_goal --> pkg_client_ui_primitives + pkg_client_ui_goal --> pkg_client_ui_slots + pkg_client_ui_goal --> pkg_goal + pkg_client_ui_goal --> pkg_invariants + pkg_client_ui_plan --> pkg_client_connection + pkg_client_ui_plan --> pkg_client_locale + pkg_client_ui_plan --> pkg_client_runtime + pkg_client_ui_plan --> pkg_client_ui_conversation + pkg_client_ui_plan --> pkg_client_ui_primitives + pkg_client_ui_plan --> pkg_client_ui_slots + pkg_client_ui_plan --> pkg_invariants + pkg_client_ui_plan --> pkg_plan_mode + 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_primitives + pkg_client_ui_subagent --> pkg_client_ui_slash + pkg_client_ui_subagent --> pkg_client_ui_slots + pkg_client_ui_subagent --> pkg_invariants + pkg_client_ui_subagent --> pkg_subagent + pkg_client_ui_subagent --> pkg_token_meter + pkg_client_ui_tool --> pkg_client_locale + pkg_client_ui_tool --> pkg_client_runtime + pkg_client_ui_tool --> pkg_client_ui_conversation + pkg_client_ui_tool --> pkg_client_ui_primitives + pkg_client_ui_tool --> pkg_client_ui_slots + pkg_client_ui_tool --> pkg_invariants pkg_agent_spine_demo --> pkg_agent pkg_agent_spine_demo --> pkg_agent_loop pkg_agent_spine_demo --> pkg_bash_env @@ -1163,6 +1145,33 @@ flowchart TD pkg_subagent_dsh_sdk --> pkg_session pkg_subagent_dsh_sdk --> pkg_subagent pkg_subagent_dsh_sdk --> pkg_subprocess + pkg_client_ui_model --> pkg_client_connection + pkg_client_ui_model --> pkg_client_locale + pkg_client_ui_model --> pkg_client_runtime + pkg_client_ui_model --> pkg_client_ui_command + pkg_client_ui_model --> pkg_client_ui_conversation + pkg_client_ui_model --> pkg_client_ui_primitives + pkg_client_ui_model --> pkg_client_ui_slash + pkg_client_ui_model --> pkg_client_ui_slots + pkg_client_ui_model --> pkg_invariants + pkg_client_ui_permission --> pkg_client_connection + pkg_client_ui_permission --> pkg_client_locale + pkg_client_ui_permission --> pkg_client_runtime + pkg_client_ui_permission --> pkg_client_schema_form + pkg_client_ui_permission --> pkg_client_ui_command + pkg_client_ui_permission --> pkg_client_ui_primitives + pkg_client_ui_permission --> pkg_client_ui_slash + pkg_client_ui_permission --> pkg_client_ui_slots + pkg_client_ui_permission --> pkg_invariants + pkg_client_ui_permission --> pkg_permission + pkg_client_ui_skill --> pkg_client_connection + pkg_client_ui_skill --> pkg_client_locale + pkg_client_ui_skill --> pkg_client_runtime + pkg_client_ui_skill --> pkg_client_ui_primitives + pkg_client_ui_skill --> pkg_client_ui_slash + pkg_client_ui_skill --> pkg_client_ui_slots + pkg_client_ui_skill --> pkg_client_ui_tool + pkg_client_ui_skill --> pkg_invariants pkg_acp_demo --> pkg_acp pkg_acp_demo --> pkg_agent_spine_demo pkg_acp_demo --> pkg_app_boot @@ -1240,7 +1249,6 @@ flowchart TD | [`bash`](../packages/bash/bash) | `bash` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) | | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | | [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/support/invariants), [`skill`](../packages/skill/skill) | -| [`compact`](../packages/compact/compact) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) | | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) | | [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) | @@ -1257,8 +1265,7 @@ flowchart TD | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | | [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | | [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | -| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | -| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | | [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings) | | [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`type-meta`](../packages/typert/type-meta) | | [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | @@ -1269,7 +1276,6 @@ flowchart TD | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`web`](../packages/web/web) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/support/invariants), [`spill`](../packages/spill/spill) | | [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | -| [`llm-replay`](../packages/support/llm-replay) | `support` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`loader-smoke`](../packages/support/loader-smoke) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`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/support/invariants) | | [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | @@ -1297,19 +1303,18 @@ flowchart TD | [`goal-session`](../packages/goal/goal-session) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | | [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | -| [`command-compact`](../packages/compact/command-compact) | `compact` | [`commands`](../packages/interaction/commands), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants) | -| [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | `compact` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | +| [`compact`](../packages/compact/compact) | `compact` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-title`](../packages/session/session-title) | | [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/interaction/user-approval) | | [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`typert-registry`](../packages/typert/registry) | | [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`token-meter`](../packages/llm/token-meter) | | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`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/support/invariants) | | [`permission`](../packages/interaction/permission) | `interaction` | [`bash`](../packages/bash/bash), [`commands`](../packages/interaction/commands), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`user-approval`](../packages/interaction/user-approval) | | [`pty-local`](../packages/pty/pty-local) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`pty`](../packages/pty/pty), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess) | | [`session-title-llm`](../packages/session/session-title-llm) | `session` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`timeout`](../packages/util/timeout) | | [`tasks-local`](../packages/tasks/tasks-local) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tasks`](../packages/tasks/tasks), [`timeout`](../packages/util/timeout) | +| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`bash-env`](../packages/bash/bash-env) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) | @@ -1317,7 +1322,7 @@ flowchart TD | [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) | | [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | -| [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | +| [`command-compact`](../packages/compact/command-compact) | `compact` | [`commands`](../packages/interaction/commands), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants) | | [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | | [`tool-web`](../packages/web/tool-web) | `web` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) | | [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) | @@ -1327,10 +1332,7 @@ flowchart TD | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/support/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) | | [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`client-ui-command`](../packages/client/ui-command) | `client` | [`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-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | -| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | -| [`client-ui-goal`](../packages/client/ui-goal) | `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), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | -| [`client-ui-tool`](../packages/client/ui-tool) | `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/support/invariants) | +| [`llm-replay`](../packages/support/llm-replay) | `support` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | | [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | | [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) | @@ -1349,6 +1351,7 @@ flowchart TD | [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | [`tool-pwsh`](../packages/bash/tool-pwsh) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | +| [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | `compact` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | @@ -1357,20 +1360,26 @@ flowchart TD | [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`web-app`](../packages/bundle/web-app) | `bundle` | [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`system-prompt`](../packages/core/system-prompt) | -| [`client-ui-model`](../packages/client/ui-model) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-command`](../packages/client/ui-command), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | -| [`client-ui-permission`](../packages/client/ui-permission) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-command`](../packages/client/ui-command), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`permission`](../packages/interaction/permission) | -| [`client-ui-plan`](../packages/client/ui-plan) | `client` | [`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/support/invariants), [`plan-mode`](../packages/plan/plan-mode) | -| [`client-ui-skill`](../packages/client/ui-skill) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-tool`](../packages/client/ui-tool), [`invariants`](../packages/support/invariants) | -| [`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-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) | +| [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | [`agent`](../packages/core/agent), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm-retry`](../packages/llm/llm-retry), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) | | [`sdk-protocol`](../packages/scaffold/protocol) | `scaffold` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`repository-plugin`](../packages/self-modification/repository-plugin) | `self-modification` | [`invariants`](../packages/support/invariants), [`mcp-client`](../packages/mcp/mcp-client), [`paths`](../packages/util/paths), [`skill-local`](../packages/skill/skill-local) | | [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | +| [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compact`](../packages/compact/compact), [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/scaffold/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | | [`subagent-spawn`](../packages/subagent/subagent-spawn) | `subagent` | [`invariants`](../packages/support/invariants), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | +| [`client-ui-command`](../packages/client/ui-command) | `client` | [`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-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | +| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | +| [`client-ui-goal`](../packages/client/ui-goal) | `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), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | +| [`client-ui-plan`](../packages/client/ui-plan) | `client` | [`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/support/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-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) | +| [`client-ui-tool`](../packages/client/ui-tool) | `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/support/invariants) | | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`bash-env`](../packages/bash/bash-env), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`paths`](../packages/util/paths), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks-local`](../packages/tasks/tasks-local), [`tool-bash`](../packages/bash/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | | [`jsonrpc`](../packages/scaffold/server) | `scaffold` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/scaffold/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`sdk-client`](../packages/scaffold/client) | `scaffold` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/scaffold/protocol), [`session`](../packages/core/session) | | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/scaffold/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | +| [`client-ui-model`](../packages/client/ui-model) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-command`](../packages/client/ui-command), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | +| [`client-ui-permission`](../packages/client/ui-permission) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-command`](../packages/client/ui-command), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`permission`](../packages/interaction/permission) | +| [`client-ui-skill`](../packages/client/ui-skill) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-tool`](../packages/client/ui-tool), [`invariants`](../packages/support/invariants) | | [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/support/invariants), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index d814b6ac6e..e676227869 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/persistence-catalog.md persistence-catalog.md: c9aa15cd827d99cee64e7a33db11995ee17f9bf8 -persistence-catalog.zh.md: 364912b88c3b2c5efd92a616034be9ef5025ee67 +persistence-catalog.zh.md: d8743a1b0f26f2a4cf50aed42eb68f6c6db1ba13 diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 364912b88c..d8743a1b0f 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -5,7 +5,7 @@ [English](persistence-catalog.md) | 中文 -会话持久事件日志中可能出现的所有事件类型:完整持久化的 `SessionEvent` 信封,以及可通过合并扩展的 `SessionEventMap` 中的每个成员,包括 `@deepseek-ai/dsh-session` 所属的词汇和本仓库中每个插件的声明合并,并附有源 JSDoc、完整 payload 声明、surface 标记和声明位置。本文档是 [session.md](subsystems/session.md)(surface 排序与 `deriveMessages()` 投影)、[persistence.md](subsystems/persistence.md)(如何让日志持久化)和 [session.md](subsystems/session.md#cordis-surface) 中生成区域(实时总线接线;日志事件**不是** cordis 事件,它通过唯一一次 `session/event` emit 到达监听器)的补充。 +会话持久事件日志中可能出现的所有事件类型:完整持久化的 `SessionEvent` 信封,以及可通过合并扩展的 `SessionEventMap` 中的每个成员,包括 `@deepseek-ai/dsh-session` 所属的词汇和本仓库中每个插件对 `@deepseek-ai/dsh-session/types` 的声明合并,并附有源 JSDoc、完整 payload 声明、surface 标记和声明位置。本文档是 [session.md](subsystems/session.md)(surface 排序与 `deriveMessages()` 投影)、[persistence.md](subsystems/persistence.md)(如何让日志持久化)和 [session.md](subsystems/session.md#cordis-surface) 中生成区域(实时总线接线;日志事件**不是** cordis 事件,它通过唯一一次 `session/event` emit 到达监听器)的补充。 英文源文件根据源码生成(`scripts/gen-persistence-catalog.ts`),并由 `pnpm run verify-persistence-catalog`(`doc-sync`(文档同步门禁)的一部分)验证新鲜度;本中文文件作为经评审对侧通过双语配对维护。声明块保留源码声明和嵌套属性的 JSDoc,只移除其所在接口/模块带来的缩进,并使用 `ts persistence-catalog` 围栏(doc-typecheck 会跳过这些围栏,因为声明引用了其所属模块中的类型)。payload 中的类型名称会链接到记录该类型的页面。参见 [persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md)。 @@ -103,7 +103,7 @@ export type SessionEvent = { } ``` -来源:[`packages/core/agent/src/types.ts:300`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:19`](../packages/core/agent/src/types.ts) ### `approval/*` @@ -214,7 +214,7 @@ export type SessionEvent = { } ``` -来源:[`packages/interaction/commands/src/index.ts:151`](../packages/interaction/commands/src/index.ts) +来源:[`packages/interaction/commands/src/types.ts:41`](../packages/interaction/commands/src/types.ts) #### `command/run` — log-only @@ -232,7 +232,7 @@ export type SessionEvent = { 'command/run': { commandId: CommandId; name: string; args?: string; source: CommandSource } ``` -来源:[`packages/interaction/commands/src/index.ts:144`](../packages/interaction/commands/src/index.ts) +来源:[`packages/interaction/commands/src/types.ts:34`](../packages/interaction/commands/src/types.ts) ### `compact/*` @@ -243,10 +243,10 @@ export type SessionEvent = { * Marks the end of a compaction — log-only, releases the lock. Its owner * matches `compact/start`; `error` records an unsuccessful attempt. */ -'compact/end': { turn: number | null; error?: string } +'compact/end': { compactionId: CompactionId; sourceCommandId?: CommandId; turn: number | null; error?: string } ``` -来源:[`packages/compact/compact/src/types.ts:65`](../packages/compact/compact/src/types.ts) +来源:[`packages/compact/compact/src/types.ts:71`](../packages/compact/compact/src/types.ts) #### `compact/prune` — log-only @@ -270,7 +270,7 @@ export type SessionEvent = { } ``` -来源:[`packages/compact/compact/src/types.ts:75`](../packages/compact/compact/src/types.ts) +来源:[`packages/compact/compact/src/types.ts:81`](../packages/compact/compact/src/types.ts) #### `compact/start` — log-only @@ -280,10 +280,10 @@ export type SessionEvent = { * `compact/end`. A numbered owner is strictly enclosed by that open turn; * `null` identifies a standalone manual transaction between turns. */ -'compact/start': { turn: number | null } +'compact/start': { compactionId: CompactionId; sourceCommandId?: CommandId; turn: number | null } ``` -来源:[`packages/compact/compact/src/types.ts:19`](../packages/compact/compact/src/types.ts) +来源:[`packages/compact/compact/src/types.ts:23`](../packages/compact/compact/src/types.ts) #### `compact/summary` — log-only @@ -298,6 +298,8 @@ export type SessionEvent = { * before it (`compact/prune` documents the shared protocol). */ 'compact/summary': { + compactionId: CompactionId + sourceCommandId?: CommandId summary: ContentBlock[] shadowedRange: { start: number; end: number } shadowedSeqs: number[] @@ -333,7 +335,7 @@ export type SessionEvent = { 类型:[ContentBlock](subsystems/core.md) · [TokenUsage](subsystems/llm-streaming.md) -来源:[`packages/compact/compact/src/types.ts:29`](../packages/compact/compact/src/types.ts) +来源:[`packages/compact/compact/src/types.ts:33`](../packages/compact/compact/src/types.ts) ### `feedback/*` @@ -414,29 +416,19 @@ export type SessionEvent = { ```ts persistence-catalog /** Durable, non-surface record of one provider-routed retry scheduled after a failed request attempt. */ -'llm/retry': { - turn: number - step: number - provider: string - mode: 'normal' - policyKey: string - retry: number - maxRetries: number - delayMs: number - failure: LlmFailure -} | { - turn: number - step: number - provider: string - mode: 'always' - policyKey: string - retry: number - delayMs: number - failure: LlmFailure -} +'llm/retry': LlmRetryEventData ``` -来源:[`packages/llm/llm-retry/src/index.ts:17`](../packages/llm/llm-retry/src/index.ts) +来源:[`packages/llm/llm-retry/src/types.ts:9`](../packages/llm/llm-retry/src/types.ts) + +#### `llm/retry-started` — log-only + +```ts persistence-catalog +/** Durable transition written after a retry wait succeeds and before the next request attempt starts. */ +'llm/retry-started': LlmRetryStartedEventData +``` + +来源:[`packages/llm/llm-retry/src/types.ts:11`](../packages/llm/llm-retry/src/types.ts) ### `permission/*` @@ -658,12 +650,10 @@ export type SessionEvent = { * before returning), so its execution-enclosure relation holds by * construction. */ -'tool/code-dispatch': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown; isError: boolean; content: ContentBlock[] } +'tool/code-dispatch': CodeDispatchEventData ``` -类型:[CallId](subsystems/core.md) · [ContentBlock](subsystems/core.md) - -来源:[`packages/core/tools/src/code-mode.ts:49`](../packages/core/tools/src/code-mode.ts) +来源:[`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types.ts) #### `tool/code-dispatch-start` — log-only @@ -681,12 +671,10 @@ export type SessionEvent = { * with `tool/code-dispatch` by `subCallId` (timing = the two events' * `time` fields). */ -'tool/code-dispatch-start': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown } +'tool/code-dispatch-start': CodeDispatchStartEventData ``` -类型:[CallId](subsystems/core.md) - -来源:[`packages/core/tools/src/code-mode.ts:33`](../packages/core/tools/src/code-mode.ts) +来源:[`packages/core/tools/src/types.ts:40`](../packages/core/tools/src/types.ts) #### `tool/result` — surface diff --git a/docs/subsystems/session.i18n.yaml b/docs/subsystems/session.i18n.yaml index 04f3cc5394..4c8cad1880 100644 --- a/docs/subsystems/session.i18n.yaml +++ b/docs/subsystems/session.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.md -session.md: a5162c77d510c5ab66fa2dd6789693e1449970fb -session.zh.md: 989a3f5368aa0b0475dec4272fde724fd4a98230 +session.md: e5eb78908ba4b7ebfc398d5c735969a5a8937374 +session.zh.md: 51729999a831c94e87e36ec1c4705201f9c8360f diff --git a/docs/subsystems/session.md b/docs/subsystems/session.md index a5162c77d5..e5eb78908b 100644 --- a/docs/subsystems/session.md +++ b/docs/subsystems/session.md @@ -580,6 +580,8 @@ Activity ordering excludes the boundary through `lastActivityTime(events)`: pick A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns, and enforces any relation in its own invariant companion. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event with its payload, surface badge, and declaration site; the compaction seam's `compact/*` semantics are discussed on [compaction.md](compaction.md). +When several events in one plugin-owned family assemble into one Web Client Conversation Node, every start, update, result, resource, or interruption event in that family carries or independently derives the same stable business id. This requirement applies to correlated Node families, not to every Session event; it lets the client group each event without guessing from adjacency or scanning history. See the [Conversation Node cookbook](../cookbook/adding-a-conversation-node.md). + The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` gets no `hook/*` record because it runs before turn 1; its context remains pending in the inbox until a waking delivery opens a turn (see [the hook-bridges Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md)). ## Durability contract diff --git a/docs/subsystems/session.zh.md b/docs/subsystems/session.zh.md index 989a3f5368..51729999a8 100644 --- a/docs/subsystems/session.zh.md +++ b/docs/subsystems/session.zh.md @@ -584,6 +584,8 @@ interface TurnEndReasonMap { 插件可以通过 declaration merging 添加额外的 `SessionEventMap` 类型。这些是**仅日志**事件:不是 `SurfaceEventType`(不携带 `surfaceOp`,不参与派生历史)。事件所有方决定它们属于一个开放的执行轮次,还是可以独立位于轮次之间,并在自己的不变量配套插件中强制所需关系。生成的[持久化日志事件目录](../persistence-catalog.md)会列出每个核心或插件贡献的事件,以及其 payload、surface 标记和声明位置;压缩 seam 的 `compact/*` 语义在 [compaction.md](compaction.md) 中讨论。 +如果同一个插件事件族中的多条事件要组装成一个 Web Client Conversation Node,该事件族中的每条 start、update、result、resource 或 interruption 事件都必须携带或独立推导出同一个稳定业务 id。此要求只约束需要关联的 Node 事件族,并不要求每条 Session 事件都有业务 id;Client 因此无须根据相邻关系猜测归属,也无须扫描历史。参见 [Conversation Node 实操手册](../cookbook/adding-a-conversation-node.md)。 + 钩子桥接层的 `hook/invoked` / `hook/result` 对(来自 `@deepseek-ai/dsh-hook-protocol`)通过 `handlerId` 关联。`UserPromptSubmit`、`PreToolUse`、`PostToolUse` 与 `Stop` 在 loop 已打开的轮次内触发,因此其 `hook/*` 记录天然位于轮次之内。`SessionStart` 不生成 `hook/*` 记录,因为它在轮次 1 之前运行;其上下文会在 inbox 中保持待处理,直到唤醒交付打开一个轮次(见[钩子桥接 Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md))。 ## 持久性约定 diff --git a/packages/client/AGENTS.md b/packages/client/AGENTS.md index 78704f52fa..9626e96e74 100644 --- a/packages/client/AGENTS.md +++ b/packages/client/AGENTS.md @@ -54,6 +54,12 @@ Non-negotiables across the layers: - **Notifier publication discipline**: `notifyNow` is only the direct echo of a user gesture; structural updates use microtask-batched `markDirty`, while visible streaming chunks use cumulative `markFrameDirty`. See `runtime/src/client/sessions/notifier.ts`. - **The web layer is pure presentation.** Nothing that is "how to draw" (tool-card views, queue states) enters the session log; the host computes such data per frame or pushes it live, and replay recomputes it — falling back to the generic form when it can't. A new *model-visible* input still requires a session event (repo-wide rule). +## Conversation Node discipline + +- A Chat business feature registers one `ConversationNodeDefinition` and its keyed `conversation.chat.node` renderer; do not add its event switch or fold to `Session`, `SessionManager`, or a central built-in dispatcher. Follow the [Conversation Node cookbook](../../docs/cookbook/adding-a-conversation-node.md). +- `match(event)` reads only the current event. Every event in a multi-event Context carries or independently derives the same stable business id; `update` folds one Match into State and remains deterministically replayable by log `seq`. +- The append hot path and renderers never scan the full event window, Contexts, or Chat Nodes. Accumulate in State, publish same-Turn/Step facts through `buildLocationData()`, and consume final Node data or constrained Location hooks. + ## Directory regime (plugin packages) One UI feature = one plugin package (`src/client/` browser half). A multi-domain package splits by future package boundaries — ui-conversation is the exemplar: `contract/` (the only shared face), domain directories that never import a sibling domain, and `apply.ts` as the single cross-domain assembly point; `scripts/verify-client-domain-graph.ts` enforces the levels. Registration goes through `slots.register` in `apply` — never module-level side effects. diff --git a/packages/client/runtime/README.i18n.yaml b/packages/client/runtime/README.i18n.yaml index d47266cd8a..139474af19 100644 --- a/packages/client/runtime/README.i18n.yaml +++ b/packages/client/runtime/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/runtime/README.md -README.md: 46a383f22032c8da7e4bb9b5fb445980661c670f -README.zh.md: 73578a8f249472abfdd7c539e3eaee6de767ad60 +README.md: df3689176cf059f46004fa7ecb31ab7c326ea0bc +README.zh.md: 9afe9773ff2a6a320bdf66978877678428c46118 diff --git a/packages/client/runtime/README.md b/packages/client/runtime/README.md index 46a383f220..df3689176c 100644 --- a/packages/client/runtime/README.md +++ b/packages/client/runtime/README.md @@ -38,6 +38,8 @@ SlotsService gives the renderer separate bare observables for `useSessions` and Each `Session` gives its contiguous event window to a `ConversationNodeAssembler`. Plugins register business Definitions that map one event to a stable `{kind, id}`, create State at the unique start event, fold correlated updates, and build final nodes for registered view targets. The assembler owns the Context index, read-only predecessor lookup, and a reference-stable Turn/Step Location index. A live append evaluates each Definition once and updates only the matched Context; loading an older page preserves existing Context and node identities, matches only the newly prepended events, and replays Contexts whose predecessor or Location facts changed. Full replacement is reserved for open, resync, and gap repair. +Definition authors keep matching local to the current event, give every correlated event a stable business id, and make updates replayable by log `seq`; renderers consume final Node data and constrained Location values rather than scanning Session or Chat collections. The [Conversation Node cookbook](../../../docs/cookbook/adding-a-conversation-node.md) gives the complete registration and pagination path. + `ui-conversation` registers the built-in Chat Definitions and the keyed Chat snapshot builder. Append-origin user, assistant, and Tool results remain the human record; model-only replacement copies stay out, except that a compaction checkpoint becomes its own marker and resolves missing summary provenance when an older page supplies it. Durable inbox splice Contexts classify next-step user messages as steering without making inbox state a Session special case. Context messages retain producer provenance and form. StatsLine reads `ConversationSnapshot.chat.legacy.nodes`, while Session mirrors that legacy slice into the top-level `nodes`, `partial`, and `runningCalls` public compatibility fields without running a second business fold. Trajectory consumes neither compatibility surface; its activated `session-history` inspection keeps an independent fold until it gains its own registered target. The Chat builder keeps one mutable keyed store per Session. Content updates notify only the affected node key, structural changes rebuild order and Location membership, and a prepend adds rows without replacing existing keyed values. Assistant chunks update Definition State for every event but request at most one materialization per animation frame; final messages and Turn/Step closure publish immediately. See the [client Tool presentation decision](../../../.agents/notes/implemented/architecture/2026-08-08-client-tool-presentation-ownership.md). diff --git a/packages/client/runtime/README.zh.md b/packages/client/runtime/README.zh.md index 73578a8f24..9afe9773ff 100644 --- a/packages/client/runtime/README.zh.md +++ b/packages/client/runtime/README.zh.md @@ -38,6 +38,8 @@ SlotsService 分别为 renderer 提供 `useSessions` 与 `useWorkspaces` 的裸 每个 `Session` 都把连续事件窗口交给 `ConversationNodeAssembler`。插件注册业务 Definition,把单个事件映射为稳定的 `{kind, id}`,在唯一 start 事件处创建 State,折叠有关联的 update,再为已注册的视图目标构造最终节点。Assembler 负责 Context 索引、只读前序 Context 查询,以及引用稳定的 Turn/Step Location 索引。实时 append 只对每个 Definition 求值一次,并且只更新命中的 Context;加载更早分页时保留已有 Context 与节点身份,只匹配新 prepend 的事件,并重放前序依赖或 Location 事实发生变化的 Context。完整替换仅用于 open、resync 和 gap repair。 +Definition 作者只根据当前事件完成匹配,为每条关联事件提供稳定业务 id,并保证 update 能按日志 `seq` 回放;renderer 只消费最终 Node data 与受限 Location value,不扫描 Session 或 Chat 集合。完整注册和分页路径见 [Conversation Node 实操手册](../../../docs/cookbook/adding-a-conversation-node.md)。 + `ui-conversation` 注册内建 Chat Definition 与 keyed Chat snapshot builder。append 来源的 user、assistant 和 Tool result 构成人类可见记录;仅供模型使用的 replacement 副本不进入 Chat,compaction 检查点除外,它会成为独立标记,并在更早分页补齐 summary 溯源后更新。持久 inbox splice Context 能把 next-step 用户消息判定为 steering,无须让 inbox 状态成为 Session 特例。上下文消息保留生产者 provenance 与 form。StatsLine 读取 `ConversationSnapshot.chat.legacy.nodes`;Session 则把该 legacy slice 镜像到顶层 `nodes`、`partial` 和 `runningCalls` 公共兼容字段,无须运行第二套业务 fold。Trajectory 不消费这两种兼容表面;在它获得独立注册 target 之前,已激活的 `session-history` inspection 继续维护独立 fold。 Chat builder 为每个 Session 保留一个 mutable keyed store。内容更新只通知受影响的 node key;结构变化才重建顺序和 Location 成员关系;prepend 只增加行,不替换既有 keyed value。每个 Assistant chunk 都会更新 Definition State,但最多每个 animation frame 请求一次物化;final message 与 Turn/Step 关闭会立即发布。参见 [Client Tool 展示所有权决策](../../../.agents/notes/implemented/architecture/2026-08-08-client-tool-presentation-ownership.md)。 diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index bb15c65029..1238e136d7 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/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-conversation/README.md -README.md: 3570e814609af0bc4e7d048a55205af756fae843 -README.zh.md: e37f867c72d83b47924e87261e6ee5ff8b374a6f +README.md: d894d3e578f065e9cb2c45676224efd1f4f76fa8 +README.zh.md: ec99a8d7daa593cdf3014f292a095fb2fd6f39d3 diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 3570e81460..d894d3e578 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -12,6 +12,8 @@ Another plugin can make one session's composer inert through `ctx.conversation.b The view ring is a slot: the strict session-body registration declares the session-scoped `'conversation.view'` list in its `children` table, that body renders the active entry through its renderSlot share (`only: `), and view tabs project from registration options (`id`/`order`/`label`). The chat view is this package's own entry; plugins such as ui-trajectory contribute tabs through `ctx.slots.register`, and each view owns its chrome. +Chat business rows are independent registry contributions rather than a closed built-in union. A client plugin declaration-merges its typed `ChatNodeDataMap` key, registers a `ConversationNodeDefinition` on `ctx.conversationEvents`, and registers the matching keyed renderer on `conversation.chat.node`; it does not modify Session folds or a central renderer switch. The [Conversation Node cookbook](../../../docs/cookbook/adding-a-conversation-node.md) covers stable event ids, append/prepend replay, Location data, and renderer constraints. + Approvals take over the composer through the chain this package declares: `ApprovalPanel` registers as a selector-routed `'conversation.composer'` entry (the ui-question pattern) and occupies the composer in place of the InputBar while an approval wait is pending (amber strip, justification headline, paired command line from the running call's args, one-shot refuse/allow). The `PendingApproval` domain face in `contract/slots.ts` owns the wire encoding — the `ApprovalResponsePayload` value with the audit correlation — over the runtime's `PendingWait` carrier; the broadcast `approval/resolved` frame settles the wait and restores the composer. The runtime manager projects every approval or question wait through `SessionSummary.pendingInteraction`, including sessions never instantiated; `ui-workspace` owns its sidebar presentation. Pending waits leave the message flow entirely: questions (ui-question) and approvals (ApprovalPanel) both answer through the composer takeover, so no display-only placeholder card remains. The composer's bottom-row Access seat mounts `PermissionSelect`, fed by the host-computed `permissions` projection through the standard-kit `useProjection` (key absence hides the chip); the chip opens a Menu-primitive dropdown whose kebab-case preset names render as title-case labels. Safe preset picks submit `/permission ` immediately through the bar's injected `command` callback, while `danger-full-access` is presented as `Full access` and first opens an in-page Modal risk confirmation. The enabling action stays disabled until the user checks the acknowledgement; cancel, Escape, close, and mask click submit nothing. The session header declares and renders the session-scoped `'conversation.session.header.actions'` list beside the title, allowing feature plugins to contribute controls without entering the skeleton. The composer chain currency includes the current conversation `session`; ui-subagent selects one-shot or parent-unavailable addressed sessions for reason-specific read-only copy, while the ordinary InputBar keeps every addressed child Send-only because the continuation service exposes no public per-Activation cancellation operation and `session.cancel` would bypass its ownership. diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index e37f867c72..ec99a8d7da 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -12,6 +12,8 @@ 视图环是一个 slot:严格会话主体注册在 `children` 表中声明 Session scope 的 `'conversation.view'` 列表,并通过自身的 renderSlot share 渲染活跃配置项(`only: `);视图标签页则从注册选项(`id`/`order`/`label`)投影而来。聊天视图是该包自身的配置项;ui-trajectory 等插件通过 `ctx.slots.register` 贡献标签页,每个视图负责自己的 chrome。 +Chat 业务行是彼此独立的注册表贡献,不是封闭的内建联合。Client 插件通过 declaration merging 增加类型化 `ChatNodeDataMap` key,在 `ctx.conversationEvents` 上注册 `ConversationNodeDefinition`,再向 `conversation.chat.node` 注册匹配的 keyed renderer;它无须修改 Session fold 或中央 renderer switch。稳定事件 id、append/prepend 回放、Location data 与 renderer 约束见 [Conversation Node 实操手册](../../../docs/cookbook/adding-a-conversation-node.md)。 + 会话页头会在标题旁声明并渲染 Session scope 的 `'conversation.session.header.actions'` 列表,使功能插件无需进入骨架即可贡献控件。编辑器链的 currency 包含当前对话 `session`;ui-subagent 会选取 one-shot 或 parent 不可用的已寻址会话,并按原因显示只读文案,而普通 InputBar 会让所有已寻址 child 仅保留 Send,因为继续执行服务不公开逐 Activation 取消操作,`session.cancel` 也会绕过其所有权。 已记录的非用户消息渲染为默认折叠的展开项,标题栏先给出运行时为该消息投影出的角色——注入为 `上下文注入`,召回为 `跨会话召回`——其后是该投影从持久来源读出的生产者名称,因此读者无需展开即可区分 skill(技能)目录、工作区指令文件与被召回的会话。来源未提供生产者名称时只显示角色。共享的 `DisclosureRow` 原子组件让该上下文界面与消息流中的其他紧凑行保持相同几何,同时保留上下文语义:展开内容区的高度会随内容自适应,最大为 141px,超出后滚动,且不会合成工具状态或摘要([历史展开项决策](../../../.agents/notes/archived/feature/2026-07-30-web-context-injection-disclosure.md)、[生产者标签决策](../../../.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md))。该内容区按生产方在持久来源上声明的形态渲染:`instructions` 在正文之上列出它对账过的文件,`catalog` 列出来源记录的条目而非面向模型的散文,其余取值——未声明、本版本不认识、或字段不可用——一律渲染 opaque 内容区,即按真实换行展示面向模型的文本,并把剩余来源字段列出。opaque 不是兜底剩余物而是有文档的默认:恢复的、fork 的、外部写入的日志,无论其生产方是否挂载在此处,都必须渲染得出来。持久或待处理的 steering(中途引导)气泡上方带有 `插话` / `Interjection` 标注,这是把中途插话与共用同一气泡的开轮提示区分开的唯一标识。 diff --git a/scripts/doc-budgets.manifest.json b/scripts/doc-budgets.manifest.json index b5c000a714..2626cec2da 100644 --- a/scripts/doc-budgets.manifest.json +++ b/scripts/doc-budgets.manifest.json @@ -1,7 +1,7 @@ { "AGENTS.md": 1782, "docs/AGENTS.md": 1320, - "docs/architecture.md": 2160, + "docs/architecture.md": 2174, "docs/cordis-primer.md": 600, "docs/defensive-patterns.md": 550, "docs/testing.md": 1150, diff --git a/website/docs.ts b/website/docs.ts index 1e97d88e24..a8d07a59c2 100644 --- a/website/docs.ts +++ b/website/docs.ts @@ -396,6 +396,14 @@ const reference = [ section: { root: '开发手册', en: 'Cookbook' }, order, }))), + ...pairedPages([{ + source: 'docs/cookbook/adding-a-conversation-node.md', + route: 'reference/cookbook/adding-a-conversation-node.md', + label: { root: '新增 Conversation Node', en: 'Adding a Conversation Node' }, + sidebar: { root: 'zh-reference', en: 'en-reference' }, + section: { root: '开发手册', en: 'Cookbook' }, + order: 4, + }]), ] /** Every canonical page published by the documentation website. */