@deepseek-harness-tui/dsh-tui 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +5 -146
  2. package/lib/types/adapter/ports/channel-session.d.ts +3 -1
  3. package/lib/types/adapter/ports/channel-session.d.ts.map +1 -1
  4. package/lib/types/components/Chat/JobCard.d.ts.map +1 -1
  5. package/lib/types/components/Chat/JobCard.js +8 -1
  6. package/lib/types/components/Markdown.d.ts +2 -2
  7. package/lib/types/components/Markdown.d.ts.map +1 -1
  8. package/lib/types/components/Markdown.js +28 -3
  9. package/lib/types/components/MessageList.d.ts.map +1 -1
  10. package/lib/types/components/MessageList.js +10 -9
  11. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  12. package/lib/types/components/StreamingMarkdown.js +61 -9
  13. package/lib/types/dsh-adapter/channel/binding-events.d.ts +5 -0
  14. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  15. package/lib/types/dsh-adapter/channel/binding-events.js +12 -2
  16. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +5 -2
  17. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  18. package/lib/types/dsh-adapter/channel/input-delivery.js +71 -7
  19. package/lib/types/dsh-adapter/channel/permissions.d.ts.map +1 -1
  20. package/lib/types/dsh-adapter/channel/permissions.js +10 -7
  21. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  22. package/lib/types/dsh-adapter/channel/session-rewind.js +9 -2
  23. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  24. package/lib/types/dsh-adapter/channel/session-tree-actions.js +9 -3
  25. package/lib/types/dsh-adapter/channel/skill-catalog.d.ts +8 -2
  26. package/lib/types/dsh-adapter/channel/skill-catalog.d.ts.map +1 -1
  27. package/lib/types/dsh-adapter/channel/skill-catalog.js +49 -17
  28. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  29. package/lib/types/dsh-adapter/channel.js +5 -2
  30. package/lib/types/dsh-adapter/compat/liveSession.d.ts +5 -3
  31. package/lib/types/dsh-adapter/compat/liveSession.d.ts.map +1 -1
  32. package/lib/types/dsh-adapter/compat/liveSession.js +5 -3
  33. package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -1
  34. package/lib/types/dsh-adapter/sessions/digest.js +40 -19
  35. package/lib/types/dsh-adapter/sessions/frames.js +2 -0
  36. package/lib/types/dsh-adapter/sessions/list.js +3 -3
  37. package/lib/types/dsh-adapter/sessions/store.d.ts +2 -2
  38. package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
  39. package/lib/types/dsh-adapter/sessions/store.js +12 -11
  40. package/lib/types/dsh-adapter/sessions/types.d.ts +2 -0
  41. package/lib/types/dsh-adapter/sessions/types.d.ts.map +1 -1
  42. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  43. package/lib/types/ink/components/ScrollBox.js +30 -33
  44. package/lib/types/ink/dom.d.ts.map +1 -1
  45. package/lib/types/ink/dom.js +32 -64
  46. package/lib/types/ink/get-max-width.d.ts +7 -15
  47. package/lib/types/ink/get-max-width.d.ts.map +1 -1
  48. package/lib/types/ink/get-max-width.js +7 -20
  49. package/lib/types/ink/ink.d.ts +1 -0
  50. package/lib/types/ink/ink.d.ts.map +1 -1
  51. package/lib/types/ink/ink.js +22 -3
  52. package/lib/types/ink/layout/node.d.ts +2 -0
  53. package/lib/types/ink/layout/node.d.ts.map +1 -1
  54. package/lib/types/ink/layout/yoga.d.ts +1 -0
  55. package/lib/types/ink/layout/yoga.d.ts.map +1 -1
  56. package/lib/types/ink/layout/yoga.js +3 -0
  57. package/lib/types/ink/node-cache.d.ts +11 -0
  58. package/lib/types/ink/node-cache.d.ts.map +1 -1
  59. package/lib/types/ink/node-cache.js +3 -0
  60. package/lib/types/ink/output.d.ts +6 -1
  61. package/lib/types/ink/output.d.ts.map +1 -1
  62. package/lib/types/ink/output.js +55 -65
  63. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  64. package/lib/types/ink/render-node-to-output.js +92 -67
  65. package/lib/types/ink/tabstops.d.ts +3 -2
  66. package/lib/types/ink/tabstops.d.ts.map +1 -1
  67. package/lib/types/ink/tabstops.js +5 -4
  68. package/lib/types/ink/wrap-text.js +2 -2
  69. package/lib/types/native-ts/yoga-layout/index.d.ts +8 -3
  70. package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -1
  71. package/lib/types/native-ts/yoga-layout/index.js +23 -8
  72. package/lib/types/screens/Chat.d.ts.map +1 -1
  73. package/lib/types/screens/Chat.js +20 -8
  74. package/package.json +3 -2
package/README.md CHANGED
@@ -67,8 +67,10 @@ Sixel 使用最多 256 色的自适应调色板,透明像素与背景合成;
67
67
  用户附图及助手/工具结果中的持久图片块会直接显示在会话记录中;Kitty graphics 或 Sixel 可用时显示等比缩略图,否则保留同尺寸文字回退。全屏下点击输入框 `[Image #N]` 或 transcript 缩略图在对话区域居中打开大图预览,卡片外的对话文字变暗,不遮挡输入栏(Esc/点击外部关闭),标题为 `Image #N — 格式 · 尺寸 · 体积 · 文件名`,本会话暂存的图片在卡片底行显示来源路径;Finder 复制的图片文件粘贴时直接入附件库为 `[Image #N]`;输入框里的 `[Image #N]` 是一个整体,光标整体跳过、删除整体生效,光标落在其上时整块反显并自动打开预览、离开时关闭。Vim 的 `x`/`X`/`d…` 同样整张删除,`u` 同时恢复文字与附件;撤销仅限当前草稿。
68
68
  终端图片预览默认开启,可在 `/settings → 终端图片预览` 或配置 `terminalImages: false` 中关闭,使用 `/restart` 后生效。已保存的 `/settings` 选择优先于 Cordis 配置;若曾保存为开启,请在 `/settings` 中关闭后再 `/restart`。关闭时保留文字信息并跳过预览解码,不影响向模型发送图片;`DSH_TUI_DISABLE_TERMINAL_IMAGES=1` 始终强制关闭预览。
69
69
  - **功能全面**:`/resume` 按工作目录分类浏览、搜索与预览历史会话(左键恢复、右键弹出操作菜单;可固定常用会话——「已固定」分组置顶显示,行内 ★ 或 `Ctrl+P` 切换,持久化到 `~/.dsh-tui`),另有 `/agentview` 会话总览(空输入 `←` 一键后台化,后台会话派发、预览、回复与停止一站式管理)、`/new`、`/compact`、`/export`、`/btw`,模型热切换(新会话默认推理强度可在 /settings → 默认推理强度 预设),原生subagent,会话fork,自动更新,输入框 `/vim` vim 编辑模式、鼠标选区编辑(拖选高亮、Shift+click 扩展、双击选词、Ctrl+C 复制选区)与全屏草稿编辑(`Ctrl+Shift+E` 或输入行 `⛶` 按钮:行号 + 当前行高亮、Enter 换行、Ctrl+Enter 发送、滚轮滚动、点击/拖选,长草稿独占整屏;`/settings` 可关);可在vs code中[以vscode插件形式启动](docs/vscode.md),已上架 VS Code Marketplace。
70
+ `/resume` 只将完整读取并确认没有用户消息的日志判为空会话;仅发图片、读取不完整或解析失败的会话不会被归入空会话清理。
70
71
  - **扩展丰富**:原生浏览器交互,compter use等大量附属功能性扩展
71
72
  - **技能归 DSH 管理**:`/skills` 展示当前 profile、用户与项目发现的技能;dsh-TUI 不预装通用技能。
73
+ 技能目录暂时不完整时,保留最后一次完整观测的技能菜单与命令注册,并按 800/1600/3200ms 最多重试三次;耗尽后等待 DSH 的 `skills/change` 通知或显式刷新,不持续轮询。只有完整观测才能移除已消失的技能,包括完整空目录。
72
74
  - **工作状态动画**:默认使用 `moon8`;读取旧版本地配置中的 `claude` 值时自动映射为 `moon8`,选择器只显示当前预设。
73
75
  - **像素鲸鱼娘**:开屏随机三选一开场动画;欢迎期(开始第一个任务前)可**点击冒爱心并唤醒睡着的鲸鱼**,闲置时摆鱼鳍、拍尾巴、入睡冒 Z(`/settings → whaleIdle` 可关)。**开始第一个任务后永久定格为静态标准帧**,零持续开销。鲸鱼娘的 22 帧手绘原图与闲置行为移植自 [dsh-ui-whale](https://github.com/lhh010/dsh-ui-whale)(作者 [@lhh010](https://github.com/lhh010)),特此致谢。
74
76
 
@@ -150,152 +152,9 @@ sh install.sh
150
152
  | 实验性(仍可能随 dsh-std / 准入规范演进调整) | 九 决策事件 · toast 通知(`ctx.tuiToast`,新增) |
151
153
  | 跟随上游(稳定性由 cordis / dsh 官方机制决定) | 一 会话事件 · 二 官方 prompt 槽位 · 三 技能打包 · 四 主题 · 五 system prompt 段 · 七 profile 组合 |
152
154
 
153
- 另:`@deepseek-harness-tui/dsh-tui/api`(纯类型入口)为实验性公开面。
154
- `@deepseek-harness-tui/dsh-tui/test-utils` 公共子路径已移除——它包含可注入真实
155
- activationId 的测试助手,不适合作为公共生产 API;需要 headless 测试请复制仓库内
156
- `scripts/lib/plugin-test-utils.ts` 的思路,在自己的测试环境走正式准入流程。
157
-
158
- 公共面的导入迁移:
159
- - **`ctx.tuiPluginHost.grants.corrupt` 已不存在**。新的 `HostGrantFacade` 只暴露
160
- `allows(pluginCtx, permission, scope)`、`defaultOf`、`knownPermissions` 和
161
- `onChange(pluginCtx, listener)`;损坏的 grant 文件不再通过一个布尔字段暴露给插件。
162
- `onChange` 是 subscribe 类托管能力:必须传入调用 activation,受 shadow 门禁约束,
163
- 返回的取消函数绑定到该 activation,避免文件轮询/监听器泄漏。需要诊断时请使用
164
- `ctx.tuiPluginHost.selfCheck()`、`/doctor`,或通过显式诊断查询在宿主侧检查
165
- grant 文件状态。
166
- - `createAdmissionCatalog` 不再从 `./plugin-host` 导出;诊断请使用
167
- `ctx.tuiPluginHost.selfCheck()` 与 `/doctor`。
168
- - `ctx.tuiPluginHost.grants` 改为调用方安全的 `HostGrantFacade`:
169
- `grants.allows(pluginCtx, permission, scope)` 由宿主从当前 activation 推导身份,
170
- 不再接受任意 `GrantPrincipal`/完整 `GrantStore`。
171
- - `GrantStore`、`GrantPrincipal` 不再是公开导出的插件 API;它们保留在
172
- `src/adapter/standard/grants.ts` 作为宿主内部实现类型。需要构造/校验 grant
173
- 文件的宿主方请走仓库内部路径,生态测试不要在公共包依赖这些类型。
174
- - `@deepseek-harness-tui/dsh-tui/test-utils` 子路径已删除;仓库内的
175
- `scripts/lib/plugin-test-utils.ts` 仅用于本仓库无头验证,不属于公共契约。
176
- 生态插件请在自己的测试环境通过正式 admission 流程复现。
177
-
178
- ### 平台已知边界(Shadow 门禁不承诺覆盖)
179
-
180
- TUI 的 shadow/门禁只覆盖自有托管接缝;以下由 Cordis/上游 DSH 拥有的路径属于已写死的
181
- 平台边界,不会被门禁描述为全路径覆盖:
182
-
183
- - 直接 `ctx.get('commands').register` / `ctx.get('commands').execute`(C-070);
184
- - `ctx.plugin()` / `candidate.plugin()` 子插件安装(Cordis 平台);
185
- - agent preset 名册 / recompose 注册(`@deepseek-ai/dsh-agent-presets`);
186
- - system prompt section 注册(`@deepseek-ai/dsh-system-prompt`);
187
- - skill registry 注册/调用(`@deepseek-ai/dsh-skill`)。
188
-
189
- 这些路径在 `verify-adapter-shadow` 中以 `platform-known boundary` 显式列出。
190
- 此外,`verify-adapter-shadow` 还显式记录两类门禁已知边界:未列入
191
- `NON_SERVICE_POLICY` 的内部 TUI 状态/视图辅助类,以及 `src/screens`/
192
- `src/components` 的 React UI 状态与事件处理——它们不是 adapter capability
193
- 入口,门禁不将其描述为已覆盖。
194
-
195
-
196
- ### Adapter live-probe 诚实性与 P2 边界
197
-
198
- 本轮把 `Command` / `LocalStorage` / `MessageObserver` 从“无实探的 staged”
199
- 推进到了“有可逆实探的 live”(在 legacy/new 模式下异步执行),并实现最小
200
- `KernelRuntime` 与 passive/replay harness:
201
-
202
- - **Command live probe**:在真实命令服务上临时注册唯一 no-op command,经
203
- `find` / `list` 验证可见,并以内存 fake agent session 执行一次,确认
204
- execute 返回 success;无论成功失败都在 `finally` 反注册。该操作不写任何
205
- DSH 持久化 session 日志,唯一瞬时副作用是进程内命令注册表的一次
206
- `commands/change` 通知。
207
- - **LocalStorage live probe**:在真实 storage 目录下创建随机临时 namespace
208
- 文件,完成 write/read/delete,并在 `finally` 删除;不触碰任何真实插件
209
- namespace,也不保留探针数据。
210
- - **MessageObserver live probe**:通过 broker 的内部 probe-only 发布通道添加一个临时探针订阅并投递一条合成事件;真实插件的 `session:*` 通配订阅不会收到任何探针消息,探针结束后订阅数回到原值。这些 live probe 方法不是插件可见的公开 service 方法,宿主内核通过内部 host-only accessor 调用。
211
- - **默认 legacy 兼容发布**:`DSH_TUI_ADAPTER_MODE` 默认是 `legacy`,不会加载新 Kernel、不会执行可逆 live probe。该模式保留旧发布语义,使用独立的 `buildLegacyHostDescriptor` 路径:只要 Command / LocalStorage / MessageObserver 的既有服务行已挂载,`describe()` / `hostDescriptor()` 就发布这些契约供插件准入使用;构建结果会在 warnings 中明确标注为 legacy 兼容声明,与新模式 live-only 的公开 descriptor 分离。
212
- - **模式配置 fail-closed**:只有未设置 `DSH_TUI_ADAPTER_MODE` 时才默认 `legacy`;显式值只接受 `legacy` / `passive-shadow` / `replay-shadow` / `new`(忽略大小写与首尾空白),空值或未知值会报错并拒绝启动。非 legacy 模式在 Kernel 未就绪、refresh skipped/failed 或已释放时返回空契约 descriptor,不回退兼容发布。
213
- - **Host probe 访问边界**:`host-probe-access` 内部的 token 是模块级不导出符号,普通包 exports 路径也拒绝 deep import。但插件与宿主同进程时,绝对路径加载内部文件仍无法被 `exports` 阻止——这是 **trusted-in-process 边界,不是安全沙箱**;宿主不会用“插件不可调用”这类无限定承诺。内部注册函数不会覆盖已引导的宿主 probe runner。
214
- - **Passive Shadow**:不执行上述可逆 probe,只做只读 detect/descriptor 快照;
215
- **Replay Shadow**:生产环境不接真实 DSH,必须通过 `scripts/verify-adapter-replay-harness.ts`
216
- 或 `src/adapter/kernel/replay.ts` 在隔离 replay context 上运行,否则 fail-closed
217
- 并输出明确提示。
218
- - Replay harness 用法:在隔离输入上运行
219
- `node --import tsx/esm scripts/verify-adapter-replay-harness.ts`(门禁脚本),
220
- 或在代码中调用 `runReplayShadow({ schemaVersion: 'tui-adapter-replay/v1', ... })`
221
- 获取 `{ kernelContracts, legacyContracts, missing, extra, lifecycles }` 对比报告。
222
- P5 起还支持真实 DSH session snapshot/transcript 的
223
- `runChannelReplay(...)` / `verify:adapter-channel-conformance`,走
224
- `tui.dsh/v1alpha1#Channel` 的 Provider/Consumer 与协议校验。
225
- - 公开 Host Descriptor 仍然只发布带真实 probe evidence 的 live 生命周期;
226
- 未完成 live refresh 或 passive/replay 下 `Command` / `LocalStorage` /
227
- `MessageObserver` 保持 staged/degraded,不伪造完整支持。`DecisionEvents`
228
- 维持逐 feature probe + 真实 channel/dispatch 拓扑发布规则。
229
- - P6 已彻底移除内部 `admissionCompat` 平行视图,并删除
230
- `src/plugin-spec/*` 与 `src/dsh-adapter/{grants,host-descriptor}.ts` compat
231
- shim。生产代码直接导入 `src/adapter/standard/*`;`verify:compat-removal`
232
- 现在扫描 `src/`、`scripts/`、`bin/`、生成 `lib/`(存在时)与 package export
233
- 图,`verify:package` 也会拒绝 npm tarball 中的旧 shim;仍保留的兼容别名
234
- (`ExtensionGrants`、`envelopeSchema`、`createAdmissionCatalog`、
235
- `facadeFromLegacy` 等)已明确标注为 P6 范围外/长期兼容面。
236
- - 新 Kernel 不再是 P1 空壳:`KernelRuntime` 管理 driver 注册/mount、detection、
237
- `declared → staged → live`、清理与诊断快照;生产 Host Descriptor、`getHostFacade()`、
238
- `/doctor`、`/plugins` 均走该 runtime。
239
- - **P3 feature-level live 拆分**:`Workspace` / `Scenes` / `Settings` /
240
- `Extensions` 不再用“注册+list+dispose”冒充整个能力 live。只有实际通过
241
- 只读/可逆探针验证的方法才进入 live(如 `host.workspaces.list`、
242
- `host.workspaces.resolve`、`host.scenes.register`、`host.settings.register`、
243
- `host.status.set`、`host.command-trees.children` 等);
244
- `host.toast.show` 因尚未验证真实生产 deliver 路径,保持 degraded;
245
- `rename` / `runCommand` / `commandShell` / `scenes.open` /
246
- `settings.subscribe` / 快捷键 dispatch / command-tree descriptions 等未验证
247
- 方法保持 degraded/staged。`Presentation` 的交互 `ask` 已桥接到真实
248
- `QuestionStore`,`approve` 在 P3 明确 staged。
249
- - **生产 P3 slices 接入**:非 `legacy` 的 `TuiPluginHostRuntime` 会把
250
- `ADAPTER_KERNEL_SLICES` 传入生产 `KernelRuntime`,进入 mount/refresh/
251
- descriptor 流程;依赖的服务未挂载时对应 slice 降级而不是崩溃。
252
- `DSH_TUI_ADAPTER_SLICES` 现在会按 slice id / capability / effect 矩阵过滤
253
- kernel slices,不再是无效果的死参数。
254
- - **宿主初始化运行时快照**:所有 adapter/宿主服务(storage/message/plugin-host、
255
- P3 的 status/workspaces/scenes/settings/toast/dialogs/command-trees/questions/approvals
256
- 等)都会在初始化时捕获不可变 `AdapterRuntimeOptions`。之后同一进程再修改
257
- `DSH_TUI_ADAPTER_MODE` / `DSH_TUI_ADAPTER_SLICES` 不能把 passive/replay 服务
258
- 解锁成 `new`;能力入口不再每次读 `process.env`。
259
- - **Slice 归属与边界**:`DSH_TUI_ADAPTER_SLICES` 现在会大小写/空白归一化、支持
260
- 常见别名(如 `dialogs` → `presentation`、`decisions` → `decisions`),未知
261
- slice id 直接拒绝并 fail-closed。`toast` 不再被 `presentation` 隐式加载,
262
- `decisions` 不再被 `messages` 隐式加载。
263
- - **P4 Channel Port/投影层(诚实表述)**:新增 `projection / actions / state /
264
- plugins / transcript` 五个 Host Port 与 `src/adapter/channel/*` 拆分模块,
265
- 并由 `channel` KernelSlice 挂载到生产 HostFacade;**生产 `src/dsh-adapter/channel.ts`
266
- 本体尚未物理拆分**,仍由 live Channel 作为实现来源,拆分模块是生产 driver
267
- 实际消费的 Port/投影层;**T1 已做核心迁移:生产 `plugin.ts` 中的通知与初始提交
268
- 已优先走 `HostFacade.channel.actions`;**非 shadow 模式下 facade 尚未 mount
269
- 时可回退原生 Channel,passive/replay shadow 下禁止回退,缺失或拒绝时丢弃**;
270
- 其余 UI/Channel 内部动作仍大部分直接调用原生 Channel,尚未完整迁移。
271
- `HostFacade` 按方法做 shadow 守卫,passive/replay 下只读投影可用、变更动作被拒。
272
- - **P5 Channel Provider/Consumer**:新增本地 Channel Provider/Consumer,
273
- 实现 `tui.dsh/v1alpha1#Channel` 的 open/subscribe/invoke/close 协议包络与
274
- 规范校验;`runChannelReplay` 支持录制 snapshot 数组,也支持把真实 DSH
275
- `agent.session.events` 形状事件投影为单调 `TuiChannelSnapshot`。该投影定位为
276
- **minimal transcript replay**:覆盖 transcript/status/基础 session 字段,
277
- 并在调用方提供元数据时带入 model/mode/preset/settings/scene/diagnostic/trace/
278
- context/pending/usage 等 RFC 相关字段;仍**不声明是完整 RFC 0007 Channel
279
- state/conformance**。未知方法按协议失败、
280
- features 必须显式声明且每个 feature 必须有 state/method 可观察证据、
281
- 重复 features 先拒绝、未知非 ignorable DSH event fail-closed、
282
- method handler 只能在 replay isolation 内执行、replay provider 不解析
283
- workspace/sessionId selector(显式 unsupported);连续性错误 fail-closed。
284
- - **P3 feature 生命周期稳定性**:`refresh` 之后执行 `mount()` / `descriptorBuild()` /
285
- `diagnosticSnapshot()` 再次触发同步 `detect()`,也不会清空已探测的 P3 feature;
286
- 这些 feature 是内部 Kernel/Port 事实,不进入公开 Host Descriptor。
287
- - **Settings section live 诚实性**:`host.settings.section` 的 live 依据是探针中
288
- 真实调用 `section()` 解析临时 namespace;解析缺失/失败时该 feature 降级,不再
289
- 仅凭 register/list 就标 live。
290
- - **Host Port 逐方法 shadow guard**:`KernelRuntime.facade()` 返回的每个
291
- Port 方法都会先执行统一 effect-class 检查。passive/replay 生产模式下
292
- `rename`、`runCommand`、`commandShell`、`scenes.open`、`settings.subscribe`、
293
- 各类 `register` 等会被拒绝;只读方法仍可用于诊断。
294
- - **Toast live probe** 使用独立 probe-only sink 管道,不替换/不吞并发生产
295
- toast;**Status live probe** 在 `clearIf` 失败或残留时降级,不会标 live。
296
- - `verify:adapter-slices` / `verify:adapter-detection` 现在同时检查生产
297
- plugin-host 确实传递 `ADAPTER_KERNEL_SLICES`,避免门禁只测直接 new
298
- KernelRuntime 的自证。
155
+ 另:`@deepseek-harness-tui/dsh-tui/api`(纯类型入口)为实验性公开面;
156
+ `@deepseek-harness-tui/dsh-tui/test-utils` 子路径与 `ctx.tuiPluginHost.grants.corrupt`
157
+ 已随 adapter 分层重构(#705)移除,`grants` 收窄为 `HostGrantFacade`,迁移细节见该 PR。
299
158
 
300
159
 
301
160
 
@@ -127,7 +127,9 @@ export interface SessionSummary {
127
127
  /** Log size in bytes; undefined when the backend owns no per-session file. */
128
128
  readonly bytes: number | undefined;
129
129
  /**
130
- * Whether the log holds a user prompt at all.
130
+ * Whether the log holds a human message (including image-only input), or
131
+ * the read was inconclusive. False requires a complete successful scan;
132
+ * unknown logs must stay visible and out of empty-session cleanup.
131
133
  *
132
134
  * `list()` reports *materialized* sessions, not conversations: a session
133
135
  * that only ever recorded its own boot policy is a real stored session with
@@ -1 +1 @@
1
- {"version":3,"file":"channel-session.d.ts","sourceRoot":"","sources":["../../../../src/adapter/ports/channel-session.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAG9E,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,EAAE,EAAE,MAAM,CAAA;IACV,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAA;IACb,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAA;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACxC,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IACvD,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IAC7D,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,QAAQ;IACvB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAChC,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8EAA8E;IAC9E,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,QAAQ,EAAE,CAAA;CACrB;AAED,8DAA8D;AAC9D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAAA;IAChD,mEAAmE;IACnE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;4CAOwC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAA;AAE9F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAC3B;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC;;wDAEoD;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IACpC,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B;;;uDAGmD;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,sBAAsB;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW;AACrB,uCAAuC;AACrC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC3B;;;GAGG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACpD;;;;GAIG;GACD;IACA,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB,CAAA;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAC7B;AAED,yDAAyD;AACzD,MAAM,MAAM,WAAW;AACrB,mFAAmF;AACjF,SAAS;AACX,yEAAyE;GACvE,MAAM;AACR,yDAAyD;GACvD,QAAQ;AACV,oEAAoE;GAClE,UAAU,CAAA;AAEd,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAA;CAChC"}
1
+ {"version":3,"file":"channel-session.d.ts","sourceRoot":"","sources":["../../../../src/adapter/ports/channel-session.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"JobCard.d.ts","sourceRoot":"","sources":["../../../../src/components/Chat/JobCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAkD1D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE;IACzD,GAAG,EAAE,MAAM,CAAA;IACX,eAAe,EAAE,OAAO,CAAA;IACxB,OAAO,CAAC,IAAI,IAAI,CAAA;CACjB,GAAG,KAAK,CAAC,SAAS,CAuDlB"}
1
+ {"version":3,"file":"JobCard.d.ts","sourceRoot":"","sources":["../../../../src/components/Chat/JobCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAkD1D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE;IACzD,GAAG,EAAE,MAAM,CAAA;IACX,eAAe,EAAE,OAAO,CAAA;IACxB,OAAO,CAAC,IAAI,IAAI,CAAA;CACjB,GAAG,KAAK,CAAC,SAAS,CA8DlB"}
@@ -77,10 +77,17 @@ export function JobCard({ job, marginTopOnTurn, onClick }) {
77
77
  // A settled job's terminal detail ('exit code: 0') rides the header; a
78
78
  // failed/killed one also keeps it as the explanatory tail line.
79
79
  const headerDetail = job.detail !== undefined && job.detail !== '' ? job.detail : undefined;
80
+ const headerName = `${t('jobs-card-prefix')}${job.id}`;
81
+ const duration = formatJobDuration(job);
82
+ const fixedHeader = [
83
+ info.glyph, headerName, '·', job.kind, '·', '', '·', duration,
84
+ ...(headerDetail === undefined ? [] : ['·', headerDetail]), '·', info.label,
85
+ ].join(' ');
86
+ const labelWidth = Math.max(0, (columns ?? 80) - stringWidth(fixedHeader));
80
87
  // 点击打开 /jobs 面板;hover 不刷整行背景(转录视觉保持安静),只把
81
88
  // 状态 glyph 提亮为品牌色作为可点指示。无外层缩进:任务卡是上方工具
82
89
  // 调用(run_in_background 卡)的延续,与工具卡通栏左对齐;子代理卡才
83
90
  // 是嵌套子实体、保留缩进。瀑布的 ` │ ` 槽自带两格,正好与工具卡正文
84
91
  // 的 ` ⎿ ` 槽位一致。
85
- return _jsxs(Box, { flexDirection: "column", marginTop: marginTopOnTurn ? 1 : 0, ref: viewportRef, onClick: onClick, onMouseEnter: clickable ? () => setHovered(true) : undefined, onMouseLeave: clickable ? () => setHovered(false) : undefined, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: hovered && clickable ? 'accent' : info.color, children: info.glyph }), _jsx(Text, { bold: true, color: hovered && clickable ? 'accent' : undefined, children: `${t('jobs-card-prefix')}${job.id}` }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: job.kind }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { children: clipLine(job.label, Math.max(10, rowWidth - 30)) }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: formatJobDuration(job) }), headerDetail !== undefined && _jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: headerDetail })] }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { color: info.color, children: info.label })] }), !settled && activity.length > 0 && activity.map((line, index) => (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${clipLine(line, rowWidth)}` }, `${job.id}-wf-${index}`))), settled && job.status !== 'completed' && headerDetail !== undefined && (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` └ ${clipLine(headerDetail, rowWidth)}` }))] });
92
+ return _jsxs(Box, { flexDirection: "column", marginTop: marginTopOnTurn ? 1 : 0, ref: viewportRef, onClick: onClick, onMouseEnter: clickable ? () => setHovered(true) : undefined, onMouseLeave: clickable ? () => setHovered(false) : undefined, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: hovered && clickable ? 'accent' : info.color, children: info.glyph }), _jsx(Text, { bold: true, color: hovered && clickable ? 'accent' : undefined, children: headerName }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: job.kind }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { children: clipLine(job.label, labelWidth) }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: duration }), headerDetail !== undefined && _jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: headerDetail })] }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { color: info.color, children: info.label })] }), !settled && activity.length > 0 && activity.map((line, index) => (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${clipLine(line, rowWidth)}` }, `${job.id}-wf-${index}`))), settled && job.status !== 'completed' && headerDetail !== undefined && (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` └ ${clipLine(headerDetail, rowWidth)}` }))] });
86
93
  }
@@ -3,8 +3,8 @@ import React from 'react';
3
3
  * Markdown 渲染组件:marked 分词 + ANSI 格式化。
4
4
  *
5
5
  * 表格 token 交给 MarkdownTable 渲染为带边框的 flexbox 布局;
6
- * 其余块级内容由 formatToken 转成 ANSI 字符串,合并后包进单个
7
- * Text(整段去首尾空白)。代码块高亮由 cli-highlight 异步提供,
6
+ * 其余块级内容由 formatToken 转成 ANSI 字符串,按块边界分批放进
7
+ * Text(只去整段首尾空白)。代码块高亮由 cli-highlight 异步提供,
8
8
  * 加载完成后自动触发一次重渲染。无 markdown 语法的纯文本走快速
9
9
  * 路径,直接合成段落 token,省掉 lexer 调用。
10
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../../src/components/Markdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB;;;;;;;;GAQG;AAEH,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AAoHD;;;;;;;;;;GAUG;AACH,iBAAS,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAgB,EAAE,WAAkB,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CA6BhG;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ,gDAMpB,CAAA"}
1
+ {"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../../src/components/Markdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB;;;;;;;;GAQG;AAEH,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AA4ID;;;;;;;;;;GAUG;AACH,iBAAS,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAgB,EAAE,WAAkB,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CA6BhG;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ,gDAMpB,CAAA"}
@@ -18,6 +18,7 @@ import { MarkdownTable } from './MarkdownTable.js';
18
18
  const TOKEN_CACHE_CAPACITY = 200;
19
19
  const TOKEN_CACHE_CHAR_BUDGET = 200_000;
20
20
  const TOKEN_CACHE_MAX_SOURCE_LENGTH = 20_000;
21
+ const TEXT_BLOCK_BUDGET = 8192;
21
22
  const tokenCache = new Map();
22
23
  let tokenCacheChars = 0;
23
24
  // 语法探针:命中任意 markdown 结构标记才值得走 lexer;内容过长时
@@ -73,16 +74,34 @@ function lexWithCache(content, allowCache) {
73
74
  }
74
75
  /**
75
76
  * 把 lexer 产出的 token 列表转成 React 节点序列:table 独立渲染,
76
- * 其余 token 的 ANSI 文本先累积拼接,再统一包成 Text(去除首尾空白)。
77
+ * 其余 token 的 ANSI 文本按完整块分批拼接,只去整段首尾空白。
77
78
  */
78
79
  function renderTokensToNodes(tokens, highlight, dimColor) {
79
80
  const nodes = [];
80
81
  let ansiText = '';
82
+ let textParts = [];
81
83
  const flushAnsiText = () => {
82
- if (!ansiText)
84
+ if (!ansiText && textParts.length === 0)
83
85
  return;
84
- nodes.push(_jsx(Text, { dimColor: dimColor, children: ansiText.trim() }, nodes.length));
86
+ if (textParts.length === 0) {
87
+ nodes.push(_jsx(Text, { dimColor: dimColor, children: ansiText.trim() }, nodes.length));
88
+ }
89
+ else {
90
+ textParts.push(ansiText);
91
+ let first = 0;
92
+ let last = textParts.length - 1;
93
+ while (first < last && textParts[first].trimStart() === '')
94
+ first++;
95
+ while (last > first && textParts[last].trimEnd() === '')
96
+ last--;
97
+ textParts[first] = textParts[first].trimStart();
98
+ textParts[last] = textParts[last].trimEnd();
99
+ // Each internal boundary replaces exactly one source newline with a
100
+ // column-child boundary. Only the whole text span trims outer space.
101
+ nodes.push(_jsx(Box, { flexDirection: "column", children: textParts.slice(first, last + 1).map((part, index) => (_jsx(Text, { dimColor: dimColor, children: index + first < last ? part.slice(0, -1) : part }, index))) }, nodes.length));
102
+ }
85
103
  ansiText = '';
104
+ textParts = [];
86
105
  };
87
106
  for (const token of tokens) {
88
107
  if (token.type === 'table') {
@@ -91,6 +110,12 @@ function renderTokensToNodes(tokens, highlight, dimColor) {
91
110
  }
92
111
  else {
93
112
  ansiText += formatToken(token, 0, null, null, highlight);
113
+ // A top-level token boundary keeps inline formatting and code fences
114
+ // intact while letting the painter cull finished offscreen text blocks.
115
+ if (ansiText.length >= TEXT_BLOCK_BUDGET && ansiText.endsWith('\n')) {
116
+ textParts.push(ansiText);
117
+ ansiText = '';
118
+ }
94
119
  }
95
120
  }
96
121
  flushAnsiText();
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"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"}
@@ -457,6 +457,12 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
457
457
  let start = 0;
458
458
  while (start < visibleRows.length && offsets[start] + heightOf(visibleRows[start]) <= relTop)
459
459
  start++;
460
+ // Resize invalidates cached heights, so a manual scrollTop can overshoot
461
+ // the entire estimated list. Keep its last row mounted to re-measure:
462
+ // an empty window has no measurement wakeup and collapses scrollHeight,
463
+ // leaving the transcript blank and the gutter believing nothing scrolls.
464
+ if (start === visibleRows.length && start > 0)
465
+ start--;
460
466
  let end = start;
461
467
  while (end < visibleRows.length && offsets[end] < relBottom)
462
468
  end++;
@@ -629,15 +635,10 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
629
635
  end = Math.max(end, idx + 1);
630
636
  }
631
637
  }
632
- // The newest failed tool call carries the trajectory footnote
633
- // (failureHint). Virtualization must not unmount it: before the window
634
- // clamp the row was always mounted, now keep mounting it explicitly while
635
- // the hint is live (verify-trace-scene's footnote check).
636
- if (failureHintRowId !== undefined && failureHintRowId !== null) {
637
- const idx = visibleRows.findIndex(row => row.id === failureHintRowId);
638
- if (idx !== -1)
639
- start = Math.min(start, idx);
640
- }
638
+ // The failure footnote is row content, not a seek request. Pinning its
639
+ // row also mounted EVERY later tool card until the trajectory was opened,
640
+ // defeating virtualization for the rest of a tool-heavy conversation.
641
+ // It reappears when scrolled into view; only forceMountRowId widens for a seek.
641
642
  const topPad = offsets[start] ?? 0;
642
643
  const mountedBottom = end < visibleRows.length ? offsets[end] : total;
643
644
  const bottomPad = total - mountedBottom;
@@ -1 +1 @@
1
- {"version":3,"file":"StreamingMarkdown.d.ts","sourceRoot":"","sources":["../../../src/components/StreamingMarkdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAkLzB,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,GAAG,KAAK,CAAC,SAAS,CAkFlB"}
1
+ {"version":3,"file":"StreamingMarkdown.d.ts","sourceRoot":"","sources":["../../../src/components/StreamingMarkdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAqMzB,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,GAAG,KAAK,CAAC,SAAS,CAyIlB"}
@@ -6,10 +6,10 @@ import { formatToken, stripPromptXMLTags } from '../terminal-utils/markdown.js';
6
6
  import { t } from '../i18n.js';
7
7
  import { Markdown } from './Markdown.js';
8
8
  /**
9
- * Renders markdown during streaming by splitting at the last top-level block
10
- * boundary: everything before is stable (memoized, never re-parsed), only the
11
- * final block is re-parsed per delta. marked.lexer() correctly handles unclosed code
12
- * fences as a single token, so block boundaries are always safe.
9
+ * Renders streaming markdown in sealed groups of completed top-level blocks.
10
+ * Only the unsealed group and growing final block change as text arrives.
11
+ * marked.lexer() keeps unclosed fences inside one token, and the boundary
12
+ * analysis below preserves the whole-document formatter's row spacing.
13
13
  */
14
14
  /**
15
15
  * Tail budget for the unstable suffix during streaming. The sticky view only
@@ -28,6 +28,7 @@ import { Markdown } from './Markdown.js';
28
28
  const SUFFIX_TAIL_BUDGET = 3584;
29
29
  const SUFFIX_BOUNDARY_LOOKBACK = 2048;
30
30
  const SUFFIX_CUT_STEP = 1024;
31
+ const STABLE_BLOCK_BUDGET = 8192;
31
32
  function clipSuffixTail(suffix, cut) {
32
33
  const total = suffix.length;
33
34
  if (total <= SUFFIX_TAIL_BUDGET) {
@@ -153,12 +154,22 @@ function analyzeSuffixStart(tokens, startIndex) {
153
154
  }
154
155
  return { kind: undefined, leadingNewlines };
155
156
  }
157
+ function gapBetween(boundary, start) {
158
+ if (start.kind === undefined)
159
+ return 0;
160
+ if (boundary.endsWithTable) {
161
+ return start.kind === 'table' && (boundary.trailingEmptyTextNode || start.leadingNewlines > 0) ? 2 : 1;
162
+ }
163
+ return start.kind === 'table' ? 1 : boundary.gap + start.leadingNewlines;
164
+ }
156
165
  export function StreamingMarkdown({ children, dimColor = false, }) {
157
- // The stable prefix is kept as ONE string identity across renders: a
158
- // fresh substring per render would break Markdown's React.memo and
159
- // re-layout the entire finished transcript tail on every token. The
160
- // identity only changes when a new block boundary advances the prefix.
166
+ // The prefix tracks source offsets; its rendered blocks are sealed once
167
+ // so an advancing boundary never reparses the entire accumulated answer.
161
168
  const prefixRef = React.useRef('');
169
+ const blocksRef = React.useRef({
170
+ blocks: [], end: 0, tokens: [], tail: '', tailGap: 0,
171
+ boundary: undefined, definitions: false,
172
+ });
162
173
  const cutRef = React.useRef(0);
163
174
  const boundaryGapRef = React.useRef(0);
164
175
  const prefixVisibleRef = React.useRef(false);
@@ -173,10 +184,24 @@ export function StreamingMarkdown({ children, dimColor = false, }) {
173
184
  prefixVisibleRef.current = false;
174
185
  prefixEndsWithTableRef.current = false;
175
186
  prefixTrailingEmptyTextRef.current = false;
187
+ blocksRef.current = {
188
+ blocks: [], end: 0, tokens: [], tail: '', tailGap: 0,
189
+ boundary: undefined, definitions: false,
190
+ };
176
191
  }
177
192
  // Lex only from current boundary — O(unstable length), not O(full text)
178
193
  const boundary = prefixRef.current.length;
179
194
  const tokens = marked.lexer(stripped.substring(boundary));
195
+ const blocks = blocksRef.current;
196
+ // Reference definitions have document-wide scope, including references
197
+ // in the growing suffix. These documents cannot use independent parsers.
198
+ if (!blocks.definitions && Object.keys(tokens.links).length > 0) {
199
+ blocks.definitions = true;
200
+ blocks.blocks = [];
201
+ blocks.tokens = [];
202
+ blocks.end = 0;
203
+ blocks.boundary = undefined;
204
+ }
180
205
  // Last non-space token is the growing block; everything before is final
181
206
  let lastContentIdx = tokens.length - 1;
182
207
  while (lastContentIdx >= 0 && tokens[lastContentIdx].type === 'space') {
@@ -190,6 +215,29 @@ export function StreamingMarkdown({ children, dimColor = false, }) {
190
215
  if (advance > 0) {
191
216
  const stableBoundary = analyzeStableBoundary(tokens, lastContentIdx);
192
217
  if (stableBoundary.safe) {
218
+ if (!blocks.definitions) {
219
+ let end = boundary;
220
+ for (let index = 0; index < lastContentIdx; index++) {
221
+ const token = tokens[index];
222
+ blocks.tokens.push(token);
223
+ end += token.raw.length;
224
+ if (end - blocks.end < STABLE_BLOCK_BUDGET)
225
+ continue;
226
+ const blockBoundary = analyzeStableBoundary(blocks.tokens, blocks.tokens.length);
227
+ if (!blockBoundary.safe)
228
+ continue;
229
+ const gap = blocks.boundary === undefined ? 0 : gapBetween(blocks.boundary, analyzeSuffixStart(blocks.tokens, 0));
230
+ // Detach the sealed slice from the growing source buffer, preserving
231
+ // UTF-16 code units rather than pinning every historical full reply.
232
+ const text = Buffer.from(stripped.substring(blocks.end, end), 'utf16le').toString('utf16le');
233
+ blocks.blocks.push({ text, gap });
234
+ blocks.end = end;
235
+ blocks.boundary = blockBoundary;
236
+ blocks.tokens = [];
237
+ }
238
+ blocks.tail = stripped.substring(blocks.end, boundary + advance);
239
+ blocks.tailGap = blocks.boundary === undefined ? 0 : gapBetween(blocks.boundary, analyzeSuffixStart(blocks.tokens, 0));
240
+ }
193
241
  prefixRef.current = stripped.substring(0, boundary + advance);
194
242
  boundaryGapRef.current = stableBoundary.gap;
195
243
  prefixVisibleRef.current = true;
@@ -198,7 +246,11 @@ export function StreamingMarkdown({ children, dimColor = false, }) {
198
246
  suffixTokenIndex = lastContentIdx;
199
247
  }
200
248
  }
249
+ if (blocks.definitions) {
250
+ return _jsx(Markdown, { dimColor: dimColor, cacheTokens: false, children: stripped });
251
+ }
201
252
  const stablePrefix = prefixRef.current;
253
+ const prefixTail = blocks.tail;
202
254
  const suffixSource = stripped.substring(stablePrefix.length);
203
255
  const unstableSuffix = clipSuffixTail(suffixSource, cutRef);
204
256
  const suffixStart = cutRef.current > 0
@@ -221,5 +273,5 @@ export function StreamingMarkdown({ children, dimColor = false, }) {
221
273
  // an overlapping empty/duplicate boundary.
222
274
  const hasDistinctSuffix = unstableSuffix !== '' &&
223
275
  (stablePrefix === '' || !unstableSuffix.startsWith(stablePrefix));
224
- return (_jsxs(Box, { flexDirection: "column", gap: boundaryGap, children: [stablePrefix && _jsx(Markdown, { dimColor: dimColor, children: stablePrefix }), hasDistinctSuffix && _jsx(Markdown, { dimColor: dimColor, cacheTokens: false, children: unstableSuffix })] }));
276
+ return (_jsxs(Box, { flexDirection: "column", children: [blocks.blocks.map((block, index) => (_jsx(Box, { flexDirection: "column", marginTop: block.gap, children: _jsx(Markdown, { dimColor: dimColor, children: block.text }) }, index))), prefixTail && (_jsx(Box, { flexDirection: "column", marginTop: blocks.tailGap, children: _jsx(Markdown, { dimColor: dimColor, children: prefixTail }) }, "prefix")), hasDistinctSuffix && (_jsx(Box, { flexDirection: "column", marginTop: boundaryGap, children: _jsx(Markdown, { dimColor: dimColor, cacheTokens: false, children: unstableSuffix }) }, "suffix"))] }));
225
277
  }
@@ -54,6 +54,11 @@ export declare function createBindingEvents(ctx: Context, deps: {
54
54
  messageObserver?: {
55
55
  publish(session: unknown, event: unknown): void;
56
56
  };
57
+ /** Drop a pre-step attachment registered by this channel for one message id
58
+ * (input-delivery's `retireAttachment`); see the discard hook below.
59
+ * Optional for direct/embed constructors that never emit inbox discards;
60
+ * channel.ts always wires it. */
61
+ retireAttachment?(messageId: string): void;
57
62
  }): {
58
63
  bind: () => void;
59
64
  };
@@ -1 +1 @@
1
- {"version":3,"file":"binding-events.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/binding-events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;IACtD,KAAK,EAAE,YAAY,CAAA;IACnB,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,EAAE,YAAY,CAAA;IACnB,QAAQ,EAAE;QACR,KAAK,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;QAC3C,IAAI,IAAI,IAAI,CAAA;QACZ,aAAa,CAAC,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAA;QACjE,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;KACxC,CAAA;IACD,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,SAAS,EAAE,iBAAiB,CAAA;IAC5B,YAAY,EAAE;QAAE,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,SAAS,EAAE,iBAAiB,CAAA;KAAE,CAAA;IACrF,WAAW,EAAE;QAAE,WAAW,IAAI,IAAI,CAAC;QAAC,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAA;IAC5F,SAAS,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAA;IACrD,SAAS,EAAE;QAAE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAC,aAAa,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC;QAAC,OAAO,CAAC,IAAI,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,IAAI,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,CAAA;IAC/T,SAAS,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAA;IAC/B,eAAe,CAAC,EAAE;QAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAA;CACtE;gBAYkB,IAAI;EAuHtB"}
1
+ {"version":3,"file":"binding-events.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/binding-events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;IACtD,KAAK,EAAE,YAAY,CAAA;IACnB,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,EAAE,YAAY,CAAA;IACnB,QAAQ,EAAE;QACR,KAAK,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;QAC3C,IAAI,IAAI,IAAI,CAAA;QACZ,aAAa,CAAC,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAA;QACjE,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;KACxC,CAAA;IACD,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,SAAS,EAAE,iBAAiB,CAAA;IAC5B,YAAY,EAAE;QAAE,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,SAAS,EAAE,iBAAiB,CAAA;KAAE,CAAA;IACrF,WAAW,EAAE;QAAE,WAAW,IAAI,IAAI,CAAC;QAAC,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAA;IAC5F,SAAS,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAA;IACrD,SAAS,EAAE;QAAE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAC,aAAa,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC;QAAC,OAAO,CAAC,IAAI,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,IAAI,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,CAAA;IAC/T,SAAS,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAA;IAC/B,eAAe,CAAC,EAAE;QAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAA;IACrE;;;sCAGkC;IAClC,gBAAgB,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3C;gBAYkB,IAAI;EAgItB"}
@@ -93,19 +93,29 @@ export function createBindingEvents(ctx, deps) {
93
93
  reconcileRetiredProjection('disposed');
94
94
  deps.state.emit();
95
95
  });
96
- const retirePending = (payload) => {
96
+ /**
97
+ * The inbox removed one message. Both events retire the pending
98
+ * preview, but ONLY a discard retires an attached-context entry:
99
+ * `agent/inbox/claimed` fires while the loop claims the batch, BEFORE
100
+ * the resident `agent/pre-step` listener can append the attachment —
101
+ * retiring there would delete the context before it is ever injected
102
+ * (dsh-agent-loop: `inbox.claim()` → claimed event → `agent/pre-step`).
103
+ */
104
+ const retirePending = (payload, alsoRetireAttachment = false) => {
97
105
  if (!current() || payload.agent !== capture.agent)
98
106
  return;
99
107
  const messageId = payload.message?.id;
100
108
  if (typeof messageId !== 'string')
101
109
  return;
110
+ if (alsoRetireAttachment)
111
+ deps.retireAttachment?.(messageId);
102
112
  const before = deps.state.pending.length;
103
113
  deps.state.pending = deps.state.pending.filter(item => item.id !== messageId);
104
114
  if (deps.state.pending.length !== before)
105
115
  deps.state.emit();
106
116
  };
107
117
  on('agent/inbox/claimed', retirePending);
108
- on('agent/inbox/discarded', retirePending);
118
+ on('agent/inbox/discarded', payload => retirePending(payload, true));
109
119
  on('session/event', (subject, event) => {
110
120
  if (!current())
111
121
  return;
@@ -1,5 +1,6 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import type { Agent } from '@deepseek-ai/dsh-agent';
3
+ import { type UserMessage } from '@deepseek-ai/dsh-llm';
3
4
  import { type ComposerImages } from './composer-images.js';
4
5
  import type { ChannelOwner } from './owner.js';
5
6
  import type { ChannelImageBlock, ChannelState, ComposerImageRef, MentionAttachments, MentionFs, PendingMessage, StagedImageInput } from './types.js';
@@ -22,8 +23,10 @@ export declare function createInputDelivery(ctx: Context, owner: ChannelOwner, b
22
23
  text: string;
23
24
  images?: readonly ComposerImageRef[];
24
25
  }, placement: PendingMessage['placement']) => void, untrackPending: (id: string) => void, composer: ComposerImages): {
25
- dispatchUserText: (text: string, placement: PendingMessage["placement"], images?: readonly ComposerImageRef[]) => void;
26
- deliverUserText: (text: string, placement: PendingMessage["placement"], images?: readonly ComposerImageRef[]) => void;
26
+ dispatchUserText: (text: string, placement: PendingMessage["placement"], images?: readonly ComposerImageRef[], attach?: UserMessage) => void;
27
+ deliverUserText: (text: string, placement: PendingMessage["placement"], images?: readonly ComposerImageRef[], attach?: UserMessage) => void;
28
+ claimAttachments: (messages: readonly UserMessage[]) => UserMessage[];
29
+ retireAttachment: (messageId: string) => void;
27
30
  withDecisionPending: <T>(name: string, pending: Promise<T>, origin?: UserTextOrigin) => Promise<T>;
28
31
  clearStagedImages: () => void;
29
32
  stageImage: (input: StagedImageInput) => Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"input-delivery.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/input-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAKnD,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAA;AAI7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAEnB;4EAC4E;AAC5E,UAAU,cAAc;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACpD,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAA;CAC5E;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAClC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,EACrE,KAAK,EAAE,MAAM,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,SAAS,GAAG,wBAAwB,CAAC,EAC7E,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC9B,YAAY,EAAE,CAAC,OAAO,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE,EAAE,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,IAAI,EAC3I,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACpC,QAAQ,EAAE,cAAc;6BAiNf,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,KAClC,IAAI;4BAYC,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,KAClC,IAAI;0BAzHsB,CAAC,QAAQ,MAAM,WAAW,OAAO,CAAC,CAAC,CAAC,WAAW,cAAc,KAAG,OAAO,CAAC,CAAC,CAAC;6BA6HzE,IAAI;wBAWZ,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC;wBACpC,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;;EAGzE"}
1
+ {"version":3,"file":"input-delivery.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/input-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAI1E,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAA;AAI7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAEnB;4EAC4E;AAC5E,UAAU,cAAc;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACpD,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAA;CAC5E;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAClC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,EACrE,KAAK,EAAE,MAAM,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,SAAS,GAAG,wBAAwB,CAAC,EAC7E,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC9B,YAAY,EAAE,CAAC,OAAO,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE,EAAE,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,IAAI,EAC3I,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACpC,QAAQ,EAAE,cAAc;6BA4Qf,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,WAC1B,WAAW,KACnB,IAAI;4BAcC,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,WAC1B,WAAW,KACnB,IAAI;iCA5Q6B,SAAS,WAAW,EAAE,KAAG,WAAW,EAAE;kCAarC,MAAM,KAAG,IAAI;0BAiIrB,CAAC,QAAQ,MAAM,WAAW,OAAO,CAAC,CAAC,CAAC,WAAW,cAAc,KAAG,OAAO,CAAC,CAAC,CAAC;6BAkIzE,IAAI;wBAaZ,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC;wBACpC,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;;EAGzE"}