feat(dsh): adapt to DSH 0.1.5-alpha.1
Move the plugin onto the DSH 0.1.5-alpha.1 baseline (peer floor ^0.1.5-alpha.1, CI pinned to the alpha dist-tag) and fix the two host contract changes that alpha line introduced. Baseline: - all @deepseek-ai/dsh-* peer/dev deps -> 0.1.5-alpha.1; optional peer @deepseek-ai/dsh-client-ui-sidebar-right (declared, unused until the native-surface phase); devDep @deepseek-ai/dsh-agent-loop (inbox fold) - dsh.plugin.json engines >=0.1.5-alpha.1; version 0.19.0-alpha.0; SIDEBAR_SERVICE_VERSION follows Live assistant stream (assistant/chunk is gone): - 0.1.5 publishes in-flight model deltas as process-local agent/assistant-stream frames instead of durable assistant/chunk events and embeds the settled stream in assistant/message / assistant/attempt. src/assistant-live.ts folds the frames into a bounded per-session buffer; sidechat.events serves the current attempt as `live` rows (replaced every poll, never a delta) and the transcript renders them; the inherited in-progress snapshot reads message content / attempt stream / live frames. Cold session reads (persistence.inspect is gone): - 0.1.5 reads a stored session through an explicit read handle (open(id,'read') -> handle.read() -> close()). src/session-store.ts owns that protocol; every cold read (sidechat transcript, cold-resume preset, session.cwd, changes replay) goes through it. Dead code and docs: - drop the remote.session.openWorkspacePath interception: 0.1.5's chat opens files through ctx.sidebarRight.openResource instead, so the wrapper had no caller and would hijack the host's "open in app" gesture; the interceptOpenPath pref, its settings row and its locale keys go with it - AGENTS.md §3 + external-plugin-guide.md describe the new baseline - vitest setup bridges Node's localStorage accessor to jsdom's store (the pre-existing 26 failures on Node >= 22) - desktop-layout e2e seeds through host RPC and types into the Lexical composer (0.1.5 replaced the textarea) instead of driving the directory picker Verified: pnpm typecheck / lint / build / test (124 files, 1327 tests) / check:consumer-types, plus pnpm test:mount against a real @deepseek-ai/dsh@0.1.5-alpha.1 host (18/18).
This commit is contained in:
@@ -131,10 +131,10 @@ jobs:
|
||||
# Pin the DSH CLI the lane mounts into; the profile's bundle stack
|
||||
# (dsh-base / dsh-web-app) resolves from this installation. Keep in
|
||||
# step with the plugin's @deepseek-ai/* peer range when bumping.
|
||||
# DSH 0.1.2-rc.1 is published under the `next` dist-tag; the lanes
|
||||
# DSH 0.1.5-alpha.1 is published under the `alpha` dist-tag; the lanes
|
||||
# speak the slash-only RPC dialect (tests/e2e/host-protocol.ts).
|
||||
- name: Install dsh CLI (pinned)
|
||||
run: npm install -g @deepseek-ai/dsh@0.1.2-rc.1
|
||||
run: npm install -g @deepseek-ai/dsh@0.1.5-alpha.1
|
||||
|
||||
# lib/ is gitignored; the tarball is produced from this build.
|
||||
- name: Build and pack the npm tarball
|
||||
|
||||
@@ -19,27 +19,27 @@
|
||||
|
||||
「npm 打包 → 真实挂载 → 无头渲染」门禁(证明打包产物在真实 DSH 挂载后不 crash):`pnpm build && pnpm pack` 产 tarball → `scripts/e2e-mount.sh` 装进全新 scratch profile(`dsh plugin --profile web add file:<tarball>`)并启动真实 `dsh web`(keyless,`--port 0`)→ `tests/e2e/mount.e2e.ts`(Playwright)断言 `[data-dsh-better-sidebar]` 挂载、无错误条/pageerror/console 错误,经「+ 菜单」逐个打开内置 tab(含终端懒加载 chunk),再经文件树打开 seed 文件强制加载 editor chunk(`client-editor.js`)。
|
||||
|
||||
本地:`pnpm build && pnpm pack && pnpm exec playwright install chromium && pnpm test:mount`。CI 钉 `@deepseek-ai/dsh@0.1.2-rc.1`(`next` dist-tag;peer 下限 `^0.1.2-rc.1`)。e2e spec 命名 `*.e2e.ts` + vitest `exclude` 双保险;**改 `exclude` 必须保留默认排除项**(exclude 整体替换默认值)。
|
||||
本地:`pnpm build && pnpm pack && pnpm exec playwright install chromium && pnpm test:mount`。CI 钉 `@deepseek-ai/dsh@0.1.5-alpha.1`(`alpha` dist-tag;peer 下限 `^0.1.5-alpha.1`)。e2e spec 命名 `*.e2e.ts` + vitest `exclude` 双保险;**改 `exclude` 必须保留默认排除项**(exclude 整体替换默认值)。
|
||||
|
||||
---
|
||||
|
||||
## 3. DSH 0.1.2 适配要点(0.1.2-rc.1+ 基线)
|
||||
## 3. DSH 0.1.5 适配要点(0.1.5-alpha.1+ 基线)
|
||||
|
||||
**v0.18.0 起插件毕业正式版(npm `latest`),仅支持 DSH 0.1.2-rc.1+**(peer 下限 `^0.1.2-rc.1`,CI 钉 `@deepseek-ai/dsh@0.1.2-rc.1`,DSH rc.x 发在 `next` dist-tag)。v0.18.0-alpha.0 进入 alpha 通道时已删除对 0.1.0-rc.8 ~ 0.1.1-rc.2 的兼容层——双方言 RPC(点分回退)、MarkdownText 双形状 labels、`dsh-client-runtime` externals/inject 残留行;stable DSH 用户请固定装 v0.17.1(npm 上的旧 latest,0.1.2-alpha.x 宿主继续用 npm `alpha` 的 v0.18.0-alpha.0)。发版:release.yml 按版本号是否含 `-` 自动选 `alpha`/`latest` dist-tag。
|
||||
**v0.19.0-alpha.0 起仅支持 DSH 0.1.5-alpha.1+**(peer 下限 `^0.1.5-alpha.1`,CI 钉 `@deepseek-ai/dsh@0.1.5-alpha.1`,DSH alpha 线发在 `alpha` dist-tag)。0.1.2-rc.1 稳定线用户继续用 v0.18.x(npm `latest`)——0.1.5 的会话事件模型与文件打开漏斗都变了,插件不再对 0.1.2 做运行时兼容。发版:release.yml 按版本号是否含 `-` 自动选 `alpha`/`latest` dist-tag。
|
||||
|
||||
宿主契约(均经真机挂载冒烟 14/14 验证):
|
||||
|
||||
1. **一次性 token 鉴权**:就绪行 `dsh web: http://127.0.0.1:<port>/?token=<43字符>`(导航换签名 cookie,干净 URL 401)。`e2e-mount.sh` 的 URL grep 必须延伸到空白(`[^ ]*`,在 `/` 截断丢 token);e2e 统一走 `tests/e2e/host.ts`(token 必选:`parseLaunchUrl` 对裸 origin 直接抛错),带 stamps 导航走 `gotoPage()`(先 addCookies 再直达——token 换 cookie 的 303 会丢弃同 URL 其它 query 参数)。插件 `/sidebar/*` 路由不受影响,同源 fetch 照旧。
|
||||
2. **Remote gateway 斜杠 RPC(唯一方言)**:`POST /api/workspace/create`,payload 恰为 `{args: {...}}`,**args 按控制器 TS 参数名包装**(`workspace/create`、`session/create` → `{args:{request:{...}}}`;`session/list` 参数名 `_request` **不可省略**——`{}` 也被拒 `args fields do not match the descriptor`);envelope `method` 与路径一致,点分路径 404。请求由 `tests/e2e/host-protocol.ts` 的 `rpcAttempt` 构造、`tests/e2e-host-protocol.spec.ts` 锁定;要调新方法先在真机验参数名再进 `RPC_ARGS_KEY`。
|
||||
3. **`MarkdownText` labels 嵌套契约**:必填 `labels: { code: { copyLabel, copiedLabel }, footnotes }`(漏传回退硬编码中文)。四个渲染点(mermaid.tsx / MarkdownHtml.tsx / TextEditor.tsx / SideChatView.tsx)统一走 `src/client/markdown-labels.tsx` 的 `markdownTextProps()`。
|
||||
4. **侧边对话转录走自有路由,不碰客户端宿主 RPC**:`ctx.connection.api`(含 `sessions.history`)在 alpha.1 整体移除,继任 `session/follow|page` 又对 `origin:'subagent'` 会话强制 subagent 地址(普通 `{kind:'session'}` 被 `agent-busy` 拒)且分页 `throughSeq` 不得超当前游标。转录因此由 **`sidechat.events`** 插件路由供给(`src/sidechat-routes.ts`:live 读 `agent.session.snapshotEvents()`(0.1.2-alpha.4 起的按需读 API,前身 `Session.events` 属性已删)、冷读 `sessionPersistence.inspect`,服务端 `session/end-seed` 切割 + `afterSeq` 增量);`ctx.connection` 镜像与 inject 已删。设计见 [docs/plans/2026-08-20-sidechat-tab-design.md](docs/plans/2026-08-20-sidechat-tab-design.md) §10。
|
||||
4. **侧边对话转录走自有路由,不碰客户端宿主 RPC**:`ctx.connection.api`(含 `sessions.history`)在 alpha.1 整体移除,继任 `session/follow|page` 又对 `origin:'subagent'` 会话强制 subagent 地址(普通 `{kind:'session'}` 被 `agent-busy` 拒)且分页 `throughSeq` 不得超当前游标。转录因此由 **`sidechat.events`** 插件路由供给(`src/sidechat-routes.ts`:live 读 `agent.session.snapshotEvents()`、冷读 `sessionPersistence.inspect`,服务端 `session/end-seed` 切割 + `afterSeq` 增量);`ctx.connection` 镜像与 inject 已删。**0.1.5 起实时增量不在日志里**:`assistant/chunk` 事件被删除,进行中的模型增量改由 `agent/assistant-stream` 瞬时帧发布(`start` / `chunk` / `end`,不入会话日志),结算时才落 `assistant/message`(内嵌 `stream`)或 `assistant/attempt`(失败尝试,内嵌 `stream`)。插件在宿主侧 `src/assistant-live.ts` 折叠这些帧成有界缓冲,`sidechat.events` 的 `live` 字段按「当前 attempt 全量、每次轮询替换」下发(不是增量),转录映射与继承快照都读它。设计见 [docs/plans/2026-08-20-sidechat-tab-design.md](docs/plans/2026-08-20-sidechat-tab-design.md) §10。
|
||||
5. **`dsh-settings` 无运行时 `settingsNamespace`**:命名空间合法性校验转为编译期模板字面量 `SettingsNamespaceInput`(小写字母开头 + `[a-z0-9-]` 尾部),`'dsh-better-sidebar'` 字面量直接过——宿主侧直接传常量(`src/index.ts` 的 settings inject)。
|
||||
6. **`dsh-subagent` 的 `SUBAGENT_DESCRIPTOR_VERSION` 2 → 3**:sidechat 种子的 `subagent/descriptor` 版本由宿主包盖章,插件不硬编码;测试断言跟随常量(`tests/sidechat-routes.spec.ts`),勿钉字面量。
|
||||
7. **`@deepseek-ai/dsh-client-runtime` 包已消亡**(继任 seed 是裸名 `dsh-client-store`,无 `/client` 子路径):peerDependencies、devDependencies、`dsh.client.inject`、chunk externals 白名单(`src/client/chunk-loader.ts` / `tsdown.config.ts` / `tests/chunk-loader.spec.ts` / `tests/manifest-consistency.spec.ts` 四处同步)均已无该条目。
|
||||
8. **e2e scratch profile 的 `minimumReleaseAgeExclude` 含 `'@deepseek-ai/*'`**(`scripts/e2e-mount.sh` / `e2e-aggregate-mount.sh`,与仓库根 `pnpm-workspace.yaml` 同策):alpha 版本常在发布后 24h 内跑 lane,pnpm 11 的 `minimumReleaseAge` 默认会拒装新鲜包。
|
||||
9. **插件开发树的 dsh-* 传递 peer 需提升为 devDependencies**(alpha.3 首见):`dsh-subagent` 等 npm 包把 `dsh-attachment` 等 dsh-* 姊妹包全部声明为 peerDependencies(由宿主 bundle 树统一提供,宿主侧无此问题),插件仓库若只直接依赖其中一部分,其余 peer 在 pnpm 下会解析到树上残留的旧版——如 `dsh-attachment@0.1.1-rc.1` 缺 `admitPromptContent` 导出、`dsh-subagent` 产物 import 它时测试加载即崩。因此 devDependencies 需涵盖 dev 树实际触达的全部 peer(attachment / code-runtime / scope / session-projection / system-prompt / user-approval / util-time 七个即为此提升,与直接依赖同款精确钉版);`@deepseek-ai/cordis` peer 自 alpha.3 起要求 `^4.0.2`(上游全线 peer 已升)。适配新 alpha 版本时先跑 `pnpm peers check`,把新失配的传递 peer 一并提升进 devDependencies。例外:**`dsh-client-locale` 的 peer/devDep 允许落后于基线**(alpha.5 时上游停在 0.1.2-alpha.3 未发新版)——peer 下限 `^0.1.2-alpha.3` 天然容纳同 tuple 的 alpha.5 运行时,此时保持旧钉版并在 `pnpm-workspace.yaml` 注明,待上游发版再追平;rc.1 起上游恢复发版,peer/devDep 已与基线一并钉 `0.1.2-rc.1`,该例外消除。
|
||||
10. **聊天文件打开漏斗是 `remote.session.openWorkspacePath`**(`ctx.workspaces.openPath` 已随 alpha 删除,`IWorkspaces` 无此方法):「聊天区文件在侧边栏打开」拦截在 `ctx.inject(['remote.session'], …)` 内以 **defineProperty 数据属性遮蔽**该命名空间方法(gateway client 的方法是 accessor 属性、异步挂载、contribution 重挂载时服务重建——inject 回调重跑即自愈);实现见 `src/client/openpath-intercept.ts`,设计见 [docs/plans/2026-08-31-openpath-intercept-alpha-design.md](docs/plans/2026-08-31-openpath-intercept-alpha-design.md)。
|
||||
11. **自定义种子必须带 fork 标记对(`meta.isSeeded: true` + `inheritedEventCount`)**:dsh-session 契约「只给 seed 不标 isSeeded,种子算重放历史而非继承前缀」——缺标记时 `Session.ownEvents()` 含整个种子,子会话 `Inbox` 构造器重放种子里的 `agent/inbox/spliced`,**继承父会话切割时刻未领取的 inbox 输入**(排队用户消息 next-turn、长回合中工具结果上下文/steering next-step——后者即「上下文很长时侧边对话先把之前的 User msg 发出去」的根因,幽灵消息排在 boundary 之前最先发给模型)。宿主 `session.fork`(api-session-controller)的调用即规范形态;回归由 `tests/sidechat-seed-validation.spec.ts`(真实 `Session.create` + `Inbox`)守护。0.1.2-rc.1 复评:无更优雅的 sidechat 宿主 API(`ContinuableStartSpec` 仍无 seed 字段),`AgentRegistry.create` 接缝补齐标记即为规范用法。
|
||||
10. **聊天文件打开漏斗已改为原生右侧栏**:0.1.5 客户端不再有任何 `remote.session.openWorkspacePath` 调用——聊天里一切文件打开(工具行 / 产物行 / 正文提及 / 行内代码路径)统一走 `ctx.sidebarRight.openResource(fileAddressFor(sessionId, cwd, path))`(`packages/client/ui-chat/src/client/apply.ts` 是唯一调用点)。插件此前的 `openpath-intercept`(defineProperty 遮蔽 `remote.session` 方法)随之删除:0.1.5 上它是死代码,且会劫持宿主的「在外部应用打开」语义。文件当前落到原生栏的内置 `text` 预览;阶段二由插件注册 `dsh-resource://file/**` 类型(默认 `extension` 带,压过 `text` 的 `fallback`)接管。
|
||||
11. **自定义种子必须带 fork 标记对(`meta.isSeeded: true` + `inheritedEventCount`)**:dsh-session 契约「只给 seed 不标 isSeeded,种子算重放历史而非继承前缀」——缺标记时 `Session.ownEvents()` 含整个种子,子会话的 inbox 折(`inboxProjectionDefinition`,0.1.5 起 `@deepseek-ai/dsh-agent` 只导出 `Inbox` 接口,实现是该投影)重放种子里的 `agent/inbox/spliced`,**继承父会话切割时刻未领取的 inbox 输入**(排队用户消息 next-turn、长回合中工具结果上下文/steering next-step——后者即「上下文很长时侧边对话先把之前的 User msg 发出去」的根因,幽灵消息排在 boundary 之前最先发给模型)。宿主 `session.fork`(api-session-controller)的调用即规范形态;回归由 `tests/sidechat-seed-validation.spec.ts`(真实 `Session.create` + 对 `ownEvents()` 跑 inbox 折)守护。0.1.5 复评:无更优雅的 sidechat 宿主 API(`ContinuableStartSpec` 仍无 seed 字段),`AgentRegistry.create` 接缝补齐标记即为规范用法;`Session.create` 的 header `version` 必须用 `SESSION_FORMAT_VERSION`(0.1.5 是 `3` 字面量类型),勿钉 `0`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 面向 **消费插件开发者**:如何让你的插件向 better-sidebar 注册新的侧边栏页面(tab)和文件类型预览器。
|
||||
>
|
||||
> 适用版本:**v0.4.0+**(`ctx.betterSidebar` 服务);声明式设置 **v0.4.1+**;text/number 设置行 **v0.11.0+**;badge/生命周期/定向打开/插件设置/版本探测 **v0.12.0+**;select 设置行(`settingSelect`)与外链认领(`urlTarget`)**v0.13.0+**;统一 `@deepseek-ai/cordis` 类型基底 **v0.15.2+**;自由窗口(`floatWindows`)**v0.16.0+**;终端固定(pin)**v0.17.0+**。当前版本 **v0.18.1**(正式版,仅支持 DSH 0.1.2-rc.1+;旧宿主 stable 线为 v0.17.1)。
|
||||
> 适用版本:**v0.4.0+**(`ctx.betterSidebar` 服务);声明式设置 **v0.4.1+**;text/number 设置行 **v0.11.0+**;badge/生命周期/定向打开/插件设置/版本探测 **v0.12.0+**;select 设置行(`settingSelect`)与外链认领(`urlTarget`)**v0.13.0+**;统一 `@deepseek-ai/cordis` 类型基底 **v0.15.2+**;自由窗口(`floatWindows`)**v0.16.0+**;终端固定(pin)**v0.17.0+**。当前版本 **v0.19.0-alpha.0**(alpha 通道,仅支持 DSH **0.1.5-alpha.1+**;0.1.2-rc.1 稳定线请用 v0.18.x)。
|
||||
> 权威代码:`src/client/service.ts`(服务实现)、`src/client/builtins/`(内置 8 tab + 6 viewer 参考实现)、`lib/types/client/service.d.ts`(类型声明)。
|
||||
> 仓库开发规则(硬约束 / CI / 发版)见 [AGENTS.md](../AGENTS.md)。
|
||||
|
||||
@@ -730,7 +730,7 @@ ctx.effect(() =>
|
||||
| **i18n 跟随** | 文案跟随 DSH `ctx.locale`(词典在 `betterSidebar` 命名空间;Host-backed `locale.preference` 优先于浏览器语言并实时切换;缺失回退浏览器)。消费插件**不要**依赖内部 `t()`——标题传字符串或 `() => string` |
|
||||
| **第三语言覆盖(ja 等)** | 可选 peer `@huanlin/dsh-plugin-better-locale`(optional)提供 ja/ko 覆盖,**借用 DSH 英文槽位**(仅 DSH=en 时生效,zh 下惰性)。经 `ctx.get('betterLocale')` 注入 `t()`;未安装整段 no-op |
|
||||
| **懒加载 chunk** | 重依赖(xterm/CodeMirror)在独立 bundle(`lib/client-<name>.js`),经 `/sidebar/bundle` 按需下发;factory 赋到 `globalThis.__dshChunks__[<name>]`,由 `src/client/chunk-loader.ts` 物化,**不经** `__ModuleLoader__`。对消费插件透明 |
|
||||
| **聊天文件打开漏斗(alpha 宿主)** | 聊天里一切文件打开(工具行 / 产物行 / 正文提及 / 行内代码路径)汇入 `ctx.remote.session.openWorkspacePath`(Typert remote 命名空间,cordis 服务 key `remote.session`,方法为 **accessor 属性**、异步挂载)。better-sidebar 的「聊天区文件在侧边栏打开」即在 `ctx.inject(['remote.session'], …)` 内以 defineProperty 遮蔽该方法(`src/client/openpath-intercept.ts`)。你的插件若要观测/旁路聊天文件打开,走同一服务;不要假设 pre-alpha 的 `ctx.workspaces.openPath` 存在(alpha 的 `IWorkspaces` 已无此方法) |
|
||||
| **聊天文件打开漏斗(0.1.5 宿主)** | 聊天里一切文件打开(工具行 / 产物行 / 正文提及 / 行内代码路径)统一走 DSH 原生右侧栏的 `ctx.sidebarRight.openResource(fileAddressFor(sessionId, cwd, path))`(`packages/client/ui-chat/src/client/apply.ts` 是唯一调用点);`remote.session.openWorkspacePath` 在 0.1.5 客户端**已无调用者**,better-sidebar 的 openpath 拦截随之删除。你的插件若要观测/旁路聊天文件打开,注册原生 tab 类型认领 `dsh-resource://file/**`(见 [AGENTS.md §3](AGENTS.md) 第 10 条);不要假设 `ctx.workspaces.openPath`(已删除)或旧 `remote.session` 漏斗存在 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"id": "dsh-external/dsh-better-sidebar",
|
||||
"version": "0.18.1",
|
||||
"version": "0.19.0-alpha.0",
|
||||
"main": "./lib/index.js",
|
||||
"description": "VSCode 风格右侧侧边栏:文件资源管理器 / 编辑器 / 终端 / Git / 浏览器,按会话隔离。暴露服务供其他插件注册侧边栏页面和文件预览器",
|
||||
"engines": {
|
||||
"dsh": ">=0.1.2-rc.1"
|
||||
"dsh": ">=0.1.5-alpha.1"
|
||||
},
|
||||
"contributes": {
|
||||
"tools": [],
|
||||
|
||||
+41
-34
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dsh-better-sidebar",
|
||||
"version": "0.18.1",
|
||||
"version": "0.19.0-alpha.0",
|
||||
"description": "DSH web plugin: a VSCode-like right sidebar (explorer / editor / terminal / git / browser), isolated per conversation session. Exposes a service for other plugins to register sidebar tabs and file viewers.",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
@@ -49,6 +49,7 @@
|
||||
"@deepseek-ai/dsh-client-locale",
|
||||
"@deepseek-ai/dsh-client-ui-slots",
|
||||
"@deepseek-ai/dsh-client-ui-conversation",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
||||
"@deepseek-ai/dsh-client-modules"
|
||||
],
|
||||
"platform": "web"
|
||||
@@ -88,19 +89,20 @@
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "^4.0.2",
|
||||
"@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-locale": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-host-webserver": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-session": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-settings": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-subagent": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-tools": "^0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-agent": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-host-webserver": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-settings": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-subagent": "^0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-tools": "^0.1.5-alpha.1",
|
||||
"@huanlin/dsh-plugin-better-locale": "^0.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
@@ -108,6 +110,9 @@
|
||||
"peerDependenciesMeta": {
|
||||
"@huanlin/dsh-plugin-better-locale": {
|
||||
"optional": true
|
||||
},
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -144,26 +149,28 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cordisjs/plugin-loader": "^1.0.0-rc.5",
|
||||
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-locale": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-code-runtime": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-host-webserver": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-scope": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-settings": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-subagent": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-user-approval": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-util-time": "0.1.2-rc.1",
|
||||
"@deepseek-ai/dsh-agent": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-agent-loop": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-attachment": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-locale": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-code-runtime": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-host-webserver": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-invariants": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-llm": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-scope": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-session": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-session-projection": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-settings": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-subagent": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-system-prompt": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-tools": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-user-approval": "0.1.5-alpha.1",
|
||||
"@deepseek-ai/dsh-util-time": "0.1.5-alpha.1",
|
||||
"@huanlin/dsh-plugin-better-locale": "^0.1.0",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@types/node": "^24.0.0",
|
||||
|
||||
Generated
+301
-230
@@ -106,71 +106,77 @@ importers:
|
||||
specifier: ^1.0.0-rc.5
|
||||
version: 1.0.0-rc.5(cordis@4.0.0-rc.8)
|
||||
'@deepseek-ai/dsh-agent':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c)
|
||||
'@deepseek-ai/dsh-agent-loop':
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(3d62ae7d0bb4adb238d8dfb4da37e073)
|
||||
'@deepseek-ai/dsh-attachment':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-client-locale':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-conversation':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(typescript@5.6.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(typescript@5.6.2)
|
||||
'@deepseek-ai/dsh-client-ui-primitives':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-settings':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-sidebar-right':
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-slots':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-code-runtime':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-host-webserver':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-session-projection':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-settings':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/schemastery@3.18.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/schemastery@3.18.2)
|
||||
'@deepseek-ai/dsh-subagent':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(6241f70df87ac98c48965b657a1f7a60)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(0904ccd91d8f9ab5907c34d0b1c82b43)
|
||||
'@deepseek-ai/dsh-system-prompt':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-tools':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(de5e1ff2d608fbe22667925341ad1dfc)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(239eb910b4462b7c15b6b44661457a15)
|
||||
'@deepseek-ai/dsh-user-approval':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-agent@0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48))(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-system-prompt@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-agent@0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c))(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-system-prompt@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-util-time':
|
||||
specifier: 0.1.2-rc.1
|
||||
version: 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
specifier: 0.1.5-alpha.1
|
||||
version: 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@eslint/js':
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1(eslint@10.9.1)
|
||||
'@huanlin/dsh-plugin-better-locale':
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-client-locale@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-primitives@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-settings@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-slots@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
version: 0.1.0(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-client-locale@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-primitives@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-settings@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-slots@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@playwright/test':
|
||||
specifier: ^1.62.1
|
||||
version: 1.62.1
|
||||
@@ -458,124 +464,153 @@ packages:
|
||||
'@deepseek-ai/cosmokit@1.8.3':
|
||||
resolution: {integrity: sha512-qBo+ronVM6Eu2WNVJXi8JcMiqZ19T9BRIpV+5qJUFPXjGH/Z0QKcQMC/IZJ7L394YTOtJgcovbk9qP0w2GsBXQ==}
|
||||
|
||||
'@deepseek-ai/dsh-agent@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-lfaqN34vUCWvbn1kJVHrhfJ6Dvt1HDHCm33ZCpmKkl07/5q6FxWqVv6rOdVX5QnM/9xz/uYiN0nQwUtBg4+Skg==}
|
||||
'@deepseek-ai/dsh-agent-loop@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-FpfSUfeyONRfVbmIC82dYGPL4G5T+9sJDd0OIqMqv8gH2M7smbILE/HOnKo/eHC29ce/YDSvL3etWr6g0PGc8w==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session-projection': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-typert-protocol': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-agent': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session-persistence': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session-projection': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-settings': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-tools': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-attachment@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-QISKUjEITusLvAkqPLRn70xDUt+GjjY41pISjtPKYcZ4EGmayzhgZ3VJNYEcC+rbaF44hs2o/0VhOwHA7lyxjw==}
|
||||
'@deepseek-ai/dsh-agent@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-Klimoo970OFP2/5sk6MaycY5l4VHeK8dN5jEIL3l9EBerr4QnoDOZMmLVkrx3cSk9OCm4KfQWrv8Gkm1HJ3fUg==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-brand': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session-projection': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-typert-protocol': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-util-values': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-brand@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-y2RHIag3kzLdY8kTBuSUSTsKzWPscJrysNObXTiNM7stXLpa3jXpshsB1caqGzNx50vYcmHeN1gFVECgx3NZHg==}
|
||||
'@deepseek-ai/dsh-attachment@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-2XBsE+J4zSFEC09OhCXVD1BPWR3KIdVlM7lKcaHBEuUHwqewS/g16DbM/yAmFUaigjXS9zuQFThCAv7QYb1JhA==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-brand': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-brand@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-R7CADJl81Q5PMeIKAQfzP9ZRauLw7niiSZDUVqZfxAkNMlGbKIqdXtUIMuxOv1TQUW8fuqqqPVSDo7m+QtKQxA==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-client-locale@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-p34i5WcZ0+b77RKEatGLc6Rh4OgJJmc15kWbBqtCtSI1BxpwZxjDzoNIMTTS3jDGUQqfEd9X7Js9D4uNJDUewg==}
|
||||
'@deepseek-ai/dsh-client-locale@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-Dz7VSN8nr59xLR+rvIlYXlP1A47l1XOgFFAHpNXnxIXupI8VdXb1GIByI5WdEdrZ+J4DmoBJZggNkIrVbjXqRg==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-conversation@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-PDrV7twPfmZE5TjdvdVVZMHOla36wCojRC8GJBUp1cdXsWUP+Omk10gxYbH1g5TQPdAWgSL+GktdjondSXMSNQ==}
|
||||
'@deepseek-ai/dsh-client-ui-conversation@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-0UiN8TEuVKrnr10lIz8T8pkp9wK/gfMap6HEnP040KB50mceTTO3y00umCsz3CeS61gYWS6DwkaNBY9th3wq8Q==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-primitives@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-4gZS+tXAHbSxAjP91+lENpxKb49XVKcO80m7ATNNgD6lJHOwbuWVGv9IwEnmQ5ebZvI8Yrp8C1J+jDe1pB9jXw==}
|
||||
'@deepseek-ai/dsh-client-ui-primitives@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-vF+hNd/Mxrhpbm5qcFVUFb7Db0JqkLnSdu+V2USu8dtQwcFIb0C5Es6v1+EB6Pq42YFs/lIg90JUTuXvF0OxBg==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-settings@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-/S16tlS84fggURMqO94HjYbE0xIH82aRJ2E4UupgM9acD3yJ6SCVam17tMcWKnqA6UWJK9llEHhl+KN9JyI/0Q==}
|
||||
'@deepseek-ai/dsh-client-ui-settings@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-+3TVwum4UVY10jzEG2Pt7LfcJAjs1PlYb6kr49v3Yq6ZCvs3bsrEJ/yAN7EDJ7jtcUsgooSSneUlvBMXVdOCrQ==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-slots@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-wcqn8xwb87RjurSQ0zPoI4K9eYhDyoBjQqg4bByDq8CNynvayHoBPnNWfiAQ8ngyOPE20teL3DMS0gbcfCTqbA==}
|
||||
'@deepseek-ai/dsh-client-ui-sidebar-right@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-3msAiVyymMsaBFKKSTsPO4q/x5isVcIYJvhQtuTCH1rEI11FFjm7/FUkOLGlBJsmIrxlubyTQ5lxhZKv+d1X5w==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-code-runtime@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-dv5UszPJb/AFcarZc2c6h5z/X80bsrSd3bfIOugwNVclSI6P+L3ixseoZKgyks7T0Ap0h6ngXGk2WeoUkmYxnQ==}
|
||||
'@deepseek-ai/dsh-client-ui-slots@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-Z7GXSKUeKlYrV0yRMU5auRSsTI0ORQ5zsr/FSojzdCI6+qQi27wMwRHvy5enIYMq/R/PfvSnH7igefzxu7OL5w==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-host-webserver@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-QVcaf4qnIa1t215Y8TRiRhqkEz4Lu5/F+KqvWT+4sHOiyWmZr8g1JndqlwP6MBnMQEPmp0I/EKYJ7PWMV32gkA==}
|
||||
'@deepseek-ai/dsh-code-runtime@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-WsHQyfhZlhy6E/E5FOg0O1nKguGTdxTQUOcE9Ixu0u9LD6kjnuNtwbJasKNCAVSRGRIMJ/LQ22OorCOVTbP+Ag==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-invariants@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-zSjuFJYcEB94kPwn7SicqAV1dfVWZkjt1I8luDboxtjfp5CN6ziwJhLv3KaB+Dh9tgkyGY4mvo886W/HhE9L9g==}
|
||||
'@deepseek-ai/dsh-host-webserver@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-0jjj2skOsThDukKE48WVE/GUtKXza4yTUrfZ7B0MwVrNbVhQPlrbMVlIyjR60OQCwNwDI8PC7Tbd6BIBAfetGw==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-llm@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-7VYsha5AXsVLnsAwYJffWXz9bwUbElw8i5N8tlTSdai9Bupk3sMbsotzPf8ZbsuGAxQYErahMwQwgGEu4qZO6g==}
|
||||
'@deepseek-ai/dsh-invariants@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-8Ote6DMq12u/X7eu5OfjlXPI1jxbEFUEm1jLIu6X03XZJT37O6S+wHvf5XCF0Kq43Ch7r5sibT3xcgTZpWRkNQ==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-scope@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-GHwbzWrGbmE0ZGzAZsXBudpyCM9ENCpXxlKODIaPFbIWhRePh6nH2jW47TAOqCIsPPedWC2p3w8Tx5WAa3I/2A==}
|
||||
'@deepseek-ai/dsh-llm@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-ncbGiWdvOG9v3K4b/LluSnrrG1w0lHKGkDsjztz1rfekvp2meLeQwbf59VCQRG68N9hdqNcr8dm0GFLEpGTE4w==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.2-rc.1
|
||||
|
||||
'@deepseek-ai/dsh-session-projection@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-qVWlIzPm4u9vzQ5G/QtE6jeSJ3b3b42hWVJj664BurEZlHbAfdqkhYc1QsoBU+5k5kUEJFHh07z8HxuXA9kTnQ==}
|
||||
'@deepseek-ai/dsh-scope@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-rY/5BQAiP0hPBArwPB2uf5kXdWfmSHcOXprB7hrDeyZ6EwciYfUgOX8n5CY/FT7e2ypd3Lwqqe90fibUxU3xGg==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-session': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-session@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-jRGNPTbQcvIx1F2MVmmkoiHLgpC3Btqo1wkl/3JDLlOWRVWKOuaC+5fQiMrFPOYfno6YiX/c2UvmA0td8qB92w==}
|
||||
'@deepseek-ai/dsh-session-persistence@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-9bWMlbGsY/IEeAr0fKHdIxC9X/FMVmFGjJFn1HzqGsLKYDtrWmU3F5jug3ZN+baIfcjhiIzyP6FVbNvkFrVQag==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-scope': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-brand': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-timeout': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-settings@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-wMmJ2w5S6I7hgmgPppuAVemltEcTmAa8gvii7fk4T2KGVCuBYgig8xPwQ8Lp2ukLZSzzE6ZVCQxhdYZyJX/USA==}
|
||||
'@deepseek-ai/dsh-session-projection@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-V68REQFndPMxtbwsX+MOPkrBF6nvSWC5L5cbFheX5EcgIqe2gJthRH4j0aQyTt3HOHeu3VRlNDXeE/Ys6pEejg==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-brand': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-session@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-15+FxQFoVDDG1Lh0wA8dqVpVz7O4iUiXtVYM+ETg/ce6+gITPafXTEDd5i8HTIMnPRjZxEwX0BUlDDsChsjjNw==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-scope': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-settings@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-nO2fAvijWIHyNvAQsWPpIVuFnz03q1adB94l7HRbfWfMi0fJwi1u96f28SM8BJuOOqLLOka7kRNmljoqc/1bDw==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-brand': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/schemastery': ^3.18.2
|
||||
|
||||
'@deepseek-ai/dsh-subagent@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-GhRsymKb1uYbc7OwHh8WTdSXqAcx/KHFJwYX4BYJk90dsOkVx9ccBq6QoJfDYja34s1KCyv2UO/8MUtrzBsITw==}
|
||||
'@deepseek-ai/dsh-subagent@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-wXjiTD9t230lI2AywTjEBfa6B5Q22eVkZqrAE8z1pUrd7/CPoyMSLmySEobfb9ugX1LcjLn8lGTngO8k+Gj8DQ==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-agent': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-agent-presets': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-attachment': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-jobs': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-sandbox': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-sandbox-policy': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session-persistence': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session-projection': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session-projection-cache': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session-query': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-tools': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-typert-protocol': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-user-approval': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-util-time': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-agent': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-agent-presets': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-attachment': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-jobs': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-sandbox': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-sandbox-policy': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session-persistence': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session-projection': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session-projection-cache': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session-query': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-tools': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-typert-protocol': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-user-approval': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-util-time': ^0.1.5-alpha.1
|
||||
peerDependenciesMeta:
|
||||
'@deepseek-ai/dsh-agent-presets':
|
||||
optional: true
|
||||
@@ -596,61 +631,61 @@ packages:
|
||||
'@deepseek-ai/dsh-user-approval':
|
||||
optional: true
|
||||
|
||||
'@deepseek-ai/dsh-system-prompt@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-7W93PZKIk4CHvjZGgLIxrrEKpk+6t8nQXje1vKR5vG73dfXH0t1MJ5WWH/fngadYc7c6KzM84ihQ66tJqJqyNQ==}
|
||||
'@deepseek-ai/dsh-system-prompt@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-sQk/K05ZOFn2xbDbbCTfDN1Jr5lMBz+dPVreqkVsXbt72QxzTGM4yw8JmDJEvUMVnOKBsKyWpLrvBpcYN8Unbw==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-timeout@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-llz11yxWeJB8suKCa6hRLjNBWVaPn3Fbd0XoBS0bkZLhPFzO1mDorx/XMca2GhI7XF9A86CURQcDt+aGjjvqJA==}
|
||||
'@deepseek-ai/dsh-timeout@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-QTcHWuKD1/LuHyXTvwX85LzWhgcqUeIza0FjajekIDYG7+jW1K6T6gzRYaOger0pAzJMylUz8iERU16cHR1c8A==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-tools@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-W9kUio00s7WbM8kEwniyd4hfb3CeUxVczsjXbOcKtakfiTywNLeyRWkpx2fvwRAH2rBfg3qCgczVfS0DOw1csg==}
|
||||
'@deepseek-ai/dsh-tools@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-TYvlnJlFZtenuoqe/SvTSrscfo6Y8NgwwV2zgaF+1rsOZBBiGPZNsUn0WG53JMs8M8wpkq7HIzIUUs2TOdThMw==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-agent': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-code-runtime': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-user-approval': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-agent': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-code-runtime': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-user-approval': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-typert-protocol@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-vBWD0NpriPd96ltbWaAw+iKeWFIEjiAcCWYtmqEcV1Ms+FaV02usjjm7LCmhSQhMZpBSBc7EZUX0spGlpjI1bA==}
|
||||
'@deepseek-ai/dsh-typert-protocol@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-TYUDD047W5uxXK+8QX8lR8z/cAUqQBBy2mzU2kX7i7STtjzzeIqf+29JzANDFDFIXjwKmszk6VjeX7pwyjinIw==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-user-approval@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-oYkLE4a/TwqVNi299pUf/QP1Yku6KScdUCTUyYbjaRBcN+/pXPpHikl2aoE2thH9D0uT/Kj6T2kz+wDDcFd9Xg==}
|
||||
'@deepseek-ai/dsh-user-approval@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-xCuH17yYnuCpDMJyrNzHfL/wYs9iEGPGFOqnc2NWyFhNMsja+UGe3cDjqhCRvpYsXImM9AKxdAtjM18bDda0Lw==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
'@deepseek-ai/dsh-agent': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-brand': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.2-rc.1
|
||||
'@deepseek-ai/dsh-agent': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-brand': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-invariants': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-llm': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-scope': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-session': ^0.1.5-alpha.1
|
||||
'@deepseek-ai/dsh-system-prompt': ^0.1.5-alpha.1
|
||||
|
||||
'@deepseek-ai/dsh-util-crypto@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-gE8FznQ1hkknw9QMrHNSlm3UEyRYuj3MLVvlJ6NyH1kmOsdjnbrVbSs+Pn72k2oIV4Fiyu3Umg0sLznUEcsiXw==}
|
||||
'@deepseek-ai/dsh-util-crypto@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-lTTpyRKowbzlvrwHJr7leMDt/Iul8cLmlrwn+JLyKid5YzX1NDK7MgkrEYivqGplX5Jd64GRcXU1bGpz9phULA==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-util-time@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-OoudDLoYothbZKDbjO6kFu3NLgAnglIS8NFkg5fjnBtz8LGwplN8w7kRURLIrZtH38HknYhpDsdJh8LZai3U6A==}
|
||||
'@deepseek-ai/dsh-util-time@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-Fry6C7D9d4pwaDzAMc0Mg7PUjHitvLZ6UxzLfHP4sm7/43aps9RWeKAdRz+2OMf2Ml51zn8jUMRrhfaQoB3Ghg==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-util-values@0.1.2-rc.1':
|
||||
resolution: {integrity: sha512-FQdgoK+KYVR0pBQb9fTsajvGb3gzUxEzJQEYnFOt+Qdwc4nkxJOPwJJdIvXuqvZaQ33DaOoqpnpcr2p5HpLu4g==}
|
||||
'@deepseek-ai/dsh-util-values@0.1.5-alpha.1':
|
||||
resolution: {integrity: sha512-slx5c7zAg1VAwvfkCNe24+NTu93FlQuI5vHLx0rb/S7HIVeCqI5Lm5158WCKjPTvHxXXOpACy7qY9L17lwCsFw==}
|
||||
peerDependencies:
|
||||
'@deepseek-ai/cordis': ^4.0.2
|
||||
|
||||
@@ -3506,32 +3541,51 @@ snapshots:
|
||||
|
||||
'@deepseek-ai/cosmokit@1.8.3': {}
|
||||
|
||||
'@deepseek-ai/dsh-agent@0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48)':
|
||||
'@deepseek-ai/dsh-agent-loop@0.1.5-alpha.1(3d62ae7d0bb4adb238d8dfb4da37e073)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-invariants': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-session-projection': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-typert-protocol': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-agent': 0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c)
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-session-persistence': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-timeout@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session-projection': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-settings': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/schemastery@3.18.2)
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-tools': 0.1.5-alpha.1(239eb910b4462b7c15b6b44661457a15)
|
||||
'@deepseek-ai/dsh-util-values': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
zod: 4.4.3
|
||||
|
||||
'@deepseek-ai/dsh-attachment@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))':
|
||||
'@deepseek-ai/dsh-agent@0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-brand': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-session-projection': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-typert-protocol': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-values': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
|
||||
'@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-attachment@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
|
||||
'@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-client-locale@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-client-locale@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-conversation@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(typescript@5.6.2)':
|
||||
'@deepseek-ai/dsh-client-ui-conversation@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(typescript@5.6.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
@@ -3544,7 +3598,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-primitives@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-client-ui-primitives@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@shikijs/langs': 4.4.3
|
||||
@@ -3570,19 +3624,27 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-settings@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-client-ui-settings@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-slots@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-client-ui-sidebar-right@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
clsx: 2.1.1
|
||||
picomatch: 4.0.5
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
'@deepseek-ai/dsh-client-ui-slots@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-code-runtime@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-code-runtime@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-host-webserver@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-host-webserver@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
@@ -3591,122 +3653,131 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
|
||||
'@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-brand': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-timeout': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-typert-protocol': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-crypto': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-values': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-timeout': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-typert-protocol': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-crypto': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-values': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
zod: 4.4.3
|
||||
|
||||
'@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))':
|
||||
'@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-invariants': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
|
||||
'@deepseek-ai/dsh-session-projection@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))':
|
||||
'@deepseek-ai/dsh-session-persistence@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-timeout@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-session': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-session': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-timeout': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-values': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
|
||||
'@deepseek-ai/dsh-session-projection@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-session': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
zod: 4.4.3
|
||||
|
||||
'@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))':
|
||||
'@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-brand': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-util-values': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-util-values': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
|
||||
'@deepseek-ai/dsh-settings@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/schemastery@3.18.2)':
|
||||
'@deepseek-ai/dsh-settings@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/schemastery@3.18.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-brand': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-session': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-util-values': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-session': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-util-values': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
|
||||
'@deepseek-ai/dsh-subagent@0.1.2-rc.1(6241f70df87ac98c48965b657a1f7a60)':
|
||||
'@deepseek-ai/dsh-subagent@0.1.5-alpha.1(0904ccd91d8f9ab5907c34d0b1c82b43)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-agent': 0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48)
|
||||
'@deepseek-ai/dsh-attachment': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-brand': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-tools': 0.1.2-rc.1(de5e1ff2d608fbe22667925341ad1dfc)
|
||||
'@deepseek-ai/dsh-typert-protocol': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-time': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-values': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-agent': 0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c)
|
||||
'@deepseek-ai/dsh-attachment': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-tools': 0.1.5-alpha.1(239eb910b4462b7c15b6b44661457a15)
|
||||
'@deepseek-ai/dsh-typert-protocol': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-time': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-util-values': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
zod: 4.4.3
|
||||
optionalDependencies:
|
||||
'@deepseek-ai/dsh-session-projection': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-user-approval': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-agent@0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48))(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-system-prompt@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-session-persistence': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-timeout@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session-projection': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-user-approval': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-agent@0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c))(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-system-prompt@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))
|
||||
|
||||
'@deepseek-ai/dsh-system-prompt@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))':
|
||||
'@deepseek-ai/dsh-system-prompt@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-invariants': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
|
||||
'@deepseek-ai/dsh-timeout@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-timeout@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-tools@0.1.2-rc.1(de5e1ff2d608fbe22667925341ad1dfc)':
|
||||
'@deepseek-ai/dsh-tools@0.1.5-alpha.1(239eb910b4462b7c15b6b44661457a15)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-agent': 0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48)
|
||||
'@deepseek-ai/dsh-brand': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-code-runtime': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-user-approval': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-agent@0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48))(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-system-prompt@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-util-values': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-agent': 0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c)
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-code-runtime': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-user-approval': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-agent@0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c))(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-system-prompt@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))
|
||||
'@deepseek-ai/dsh-util-values': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
|
||||
'@deepseek-ai/dsh-typert-protocol@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-typert-protocol@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-user-approval@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-agent@0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48))(@deepseek-ai/dsh-brand@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))(@deepseek-ai/dsh-session@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-system-prompt@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))))':
|
||||
'@deepseek-ai/dsh-user-approval@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-agent@0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c))(@deepseek-ai/dsh-brand@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))(@deepseek-ai/dsh-session@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))(@deepseek-ai/dsh-system-prompt@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))))':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
'@deepseek-ai/dsh-agent': 0.1.2-rc.1(6f833cf90b9dd3f0d529840b1aebfb48)
|
||||
'@deepseek-ai/dsh-brand': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-agent': 0.1.5-alpha.1(288d2f4271bc6bef491bb5a5c084f12c)
|
||||
'@deepseek-ai/dsh-brand': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-llm': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-scope': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))
|
||||
'@deepseek-ai/dsh-session': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/dsh-system-prompt': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-llm@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-scope@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)))
|
||||
'@deepseek-ai/schemastery': 3.18.2
|
||||
|
||||
'@deepseek-ai/dsh-util-crypto@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-util-crypto@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-util-time@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-util-time@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
'@deepseek-ai/dsh-util-values@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)':
|
||||
'@deepseek-ai/dsh-util-values@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
|
||||
@@ -3767,15 +3838,15 @@ snapshots:
|
||||
|
||||
'@exodus/bytes@1.15.1': {}
|
||||
|
||||
'@huanlin/dsh-plugin-better-locale@0.1.0(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-client-locale@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-primitives@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-settings@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-slots@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.2-rc.1(@deepseek-ai/cordis@4.0.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
'@huanlin/dsh-plugin-better-locale@0.1.0(@deepseek-ai/cordis@4.0.2)(@deepseek-ai/dsh-client-locale@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-primitives@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-settings@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-client-ui-slots@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(@deepseek-ai/dsh-invariants@0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@deepseek-ai/cordis': 4.0.2
|
||||
optionalDependencies:
|
||||
'@deepseek-ai/dsh-client-locale': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-primitives': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-settings': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-slots': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.2-rc.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-locale': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-primitives': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-settings': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-client-ui-slots': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
'@deepseek-ai/dsh-invariants': 0.1.5-alpha.1(@deepseek-ai/cordis@4.0.2)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Process-local live assistant stream buffer (DSH 0.1.5+).
|
||||
*
|
||||
* 0.1.2 appended a durable `assistant/chunk` session event for every model
|
||||
* delta, so the side-chat transcript and the inherited in-progress snapshot
|
||||
* could read streaming text straight out of the session log. 0.1.5 removed
|
||||
* that event: an in-flight attempt now publishes `agent/assistant-stream`
|
||||
* frames (start / chunk / end) that are NOT part of the log, and the durable
|
||||
* record lands only at settlement — `assistant/message` (with the exact
|
||||
* `stream` embedded) or `assistant/attempt` (a failed attempt that committed
|
||||
* no message, also with its `stream`).
|
||||
*
|
||||
* This module folds those frames into a bounded per-session buffer of
|
||||
* normalized chunks — the same information the old `assistant/chunk` events
|
||||
* carried — so the plugin keeps a live transcript and an honest in-progress
|
||||
* snapshot. The buffer is cleared when an attempt ends, so a settled step
|
||||
* never duplicates its durable message.
|
||||
*/
|
||||
import type { Context } from './context-types.ts'
|
||||
|
||||
/** One normalized live delta, keyed by its attempt and dense position. */
|
||||
export interface AssistantLiveChunk {
|
||||
/** The attempt these chunks belong to (DSH `LlmAttemptId`). */
|
||||
readonly attemptId: string
|
||||
/** The turn the attempt belongs to. */
|
||||
readonly turn: number
|
||||
/** The step the attempt belongs to. */
|
||||
readonly step: number
|
||||
/** Dense zero-based position within the attempt (DSH's own index). */
|
||||
readonly index: number
|
||||
/** Safe-integer timestamp of the frame (reused by the durable stream). */
|
||||
readonly time: number
|
||||
/** The raw model stream chunk (`text-delta` / `reasoning-delta` / …). */
|
||||
readonly chunk: Record<string, unknown>
|
||||
}
|
||||
|
||||
/** The plugin's read face over the live frames of every session. */
|
||||
export interface AssistantLiveBuffer {
|
||||
/**
|
||||
* The live chunks of one session's active attempt, in index order.
|
||||
* @param sessionId - the session whose attempt is streaming.
|
||||
* @returns the chunks; empty when nothing is streaming for that session.
|
||||
*/
|
||||
chunksFor(sessionId: string): readonly AssistantLiveChunk[]
|
||||
/** Stop observing the agent stream. */
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
/** Per-attempt buffer ceiling; beyond it the oldest deltas are dropped. */
|
||||
export const LIVE_CHUNK_CAP = 4000
|
||||
|
||||
/** How many sessions may hold a live buffer at once (the oldest is dropped). */
|
||||
export const LIVE_SESSION_CAP = 64
|
||||
|
||||
/** One session's active attempt as it accumulates. */
|
||||
interface Attempt {
|
||||
readonly attemptId: string
|
||||
readonly turn: number
|
||||
readonly step: number
|
||||
readonly chunks: AssistantLiveChunk[]
|
||||
/** The next dense frame index this attempt accepts (independent of the
|
||||
* window below, which drops the oldest chunks once the cap is reached). */
|
||||
nextIndex: number
|
||||
}
|
||||
|
||||
/** A frame's `chunk` payload must be a JSON record to be worth buffering. */
|
||||
function chunkOf(value: unknown): Record<string, unknown> | undefined {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
? value as Record<string, unknown>
|
||||
: undefined
|
||||
}
|
||||
|
||||
/** A finite non-negative integer, or undefined. */
|
||||
function countOf(value: unknown): number | undefined {
|
||||
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Fold `agent/assistant-stream` frames into per-session live buffers.
|
||||
*
|
||||
* Frames arrive for every attached agent, so the buffer is keyed by the
|
||||
* emitting session and ignores anything it cannot identify. An out-of-order
|
||||
* or mismatched chunk drops the attempt rather than splicing a gap: a
|
||||
* transcript with a hole is worse than one that settles at the next durable
|
||||
* event.
|
||||
* @param ctx - host plugin context (its `on` subscribes the session feed).
|
||||
* @param cap - per-attempt chunk ceiling.
|
||||
* @returns the read face and a disposer unbinding the listener.
|
||||
*/
|
||||
export function createAssistantLiveBuffer(ctx: Context, cap: number = LIVE_CHUNK_CAP): AssistantLiveBuffer {
|
||||
const attempts = new Map<string, Attempt>()
|
||||
const off = ctx.on('agent/assistant-stream', (payload) => {
|
||||
const record = payload as { agent?: unknown; frame?: unknown } | null
|
||||
const frame = record?.frame as Record<string, unknown> | undefined
|
||||
if (frame === undefined) return
|
||||
const session = (record?.agent as { session?: { id?: unknown } } | undefined)?.session
|
||||
const sessionId = typeof session?.id === 'string' ? session.id : undefined
|
||||
if (sessionId === undefined) return
|
||||
const type = frame['type']
|
||||
if (type === 'end') {
|
||||
attempts.delete(sessionId)
|
||||
return
|
||||
}
|
||||
const attemptId = typeof frame['attemptId'] === 'string' ? frame.attemptId : undefined
|
||||
if (attemptId === undefined) return
|
||||
if (type === 'start') {
|
||||
const turn = countOf(frame['turn'])
|
||||
const step = countOf(frame['step'])
|
||||
if (turn === undefined || step === undefined) return
|
||||
const known = attempts.has(sessionId)
|
||||
attempts.delete(sessionId)
|
||||
// Only a NEW session may evict the oldest tracked one.
|
||||
if (!known && attempts.size >= LIVE_SESSION_CAP) {
|
||||
const oldest = attempts.keys().next().value
|
||||
if (oldest !== undefined) attempts.delete(oldest)
|
||||
}
|
||||
attempts.set(sessionId, { attemptId, turn, step, chunks: [], nextIndex: 0 })
|
||||
return
|
||||
}
|
||||
if (type !== 'chunk') return
|
||||
const attempt = attempts.get(sessionId)
|
||||
if (attempt === undefined || attempt.attemptId !== attemptId) return
|
||||
const index = countOf(frame['index'])
|
||||
const chunk = chunkOf(frame['chunk'])
|
||||
// Dense positions only: a gap means the buffer missed frames (a
|
||||
// replacement attempt, a reconnect) and its text would be wrong.
|
||||
if (index === undefined || chunk === undefined || index !== attempt.nextIndex) {
|
||||
attempts.delete(sessionId)
|
||||
return
|
||||
}
|
||||
attempt.nextIndex = index + 1
|
||||
const time = countOf(frame['time']) ?? 0
|
||||
attempt.chunks.push({ attemptId, turn: attempt.turn, step: attempt.step, index, time, chunk })
|
||||
if (attempt.chunks.length > cap) attempt.chunks.splice(0, attempt.chunks.length - cap)
|
||||
})
|
||||
return {
|
||||
chunksFor: sessionId => attempts.get(sessionId)?.chunks ?? [],
|
||||
dispose: () => { off() },
|
||||
}
|
||||
}
|
||||
@@ -901,17 +901,6 @@ export function SideCardSection({ store, service }: SideCardSectionProps) {
|
||||
<span className={css.suffix}>{t('settingsWidthSuffix')}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className={css.row}>
|
||||
<span className={css.rowText}>
|
||||
<span className={css.title}>{t('settingsOpenPathTitle')}</span>
|
||||
<span className={css.desc}>{t('settingsOpenPathDesc')}</span>
|
||||
</span>
|
||||
<Switch
|
||||
label={t('settingsOpenPathTitle')}
|
||||
checked={prefs.interceptOpenPath}
|
||||
onChange={(next) => { applyPref({ interceptOpenPath: next }) }}
|
||||
/>
|
||||
</div>
|
||||
<div className={css.row}>
|
||||
<span className={css.rowText}>
|
||||
<span className={css.title}>{t('settingsOpenToolsTitle')}</span>
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
sideThreadRows,
|
||||
threadHasCompletedTurn,
|
||||
threadTrailingPending,
|
||||
type SidechatLiveEvent,
|
||||
type SidechatThreadInfo,
|
||||
} from '../sidechat-core.ts'
|
||||
import {
|
||||
@@ -112,6 +113,8 @@ const inFlightStarts = new Set<string>()
|
||||
* the afterSeq delta and never re-download what they already hold). */
|
||||
interface ThreadCache {
|
||||
entries: SidebarHistoryEntry[]
|
||||
/** The CURRENT attempt's live deltas (replaced every pull, never merged). */
|
||||
live: SidechatLiveEvent[]
|
||||
}
|
||||
|
||||
/** Row-render labels (locale-dependent, memoized once per mount). */
|
||||
@@ -383,7 +386,7 @@ export function SideChatView(props: {
|
||||
const [info, setInfo] = useState<SidechatThreadInfo | null>(null)
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
|
||||
const cacheRef = useRef<ThreadCache>({ entries: [] })
|
||||
const cacheRef = useRef<ThreadCache>({ entries: [], live: [] })
|
||||
// The previous poll's rows (see the mapping's reuse pass below).
|
||||
const prevRowsRef = useRef<SidechatTranscriptRow[]>([])
|
||||
const controllerRef = useRef<AbortController | null>(null)
|
||||
@@ -457,14 +460,18 @@ export function SideChatView(props: {
|
||||
try {
|
||||
const cache = cacheRef.current
|
||||
const afterSeq = cache.entries.at(-1)?.event.seq
|
||||
const { events } = await api.sidechatEvents(childId, afterSeq, controller.signal)
|
||||
const { events, live } = await api.sidechatEvents(childId, afterSeq, controller.signal)
|
||||
// The live set is the CURRENT attempt, so it replaces whatever the
|
||||
// previous pull held; a settled step drops out of the host buffer and
|
||||
// its durable assistant/message takes over in the mapping.
|
||||
cache.live = live ?? []
|
||||
if (events.length > 0) {
|
||||
// Wire events arrive as parsed JSON; the mirror narrows data to the
|
||||
// record the mapping reads.
|
||||
const incoming = events.map(event => ({ event: event as SidebarSessionEvent }))
|
||||
cache.entries = mergeBySeq(cache.entries, incoming)
|
||||
setRevision(value => value + 1)
|
||||
}
|
||||
setRevision(value => value + 1)
|
||||
} catch {
|
||||
// Aborted by a newer pull or a wire failure: keep the last rows.
|
||||
}
|
||||
@@ -482,7 +489,7 @@ export function SideChatView(props: {
|
||||
// Reset the transcript cache whenever the binding changes, then focus
|
||||
// the composer — it owns the first message of a fresh thread.
|
||||
useEffect(() => {
|
||||
cacheRef.current = { entries: [] }
|
||||
cacheRef.current = { entries: [], live: [] }
|
||||
prevRowsRef.current = []
|
||||
controllerRef.current?.abort()
|
||||
setError(null)
|
||||
@@ -532,8 +539,24 @@ export function SideChatView(props: {
|
||||
// The previous poll's rows ride into the mapping so unchanged rows keep
|
||||
// their object identity (see reuseRows): the 2s poll re-renders only the
|
||||
// changed tail instead of re-parsing markdown for the whole transcript.
|
||||
// The live deltas are appended AFTER the durable events (they are the
|
||||
// in-flight tail), and a delta whose step already settled durably is
|
||||
// dropped: the durable message is authoritative and would otherwise render
|
||||
// twice.
|
||||
const rows = useMemo(() => {
|
||||
const next = threadId === undefined ? [] : transcriptRows(cacheRef.current.entries, prevRowsRef.current)
|
||||
const cache = cacheRef.current
|
||||
const settled = new Set(
|
||||
cache.entries
|
||||
.filter(entry => entry.event.type === 'assistant/message')
|
||||
.map(entry => {
|
||||
const data = entry.event.data as { turn?: unknown; step?: unknown }
|
||||
return `${String(data.turn)}:${String(data.step)}`
|
||||
}),
|
||||
)
|
||||
const live = cache.live
|
||||
.filter(event => !settled.has(`${String(event.data.turn)}:${String(event.data.step)}`))
|
||||
.map(event => ({ event: event as SidebarSessionEvent }))
|
||||
const next = threadId === undefined ? [] : transcriptRows([...cache.entries, ...live], prevRowsRef.current)
|
||||
prevRowsRef.current = next
|
||||
return next
|
||||
},
|
||||
|
||||
+6
-3
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
import { encodeHtmlUrl } from '../html-route.ts'
|
||||
import type { LastActivity } from '../subagent-activity.ts'
|
||||
import type { SidechatLogEvent, SidechatThreadInfo } from '../sidechat-core.ts'
|
||||
import type { SidechatLiveEvent, SidechatLogEvent, SidechatThreadInfo } from '../sidechat-core.ts'
|
||||
import type { SidebarSessionEvent } from '../context-types.ts'
|
||||
import type { BrowserProbeResult } from './browser.ts'
|
||||
|
||||
@@ -390,9 +390,12 @@ export const api = {
|
||||
call<SidechatThreadInfo>('sidechat.info', { childId }),
|
||||
/** One transcript pull of a Side Chat thread: the thread's OWN events
|
||||
* (the inherited seed is cut host-side and never crosses the wire).
|
||||
* `afterSeq` narrows the response to the delta beyond it (poll tail). */
|
||||
* `afterSeq` narrows the response to the delta beyond it (poll tail).
|
||||
* `live` is the thread's in-flight model deltas (DSH 0.1.5 publishes them
|
||||
* outside the session log) — the CURRENT attempt on every pull, never a
|
||||
* delta, so the caller replaces its live set instead of appending. */
|
||||
sidechatEvents: (childId: string, afterSeq?: number, signal?: AbortSignal) =>
|
||||
call<{ events: SidechatLogEvent[] }>('sidechat.events', {
|
||||
call<{ events: SidechatLogEvent[]; live: SidechatLiveEvent[] }>('sidechat.events', {
|
||||
childId,
|
||||
...(afterSeq !== undefined ? { afterSeq } : {}),
|
||||
}, signal),
|
||||
|
||||
+1
-13
@@ -17,7 +17,7 @@ import { revalidateChunksOnReactivate, setChunkModuleSystem } from './chunk-load
|
||||
import { registerBuiltins } from './builtins/index.ts'
|
||||
import { Sidebar } from './Sidebar.tsx'
|
||||
import { RenderBoundary } from './RenderBoundary.tsx'
|
||||
import { registerOpenPathInterception, registerTurnTailInterception } from './intercept.tsx'
|
||||
import { registerTurnTailInterception } from './intercept.tsx'
|
||||
import { registerLinkInterception } from './link-intercept.ts'
|
||||
import { registerImeGuard } from './ime-guard.ts'
|
||||
import { registerSettingsNavIcon } from './settings-nav-icon.ts'
|
||||
@@ -342,18 +342,6 @@ export function apply(ctx: Context): void {
|
||||
'dsh-better-sidebar: turn-tail interception',
|
||||
)
|
||||
|
||||
ctx.effect(
|
||||
() => {
|
||||
try {
|
||||
return registerOpenPathInterception(ctx, sidebarStore)
|
||||
} catch (error) {
|
||||
fail('interception', error)
|
||||
return () => {}
|
||||
}
|
||||
},
|
||||
'dsh-better-sidebar: open-path interception',
|
||||
)
|
||||
|
||||
ctx.effect(
|
||||
() => {
|
||||
try {
|
||||
|
||||
@@ -11,7 +11,6 @@ import type { Context } from '../context-types.ts'
|
||||
import { firstLeaf, revealPaths, togglePanel, type SidebarStore } from './state.ts'
|
||||
import { t } from './locales.ts'
|
||||
import { resolveSidebarPath, selectProducedFiles } from './produced-files.ts'
|
||||
import { wrapOpenWorkspacePath, type OpenWorkspacePathService } from './openpath-intercept.ts'
|
||||
import css from './sidebar.module.css'
|
||||
|
||||
/** Open a file in the sidebar's editor (used by the intercepted row and the explorer). */
|
||||
@@ -25,13 +24,6 @@ export function openSidebarFile(ctx: Context, store: SidebarStore, sessionId: st
|
||||
ctx.get('betterSidebar')?.openTab({ type: 'editor', title, path: absolute, id: `editor:${absolute}` })
|
||||
}
|
||||
|
||||
/**
|
||||
* The produced files the turn-tail selector last matched for the visible
|
||||
* session. The "Show in folder" gesture carries no file path of its own
|
||||
* (`'.'`), so the reveal highlights exactly these rows when available.
|
||||
*/
|
||||
let lastProduced: readonly string[] = []
|
||||
|
||||
/**
|
||||
* Reveal the produced files in the sidebar explorer: expand their parent
|
||||
* directories, highlight the rows, and focus the explorer tab (expanding the
|
||||
@@ -137,9 +129,7 @@ export function registerTurnTailInterception(ctx: Context, store: SidebarStore):
|
||||
select: (owner) => {
|
||||
if (store.getSuspended()) return null
|
||||
if (store.getPrefs().tabsEnabled['editor'] === false) return null
|
||||
const matched = selectProducedFiles(owner)
|
||||
if (matched !== null) lastProduced = matched
|
||||
return matched
|
||||
return selectProducedFiles(owner)
|
||||
},
|
||||
priority: -1,
|
||||
registrant: 'dsh-better-sidebar',
|
||||
@@ -149,38 +139,3 @@ export function registerTurnTailInterception(ctx: Context, store: SidebarStore):
|
||||
}),
|
||||
}, SidebarProducedFiles))
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the chat file-open interception: shadows
|
||||
* `remote.session.openWorkspacePath` — the single funnel every chat-side
|
||||
* file open goes through on alpha hosts (tool-row path links, the
|
||||
* produced-files row, prose mentions, inline-code paths) — so opens land in
|
||||
* the sidebar editor instead of the Host OS. The folder-reveal gesture
|
||||
* ("Show in folder" passes `'.'`) is the one exception: it is routed to the
|
||||
* explorer. Gated by BOTH the `interceptOpenPath` pref and the editor tab's
|
||||
* enable switch; declined opens fall through to the original remote call.
|
||||
*
|
||||
* The `remote.session` namespace service mounts asynchronously (the gateway
|
||||
* client creates it when the session-controller contribution arrives) and
|
||||
* is recreated on contribution remounts, so the wrapper installs through
|
||||
* `ctx.inject`: the callback runs once the service exists and re-runs after
|
||||
* every remount, re-applying the shadow on the fresh instance. Returns the
|
||||
* disposer (disposes the inject fiber, which restores the original method
|
||||
* descriptor — HMR-safe).
|
||||
*/
|
||||
export function registerOpenPathInterception(ctx: Context, store: SidebarStore): () => void {
|
||||
const fiber = ctx.inject(['remote.session'], (fctx) => {
|
||||
fctx.effect(() => {
|
||||
const service = fctx.get('remote.session') as OpenWorkspacePathService
|
||||
return wrapOpenWorkspacePath(service, {
|
||||
takeoverEnabled: () => !store.getSuspended()
|
||||
&& store.getPrefs().interceptOpenPath !== false
|
||||
&& store.getPrefs().tabsEnabled['editor'] !== false,
|
||||
currentSessionId: () => ctx.sessions.list.getSnapshot().current,
|
||||
openInSidebar: (path, sessionId) => { openSidebarFile(ctx, store, sessionId, path) },
|
||||
revealInExplorer: (_path, sessionId) => { revealInExplorer(ctx, store, sessionId, lastProduced) },
|
||||
})
|
||||
}, 'dsh-better-sidebar: open-path interception wrap')
|
||||
})
|
||||
return () => { void fiber.dispose() }
|
||||
}
|
||||
|
||||
@@ -238,8 +238,6 @@ export const ar: Record<string, string> = {
|
||||
settingsWidthTitle: 'الحصة الافتراضية للعرض',
|
||||
settingsWidthDesc: 'الحصة الافتراضية للبطاقة الجانبية من عرض النافذة للمحادثات الجديدة (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'فتح ملفات المحادثة في الشريط الجانبي',
|
||||
settingsOpenPathDesc: 'فتح روابط الملفات في المحادثة (صفوف الأدوات، الملفات الناتجة، الإشارات) في محرّر الشريط الجانبي بدلاً من التطبيق الافتراضي للنظام',
|
||||
settingsOpenToolsTitle: 'حقن أداة فتح الشريط الجانبي للنموذج',
|
||||
settingsOpenToolsDesc: 'عند التفعيل، يمكن للنموذج فتح الملفات والمجلدات وصفحات HTTP(S) في الشريط الجانبي عبر أداة sidebar_open (معطّل افتراضياً)',
|
||||
settingsTitleBarTitle: 'وضع توافق الموضع',
|
||||
|
||||
@@ -223,8 +223,6 @@ export const de: Record<string, string> = {
|
||||
settingsWidthTitle: 'Standardbreite',
|
||||
settingsWidthDesc: 'Standardanteil der Seitenkarte an der Fensterbreite für neue Unterhaltungen (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Chat-Dateien in der Seitenleiste öffnen',
|
||||
settingsOpenPathDesc: 'Dateilinks im Chat (Werkzeugzeilen, erstellte Dateien, Erwähnungen) werden im Seitenleisten-Editor geöffnet statt in der System-Standardanwendung',
|
||||
settingsOpenToolsTitle: 'Seitenleisten-Öffnungswerkzeug für das Modell bereitstellen',
|
||||
settingsOpenToolsDesc: 'Wenn aktiviert, kann das Modell über das sidebar_open-Werkzeug Dateien, Ordner und HTTP(S)-Seiten in der Seitenleiste öffnen (standardmäßig deaktiviert)',
|
||||
settingsTitleBarTitle: 'Kompatibilitätsmodus der Position',
|
||||
|
||||
@@ -230,8 +230,6 @@ export const fr: Record<string, string> = {
|
||||
settingsWidthTitle: 'Part de largeur par défaut',
|
||||
settingsWidthDesc: 'Pourcentage de la largeur de la fenêtre occupé par la carte latérale pour les nouvelles sessions (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Ouvrir les fichiers de discussion dans la barre latérale',
|
||||
settingsOpenPathDesc: 'À la place de l’application par défaut du système, ouvrir dans l’éditeur de la barre latérale les liens vers les fichiers du chat (lignes d’outils, listes de produits, mentions de fichiers)',
|
||||
settingsOpenToolsTitle: 'Injecter l\'outil d\'ouverture latérale pour le modèle',
|
||||
settingsOpenToolsDesc: 'Une fois activé, le modèle peut ouvrir des fichiers, dossiers et pages HTTP(S) dans la barre latérale via l\'outil sidebar_open (désactivé par défaut)',
|
||||
settingsTitleBarTitle: 'Mode de compatibilité de position',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const hi: Record<string, string> = {
|
||||
settingsWidthTitle: 'डिफ़ॉल्ट चौड़ाई हिस्सा',
|
||||
settingsWidthDesc: 'नए वार्तालाप के लिए साइड कार्ड की विंडो चौड़ाई का डिफ़ॉल्ट हिस्सा (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'चैट फ़ाइलें साइडबार में खोलें',
|
||||
settingsOpenPathDesc: 'चैट में फ़ाइल लिंक (टूल पंक्ति, उत्पादित फ़ाइलें, उल्लेख) क्लिक करने पर सिस्टम डिफ़ॉल्ट ऐप के बजाय साइडबार एडिटर में खोलें',
|
||||
settingsOpenToolsTitle: 'मॉडल के लिए साइडबार ओपन टूल इंजेक्ट करें',
|
||||
settingsOpenToolsDesc: 'चालू होने पर, मॉडल sidebar_open टूल से साइडबार में फ़ाइलें, फ़ोल्डर और HTTP(S) पेज खोल सकता है (डिफ़ॉल्ट रूप से बंद)',
|
||||
settingsTitleBarTitle: 'स्थिति संगतता मोड',
|
||||
|
||||
@@ -235,8 +235,6 @@ export const id: Record<string, string> = {
|
||||
settingsWidthTitle: 'Porsi lebar default',
|
||||
settingsWidthDesc: 'Porsi lebar jendela default kartu samping untuk obrolan baru (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Buka berkas obrolan di sidebar',
|
||||
settingsOpenPathDesc: 'Buka tautan berkas di obrolan (baris alat, berkas yang dihasilkan, mention) di editor sidebar alih-alih aplikasi default sistem',
|
||||
settingsOpenToolsTitle: 'Suntik alat buka sidebar untuk model',
|
||||
settingsOpenToolsDesc: 'Saat diaktifkan, model dapat membuka file, folder, dan halaman HTTP(S) di sidebar melalui alat sidebar_open (nonaktif secara default)',
|
||||
settingsTitleBarTitle: 'Mode kompatibilitas posisi',
|
||||
|
||||
@@ -228,8 +228,6 @@ export const it: Record<string, string> = {
|
||||
settingsWidthTitle: 'Quota di larghezza predefinita',
|
||||
settingsWidthDesc: 'La quota predefinita della scheda laterale sulla larghezza della finestra per le nuove conversazioni (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Apri i file della chat nella barra laterale',
|
||||
settingsOpenPathDesc: 'Apre i collegamenti ai file nella chat (righe di strumenti, file prodotti, menzioni) nell’editor della barra laterale invece dell’app predefinita di sistema',
|
||||
settingsOpenToolsTitle: 'Iniettare lo strumento di apertura della barra laterale per il modello',
|
||||
settingsOpenToolsDesc: 'Se attivato, il modello può aprire file, cartelle e pagine HTTP(S) nella barra laterale tramite lo strumento sidebar_open (disattivato per impostazione predefinita)',
|
||||
settingsTitleBarTitle: 'Modalità di compatibilità della posizione',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const ja: Record<string, string> = {
|
||||
settingsWidthTitle: 'デフォルト幅の比率',
|
||||
settingsWidthDesc: '新規会話時のサイドカードがウィンドウ幅に占める割合 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'チャット内ファイルをサイドバーで開く',
|
||||
settingsOpenPathDesc: 'チャット内のファイルリンク(ツール行、産物リスト、ファイル言及)クリック時に、システム既定アプリではなくサイドバーエディターで開く',
|
||||
settingsOpenToolsTitle: 'モデルにサイドバー開くツールを注入',
|
||||
settingsOpenToolsDesc: 'オンにすると、モデルは sidebar_open ツールでサイドバーにファイル・フォルダー・HTTP(S) ページを開ける(デフォルトオフ)',
|
||||
settingsTitleBarTitle: '位置互換モード',
|
||||
|
||||
@@ -229,8 +229,6 @@ export const ko: Record<string, string> = {
|
||||
settingsWidthTitle: '기본 너비 비율',
|
||||
settingsWidthDesc: '새 대화에서 사이드 카드가 창 너비에서 차지하는 비율(%) (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '채팅 영역 파일을 사이드바에서 열기',
|
||||
settingsOpenPathDesc: '채팅에서 파일 링크(도구 행, 산출물 목록, 파일 언급)를 클릭하면 사이드바 편집기에서 열고, 시스템 기본 앱은 호출하지 않습니다',
|
||||
settingsOpenToolsTitle: '모델에 사이드바 열기 도구 주입',
|
||||
settingsOpenToolsDesc: '켜면 모델이 sidebar_open 도구로 사이드바에서 파일·폴더·HTTP(S) 페이지를 열 수 있습니다(기본 꺼짐)',
|
||||
settingsTitleBarTitle: '위치 호환 모드',
|
||||
|
||||
@@ -235,8 +235,6 @@ export const nl: Record<string, string> = {
|
||||
settingsWidthTitle: 'Standaard breedte-aandeel',
|
||||
settingsWidthDesc: 'Het standaard breedte-aandeel van de zijkaart voor nieuwe conversaties (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Chatbestanden in de zijbalk openen',
|
||||
settingsOpenPathDesc: 'Bestandslinks in de chat (toolrijen, geproduceerde bestanden, vermeldingen) openen in de zijbalk-editor in plaats van de standaard systeemapp',
|
||||
settingsOpenToolsTitle: 'Zijbalk-openen-tool voor het model injecteren',
|
||||
settingsOpenToolsDesc: 'Indien ingeschakeld kan het model bestanden, mappen en HTTP(S)-pagina\'s in de zijbalk openen via de sidebar_open-tool (standaard uit)',
|
||||
settingsTitleBarTitle: 'Positiecompatibiliteitsmodus',
|
||||
|
||||
@@ -239,8 +239,6 @@ export const pl: Record<string, string> = {
|
||||
settingsWidthTitle: 'Domyślny udział szerokości',
|
||||
settingsWidthDesc: 'Domyślny udział szerokości okna dla karty bocznej w nowych rozmowach (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Otwieraj pliki czatu w panelu bocznym',
|
||||
settingsOpenPathDesc: 'Otwieraj linki plików na czacie (wiersze narzędzi, wyprodukowane pliki, wzmianki) w edytorze panelu bocznego zamiast w domyślnej aplikacji systemu',
|
||||
settingsOpenToolsTitle: 'Wstrzyknij narzędzie otwierania panelu bocznego dla modelu',
|
||||
settingsOpenToolsDesc: 'Po włączeniu model może otwierać pliki, foldery i strony HTTP(S) w panelu bocznym za pomocą narzędzia sidebar_open (domyślnie wyłączone)',
|
||||
settingsTitleBarTitle: 'Tryb zgodności pozycji',
|
||||
|
||||
@@ -220,8 +220,6 @@ export const pt: Record<string, string> = {
|
||||
settingsWidthTitle: 'Participação padrão na largura',
|
||||
settingsWidthDesc: 'Participação padrão do cartão lateral na largura da janela em novas conversas (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Abrir arquivos do chat na barra lateral',
|
||||
settingsOpenPathDesc: 'Abrir links de arquivos no chat (linhas de ferramentas, arquivos produzidos, menções) no editor da barra lateral em vez do aplicativo padrão do sistema',
|
||||
settingsOpenToolsTitle: 'Injetar a ferramenta de abertura na barra lateral para o modelo',
|
||||
settingsOpenToolsDesc: 'Quando ativado, o modelo pode abrir arquivos, pastas e páginas HTTP(S) na barra lateral pela ferramenta sidebar_open (desativado por padrão)',
|
||||
settingsTitleBarTitle: 'Modo de compatibilidade de posição',
|
||||
|
||||
@@ -234,8 +234,6 @@ export const ru: Record<string, string> = {
|
||||
settingsWidthTitle: 'Доля ширины по умолчанию',
|
||||
settingsWidthDesc: 'Доля боковой карточки в ширине окна для новых сессий, в процентах (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Открывать файлы чата в боковой панели',
|
||||
settingsOpenPathDesc: 'При щелчке по ссылке на файл в чате (строки инструментов, списки результатов, упоминания) файл открывается в редакторе боковой панели, а не в системном приложении по умолчанию',
|
||||
settingsOpenToolsTitle: 'Внедрить инструмент открытия в боковой панели для модели',
|
||||
settingsOpenToolsDesc: 'Если включено, модель может открывать файлы, папки и HTTP(S)-страницы в боковой панели с помощью инструмента sidebar_open (по умолчанию выключено)',
|
||||
settingsTitleBarTitle: 'Режим совместимости заголовка',
|
||||
|
||||
@@ -220,8 +220,6 @@ export const sv: Record<string, string> = {
|
||||
settingsWidthTitle: 'Standardbreddandel',
|
||||
settingsWidthDesc: 'Sidokortets standardandel av fönsterbredden för nya konversationer (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Öppna chattfiler i sidopanelen',
|
||||
settingsOpenPathDesc: 'Öppna fillänkar i chatten (verktygsrader, producerade filer, omnämnanden) i sidopanelens editor i stället för systemets standardapp',
|
||||
settingsOpenToolsTitle: 'Injicera sidopanel-öppningsverktyg för modellen',
|
||||
settingsOpenToolsDesc: 'När aktiverat kan modellen öppna filer, mappar och HTTP(S)-sidor i sidopanelen via sidebar_open-verktyget (av som standard)',
|
||||
settingsTitleBarTitle: 'Positions kompatibilitetsläge',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const th: Record<string, string> = {
|
||||
settingsWidthTitle: 'สัดส่วนความกว้างเริ่มต้น',
|
||||
settingsWidthDesc: 'สัดส่วนความกว้างเริ่มต้นของการ์ดด้านข้างจากความกว้างหน้าต่างสำหรับแชทใหม่ (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'เปิดไฟล์แชทในแถบด้านข้าง',
|
||||
settingsOpenPathDesc: 'เปิดลิงก์ไฟล์ในแชท (แถวเครื่องมือ, ไฟล์ที่สร้าง, การกล่าวถึง) ในตัวแก้ไขแถบด้านข้างแทนแอปเริ่มต้นของระบบ',
|
||||
settingsOpenToolsTitle: 'ฉีดเครื่องมือเปิดแถบด้านข้างสำหรับโมเดล',
|
||||
settingsOpenToolsDesc: 'เมื่อเปิดใช้ โมเดลสามารถเปิดไฟล์ โฟลเดอร์ และหน้า HTTP(S) ในแถบด้านข้างผ่านเครื่องมือ sidebar_open (ปิดเป็นค่าเริ่มต้น)',
|
||||
settingsTitleBarTitle: 'โหมดความเข้ากันได้ของตำแหน่ง',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const tr: Record<string, string> = {
|
||||
settingsWidthTitle: 'Varsayılan genişlik payı',
|
||||
settingsWidthDesc: 'Yeni sohbetler için yan kartın pencere genişliğindeki varsayılan payı (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Sohbet dosyalarını kenar çubuğunda aç',
|
||||
settingsOpenPathDesc: 'Sohbetteki dosya bağlantılarını (araç satırları, üretilen dosyalar, anılmalar) sistem varsayılan uygulaması yerine kenar çubuğu düzenleyicisinde aç',
|
||||
settingsOpenToolsTitle: 'Model için kenar çubuğu açma aracı enjekte et',
|
||||
settingsOpenToolsDesc: 'Etkinleştirildiğinde model, sidebar_open aracıyla kenar çubuğunda dosyaları, klasörleri ve HTTP(S) sayfalarını açabilir (varsayılan kapalı)',
|
||||
settingsTitleBarTitle: 'Konum uyumluluk modu',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const vi: Record<string, string> = {
|
||||
settingsWidthTitle: 'Tỷ lệ chiều rộng mặc định',
|
||||
settingsWidthDesc: 'Tỷ lệ phần trăm chiều rộng cửa sổ mà thẻ bên chiếm cho phiên mới (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Mở tệp chat trong thanh bên',
|
||||
settingsOpenPathDesc: 'Khi nhấp link tệp trong chat (dòng công cụ, danh sách sản phẩm, nhắc tệp), mở trong trình soạn thảo thanh bên thay vì ứng dụng mặc định hệ thống',
|
||||
settingsOpenToolsTitle: 'Tiêm công cụ mở thanh bên cho mô hình',
|
||||
settingsOpenToolsDesc: 'Khi bật, mô hình có thể mở tệp, thư mục và trang HTTP(S) trong thanh bên qua công cụ sidebar_open (mặc định tắt)',
|
||||
settingsTitleBarTitle: 'Chế độ tương thích thanh tiêu đề',
|
||||
|
||||
@@ -252,8 +252,6 @@ export const zhHK: Record<string, string> = {
|
||||
settingsWidthTitle: '預設寬度佔比',
|
||||
settingsWidthDesc: '新增工作階段時側邊卡片佔視窗寬度的百分比 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '聊天區檔案在側邊欄開啟',
|
||||
settingsOpenPathDesc: '在聊天裡點擊檔案連結(工具行、產物列表、檔案提及)時,在側邊欄編輯器中開啟,不再呼叫系統預設應用程式',
|
||||
settingsOpenToolsTitle: '為模型注入側邊欄開啟工具',
|
||||
settingsOpenToolsDesc: '開啟後,模型可透過 sidebar_open 工具在側邊欄主動開啟檔案、資料夾和 HTTP(S) 網頁(預設關閉)',
|
||||
settingsTitleBarTitle: '位置相容模式',
|
||||
|
||||
@@ -252,8 +252,6 @@ export const zhMO: Record<string, string> = {
|
||||
settingsWidthTitle: '預設寬度佔比',
|
||||
settingsWidthDesc: '新增工作階段時側邊卡片佔視窗寬度的百分比 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '聊天區檔案在側邊欄開啟',
|
||||
settingsOpenPathDesc: '在聊天裡點擊檔案連結(工具行、產物列表、檔案提及)時,在側邊欄編輯器中開啟,不再呼叫系統預設應用程式',
|
||||
settingsOpenToolsTitle: '為模型注入側邊欄開啟工具',
|
||||
settingsOpenToolsDesc: '開啟後,模型可透過 sidebar_open 工具在側邊欄主動開啟檔案、資料夾和 HTTP(S) 網頁(預設關閉)',
|
||||
settingsTitleBarTitle: '位置相容模式',
|
||||
|
||||
@@ -252,8 +252,6 @@ export const zhTW: Record<string, string> = {
|
||||
settingsWidthTitle: '預設寬度佔比',
|
||||
settingsWidthDesc: '新增工作階段時側邊卡片佔視窗寬度的百分比 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '聊天區檔案在側邊欄開啟',
|
||||
settingsOpenPathDesc: '在聊天裡點擊檔案連結(工具行、產物列表、檔案提及)時,在側邊欄編輯器中開啟,不再呼叫系統預設應用程式',
|
||||
settingsOpenToolsTitle: '為模型注入側邊欄開啟工具',
|
||||
settingsOpenToolsDesc: '開啟後,模型可透過 sidebar_open 工具在側邊欄主動開啟檔案、資料夾和 HTTP(S) 網頁(預設關閉)',
|
||||
settingsTitleBarTitle: '位置相容模式',
|
||||
|
||||
@@ -249,8 +249,6 @@ export const zh = {
|
||||
settingsWidthTitle: '默认宽度占比',
|
||||
settingsWidthDesc: '新建会话时侧边卡片占窗口宽度的百分比 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '聊天区文件在侧边栏打开',
|
||||
settingsOpenPathDesc: '在聊天里点击文件链接(工具行、产物列表、文件提及)时,在侧边栏编辑器中打开,不再调用系统默认应用',
|
||||
settingsOpenToolsTitle: '为模型注入侧边栏打开工具',
|
||||
settingsOpenToolsDesc: '开启后,模型可通过 sidebar_open 工具在侧边栏主动打开文件、文件夹和 HTTP(S) 网页(默认关闭)',
|
||||
settingsTitleBarTitle: '位置兼容模式',
|
||||
@@ -699,8 +697,6 @@ export const en: Record<keyof typeof zh, string> = {
|
||||
settingsWidthTitle: 'Default width share',
|
||||
settingsWidthDesc: 'The side card\'s default share of the window width for new conversations (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Open chat files in the sidebar',
|
||||
settingsOpenPathDesc: 'Open file links in the chat (tool rows, produced files, mentions) in the sidebar editor instead of the system default app',
|
||||
settingsOpenToolsTitle: 'Inject the sidebar-open tool for the model',
|
||||
settingsOpenToolsDesc: 'When enabled, the model can actively open files, folders, and HTTP(S) pages in the sidebar through the sidebar_open tool (off by default)',
|
||||
settingsTitleBarTitle: 'Position compatibility mode',
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/**
|
||||
* Interception of the chat's file-open funnel (DSH 0.1.2-alpha hosts).
|
||||
*
|
||||
* The alpha funnel is the Typert remote namespace `remote.session`: every
|
||||
* chat-side file open (tool-row path links, the produced-files row, prose
|
||||
* file mentions, inline-code paths) funnels through ChatView's injected
|
||||
* `openFile`, which calls `ctx.remote.session.openWorkspacePath({ path })`
|
||||
* with the path ALREADY resolved against the session cwd (verified against
|
||||
* the DSH source: `packages/client/ui-chat/src/client/apply.ts` is the only
|
||||
* production caller). The host side (`api/session-controller`) hands the
|
||||
* path to the OS's default application (`openNativePath` → xdg-open on
|
||||
* Linux). The pre-alpha funnel `ctx.workspaces.openPath` is gone — the
|
||||
* alpha `IWorkspaces` has no openPath at all.
|
||||
*
|
||||
* `remote.session` is a cordis service (the gateway client's
|
||||
* RemoteNamespaceService, key `remote.session`) whose methods are installed
|
||||
* as ACCESSOR properties (`Object.defineProperty(service, method, {
|
||||
* configurable: true, get })`) — the getter reads the method table at
|
||||
* ACCESS time and returns the invocation closure. A plain assignment would
|
||||
* not stick (no setter), so the wrapper shadows the method with a data
|
||||
* property and restores the captured accessor descriptor on dispose.
|
||||
*
|
||||
* The namespace service appears asynchronously (the session-controller
|
||||
* contribution mounts after the connection is up) and is disposed/recreated
|
||||
* on contribution remounts; the caller therefore enters through
|
||||
* `ctx.inject(['remote.session'], …)`, which re-fires on every remount.
|
||||
*
|
||||
* The wrapper is dependency-free by design (no React / ui-primitives), so
|
||||
* the takeover logic is unit-testable and the file stays importable from
|
||||
* the test runtime.
|
||||
*/
|
||||
|
||||
/** The one request shape the funnel method takes (mirror of the host's
|
||||
* SessionOpenWorkspacePathRequest). */
|
||||
export interface OpenWorkspacePathRequest {
|
||||
/** Absolute path (the chat caller already resolved it against the cwd). */
|
||||
path: string
|
||||
}
|
||||
|
||||
/**
|
||||
* What the funnel method resolves to (mirror of the typert `RemoteResult`
|
||||
* envelope): EVERY remote method folds its business value into
|
||||
* `{ ok: true, value }` / `{ ok: false, error }` — the chat caller branches
|
||||
* on `result.ok`, so a bare business value would be misread as a failure
|
||||
* (`result.error.message` reads `message` off undefined).
|
||||
*/
|
||||
export type OpenWorkspacePathResult =
|
||||
| { readonly ok: true; readonly value: { opened: boolean } }
|
||||
| { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details: object } }
|
||||
|
||||
/** The namespace slice the wrapper shadows (mirror of the gateway client's
|
||||
* RemoteNamespaceService for the `session` namespace). */
|
||||
export interface OpenWorkspacePathService {
|
||||
openWorkspacePath(request: OpenWorkspacePathRequest, signal?: AbortSignal): Promise<OpenWorkspacePathResult>
|
||||
}
|
||||
|
||||
/** Per-call decisions the wrapper needs (wired to the store + ctx in the client half). */
|
||||
export interface OpenPathInterceptDeps {
|
||||
/**
|
||||
* Whether to take over this call: the `interceptOpenPath` pref AND the
|
||||
* editor tab's own enable switch must both be on (an editor that cannot
|
||||
* open must not swallow opens — they fall through to the Host).
|
||||
*/
|
||||
takeoverEnabled(): boolean
|
||||
/** The session whose scope the sidebar editor loads the file in (current session). */
|
||||
currentSessionId(): string | undefined
|
||||
/** Route the open into the sidebar editor (the established openSidebarFile). */
|
||||
openInSidebar(path: string, sessionId: string): void
|
||||
/** Route a folder-reveal gesture ("Show in folder" passes '.') into the sidebar explorer. */
|
||||
revealInExplorer(path: string, sessionId: string): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a path is the "Show in folder" folder-reveal gesture. The stock
|
||||
* ui-deliverables row passes `'.'` (the session workspace root, resolved by
|
||||
* the chat view to `"<cwd>/."`); any path whose final segment is `.` is the
|
||||
* same gesture. A directory has no editor content, so these opens must reach
|
||||
* the explorer instead of an editor tab.
|
||||
*/
|
||||
export function isFolderRevealPath(path: string): boolean {
|
||||
if (path === '.' || path === './') return true
|
||||
const trimmed = path.replace(/[\\/]+$/, '')
|
||||
return trimmed === '.' || /[\\/]\.$/.test(trimmed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shadow `remote.session.openWorkspacePath`: intercepted calls open the file
|
||||
* in the sidebar editor and resolve with the remote SUCCESS ENVELOPE
|
||||
* (`{ ok: true, value: { opened: true } }`, so ChatView shows no error
|
||||
* dialog); anything that declines falls through to the captured original
|
||||
* closure (the host OS's default application) untouched.
|
||||
* The one exception is the folder-reveal gesture, which is routed to
|
||||
* {@link OpenPathInterceptDeps.revealInExplorer} instead.
|
||||
*
|
||||
* The original closure is captured by ACCESSING the accessor once at wrap
|
||||
* time — it invokes whatever method records are mounted at that moment. If
|
||||
* the contribution remounts its methods while the shadow is installed, the
|
||||
* captured closure points at the old records; the session namespace is
|
||||
* effectively permanent in practice, so this is accepted and the shadow is
|
||||
* re-applied anyway when the remount recreates the service (the caller's
|
||||
* `ctx.inject` re-fires).
|
||||
*
|
||||
* @param service - the `remote.session` namespace service.
|
||||
* @param deps - per-call takeover decisions.
|
||||
* @returns the disposer restoring the original accessor descriptor (HMR-safe).
|
||||
*/
|
||||
export function wrapOpenWorkspacePath(
|
||||
service: OpenWorkspacePathService,
|
||||
deps: OpenPathInterceptDeps,
|
||||
): () => void {
|
||||
const KEY = 'openWorkspacePath'
|
||||
const target = service as object
|
||||
const descriptor = Object.getOwnPropertyDescriptor(target, KEY)
|
||||
// Invoke the accessor once to capture the CURRENT invocation closure.
|
||||
const original = service.openWorkspacePath
|
||||
// The gateway installs the namespace's whole method group in the same
|
||||
// synchronous window as the service registration, so an inject-observed
|
||||
// service always has the method — but a hand-rolled composition might not;
|
||||
// wrapping nothing would turn fall-throughs into crashes, so decline.
|
||||
if (typeof original !== 'function') return () => {}
|
||||
const wrapped = (request: OpenWorkspacePathRequest, signal?: AbortSignal): Promise<OpenWorkspacePathResult> => {
|
||||
if (deps.takeoverEnabled()) {
|
||||
const sessionId = deps.currentSessionId()
|
||||
if (sessionId !== undefined) {
|
||||
if (isFolderRevealPath(request.path)) deps.revealInExplorer(request.path, sessionId)
|
||||
else deps.openInSidebar(request.path, sessionId)
|
||||
return Promise.resolve({ ok: true, value: { opened: true } })
|
||||
}
|
||||
}
|
||||
return original.call(service, request, signal)
|
||||
}
|
||||
Object.defineProperty(target, KEY, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: wrapped,
|
||||
})
|
||||
return () => {
|
||||
// Restore the exact original property (the gateway's accessor) so a
|
||||
// chain of wrappers keeps working across disposals in any order.
|
||||
if (descriptor !== undefined) Object.defineProperty(target, KEY, descriptor)
|
||||
else Reflect.deleteProperty(target, KEY)
|
||||
}
|
||||
}
|
||||
@@ -76,9 +76,6 @@ export function parsePrefs(value: unknown): SidebarPrefs {
|
||||
terminalFontSize: typeof record.terminalFontSize === 'number' && Number.isFinite(record.terminalFontSize)
|
||||
? clampTerminalFontSize(record.terminalFontSize)
|
||||
: SIDEBAR_PREFS_DEFAULTS.terminalFontSize,
|
||||
interceptOpenPath: typeof record.interceptOpenPath === 'boolean'
|
||||
? record.interceptOpenPath
|
||||
: SIDEBAR_PREFS_DEFAULTS.interceptOpenPath,
|
||||
editorExplorer: typeof record.editorExplorer === 'boolean'
|
||||
? record.editorExplorer
|
||||
: SIDEBAR_PREFS_DEFAULTS.editorExplorer,
|
||||
|
||||
@@ -465,7 +465,7 @@ export function matchUrlTarget(tabs: readonly TabDescriptor[], url: URL): TabDes
|
||||
* The plugin version this service instance reports. Keep in lockstep with
|
||||
* `package.json`'s version — `tests/service.spec.ts` asserts the pair.
|
||||
*/
|
||||
export const SIDEBAR_SERVICE_VERSION = '0.18.1'
|
||||
export const SIDEBAR_SERVICE_VERSION = '0.19.0-alpha.0'
|
||||
|
||||
/**
|
||||
* Monotonic capability list consumers use to gate new API usage (features
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* shows only the thread's own conversation.
|
||||
*
|
||||
* Live streaming: `assistant/message` events only land when a step
|
||||
* completes, but `assistant/chunk` events stream token-level text and
|
||||
* reasoning deltas. The mapping accumulates both per block and supersedes
|
||||
* them with the assembled message once it lands (settled rows).
|
||||
* completes, but the host's `assistant/live-chunk` rows stream token-level
|
||||
* text and reasoning deltas (DSH 0.1.5 publishes them outside the session
|
||||
* log — see assistant-live.ts). The mapping accumulates both per block and
|
||||
* supersedes them with the assembled message once it lands (settled rows).
|
||||
*/
|
||||
import type { DiffHunk, ReadBlockLine } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { SidebarHistoryEntry } from '../context-types.ts'
|
||||
@@ -299,12 +300,13 @@ function resultCard(
|
||||
/**
|
||||
* Map a thread child's history rows onto compact transcript rows: the
|
||||
* inherited fork seed is cut at the last `session/end-seed`, context
|
||||
* injections map onto a collapsible injection row, `assistant/chunk`
|
||||
* injections map onto a collapsible injection row, `assistant/live-chunk`
|
||||
* deltas accumulate into streaming rows per (turn, step, block) and are
|
||||
* superseded by the assembled `assistant/message`, and tool invocations
|
||||
* render one expandable line each (arguments, paired result text, failure
|
||||
* marker; a still-executing call is marked until its result lands).
|
||||
* @param entries - history rows (event + host-computed view) in seq order.
|
||||
* @param entries - history rows (event + host-computed view) in seq order,
|
||||
* the live deltas of the in-flight attempt appended after the durable tail.
|
||||
* @returns display rows in log order.
|
||||
*/
|
||||
export function transcriptRows(entries: readonly SidebarHistoryEntry[], prev?: readonly SidechatTranscriptRow[]): SidechatTranscriptRow[] {
|
||||
@@ -374,7 +376,7 @@ export function transcriptRows(entries: readonly SidebarHistoryEntry[], prev?: r
|
||||
rows.push({ kind: 'user', seq: event.seq, text })
|
||||
break
|
||||
}
|
||||
case 'assistant/chunk': {
|
||||
case 'assistant/live-chunk': {
|
||||
const chunk = data.chunk as { type?: unknown; text?: unknown } | undefined
|
||||
if (chunk === null || typeof chunk !== 'object') break
|
||||
const kind = chunk.type === 'text-delta' ? 'assistant' : chunk.type === 'reasoning-delta' ? 'reasoning' : null
|
||||
|
||||
@@ -124,7 +124,6 @@ export const PrefsSchema: z<SidebarPrefs> = z.object({
|
||||
bottomPanelAutoTerminal: z.boolean().default(true),
|
||||
terminalFontFamily: z.string().default(''),
|
||||
terminalFontSize: z.number().step(1).min(TERMINAL_FONT_SIZE_MIN).max(TERMINAL_FONT_SIZE_MAX).default(TERMINAL_FONT_SIZE_DEFAULT),
|
||||
interceptOpenPath: z.boolean().default(true),
|
||||
editorExplorer: z.boolean().default(false),
|
||||
changesDiffFloat: z.boolean().default(true),
|
||||
workspaceFence: z.boolean().default(true),
|
||||
|
||||
+34
-31
@@ -296,14 +296,34 @@ export interface SidebarSessionTitleService {
|
||||
rename(session: unknown, title: string): { title: string; eventSeq: number }
|
||||
}
|
||||
|
||||
/** The host session-persistence face (mirror of the sessionPersistence
|
||||
* service): detached inspection of a persisted session, used to compose the
|
||||
* recorded preset when a Side Chat thread cold-resumes. */
|
||||
/**
|
||||
* The host session-persistence face (mirror of the `sessionPersistence`
|
||||
* service): durable, handle-addressed session storage.
|
||||
*
|
||||
* DSH 0.1.5 replaced the detached `inspect(id)` call with an explicit read
|
||||
* handle: `open(id, 'read')` never takes write ownership and works while
|
||||
* another process owns the session, `handle.read()` returns one contiguous
|
||||
* slice of the log, and `close()` releases it. Every cold read in this plugin
|
||||
* goes through {@link readPersistedSession} so the handle is always closed.
|
||||
*/
|
||||
export interface SidebarSessionPersistenceService {
|
||||
inspect(sessionId: string): Promise<{
|
||||
meta: { cwd?: string; agentPreset?: string }
|
||||
events: readonly SidebarSessionEvent[]
|
||||
}>
|
||||
open(sessionId: string, access: 'read' | 'write'): Promise<SidebarSessionHandle>
|
||||
}
|
||||
|
||||
/** One open channel onto a stored session (the fields this plugin reads). */
|
||||
export interface SidebarSessionHandle {
|
||||
/** Immutable stored header (cwd / agentPreset live here). */
|
||||
readonly header: { cwd?: string; agentPreset?: string } & Record<string, unknown>
|
||||
/** Exact fork-inherited prefix length stored with the log. */
|
||||
readonly inheritedEventCount?: number
|
||||
/**
|
||||
* Read a slice of the valid contiguous log.
|
||||
* @param offset - first logical seq to include (defaults to 0).
|
||||
* @param length - maximum events (defaults to the rest of the log).
|
||||
*/
|
||||
read(offset?: number, length?: number): Promise<{ events: readonly SidebarSessionEvent[] }>
|
||||
/** Release the handle (idempotent). */
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
/** The client session list snapshot the sidebar subscribes to. */
|
||||
@@ -408,30 +428,6 @@ export interface SidebarConversation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The client `remote.session` namespace face (mirror of the gateway client's
|
||||
* RemoteNamespaceService for the session-controller contribution on alpha
|
||||
* hosts). The chat's file-open funnel is `openWorkspacePath`: the caller
|
||||
* resolves the path against the session cwd, and the host hands it to the
|
||||
* OS's default application. Namespace methods are accessor properties (see
|
||||
* `client/openpath-intercept.ts` for how the interception shadows them).
|
||||
*/
|
||||
export interface SidebarRemoteSessionService {
|
||||
/**
|
||||
* Open an absolute path with the Host operating system's default
|
||||
* application. Resolves with the typert `RemoteResult` envelope
|
||||
* (`{ ok: true, value: { opened } }` / `{ ok: false, error }`), like every
|
||||
* remote method — callers branch on `result.ok`.
|
||||
*/
|
||||
openWorkspacePath(
|
||||
request: { path: string },
|
||||
signal?: AbortSignal,
|
||||
): Promise<
|
||||
| { readonly ok: true; readonly value: { opened: boolean } }
|
||||
| { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details: object } }
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
* The invariant service face (mirror of @deepseek-ai/dsh-invariants'
|
||||
* InvariantRegistry). The upstream augmentation does not reach this Context
|
||||
@@ -564,6 +560,13 @@ export interface SidebarContextShape {
|
||||
* LIVE Session instance that appended it.
|
||||
*/
|
||||
on(event: string, listener: (session: unknown, event: SidebarSessionEvent) => void): () => void
|
||||
/**
|
||||
* The agent's process-local assistant stream (DSH 0.1.5+): one payload per
|
||||
* `start` / `chunk` / `end` frame, carrying the emitting agent and the
|
||||
* frame. These frames are NOT session events — see
|
||||
* {@link ./assistant-live.ts} for why the plugin needs them.
|
||||
*/
|
||||
on(event: 'agent/assistant-stream', listener: (payload: { agent?: unknown; frame?: unknown }) => void): () => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+15
-5
@@ -54,7 +54,9 @@ import { AgentOpenRegistry, registerOpenTool, type AgentOpenRequest } from './ag
|
||||
import { buildJobsApi, type SidebarJobsRoutes } from './jobs-routes.ts'
|
||||
import { buildSubagentLiveApi, type SidebarSubagentLiveRoutes } from './subagent-live-route.ts'
|
||||
import { buildSidechatApi } from './sidechat-routes.ts'
|
||||
import { createAssistantLiveBuffer, type AssistantLiveBuffer } from './assistant-live.ts'
|
||||
import { readJsonBody, requireString, SidebarError, writeError, writeJson, writeOk } from './wire.ts'
|
||||
import { readPersistedSession } from './session-store.ts'
|
||||
|
||||
export { Config }
|
||||
export type { SidebarConfig, ResolvedSidebarConfig }
|
||||
@@ -129,8 +131,8 @@ async function sessionCwdOf(ctx: Context, sessionId: string, clientCwd?: string)
|
||||
}
|
||||
const persistence = ctx.get('sessionPersistence')
|
||||
if (persistence !== undefined) {
|
||||
const inspected = await persistence.inspect(sessionId)
|
||||
const metaCwd = inspected.meta.cwd
|
||||
const persisted = await readPersistedSession(persistence, sessionId)
|
||||
const metaCwd = persisted.header.cwd
|
||||
if (metaCwd !== undefined && metaCwd !== '') {
|
||||
try {
|
||||
return requireAbsolute(metaCwd)
|
||||
@@ -298,6 +300,7 @@ function buildApi(
|
||||
resolved: ResolvedSidebarConfig,
|
||||
terminalShell: string,
|
||||
getSettings: () => SidebarSettingsFace | undefined,
|
||||
assistantLive: AssistantLiveBuffer,
|
||||
): Record<string, ApiMethod> {
|
||||
const cwdOf = async (payload: unknown): Promise<{ sessionId: string; cwd: string }> => {
|
||||
const sessionId = requireString(payload, 'sessionId')
|
||||
@@ -507,7 +510,7 @@ function buildApi(
|
||||
const persistence = ctx.get('sessionPersistence')
|
||||
if (persistence !== undefined) {
|
||||
try {
|
||||
events = (await persistence.inspect(sessionId)).events
|
||||
events = (await readPersistedSession(persistence, sessionId)).events
|
||||
} catch {
|
||||
// Cold read unavailable (session never persisted): an empty
|
||||
// window is the honest answer, not a wire error.
|
||||
@@ -694,7 +697,7 @@ function buildApi(
|
||||
// identities are fenced from the generic session RPCs (agent-lookup
|
||||
// ownership), and the thread is created with a CUSTOM seed the stock
|
||||
// fork APIs cannot express.
|
||||
...buildSidechatApi(ctx),
|
||||
...buildSidechatApi(ctx, assistantLive),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,7 +862,14 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
|
||||
})
|
||||
|
||||
// ── JSON API ────────────────────────────────────────────────────────────
|
||||
const api = buildApi(ctx, ptyManager, agentPtyRegistry, resolved, terminalShell, () => settingsFace)
|
||||
// The live assistant stream buffer: DSH 0.1.5 publishes in-flight model
|
||||
// deltas as process-local `agent/assistant-stream` frames instead of the
|
||||
// durable `assistant/chunk` events 0.1.2 logged, so the side-chat
|
||||
// transcript and the inherited in-progress snapshot read them here. The
|
||||
// effect releases the listener on fiber disposal.
|
||||
const assistantLive = createAssistantLiveBuffer(ctx)
|
||||
ctx.effect(() => () => { assistantLive.dispose() }, 'dsh-better-sidebar: live assistant stream buffer')
|
||||
const api = buildApi(ctx, ptyManager, agentPtyRegistry, resolved, terminalShell, () => settingsFace, assistantLive)
|
||||
ctx.effect(() => ctx.webServer.register({
|
||||
kind: 'prefix',
|
||||
path: '/sidebar/api',
|
||||
|
||||
@@ -63,14 +63,6 @@ export interface SidebarPrefs {
|
||||
* row in the Side card settings.
|
||||
*/
|
||||
bottomPanelAutoTerminal: boolean
|
||||
/**
|
||||
* Whether chat-side file opens (tool-row path links, the produced-files
|
||||
* row, prose file mentions — every path that funnels through the client
|
||||
* runtime's `remote.session.openWorkspacePath`) open in the sidebar editor
|
||||
* instead of the Host OS's default application. On by default; the editor
|
||||
* tab's own enable switch gates it too (both must be on for the takeover).
|
||||
*/
|
||||
interceptOpenPath: boolean
|
||||
/**
|
||||
* Whether the editor tab runs in merged mode: a path input replaces the
|
||||
* plain header and a toggleable file-tree panel (with a global name
|
||||
@@ -276,7 +268,6 @@ export const SIDEBAR_PREFS_DEFAULTS: SidebarPrefs = {
|
||||
bottomPanelAutoTerminal: true,
|
||||
terminalFontFamily: '',
|
||||
terminalFontSize: TERMINAL_FONT_SIZE_DEFAULT,
|
||||
interceptOpenPath: true,
|
||||
editorExplorer: false,
|
||||
changesDiffFloat: true,
|
||||
workspaceFence: true,
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Cold-session reads over the host `sessionPersistence` service.
|
||||
*
|
||||
* DSH 0.1.5 removed the detached `inspect(id)` call: a persisted session is
|
||||
* read through an explicit handle — `open(id, 'read')` never takes write
|
||||
* ownership (so it works while the session is live in another process),
|
||||
* `handle.read()` returns one contiguous slice of the log, and `close()`
|
||||
* releases it. Every cold read in this plugin goes through
|
||||
* {@link readPersistedSession} so the handle is always released, even when
|
||||
* the read throws.
|
||||
*/
|
||||
import type { Context, SidebarSessionEvent, SidebarSessionPersistenceService } from './context-types.ts'
|
||||
|
||||
/** One persisted session as this plugin reads it. */
|
||||
export interface PersistedSession {
|
||||
/** Immutable stored header (cwd / agentPreset live here). */
|
||||
readonly header: { cwd?: string; agentPreset?: string } & Record<string, unknown>
|
||||
/** The full stored event log. */
|
||||
readonly events: readonly SidebarSessionEvent[]
|
||||
/** Exact fork-inherited prefix length (0 when the header is not seeded). */
|
||||
readonly inheritedEventCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Read one persisted session's header and full event log.
|
||||
* @param persistence - the live `sessionPersistence` service.
|
||||
* @param sessionId - the stored session to read.
|
||||
* @returns the header and log; rejects when the session does not exist.
|
||||
*/
|
||||
export async function readPersistedSession(
|
||||
persistence: SidebarSessionPersistenceService,
|
||||
sessionId: string,
|
||||
): Promise<PersistedSession> {
|
||||
const handle = await persistence.open(sessionId, 'read')
|
||||
try {
|
||||
const { events } = await handle.read()
|
||||
return {
|
||||
header: handle.header,
|
||||
events,
|
||||
inheritedEventCount: handle.inheritedEventCount ?? 0,
|
||||
}
|
||||
} finally {
|
||||
await handle.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the service and read one persisted session, or `undefined` when
|
||||
* the service is absent or the session cannot be read.
|
||||
* @param ctx - host plugin context.
|
||||
* @param sessionId - the stored session to read.
|
||||
* @returns the persisted session, or `undefined`.
|
||||
*/
|
||||
export async function readPersistedSessionOf(
|
||||
ctx: Context,
|
||||
sessionId: string,
|
||||
): Promise<PersistedSession | undefined> {
|
||||
const persistence = ctx.get('sessionPersistence')
|
||||
if (persistence === undefined) return undefined
|
||||
try {
|
||||
return await readPersistedSession(persistence, sessionId)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
+143
-12
@@ -17,6 +17,7 @@
|
||||
* snapshot inside the boundary prompt.
|
||||
*/
|
||||
import type { SidebarHistoryEntry, SidebarSessionSummary } from './context-types.ts'
|
||||
import type { AssistantLiveChunk } from './assistant-live.ts'
|
||||
|
||||
/** The durable thread-label prefix (also the row filter in the client list). */
|
||||
export const SIDE_LABEL_PREFIX = 'Side: '
|
||||
@@ -81,6 +82,55 @@ export interface SidechatLogEvent {
|
||||
data: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* One live assistant delta on the plugin's wire, mirroring DSH 0.1.5's
|
||||
* client-only `assistant/live-chunk` presentation row.
|
||||
*
|
||||
* DSH 0.1.5 no longer logs `assistant/chunk`: an in-flight attempt's deltas
|
||||
* are process-local frames (`agent/assistant-stream`, folded by
|
||||
* {@link ./assistant-live.ts}) and reach the transcript through the plugin's
|
||||
* own `sidechat.events` route as these rows. They are NOT durable — the
|
||||
* route returns the current attempt's rows on every poll and the client
|
||||
* replaces its live set each time; the durable `assistant/message` settles
|
||||
* them by `turn:step`.
|
||||
*/
|
||||
export interface SidechatLiveEvent {
|
||||
type: 'assistant/live-chunk'
|
||||
/** Ordering key among live rows only; durable seqs stay authoritative. */
|
||||
seq: number
|
||||
time: number
|
||||
data: {
|
||||
attemptId: string
|
||||
turn: number
|
||||
step: number
|
||||
/** Dense zero-based position within the attempt. */
|
||||
index: number
|
||||
/** The raw model stream chunk. */
|
||||
chunk: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Project buffered live chunks into wire rows.
|
||||
* @param chunks - the session's active-attempt chunks, in index order.
|
||||
* @param tailSeq - the session's last durable seq (live rows order after it).
|
||||
* @returns the rows to append to the transcript feed.
|
||||
*/
|
||||
export function liveEventsOf(chunks: readonly AssistantLiveChunk[], tailSeq: number): SidechatLiveEvent[] {
|
||||
return chunks.map((delta, position) => ({
|
||||
type: 'assistant/live-chunk',
|
||||
seq: tailSeq + 1 + position,
|
||||
time: delta.time,
|
||||
data: {
|
||||
attemptId: delta.attemptId,
|
||||
turn: delta.turn,
|
||||
step: delta.step,
|
||||
index: delta.index,
|
||||
chunk: delta.chunk,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
/** The result of cutting a parent log into a side-thread inheritance. */
|
||||
export interface SidechatInheritance {
|
||||
/** The child seed: contiguous from seq 0, ends outside any open turn. */
|
||||
@@ -210,8 +260,14 @@ const SNAPSHOT_TOTAL_CAP = 8000
|
||||
/**
|
||||
* Build the side-thread inheritance for one parent log: the full event log
|
||||
* up to the click moment, honestly closed when it ends inside an open turn.
|
||||
* @param events - the parent's log (live or persisted).
|
||||
* @param live - the parent's in-flight stream chunks (DSH 0.1.5+ publishes
|
||||
* them outside the log); used only by the snapshot fallback.
|
||||
*/
|
||||
export function buildSidechatInheritance(events: readonly SidechatLogEvent[]): SidechatInheritance {
|
||||
export function buildSidechatInheritance(
|
||||
events: readonly SidechatLogEvent[],
|
||||
live: readonly AssistantLiveChunk[] = [],
|
||||
): SidechatInheritance {
|
||||
if (events.length === 0) return { seed: [], snapshot: null }
|
||||
const boundary = lastTurnBoundary(events)
|
||||
if (boundary < 0 || events[boundary]?.type === 'turn/end') {
|
||||
@@ -225,7 +281,7 @@ export function buildSidechatInheritance(events: readonly SidechatLogEvent[]): S
|
||||
// the structured snapshot to the boundary prompt instead.
|
||||
return {
|
||||
seed: copyEvents(events.slice(0, boundary)),
|
||||
snapshot: buildOpenTurnSnapshot(events),
|
||||
snapshot: buildOpenTurnSnapshot(events, live),
|
||||
}
|
||||
}
|
||||
const seed = copyEvents(events)
|
||||
@@ -245,16 +301,79 @@ export function buildSidechatInheritance(events: readonly SidechatLogEvent[]): S
|
||||
return { seed, snapshot: null }
|
||||
}
|
||||
|
||||
/** One assistant content block reduced to the text the snapshot shows. */
|
||||
function messageTexts(message: unknown): { text: string; reasoning: string } {
|
||||
const content = (message as { content?: unknown } | undefined)?.content
|
||||
let text = ''
|
||||
let reasoning = ''
|
||||
if (!Array.isArray(content)) return { text, reasoning }
|
||||
for (const block of content) {
|
||||
if (block === null || typeof block !== 'object') continue
|
||||
const candidate = block as { type?: unknown; text?: unknown }
|
||||
if (typeof candidate.text !== 'string' || candidate.text === '') continue
|
||||
if (candidate.type === 'text') text += candidate.text
|
||||
else if (candidate.type === 'reasoning') reasoning += candidate.text
|
||||
}
|
||||
return { text, reasoning }
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand one attempt's compact `AssistantStreamRecord[]` (the durable stream
|
||||
* DSH 0.1.5 embeds in `assistant/message.stream` and `assistant/attempt.stream`)
|
||||
* into the text/reasoning it carried. Tool-call records contribute no text.
|
||||
*/
|
||||
function streamTexts(stream: unknown): { text: string; reasoning: string } {
|
||||
let text = ''
|
||||
let reasoning = ''
|
||||
if (!Array.isArray(stream)) return { text, reasoning }
|
||||
for (const record of stream) {
|
||||
if (record === null || typeof record !== 'object') continue
|
||||
const entry = record as { type?: unknown; texts?: unknown; chunk?: unknown }
|
||||
if (entry.type === 'text-chunks' || entry.type === 'reasoning-chunks') {
|
||||
if (!Array.isArray(entry.texts)) continue
|
||||
const joined = entry.texts.filter((part): part is string => typeof part === 'string').join('')
|
||||
if (entry.type === 'text-chunks') text += joined
|
||||
else reasoning += joined
|
||||
continue
|
||||
}
|
||||
if (entry.type === 'chunk') {
|
||||
const chunk = entry.chunk as { type?: unknown; text?: unknown } | undefined
|
||||
if (chunk === null || typeof chunk !== 'object' || typeof chunk.text !== 'string') continue
|
||||
if (chunk.type === 'text-delta') text += chunk.text
|
||||
else if (chunk.type === 'reasoning-delta') reasoning += chunk.text
|
||||
}
|
||||
}
|
||||
return { text, reasoning }
|
||||
}
|
||||
|
||||
/** One live delta's contribution to the snapshot. */
|
||||
function liveTexts(chunk: Record<string, unknown>): { text: string; reasoning: string } {
|
||||
if (typeof chunk.text !== 'string' || chunk.text === '') return { text: '', reasoning: '' }
|
||||
if (chunk.type === 'text-delta') return { text: chunk.text, reasoning: '' }
|
||||
if (chunk.type === 'reasoning-delta') return { text: '', reasoning: chunk.text }
|
||||
return { text: '', reasoning: '' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Structured text snapshot of the parent's OPEN turn (from its `turn/start`
|
||||
* to the log tail): the accumulated assistant/reasoning output verbatim
|
||||
* (code blocks ride the raw deltas) and the tool activity — executed tools
|
||||
* with their result text, the still-executing one marked. Returns null when
|
||||
* there is no open turn or nothing to show.
|
||||
* to the log tail): the assistant/reasoning output so far and the tool
|
||||
* activity — executed tools with their result text, the still-executing one
|
||||
* marked. Returns null when there is no open turn or nothing to show.
|
||||
*
|
||||
* The in-flight step's text is NOT in the log on DSH 0.1.5 (the model stream
|
||||
* is process-local until it settles), so it comes from `live`; settled steps
|
||||
* read their durable `assistant/message` content, and a failed attempt reads
|
||||
* its embedded `assistant/attempt.stream`.
|
||||
* @param events - the parent's log.
|
||||
* @param live - the parent's in-flight stream chunks, in index order.
|
||||
*/
|
||||
export function buildOpenTurnSnapshot(events: readonly SidechatLogEvent[]): string | null {
|
||||
export function buildOpenTurnSnapshot(
|
||||
events: readonly SidechatLogEvent[],
|
||||
live: readonly AssistantLiveChunk[] = [],
|
||||
): string | null {
|
||||
const boundary = lastTurnBoundary(events)
|
||||
if (boundary < 0 || events[boundary]?.type !== 'turn/start') return null
|
||||
const openTurn = numberAt(dataOf(events[boundary]!), 'turn')
|
||||
let text = ''
|
||||
let reasoning = ''
|
||||
const tools: string[] = []
|
||||
@@ -267,11 +386,16 @@ export function buildOpenTurnSnapshot(events: readonly SidechatLogEvent[]): stri
|
||||
pendingCalls.clear()
|
||||
continue
|
||||
}
|
||||
if (event.type === 'assistant/chunk') {
|
||||
const chunk = data.chunk as { type?: unknown; text?: unknown } | undefined
|
||||
if (chunk === null || typeof chunk !== 'object') continue
|
||||
if (chunk.type === 'text-delta' && typeof chunk.text === 'string') text += chunk.text
|
||||
else if (chunk.type === 'reasoning-delta' && typeof chunk.text === 'string') reasoning += chunk.text
|
||||
if (event.type === 'assistant/message') {
|
||||
const settled = messageTexts(data.message)
|
||||
text += settled.text
|
||||
reasoning += settled.reasoning
|
||||
continue
|
||||
}
|
||||
if (event.type === 'assistant/attempt') {
|
||||
const attempt = streamTexts(data.stream)
|
||||
text += attempt.text
|
||||
reasoning += attempt.reasoning
|
||||
continue
|
||||
}
|
||||
if (event.type === 'tool/call') {
|
||||
@@ -304,6 +428,13 @@ export function buildOpenTurnSnapshot(events: readonly SidechatLogEvent[]): stri
|
||||
const line = `- \`${call.name}\` (executing) — arguments: \`${call.args}\``
|
||||
tools.push(line)
|
||||
}
|
||||
// The in-flight step: its deltas never reached the log.
|
||||
for (const delta of live) {
|
||||
if (delta.turn !== openTurn) continue
|
||||
const contribution = liveTexts(delta.chunk)
|
||||
text += contribution.text
|
||||
reasoning += contribution.reasoning
|
||||
}
|
||||
const sections: string[] = []
|
||||
if (text.trim() !== '') sections.push(`Assistant output so far:\n\n${text}`)
|
||||
if (reasoning.trim() !== '') sections.push(`Reasoning so far:\n\n${reasoning}`)
|
||||
|
||||
+26
-10
@@ -37,17 +37,21 @@ import type {
|
||||
import {
|
||||
boundaryDelivered,
|
||||
buildSidechatInheritance,
|
||||
liveEventsOf,
|
||||
resolvePresetId,
|
||||
SIDE_BOUNDARY_PROMPT,
|
||||
SIDE_INJECTION_PLUGIN,
|
||||
SIDE_NEW_THREAD_TITLE,
|
||||
sideLabel,
|
||||
type SeedEvent,
|
||||
type SidechatLiveEvent,
|
||||
type SidechatLogEvent,
|
||||
type SidechatThreadInfo,
|
||||
threadOwnLogEvents,
|
||||
} from './sidechat-core.ts'
|
||||
import type { AssistantLiveBuffer } from './assistant-live.ts'
|
||||
import { requireString, SidebarError } from './wire.ts'
|
||||
import { readPersistedSession } from './session-store.ts'
|
||||
|
||||
/** The six Side Chat routes of the sidebar API (wire method names). */
|
||||
export interface SidechatRoutes {
|
||||
@@ -66,8 +70,10 @@ export interface SidechatRoutes {
|
||||
'sidechat.info'(payload: unknown): Promise<SidechatThreadInfo>
|
||||
/** The thread's OWN transcript events, seed-cut host-side (the inherited
|
||||
* parent log never crosses the wire); `afterSeq` narrows the response to
|
||||
* the delta beyond it (poll tail). */
|
||||
'sidechat.events'(payload: unknown): Promise<{ events: SidechatLogEvent[] }>
|
||||
* the delta beyond it (poll tail). `live` carries the thread's in-flight
|
||||
* model deltas, which DSH 0.1.5 publishes outside the session log — it is
|
||||
* the CURRENT attempt's rows on every poll, never a delta. */
|
||||
'sidechat.events'(payload: unknown): Promise<{ events: SidechatLogEvent[]; live: SidechatLiveEvent[] }>
|
||||
}
|
||||
|
||||
/** Timeout guarding the create call (the registry detaches it before the
|
||||
@@ -117,8 +123,8 @@ async function composePersistedSetup(
|
||||
if (persistence === undefined) {
|
||||
return () => Promise.resolve()
|
||||
}
|
||||
const inspected = await persistence.inspect(childId)
|
||||
const presetId = resolvePresetId(inspected.meta, inspected.events)
|
||||
const inspected = await readPersistedSession(persistence, childId)
|
||||
const presetId = resolvePresetId(inspected.header, inspected.events)
|
||||
const presets = ctx.get('agentPresets') as SidebarAgentPresetsService | undefined
|
||||
if (presets === undefined || presetId === undefined) {
|
||||
return () => Promise.resolve()
|
||||
@@ -186,7 +192,7 @@ async function threadLogEvents(ctx: Context, childId: string): Promise<readonly
|
||||
throw new SidebarError('sidechat-error', 'the session persistence service is unavailable', 503)
|
||||
}
|
||||
try {
|
||||
const inspected = await persistence.inspect(childId)
|
||||
const inspected = await readPersistedSession(persistence, childId)
|
||||
return inspected.events as unknown as readonly SidechatLogEvent[]
|
||||
} catch (error: unknown) {
|
||||
throw new SidebarError(
|
||||
@@ -199,8 +205,11 @@ async function threadLogEvents(ctx: Context, childId: string): Promise<readonly
|
||||
|
||||
/** Build the Side Chat routes (all optional services degrade to a wire
|
||||
* error the tab surfaces inline). The record keys are the FULL wire method
|
||||
* names the /sidebar/api dispatcher looks up (`api[method]`). */
|
||||
export function buildSidechatApi(ctx: Context): SidechatRoutes {
|
||||
* names the /sidebar/api dispatcher looks up (`api[method]`).
|
||||
* @param ctx - host plugin context.
|
||||
* @param live - the live assistant stream buffer; absent only in tests that
|
||||
* never exercise streaming (then every `live` response is empty). */
|
||||
export function buildSidechatApi(ctx: Context, live?: AssistantLiveBuffer): SidechatRoutes {
|
||||
return {
|
||||
'sidechat.start': async (payload: unknown) => {
|
||||
const sessionId = requireString(payload, 'sessionId')
|
||||
@@ -213,6 +222,7 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
|
||||
const parentSession = parent.session
|
||||
const inheritance = buildSidechatInheritance(
|
||||
parentSession.snapshotEvents() as unknown as readonly SidechatLogEvent[],
|
||||
live?.chunksFor(sessionId) ?? [],
|
||||
)
|
||||
const { agentPreset, setup } = await composeChildSetup(
|
||||
ctx,
|
||||
@@ -388,8 +398,8 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
|
||||
const persistence = ctx.get('sessionPersistence') as SidebarSessionPersistenceService | undefined
|
||||
if (persistence !== undefined) {
|
||||
try {
|
||||
const inspected = await persistence.inspect(childId)
|
||||
const preset = resolvePresetId(inspected.meta, inspected.events)
|
||||
const inspected = await readPersistedSession(persistence, childId)
|
||||
const preset = resolvePresetId(inspected.header, inspected.events)
|
||||
return { live: false, ...(preset === undefined ? {} : { preset }) }
|
||||
} catch {
|
||||
// Unknown/gone session: report a bare cold info.
|
||||
@@ -408,7 +418,13 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
|
||||
const events = await threadLogEvents(ctx, childId)
|
||||
const own = threadOwnLogEvents(events)
|
||||
const fresh = rawAfter === undefined ? own : own.filter(event => event.seq > rawAfter)
|
||||
return { events: fresh.length > EVENTS_CAP ? fresh.slice(fresh.length - EVENTS_CAP) : fresh }
|
||||
const tailSeq = own.at(-1)?.seq ?? -1
|
||||
return {
|
||||
events: fresh.length > EVENTS_CAP ? fresh.slice(fresh.length - EVENTS_CAP) : fresh,
|
||||
// The live rows ride every poll: they are process-local, so there is
|
||||
// no durable seq to page them by. An idle thread has none.
|
||||
live: liveEventsOf(live?.chunksFor(childId) ?? [], tailSeq),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,11 @@ export interface LastActivity {
|
||||
|
||||
/**
|
||||
* Fold a session event log into the last text output + last tool call (each
|
||||
* is the LAST occurrence in event order). Lifecycle events and raw
|
||||
* `assistant/chunk` rows are ignored — the card shows what the subagent is
|
||||
* doing right now, not its plumbing. The scan runs BACKWARD from the newest
|
||||
* is the LAST occurrence in event order). Lifecycle events and raw stream
|
||||
* rows are ignored — the card shows what the subagent is doing right now,
|
||||
* not its plumbing. (DSH 0.1.5 publishes the raw deltas as process-local
|
||||
* frames instead of log events, so only the assembled `assistant/message`
|
||||
* reaches this scan.) The scan runs BACKWARD from the newest
|
||||
* event and stops once both fields are found, so a long history costs only
|
||||
* the recent tail in the common case.
|
||||
* @param events - the session's append-only event log (oldest → newest).
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Unit tests for the live assistant stream buffer (src/assistant-live.ts):
|
||||
* the fold that replaces DSH 0.1.2's durable `assistant/chunk` events with
|
||||
* the process-local `agent/assistant-stream` frames 0.1.5 publishes instead.
|
||||
*/
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createAssistantLiveBuffer, LIVE_CHUNK_CAP, LIVE_SESSION_CAP } from '../src/assistant-live.ts'
|
||||
import type { Context } from '../src/context-types.ts'
|
||||
|
||||
/** A context whose `on` records the stream listener the buffer installs. */
|
||||
function harness(): {
|
||||
ctx: Context
|
||||
emit: (payload: unknown) => void
|
||||
off: ReturnType<typeof vi.fn>
|
||||
} {
|
||||
let listener: ((payload: unknown) => void) | undefined
|
||||
const off = vi.fn()
|
||||
const ctx = {
|
||||
on: (event: string, fn: (payload: unknown) => void) => {
|
||||
expect(event).toBe('agent/assistant-stream')
|
||||
listener = fn
|
||||
return off
|
||||
},
|
||||
} as unknown as Context
|
||||
return { ctx, emit: payload => { listener?.(payload) }, off }
|
||||
}
|
||||
|
||||
/** One frame as DSH emits it, for the session named by `sessionId`. */
|
||||
function frame(sessionId: string, frame: Record<string, unknown>): Record<string, unknown> {
|
||||
return { agent: { session: { id: sessionId } }, frame }
|
||||
}
|
||||
|
||||
describe('createAssistantLiveBuffer', () => {
|
||||
it('folds start + chunk frames into ordered per-session chunks', () => {
|
||||
const { ctx, emit } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
emit(frame('s1', { type: 'start', attemptId: 'a1', revision: 1, turn: 2, step: 1 }))
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a1', revision: 1, index: 0, time: 10, chunk: { type: 'text-delta', index: 0, text: 'he' } }))
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a1', revision: 1, index: 1, time: 11, chunk: { type: 'reasoning-delta', index: 1, text: 'hmm' } }))
|
||||
const chunks = live.chunksFor('s1')
|
||||
expect(chunks.map(chunk => [chunk.turn, chunk.step, chunk.index, chunk.time])).toEqual([
|
||||
[2, 1, 0, 10],
|
||||
[2, 1, 1, 11],
|
||||
])
|
||||
expect(chunks[1]?.chunk).toEqual({ type: 'reasoning-delta', index: 1, text: 'hmm' })
|
||||
expect(live.chunksFor('other')).toEqual([])
|
||||
})
|
||||
|
||||
it('ignores payloads it cannot identify', () => {
|
||||
const { ctx, emit } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
emit(undefined)
|
||||
emit({})
|
||||
emit({ agent: {}, frame: { type: 'chunk', attemptId: 'a1', index: 0, chunk: {} } })
|
||||
emit({ agent: { session: { id: 's1' } }, frame: { type: 'chunk', attemptId: 'a1', index: 0, chunk: {} } })
|
||||
// A chunk with no start is not an attempt this buffer knows.
|
||||
expect(live.chunksFor('s1')).toEqual([])
|
||||
emit(frame('s1', { type: 'start', attemptId: 'a1', turn: 1, step: 1 }))
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a1', index: 0, chunk: 'not-a-record' }))
|
||||
expect(live.chunksFor('s1')).toEqual([])
|
||||
})
|
||||
|
||||
it('drops the attempt on a gap rather than splicing incomplete text', () => {
|
||||
const { ctx, emit } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
emit(frame('s1', { type: 'start', attemptId: 'a1', turn: 1, step: 1 }))
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a1', index: 0, chunk: { type: 'text-delta', text: 'a' } }))
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a1', index: 2, chunk: { type: 'text-delta', text: 'c' } }))
|
||||
expect(live.chunksFor('s1')).toEqual([])
|
||||
})
|
||||
|
||||
it('drops the attempt when a chunk names another attempt', () => {
|
||||
const { ctx, emit } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
emit(frame('s1', { type: 'start', attemptId: 'a1', turn: 1, step: 1 }))
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a2', index: 0, chunk: { type: 'text-delta', text: 'a' } }))
|
||||
expect(live.chunksFor('s1')).toEqual([])
|
||||
})
|
||||
|
||||
it('clears the buffer when the attempt ends', () => {
|
||||
const { ctx, emit } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
emit(frame('s1', { type: 'start', attemptId: 'a1', turn: 1, step: 1 }))
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a1', index: 0, chunk: { type: 'text-delta', text: 'a' } }))
|
||||
emit(frame('s1', { type: 'end', attemptId: 'a1', index: 1, outcome: { kind: 'abandoned' } }))
|
||||
expect(live.chunksFor('s1')).toEqual([])
|
||||
})
|
||||
|
||||
it('a new start replaces the previous attempt of the same session', () => {
|
||||
const { ctx, emit } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
emit(frame('s1', { type: 'start', attemptId: 'a1', turn: 1, step: 1 }))
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a1', index: 0, chunk: { type: 'text-delta', text: 'old' } }))
|
||||
emit(frame('s1', { type: 'start', attemptId: 'a2', turn: 1, step: 2 }))
|
||||
expect(live.chunksFor('s1')).toEqual([])
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a2', index: 0, chunk: { type: 'text-delta', text: 'new' } }))
|
||||
expect(live.chunksFor('s1').map(chunk => chunk.chunk['text'])).toEqual(['new'])
|
||||
})
|
||||
|
||||
it('caps one attempt at LIVE_CHUNK_CAP chunks (oldest dropped)', () => {
|
||||
const { ctx, emit } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
emit(frame('s1', { type: 'start', attemptId: 'a1', turn: 1, step: 1 }))
|
||||
for (let index = 0; index < LIVE_CHUNK_CAP + 3; index++) {
|
||||
emit(frame('s1', { type: 'chunk', attemptId: 'a1', index, chunk: { type: 'text-delta', text: String(index) } }))
|
||||
}
|
||||
const chunks = live.chunksFor('s1')
|
||||
expect(chunks).toHaveLength(LIVE_CHUNK_CAP)
|
||||
expect(chunks[0]?.index).toBe(3)
|
||||
expect(chunks.at(-1)?.index).toBe(LIVE_CHUNK_CAP + 2)
|
||||
})
|
||||
|
||||
it('caps tracked sessions at LIVE_SESSION_CAP (oldest dropped)', () => {
|
||||
const { ctx, emit } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
for (let index = 0; index < LIVE_SESSION_CAP + 1; index++) {
|
||||
emit(frame(`s${index}`, { type: 'start', attemptId: 'a1', turn: 1, step: 1 }))
|
||||
}
|
||||
expect(live.chunksFor('s0')).toEqual([])
|
||||
expect(live.chunksFor(`s${LIVE_SESSION_CAP}`)).toEqual([])
|
||||
// The most recent session is the only live one, and it has no chunks yet.
|
||||
emit(frame(`s${LIVE_SESSION_CAP}`, { type: 'chunk', attemptId: 'a1', index: 0, chunk: { type: 'text-delta', text: 'x' } }))
|
||||
expect(live.chunksFor(`s${LIVE_SESSION_CAP}`)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('dispose unbinds the stream listener', () => {
|
||||
const { ctx, off } = harness()
|
||||
const live = createAssistantLiveBuffer(ctx)
|
||||
live.dispose()
|
||||
expect(off).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -2,14 +2,16 @@
|
||||
import { mkdirSync } from 'node:fs'
|
||||
import { homedir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { PAGE_URL } from './host'
|
||||
import { expect, test, type APIRequestContext, type Page } from '@playwright/test'
|
||||
import { PAGE_URL, createHostApi, hostRpc } from './host'
|
||||
|
||||
const WORKSPACE_PATH = process.env.DSH_E2E_DESKTOP_WORKSPACE
|
||||
?? (process.env.DSH_E2E_WORKSPACE === undefined
|
||||
? join(homedir(), 'dsh-e2e-desktop-layout-workspace')
|
||||
: `${process.env.DSH_E2E_WORKSPACE}-desktop-layout`)
|
||||
|
||||
let api: APIRequestContext
|
||||
|
||||
async function dismissOnboarding(page: Page): Promise<void> {
|
||||
await expect
|
||||
.poll(() => page.getByRole('button', { name: /^(Continue|Configure later|继续|稍后配置)$/ }).count(), { timeout: 60_000 })
|
||||
@@ -28,8 +30,20 @@ async function dismissOnboarding(page: Page): Promise<void> {
|
||||
throw new Error('onboarding takeovers did not settle after visible dismissal')
|
||||
}
|
||||
|
||||
test.beforeAll(() => {
|
||||
test.beforeAll(async () => {
|
||||
mkdirSync(WORKSPACE_PATH, { recursive: true })
|
||||
// Seed one workspace + one session through the host's own RPC surface (the
|
||||
// calls the UI makes) instead of driving the workspace picker: DSH 0.1.5
|
||||
// ships BOTH a browser directory dialog and an OS-native chooser and picks
|
||||
// one by platform, so a UI-driven flow only works on hosts whose chooser
|
||||
// renders in the page.
|
||||
api = await createHostApi()
|
||||
const workspace = await hostRpc<{ workspace: { workspaceId: string } }>(api, 'workspace.create', { path: WORKSPACE_PATH })
|
||||
await hostRpc(api, 'session.create', { workspaceId: workspace.value.workspace.workspaceId })
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await api?.dispose()
|
||||
})
|
||||
|
||||
test('right panel keeps desktop session actions in their header positions', async ({ page }) => {
|
||||
@@ -45,38 +59,37 @@ test('right panel keeps desktop session actions in their header positions', asyn
|
||||
await page.goto(PAGE_URL, { waitUntil: 'domcontentloaded' })
|
||||
await dismissOnboarding(page)
|
||||
|
||||
await page.getByRole('button', { name: /^(Choose workspace|选择工作区)$/ }).click()
|
||||
const picker = page.getByRole('dialog', { name: /^(Select Workspace Directory|选择工作区目录)$/ })
|
||||
await expect(picker).toBeVisible()
|
||||
await picker.getByRole('button', { name: /^(Edit path|编辑路径)$/ }).click()
|
||||
const pathInput = picker.getByRole('textbox', { name: /^(Edit path|编辑路径)$/ })
|
||||
await pathInput.fill(WORKSPACE_PATH)
|
||||
await pathInput.press('Enter')
|
||||
await expect(pathInput).toBeHidden()
|
||||
await picker.getByRole('button', { name: /^(Open|打开)$/ }).click()
|
||||
|
||||
// Target the ACTIVE composer by its accessible name. DSH 0.1.2-alpha hosts
|
||||
// keep an INERT `data-composer-input` "Choose workspace" textbox (with
|
||||
// contenteditable="false") later in DOM order than the real composer — a
|
||||
// bare `getByRole('textbox').last()` resolved to that one and fill() threw
|
||||
// ("not an <input>, <textarea> or [contenteditable] element").
|
||||
// The seeded blank session opens with its workspace already attached (the
|
||||
// chip shows the workspace title), so the hero composer is live without
|
||||
// touching the workspace picker at all. One message gives the header its
|
||||
// real actions, which this spec measures.
|
||||
const composer = page.getByRole('textbox', { name: /^(Describe what you want to build|描述你想要构建)/ })
|
||||
await expect(composer).toBeVisible()
|
||||
await composer.fill('Create a desktop side-card layout test session.')
|
||||
await page.getByRole('button', { name: /^(Send message|发送消息)$/ }).click()
|
||||
// DSH 0.1.5's composer is a Lexical contenteditable: fill() writes the DOM
|
||||
// but not the editor model, so the send button would stay disabled — type
|
||||
// real keys instead.
|
||||
await composer.click()
|
||||
await page.keyboard.type('Create a desktop side-card layout test session.')
|
||||
const send = page.getByRole('button', { name: /^(Send message|发送消息)$/ })
|
||||
await expect(send).toBeEnabled({ timeout: 30_000 })
|
||||
await send.click()
|
||||
const sessionLog = page.getByRole('button', { name: 'Session log', exact: true })
|
||||
const sessionLogLabel = sessionLog.getByText('Session log', { exact: true })
|
||||
await expect(sessionLogLabel, 'the desktop session-log action starts as a text button').toBeVisible({ timeout: 30_000 })
|
||||
|
||||
const root = page.locator('#root')
|
||||
const frame = page.locator('#root [data-dsh-frame], #root > [data-slot="root"] > div').first()
|
||||
const appSidebarExpanded = root.getByRole('button', { name: /^(Collapse sidebar|收起侧边栏)$/ })
|
||||
const sidebar = page.locator('[data-dsh-better-sidebar]')
|
||||
const sessionLog = page.getByRole('button', { name: 'Session log', exact: true })
|
||||
const sessionLogLabel = sessionLog.getByText('Session log', { exact: true })
|
||||
await expect(sessionLogLabel, 'the desktop session-log action starts as a text button').toBeVisible({ timeout: 30_000 })
|
||||
await expect(appSidebarExpanded, 'the desktop frame starts with its app sidebar expanded').toBeVisible()
|
||||
const beforeRoot = await root.boundingBox()
|
||||
const beforeFrame = await frame.boundingBox()
|
||||
expect(beforeRoot).not.toBeNull()
|
||||
expect(beforeFrame).not.toBeNull()
|
||||
// The header action's own width is the baseline: the assertion below is
|
||||
// "the panel does not squeeze it", not a host-styling constant.
|
||||
const beforeSessionLog = await sessionLog.boundingBox()
|
||||
expect(beforeSessionLog).not.toBeNull()
|
||||
|
||||
await sidebar.getByRole('button', { name: /^(Expand sidebar|展开侧边栏)$/ }).click()
|
||||
const panel = page.locator('[data-dsh-panel]:not([data-dsh-bottom-panel])')
|
||||
@@ -103,7 +116,10 @@ test('right panel keeps desktop session actions in their header positions', asyn
|
||||
const [sessionLogBox, panelBox] = await Promise.all([sessionLog.boundingBox(), panel.boundingBox()])
|
||||
expect(sessionLogBox).not.toBeNull()
|
||||
expect(panelBox).not.toBeNull()
|
||||
expect(sessionLogBox!.width, 'the desktop session-log action must keep its full button width').toBeGreaterThanOrEqual(110)
|
||||
expect(
|
||||
sessionLogBox!.width,
|
||||
'the desktop session-log action must keep its full button width',
|
||||
).toBeGreaterThanOrEqual(beforeSessionLog!.width - 1)
|
||||
expect(sessionLogBox!.x + sessionLogBox!.width, 'the session-log action must not overlap the plugin panel chrome').toBeLessThanOrEqual(panelBox!.x - 8)
|
||||
expect(pageErrors).toEqual([])
|
||||
expect(consoleErrors).toEqual([])
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { registerOpenPathInterception } from '../src/client/intercept.tsx'
|
||||
import {
|
||||
isFolderRevealPath,
|
||||
wrapOpenWorkspacePath,
|
||||
type OpenPathInterceptDeps,
|
||||
type OpenWorkspacePathService,
|
||||
} from '../src/client/openpath-intercept.ts'
|
||||
import { createSidebarStore } from '../src/client/state.ts'
|
||||
import type { Context } from '../src/context-types.ts'
|
||||
|
||||
/**
|
||||
* A fake of the gateway client's RemoteNamespaceService: the method is an
|
||||
* ACCESSOR property (getter returning the invocation closure), exactly like
|
||||
* `Object.defineProperty(service, method, { configurable: true, get })` in
|
||||
* the real implementation — the wrapper must shadow it with a data property
|
||||
* and restore the accessor on dispose.
|
||||
*/
|
||||
function fakeNamespaceService(opened: string[]): OpenWorkspacePathService {
|
||||
const target = {} as Record<string, unknown>
|
||||
Object.defineProperty(target, 'openWorkspacePath', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return async (request: { path: string }) => {
|
||||
opened.push(request.path)
|
||||
return { ok: true, value: { opened: true } } as const
|
||||
}
|
||||
},
|
||||
})
|
||||
return target as unknown as OpenWorkspacePathService
|
||||
}
|
||||
|
||||
describe('open-path interception (wrapOpenWorkspacePath)', () => {
|
||||
const deps = (overrides: Partial<OpenPathInterceptDeps> = {}): OpenPathInterceptDeps & {
|
||||
sidebar: string[]
|
||||
revealed: string[]
|
||||
} => {
|
||||
const sidebar: string[] = []
|
||||
const revealed: string[] = []
|
||||
return {
|
||||
sidebar,
|
||||
revealed,
|
||||
takeoverEnabled: () => true,
|
||||
currentSessionId: () => 's1',
|
||||
openInSidebar: (path, sessionId) => { sidebar.push(`${sessionId}:${path}`) },
|
||||
revealInExplorer: (path, sessionId) => { revealed.push(`${sessionId}:${path}`) },
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
it('routes an intercepted open into the sidebar and resolves with the remote success envelope', async () => {
|
||||
const opened: string[] = []
|
||||
const service = fakeNamespaceService(opened)
|
||||
const d = deps()
|
||||
const restore = wrapOpenWorkspacePath(service, d)
|
||||
// The caller (ChatView's openFile) branches on `result.ok` and reads
|
||||
// `result.error.message` on falsy — a bare business value would surface
|
||||
// as a bogus "cannot open" dialog.
|
||||
await expect(service.openWorkspacePath({ path: '/abs/a.ts' }))
|
||||
.resolves.toEqual({ ok: true, value: { opened: true } })
|
||||
expect(opened).toEqual([])
|
||||
expect(d.sidebar).toEqual(['s1:/abs/a.ts'])
|
||||
restore()
|
||||
})
|
||||
|
||||
it('falls through to the original when the takeover is disabled', async () => {
|
||||
const opened: string[] = []
|
||||
const service = fakeNamespaceService(opened)
|
||||
const d = deps({ takeoverEnabled: () => false })
|
||||
const restore = wrapOpenWorkspacePath(service, d)
|
||||
await service.openWorkspacePath({ path: '/abs/a.ts' })
|
||||
expect(opened).toEqual(['/abs/a.ts'])
|
||||
expect(d.sidebar).toEqual([])
|
||||
restore()
|
||||
})
|
||||
|
||||
it('falls through when no session is current (nothing to scope the editor load to)', async () => {
|
||||
const opened: string[] = []
|
||||
const service = fakeNamespaceService(opened)
|
||||
const d = deps({ currentSessionId: () => undefined })
|
||||
const restore = wrapOpenWorkspacePath(service, d)
|
||||
await service.openWorkspacePath({ path: '/abs/a.ts' })
|
||||
expect(opened).toEqual(['/abs/a.ts'])
|
||||
expect(d.sidebar).toEqual([])
|
||||
restore()
|
||||
})
|
||||
|
||||
it('passes the current session into the sidebar opener', async () => {
|
||||
const opened: string[] = []
|
||||
const service = fakeNamespaceService(opened)
|
||||
let current = 's1'
|
||||
const d = deps({ currentSessionId: () => current })
|
||||
const restore = wrapOpenWorkspacePath(service, d)
|
||||
await service.openWorkspacePath({ path: '/abs/a.ts' })
|
||||
current = 's2'
|
||||
await service.openWorkspacePath({ path: '/abs/b.ts' })
|
||||
expect(d.sidebar).toEqual(['s1:/abs/a.ts', 's2:/abs/b.ts'])
|
||||
restore()
|
||||
})
|
||||
|
||||
it('routes the folder-reveal gesture to the explorer, not the editor', async () => {
|
||||
const opened: string[] = []
|
||||
const service = fakeNamespaceService(opened)
|
||||
const d = deps()
|
||||
const restore = wrapOpenWorkspacePath(service, d)
|
||||
for (const path of ['.', './', '/w/.', 'C:\\w\\.']) {
|
||||
await service.openWorkspacePath({ path })
|
||||
}
|
||||
expect(opened).toEqual([])
|
||||
expect(d.sidebar).toEqual([])
|
||||
expect(d.revealed).toEqual(['s1:.', 's1:./', 's1:/w/.', 's1:C:\\w\\.'])
|
||||
restore()
|
||||
})
|
||||
|
||||
it('restores the original accessor on dispose (HMR-safe)', async () => {
|
||||
const opened: string[] = []
|
||||
const service = fakeNamespaceService(opened)
|
||||
const before = Object.getOwnPropertyDescriptor(service, 'openWorkspacePath')
|
||||
expect(typeof before?.get).toBe('function')
|
||||
const d = deps()
|
||||
const restore = wrapOpenWorkspacePath(service, d)
|
||||
// While wrapped, the property is our data-property shadow.
|
||||
const during = Object.getOwnPropertyDescriptor(service, 'openWorkspacePath')
|
||||
expect(during?.get).toBeUndefined()
|
||||
expect(typeof during?.value).toBe('function')
|
||||
restore()
|
||||
const after = Object.getOwnPropertyDescriptor(service, 'openWorkspacePath')
|
||||
expect(typeof after?.get).toBe('function')
|
||||
await service.openWorkspacePath({ path: '/abs/a.ts' })
|
||||
expect(opened).toEqual(['/abs/a.ts'])
|
||||
expect(d.sidebar).toEqual([])
|
||||
})
|
||||
|
||||
it('propagates a rejection from the original (no swallowing)', async () => {
|
||||
const target = {} as Record<string, unknown>
|
||||
Object.defineProperty(target, 'openWorkspacePath', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => async () => { throw new Error('host refused') },
|
||||
})
|
||||
const service = target as unknown as OpenWorkspacePathService
|
||||
const d = deps({ takeoverEnabled: () => false })
|
||||
const restore = wrapOpenWorkspacePath(service, d)
|
||||
await expect(service.openWorkspacePath({ path: '/abs/a.ts' })).rejects.toThrow('host refused')
|
||||
restore()
|
||||
})
|
||||
|
||||
it('declines to wrap when the method is not installed (hand-rolled composition)', async () => {
|
||||
const service = {} as OpenWorkspacePathService
|
||||
const d = deps()
|
||||
const restore = wrapOpenWorkspacePath(service, d)
|
||||
expect(service.openWorkspacePath).toBeUndefined()
|
||||
expect(d.sidebar).toEqual([])
|
||||
restore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isFolderRevealPath', () => {
|
||||
it('recognizes the dot gestures on both separator styles', () => {
|
||||
expect(isFolderRevealPath('.')).toBe(true)
|
||||
expect(isFolderRevealPath('./')).toBe(true)
|
||||
expect(isFolderRevealPath('/w/.')).toBe(true)
|
||||
expect(isFolderRevealPath('/w/./')).toBe(true)
|
||||
expect(isFolderRevealPath('C:\\w\\.')).toBe(true)
|
||||
expect(isFolderRevealPath('/w/a.ts')).toBe(false)
|
||||
expect(isFolderRevealPath('/w/.hidden')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('open-path interception wiring', () => {
|
||||
/**
|
||||
* A client-context fake whose `inject` mimics cordis: the callback runs
|
||||
* once the dependency appears (here: when `mount()` is called) and its
|
||||
* effects dispose with the fiber.
|
||||
*/
|
||||
function fakeCtx(opened: Array<Record<string, unknown>>, remoteSession: OpenWorkspacePathService) {
|
||||
let effectDisposer: (() => void) | undefined
|
||||
let injectCallback: ((fctx: unknown) => void) | undefined
|
||||
const fctx = {
|
||||
get: (name: string) => (name === 'remote.session' ? remoteSession : undefined),
|
||||
effect: (fn: () => () => void) => { effectDisposer = fn() },
|
||||
}
|
||||
const ctx = {
|
||||
sessions: {
|
||||
list: { getSnapshot: () => ({ current: 's1', byId: { s1: { cwd: '/w' } } }) },
|
||||
},
|
||||
get: (name: string) => name === 'betterSidebar'
|
||||
? { openTab: (seed: unknown) => { opened.push(seed as Record<string, unknown>) } }
|
||||
: undefined,
|
||||
inject: (_deps: string[], callback: (fctx: unknown) => void) => {
|
||||
injectCallback = callback
|
||||
return {
|
||||
dispose: async () => { effectDisposer?.() },
|
||||
}
|
||||
},
|
||||
/** Test helper: simulate the namespace service appearing. */
|
||||
mount: () => { injectCallback?.(fctx) },
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
it('registers through ctx.inject and routes chat opens into the editor tab', async () => {
|
||||
const opened: Array<Record<string, unknown>> = []
|
||||
const hostOpened: string[] = []
|
||||
const remoteSession = fakeNamespaceService(hostOpened)
|
||||
const ctx = fakeCtx(opened, remoteSession)
|
||||
const store = createSidebarStore()
|
||||
const restore = registerOpenPathInterception(ctx as unknown as Context, store)
|
||||
|
||||
// Before the namespace mounts, nothing is wrapped.
|
||||
expect(Object.getOwnPropertyDescriptor(remoteSession, 'openWorkspacePath')?.get).toBeDefined()
|
||||
ctx.mount()
|
||||
|
||||
// Default prefs: the takeover routes the open into the sidebar editor
|
||||
// with the session-scoped absolute path (chat already resolved it).
|
||||
await remoteSession.openWorkspacePath({ path: '/w/src/a.ts' })
|
||||
expect(opened).toEqual([{
|
||||
type: 'editor',
|
||||
title: 'a.ts',
|
||||
path: '/w/src/a.ts',
|
||||
id: 'editor:/w/src/a.ts',
|
||||
}])
|
||||
expect(hostOpened).toEqual([])
|
||||
|
||||
// The interceptOpenPath pref off → the original funnel runs untouched.
|
||||
store.setPrefs({ ...store.getPrefs(), interceptOpenPath: false })
|
||||
await remoteSession.openWorkspacePath({ path: '/w/src/b.ts' })
|
||||
expect(hostOpened).toEqual(['/w/src/b.ts'])
|
||||
expect(opened).toHaveLength(1)
|
||||
|
||||
// The editor tab disabled → falls through too (an editor that cannot
|
||||
// open must not swallow opens).
|
||||
store.setPrefs({ ...store.getPrefs(), interceptOpenPath: true, tabsEnabled: { editor: false } })
|
||||
await remoteSession.openWorkspacePath({ path: '/w/src/c.ts' })
|
||||
expect(hostOpened).toEqual(['/w/src/b.ts', '/w/src/c.ts'])
|
||||
|
||||
// Disposal restores the accessor (HMR-safe).
|
||||
restore()
|
||||
const descriptor = Object.getOwnPropertyDescriptor(remoteSession, 'openWorkspacePath')
|
||||
expect(typeof descriptor?.get).toBe('function')
|
||||
})
|
||||
|
||||
it('re-applies the shadow when the namespace service is remounted', async () => {
|
||||
const opened: Array<Record<string, unknown>> = []
|
||||
const hostOpened: string[] = []
|
||||
// A ctx whose inject can fire multiple times (service recreate).
|
||||
let effectDisposer: (() => void) | undefined
|
||||
let injectCallback: ((fctx: unknown) => void) | undefined
|
||||
let current = fakeNamespaceService(hostOpened)
|
||||
const ctx = {
|
||||
sessions: { list: { getSnapshot: () => ({ current: 's1', byId: { s1: { cwd: '/w' } } }) } },
|
||||
get: (name: string) => name === 'betterSidebar'
|
||||
? { openTab: (seed: unknown) => { opened.push(seed as Record<string, unknown>) } }
|
||||
: undefined,
|
||||
inject: (_deps: string[], callback: (fctx: unknown) => void) => {
|
||||
injectCallback = callback
|
||||
return { dispose: async () => { effectDisposer?.() } }
|
||||
},
|
||||
remount: () => {
|
||||
effectDisposer?.()
|
||||
current = fakeNamespaceService(hostOpened)
|
||||
injectCallback?.({
|
||||
get: (name: string) => (name === 'remote.session' ? current : undefined),
|
||||
effect: (fn: () => () => void) => { effectDisposer = fn() },
|
||||
})
|
||||
},
|
||||
}
|
||||
const store = createSidebarStore()
|
||||
registerOpenPathInterception(ctx as unknown as Context, store)
|
||||
ctx.remount()
|
||||
await current.openWorkspacePath({ path: '/w/src/a.ts' })
|
||||
expect(opened).toHaveLength(1)
|
||||
// A remount (dispose + fresh service) must not leak the old shadow and
|
||||
// must wrap the fresh instance.
|
||||
ctx.remount()
|
||||
await current.openWorkspacePath({ path: '/w/src/b.ts' })
|
||||
expect(opened).toHaveLength(2)
|
||||
expect(hostOpened).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -97,6 +97,6 @@ describe('dsh-better-sidebar plugin export shape', () => {
|
||||
const overridden = (PrefsSchema as unknown as {
|
||||
(input: Record<string, unknown> | undefined): Record<string, unknown>
|
||||
})({ openByDefault: false, defaultWidthPercent: 45 })
|
||||
expect(overridden).toEqual({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: false, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: '', changesDiffFloat: true, tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
expect(overridden).toEqual({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, editorExplorer: false, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: '', changesDiffFloat: true, tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
})
|
||||
})
|
||||
|
||||
+5
-18
@@ -41,7 +41,6 @@ describe('side card preferences', () => {
|
||||
bottomPanelAutoTerminal: true,
|
||||
terminalFontFamily: '',
|
||||
terminalFontSize: 13,
|
||||
interceptOpenPath: true,
|
||||
editorExplorer: false, changesDiffFloat: true,
|
||||
workspaceFence: true,
|
||||
terminalShell: '',
|
||||
@@ -75,7 +74,6 @@ describe('side card preferences', () => {
|
||||
bottomPanelAutoTerminal: true,
|
||||
terminalFontFamily: '',
|
||||
terminalFontSize: 13,
|
||||
interceptOpenPath: true,
|
||||
editorExplorer: false, changesDiffFloat: true,
|
||||
workspaceFence: true,
|
||||
terminalShell: '',
|
||||
@@ -109,7 +107,6 @@ describe('side card preferences', () => {
|
||||
bottomPanelAutoTerminal: true,
|
||||
terminalFontFamily: '',
|
||||
terminalFontSize: 13,
|
||||
interceptOpenPath: true,
|
||||
editorExplorer: false, changesDiffFloat: true,
|
||||
workspaceFence: true,
|
||||
terminalShell: '',
|
||||
@@ -153,16 +150,6 @@ describe('side card preferences', () => {
|
||||
.toBe(false)
|
||||
})
|
||||
|
||||
it('defaults interceptOpenPath to true; only an explicit false turns the takeover off', async () => {
|
||||
// Absent or malformed → on (the takeover is the safe default).
|
||||
expect((await loadPrefs(wire({}))).interceptOpenPath).toBe(true)
|
||||
expect((await loadPrefs(wire({ interceptOpenPath: 'yes' }))).interceptOpenPath).toBe(true)
|
||||
expect((await loadPrefs(wire({ interceptOpenPath: 0 }))).interceptOpenPath).toBe(true)
|
||||
// Explicit booleans survive verbatim.
|
||||
expect((await loadPrefs(wire({ interceptOpenPath: false }))).interceptOpenPath).toBe(false)
|
||||
expect((await loadPrefs(wire({ interceptOpenPath: true }))).interceptOpenPath).toBe(true)
|
||||
})
|
||||
|
||||
it('defaults editorExplorer to false; only an explicit true enables the merged editor-explorer', async () => {
|
||||
// Absent or malformed → off (separate file windows are the default).
|
||||
expect((await loadPrefs(wire({}))).editorExplorer).toBe(false)
|
||||
@@ -282,9 +269,9 @@ describe('side card preferences', () => {
|
||||
const store = createSidebarStore()
|
||||
// Node environment: no window → the width falls back to PANEL_DEFAULT,
|
||||
// while the open flag still follows the preference.
|
||||
store.setPrefs({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
store.setPrefs({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, editorExplorer: true, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
store.setSession('fresh-session')
|
||||
expect(store.getPrefs()).toEqual({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
expect(store.getPrefs()).toEqual({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, editorExplorer: true, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
const snapshot = store.getSnapshot()
|
||||
expect(snapshot.sessionId).toBe('fresh-session')
|
||||
expect(snapshot.state?.panelOpen).toBe(false)
|
||||
@@ -320,7 +307,7 @@ describe('side card preferences', () => {
|
||||
|
||||
it('skips the default seed tab when the editor (files window) type is disabled', () => {
|
||||
const store = createSidebarStore()
|
||||
store.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: { editor: false }, viewersEnabled: {}, pluginSettings: {} })
|
||||
store.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, editorExplorer: true, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: { editor: false }, viewersEnabled: {}, pluginSettings: {} })
|
||||
store.setSession('no-editor')
|
||||
const state = store.getSnapshot().state!
|
||||
const tabs = allLeaves(state.splits).flatMap(leaf => leaf.tabs)
|
||||
@@ -330,7 +317,7 @@ describe('side card preferences', () => {
|
||||
// editorExplorer modes.
|
||||
for (const editorExplorer of [true, false]) {
|
||||
const openStore = createSidebarStore()
|
||||
openStore.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
openStore.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, editorExplorer, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
openStore.setSession(`with-editor-${editorExplorer}`)
|
||||
const openTabs = allLeaves(openStore.getSnapshot().state!.splits).flatMap(leaf => leaf.tabs)
|
||||
expect(openTabs.map(tab => tab.type)).toEqual(['editor'])
|
||||
@@ -340,7 +327,7 @@ describe('side card preferences', () => {
|
||||
it('seeds the empty editor home tab (files window) in both editorExplorer modes', () => {
|
||||
for (const editorExplorer of [true, false]) {
|
||||
const store = createSidebarStore()
|
||||
store.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
store.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, agentOpenTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, editorExplorer, changesDiffFloat: true, workspaceFence: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
||||
store.setSession(`fresh-${editorExplorer}`)
|
||||
const tabs = allLeaves(store.getSnapshot().state!.splits).flatMap(leaf => leaf.tabs)
|
||||
expect(tabs).toHaveLength(1)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Vitest global setup.
|
||||
*
|
||||
* Node ≥ 22 defines a `localStorage` accessor on globalThis that resolves to
|
||||
* `undefined` unless the process was started with `--localstorage-file`
|
||||
* ("localStorage is not available because --localstorage-file was not
|
||||
* provided"). Because that key already exists, the jsdom environment's
|
||||
* global population never installs the document's own store, so a test
|
||||
* calling the bare global `localStorage` throws "Cannot read properties of
|
||||
* undefined" even though jsdom has a working one.
|
||||
*
|
||||
* Bridge the two: point the global at the jsdom window's Storage. The jsdom
|
||||
* environment exposes its instance as `globalThis.jsdom`; node-only suites
|
||||
* (no jsdom global) keep Node's accessor untouched.
|
||||
*/
|
||||
const jsdomWindow = (globalThis as { jsdom?: { window?: Window } }).jsdom?.window
|
||||
const storage = jsdomWindow?.localStorage ?? (typeof window === 'undefined' ? undefined : window.localStorage)
|
||||
if (storage !== undefined) {
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
value: storage,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
})
|
||||
}
|
||||
@@ -90,14 +90,14 @@ describe('SideCardSection declarative inventory', () => {
|
||||
expect(html).toContain('>explorer<')
|
||||
expect(html).toContain('data-icon="subagent"')
|
||||
expect(html).toContain('>Subagents<')
|
||||
// Default prefs: only the interceptOpenPath switch is checked (openByDefault
|
||||
// now defaults off), and both tabs + the image viewer cards pressed
|
||||
// Default prefs: no general switch is on (openByDefault and agentOpenTools
|
||||
// both default off), and both tabs + the image viewer cards pressed
|
||||
// (3 aria-pressed cards).
|
||||
// The nested auto-open toggle is NOT an inline card (it lives in the popup).
|
||||
expect(pressedCount(html, 'true')).toBe(3)
|
||||
expect(pressedCount(html, 'false')).toBe(0)
|
||||
// The general toggles are custom switches (real checkboxes, one checked).
|
||||
expect(html.match(/checked=""/g)?.length).toBe(1)
|
||||
// The general toggles are custom switches (real checkboxes, none checked).
|
||||
expect(html.match(/checked=""/g)?.length ?? 0).toBe(0)
|
||||
expect(html).not.toContain('Auto-open Subagents')
|
||||
})
|
||||
|
||||
@@ -153,9 +153,9 @@ describe('SideCardSection declarative inventory', () => {
|
||||
expect(html).toContain('>Subagents<')
|
||||
expect(html).toContain('>Image<')
|
||||
expect(pressedCount(html, 'false')).toBe(2)
|
||||
// The explorer card stays pressed; the one default-on general switch stays checked.
|
||||
// The explorer card stays pressed; no general switch is on by default.
|
||||
expect(pressedCount(html, 'true')).toBe(1)
|
||||
expect(html.match(/checked=""/g)?.length).toBe(1)
|
||||
expect(html.match(/checked=""/g)?.length ?? 0).toBe(0)
|
||||
})
|
||||
|
||||
it('hides the gear of a disabled feature (its related settings are dormant)', () => {
|
||||
@@ -178,11 +178,10 @@ describe('SideCardSection declarative inventory', () => {
|
||||
expect(html).toContain('Pick the title-bar compatibility scheme: auto-detect (default, conservative) / DSH official web / known desktop shells / custom (shift distance + custom CSS)')
|
||||
expect(html).not.toContain('<select')
|
||||
expect(html).toContain('>Auto-detect<')
|
||||
// Three general-row switches remain (openByDefault + interceptOpenPath
|
||||
// + agentOpenTools), only interceptOpenPath checked by default — the
|
||||
// scheme row is a dropdown, not a switch.
|
||||
expect(html.match(/type="checkbox"/g)?.length).toBe(3)
|
||||
expect(html.match(/checked=""/g)?.length).toBe(1)
|
||||
// Two general-row switches remain (openByDefault + agentOpenTools), neither
|
||||
// on by default — the scheme row is a dropdown, not a switch.
|
||||
expect(html.match(/type="checkbox"/g)?.length).toBe(2)
|
||||
expect(html.match(/checked=""/g)?.length ?? 0).toBe(0)
|
||||
// Auto (default) needs no further settings → no gear.
|
||||
expect(html).not.toContain('Position compatibility mode Feature settings')
|
||||
|
||||
|
||||
+33
-12
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { SidebarHistoryEntry, SidebarSessionEvent, SidebarSessionSummary } from '../src/context-types.ts'
|
||||
import type { AssistantLiveChunk } from '../src/assistant-live.ts'
|
||||
import {
|
||||
boundaryDelivered,
|
||||
buildOpenTurnSnapshot,
|
||||
@@ -35,6 +36,24 @@ function ev(type: string, seq: number, data: Record<string, unknown> = {}): Side
|
||||
return event
|
||||
}
|
||||
|
||||
/** One live delta as the 0.1.5 stream buffer produces it: `index` is the
|
||||
* dense frame position, the chunk carries its own block index. */
|
||||
function live(
|
||||
turn: number,
|
||||
step: number,
|
||||
text: string,
|
||||
over: { kind?: 'text' | 'reasoning'; index?: number; block?: number } = {},
|
||||
): AssistantLiveChunk {
|
||||
return {
|
||||
attemptId: 'a1',
|
||||
turn,
|
||||
step,
|
||||
index: over.index ?? 0,
|
||||
time: 0,
|
||||
chunk: { type: over.kind === 'reasoning' ? 'reasoning-delta' : 'text-delta', index: over.block ?? 0, text },
|
||||
}
|
||||
}
|
||||
|
||||
/** A completed user→assistant turn block (turn T, step 1, optional tools). */
|
||||
function completedTurn(seq: number, turn: number, over: {
|
||||
text?: string
|
||||
@@ -136,7 +155,6 @@ describe('buildSidechatInheritance', () => {
|
||||
ev('user/message', 6, { content: [{ type: 'text', text: 'next?' }], source: { kind: 'user' } }),
|
||||
ev('turn/start', 7, { turn: 2 }),
|
||||
ev('step/start', 8, { turn: 2, step: 1 }),
|
||||
ev('assistant/chunk', 9, { turn: 2, step: 1, chunk: { type: 'text-delta', index: 0, text: 'partial answer' } }),
|
||||
]
|
||||
const { seed, snapshot } = buildSidechatInheritance(events)
|
||||
expectContiguous(seed)
|
||||
@@ -145,10 +163,12 @@ describe('buildSidechatInheritance', () => {
|
||||
const turnEnd = seed.at(-1)!
|
||||
expect(stepEnd).toMatchObject({ type: 'step/end', data: { turn: 2, step: 1 } })
|
||||
expect(turnEnd).toMatchObject({ type: 'turn/end', data: { turn: 2, reason: { kind: 'interrupted' } } })
|
||||
// The pending user message and the partial chunk survive as REAL events.
|
||||
// The pending user message survives as a REAL event. The in-flight
|
||||
// step's deltas are process-local on 0.1.5, so they are NOT seed events —
|
||||
// the snapshot fallback is the only path that carries them.
|
||||
expect(seed.some(event => event.type === 'user/message'
|
||||
&& (event.data.content as Array<{ text: string }>)[0]?.text === 'next?')).toBe(true)
|
||||
expect(seed.some(event => event.type === 'assistant/chunk')).toBe(true)
|
||||
expect(seed.every(event => event.type !== 'assistant/live-chunk')).toBe(true)
|
||||
expect(snapshot).toBeNull()
|
||||
})
|
||||
|
||||
@@ -166,7 +186,7 @@ describe('buildSidechatInheritance', () => {
|
||||
content: [{ type: 'tool-result', toolCallId: 'c1', content: [{ type: 'text', text: 'hit' }] }],
|
||||
},
|
||||
}),
|
||||
ev('assistant/chunk', 5, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'found ' } }),
|
||||
ev('assistant/message', 5, { turn: 1, step: 1, message: { content: [{ type: 'text', text: 'found it' }] }, stream: [] }),
|
||||
]
|
||||
const { seed, snapshot } = buildSidechatInheritance(events)
|
||||
expectContiguous(seed)
|
||||
@@ -180,11 +200,12 @@ describe('buildSidechatInheritance', () => {
|
||||
ev('user/message', 0, { content: [{ type: 'text', text: 'q' }], source: { kind: 'user' } }),
|
||||
ev('turn/start', 1, { turn: 1 }),
|
||||
ev('step/start', 2, { turn: 1, step: 1 }),
|
||||
ev('assistant/chunk', 3, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'running ' } }),
|
||||
ev('tool/call', 4, { turn: 1, step: 1, callId: 'c1', name: 'bash', arguments: '{"cmd":"sleep 9"}' }),
|
||||
]
|
||||
expect(hasDanglingToolCall(events, 1)).toBe(true)
|
||||
const { seed, snapshot } = buildSidechatInheritance(events)
|
||||
// The in-flight text is process-local on 0.1.5: it reaches the snapshot
|
||||
// through the live buffer, not through the log.
|
||||
const { seed, snapshot } = buildSidechatInheritance(events, [live(1, 1, 'running ')])
|
||||
expectContiguous(seed)
|
||||
// The seed stops BEFORE the open turn; the pending user message stays.
|
||||
expect(seed.at(-1)?.type).toBe('user/message')
|
||||
@@ -200,10 +221,9 @@ describe('buildSidechatInheritance', () => {
|
||||
ev('user/message', 0, { content: [{ type: 'text', text: 'q' }], source: { kind: 'user' } }),
|
||||
ev('turn/start', 1, { turn: 1 }),
|
||||
ev('step/start', 2, { turn: 1, step: 1 }),
|
||||
ev('assistant/message', 3, { turn: 1, step: 1, message: { content: [{ type: 'text', text: 'first' }] } }),
|
||||
ev('assistant/message', 3, { turn: 1, step: 1, message: { content: [{ type: 'text', text: 'first' }] }, stream: [] }),
|
||||
ev('step/end', 4, { turn: 1, step: 1 }),
|
||||
ev('step/start', 5, { turn: 1, step: 2 }),
|
||||
ev('assistant/chunk', 6, { turn: 1, step: 2, chunk: { type: 'text-delta', index: 0, text: 'second' } }),
|
||||
]
|
||||
const { seed } = buildSidechatInheritance(events)
|
||||
expectContiguous(seed)
|
||||
@@ -222,9 +242,6 @@ describe('buildOpenTurnSnapshot', () => {
|
||||
const events = [
|
||||
ev('turn/start', 0, { turn: 1 }),
|
||||
ev('step/start', 1, { turn: 1, step: 1 }),
|
||||
ev('assistant/chunk', 2, { turn: 1, step: 1, chunk: { type: 'reasoning-delta', index: 0, text: 'think' } }),
|
||||
ev('assistant/chunk', 3, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: '```js\ncode' } }),
|
||||
ev('assistant/chunk', 4, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: '\n```' } }),
|
||||
ev('tool/call', 5, { turn: 1, step: 1, callId: 'c1', name: 'read', arguments: '{"path":"a.txt"}' }),
|
||||
ev('tool/result', 6, {
|
||||
turn: 1,
|
||||
@@ -236,7 +253,11 @@ describe('buildOpenTurnSnapshot', () => {
|
||||
}),
|
||||
ev('tool/call', 7, { turn: 1, step: 1, callId: 'c2', name: 'bash', arguments: '{"cmd":"long"}' }),
|
||||
]
|
||||
const snapshot = buildOpenTurnSnapshot(events)
|
||||
const snapshot = buildOpenTurnSnapshot(events, [
|
||||
live(1, 1, 'think', { kind: 'reasoning', index: 0, block: 0 }),
|
||||
live(1, 1, '```js\ncode', { index: 1, block: 1 }),
|
||||
live(1, 1, '\n```', { index: 2, block: 1 }),
|
||||
])
|
||||
expect(snapshot).not.toBeNull()
|
||||
expect(snapshot).toContain('```js\ncode\n```')
|
||||
expect(snapshot).toContain('Reasoning so far')
|
||||
|
||||
@@ -48,18 +48,29 @@ function happyServices(parent: AgentLike | undefined, child: AgentLike) {
|
||||
const rename = vi.fn((_session: unknown, _title: string) => ({ title: 'x', eventSeq: 1 }))
|
||||
const resolve = vi.fn(async (_id?: string) => ({ id: 'preset-a' }))
|
||||
const mount = vi.fn(async () => {})
|
||||
const inspect = vi.fn(async (_id?: string): Promise<{ meta: Record<string, unknown>; events: unknown[] }> => ({ meta: { agentPreset: 'preset-a' }, events: [] }))
|
||||
/** The 0.1.5 persistence face: open(id, 'read') -> handle.read() -> close(). */
|
||||
const open = vi.fn(async (_id?: string): Promise<{
|
||||
header: { agentPreset?: string; cwd?: string }
|
||||
inheritedEventCount: number
|
||||
read: () => Promise<{ events: unknown[] }>
|
||||
close: () => Promise<void>
|
||||
}> => ({
|
||||
header: { agentPreset: 'preset-a' },
|
||||
inheritedEventCount: 0,
|
||||
read: async () => ({ events: [] }),
|
||||
close: async () => {},
|
||||
}))
|
||||
return {
|
||||
agents: { get, create, resume },
|
||||
agentPresets: { resolve, mount },
|
||||
sessionTitle: { rename },
|
||||
sessionPersistence: { inspect },
|
||||
sessionPersistence: { open },
|
||||
create,
|
||||
resume,
|
||||
get,
|
||||
rename,
|
||||
mount,
|
||||
inspect,
|
||||
open,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +181,6 @@ describe('sidechat.start', () => {
|
||||
ev('user/message', 0, { content: [{ type: 'text', text: 'q' }], source: { kind: 'user' } }),
|
||||
ev('turn/start', 1, { turn: 1 }),
|
||||
ev('step/start', 2, { turn: 1, step: 1 }),
|
||||
ev('assistant/chunk', 3, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'streaming' } }),
|
||||
],
|
||||
})
|
||||
const child = agent('child')
|
||||
@@ -181,7 +191,7 @@ describe('sidechat.start', () => {
|
||||
|
||||
const options = services.create.mock.calls[0]![0] as { seed: Array<{ type: string; data: Record<string, unknown> }> }
|
||||
expect(options.seed.map(event => event.type)).toEqual([
|
||||
'user/message', 'turn/start', 'step/start', 'assistant/chunk', 'step/end', 'turn/end',
|
||||
'user/message', 'turn/start', 'step/start', 'step/end', 'turn/end',
|
||||
'subagent/descriptor',
|
||||
])
|
||||
expect(options.seed.at(-2)?.data).toEqual({ turn: 1, reason: { kind: 'interrupted' } })
|
||||
@@ -306,7 +316,7 @@ describe('sidechat.prompt', () => {
|
||||
const resumeOptions = services.resume.mock.calls[0]![0] as { resumeSessionId: string; setup: unknown }
|
||||
expect(resumeOptions.resumeSessionId).toBe('child')
|
||||
expect(typeof resumeOptions.setup).toBe('function')
|
||||
expect(services.inspect).toHaveBeenCalledWith('child')
|
||||
expect(services.open).toHaveBeenCalledWith('child', 'read')
|
||||
expect(child.followup).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -368,14 +378,14 @@ describe('sidechat.info', () => {
|
||||
const api = buildSidechatApi(ctxWith(services))
|
||||
const info = await api['sidechat.info']({ childId: 'child' })
|
||||
expect(info).toEqual({ live: false, preset: 'preset-a' })
|
||||
expect(services.inspect).toHaveBeenCalledWith('child')
|
||||
expect(services.open).toHaveBeenCalledWith('child', 'read')
|
||||
})
|
||||
|
||||
it('degrades to a bare cold info when the session is gone', async () => {
|
||||
const services = happyServices(undefined, agent('child'))
|
||||
services.agents.get = vi.fn((_id: unknown) => undefined)
|
||||
services.inspect = vi.fn(async () => { throw new Error('unknown session') })
|
||||
services.sessionPersistence = { inspect: services.inspect }
|
||||
services.open = vi.fn(async () => { throw new Error('unknown session') })
|
||||
services.sessionPersistence = { open: services.open }
|
||||
const api = buildSidechatApi(ctxWith(services))
|
||||
await expect(api['sidechat.info']({ childId: 'ghost' })).resolves.toEqual({ live: false })
|
||||
})
|
||||
@@ -391,8 +401,8 @@ function threadLog(): Array<ReturnType<typeof ev>> {
|
||||
ev('subagent/descriptor', 3, { mode: 'continuable' }),
|
||||
ev('user/message', 4, { content: [{ type: 'text', text: 'Side conversation boundary.' }], source: { kind: 'plugin', plugin: 'dsh-better-sidebar' } }),
|
||||
ev('user/message', 5, { content: [{ type: 'text', text: 'the side question' }], source: { kind: 'user' } }),
|
||||
ev('assistant/chunk', 6, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'an' } }),
|
||||
ev('assistant/chunk', 7, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'swer' } }),
|
||||
ev('assistant/attempt', 6, { turn: 1, step: 1, stream: [] }),
|
||||
ev('assistant/attempt', 7, { turn: 1, step: 1, stream: [] }),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -404,7 +414,7 @@ describe('sidechat.events', () => {
|
||||
const result = await api['sidechat.events']({ childId: 'child' })
|
||||
expect(result.events.map(event => event.seq)).toEqual([3, 4, 5, 6, 7])
|
||||
expect(result.events[0]).toMatchObject({ type: 'subagent/descriptor', seq: 3 })
|
||||
expect(services.inspect).not.toHaveBeenCalled()
|
||||
expect(services.open).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('narrows to the afterSeq delta on polls (and never resurrects the seed)', async () => {
|
||||
@@ -422,11 +432,16 @@ describe('sidechat.events', () => {
|
||||
const child = agent('child')
|
||||
const services = happyServices(undefined, child)
|
||||
services.agents.get = vi.fn((_id: unknown) => undefined)
|
||||
services.inspect.mockImplementation(async () => ({ meta: { agentPreset: 'preset-a' }, events: threadLog() }))
|
||||
services.open.mockImplementation(async () => ({
|
||||
header: { agentPreset: 'preset-a' },
|
||||
inheritedEventCount: 0,
|
||||
read: async () => ({ events: threadLog() }),
|
||||
close: async () => {},
|
||||
}))
|
||||
const api = buildSidechatApi(ctxWith(services))
|
||||
const result = await api['sidechat.events']({ childId: 'child' })
|
||||
expect(result.events.map(event => event.seq)).toEqual([3, 4, 5, 6, 7])
|
||||
expect(services.inspect).toHaveBeenCalledWith('child')
|
||||
expect(services.open).toHaveBeenCalledWith('child', 'read')
|
||||
})
|
||||
|
||||
it('returns a marker-less legacy log whole', async () => {
|
||||
@@ -443,7 +458,7 @@ describe('sidechat.events', () => {
|
||||
})
|
||||
|
||||
it('caps a pathological response at its tail (8000 events)', async () => {
|
||||
const events = Array.from({ length: 8_003 }, (_, index) => ev('assistant/chunk', index, { turn: 1, step: 1 }))
|
||||
const events = Array.from({ length: 8_003 }, (_, index) => ev('assistant/attempt', index, { turn: 1, step: 1, stream: [] }))
|
||||
const child = agent('child', { events })
|
||||
const services = happyServices(undefined, child)
|
||||
const api = buildSidechatApi(ctxWith(services))
|
||||
@@ -463,7 +478,7 @@ describe('sidechat.events', () => {
|
||||
.rejects.toMatchObject({ code: 'bad-request' })
|
||||
|
||||
services.agents.get = vi.fn((_id: unknown) => undefined)
|
||||
services.inspect.mockImplementation(async () => { throw new Error('no such session') })
|
||||
services.open.mockImplementation(async () => { throw new Error('no such session') })
|
||||
await expect(api['sidechat.events']({ childId: 'ghost' }))
|
||||
.rejects.toMatchObject({ code: 'not-found', status: 404 })
|
||||
})
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
* not a mock, so the class of bug cannot silently return.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Session, SessionLogOffset } from '@deepseek-ai/dsh-session'
|
||||
import { Session, SessionLogOffset, SESSION_FORMAT_VERSION } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { Inbox } from '@deepseek-ai/dsh-agent'
|
||||
import type { InboxState } from '@deepseek-ai/dsh-agent'
|
||||
import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
|
||||
import type { SidebarSessionEvent } from '../src/context-types.ts'
|
||||
import { buildSidechatInheritance } from '../src/sidechat-core.ts'
|
||||
@@ -42,19 +42,20 @@ function assistantMessage(text: string): Record<string, unknown> {
|
||||
}
|
||||
|
||||
/** A parent log with a completed turn, a pending question, and an open
|
||||
* in-progress turn (the exact shape a mid-stream thread creation sees). */
|
||||
* in-progress turn (the exact shape a mid-stream thread creation sees).
|
||||
* DSH 0.1.5 logs nothing for the in-flight step: its deltas are
|
||||
* process-local `agent/assistant-stream` frames, not session events. */
|
||||
function parentLog(): SidebarSessionEvent[] {
|
||||
return [
|
||||
ev('user/message', 0, userMessage('first question')),
|
||||
ev('turn/start', 1, { turn: 1 }),
|
||||
ev('step/start', 2, { turn: 1, step: 1 }),
|
||||
ev('assistant/message', 3, { turn: 1, step: 1, message: assistantMessage('first answer') }),
|
||||
ev('assistant/message', 3, { turn: 1, step: 1, message: assistantMessage('first answer'), stream: [] }),
|
||||
ev('step/end', 4, { turn: 1, step: 1 }),
|
||||
ev('turn/end', 5, { turn: 1, reason: { kind: 'completed' } }),
|
||||
ev('user/message', 6, userMessage('pending question')),
|
||||
ev('turn/start', 7, { turn: 2 }),
|
||||
ev('step/start', 8, { turn: 2, step: 1 }),
|
||||
ev('assistant/chunk', 9, { turn: 2, step: 1, chunk: { type: 'text-delta', index: 0, text: 'in-progress' } }),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ describe('sidechat seed against the real dsh-session validator', () => {
|
||||
const types = child.snapshotEvents().map(event => event.type)
|
||||
expect(types).toEqual([
|
||||
'user/message', 'turn/start', 'step/start', 'assistant/message', 'step/end', 'turn/end',
|
||||
'user/message', 'turn/start', 'step/start', 'assistant/chunk', 'step/end', 'turn/end',
|
||||
'user/message', 'turn/start', 'step/start', 'step/end', 'turn/end',
|
||||
'session/end-seed',
|
||||
])
|
||||
// The synthetic close is honest: the frozen turn ends interrupted.
|
||||
@@ -113,18 +114,24 @@ describe('sidechat seed against the real dsh-session validator', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('sidechat seed fork markers vs the real dsh-agent Inbox replay', () => {
|
||||
describe('sidechat seed fork markers vs the reconstructed inbox', () => {
|
||||
/**
|
||||
* Regression: the thread-create call originally passed `seed` WITHOUT the
|
||||
* fork-marker pair (`meta.isSeeded: true` + `inheritedEventCount`). A seed
|
||||
* without them is REPLAY history, not an inherited prefix, so the child's
|
||||
* ownEvents() includes the whole seed and its Inbox constructor replays the
|
||||
* ownEvents() includes the whole seed and its inbox fold replays the
|
||||
* parent's `agent/inbox/spliced` events — inheriting whatever input sat
|
||||
* UNCLAIMED in the parent at the click moment (a queued follow-up, or a
|
||||
* tool-result context spliced into next-step between step boundaries of a
|
||||
* long-running turn). The first side prompt then claimed and SENT that stale
|
||||
* message before the boundary + question. These tests run the REAL
|
||||
* dsh-session Session and dsh-agent Inbox.
|
||||
* message before the boundary + question.
|
||||
*
|
||||
* The fold below is the standard inbox projection's rule
|
||||
* (`inboxProjectionDefinition` in `@deepseek-ai/dsh-agent-loop/inbox`,
|
||||
* which 0.1.5 does not re-export from the package root): pending input is
|
||||
* reconstructed from the session's OWN splice events, so the
|
||||
* `inheritedEventCount` the marker pair supplies is exactly what keeps the
|
||||
* inherited prefix out of it.
|
||||
*/
|
||||
const pendingMessage = (id: string, kind: string, text: string) => ({
|
||||
id,
|
||||
@@ -133,15 +140,24 @@ describe('sidechat seed fork markers vs the real dsh-agent Inbox replay', () =>
|
||||
source: { kind },
|
||||
})
|
||||
const inboxOver = (session: Session): { nextTurn: number; nextStep: number } => {
|
||||
const inbox = new Inbox(session, { inserted() {}, discarded() {}, claimed() {} })
|
||||
return { nextTurn: inbox.nextTurn.length, nextStep: inbox.nextStep.length }
|
||||
let state: InboxState = { 'next-turn': [], 'next-step': [] }
|
||||
for (const event of session.ownEvents()) {
|
||||
if (event.type !== 'agent/inbox/spliced') continue
|
||||
const splice = event.data
|
||||
const list = state[splice.target]
|
||||
state = {
|
||||
...state,
|
||||
[splice.target]: list.toSpliced(splice.start, splice.removedCount ?? 0, ...splice.inserted),
|
||||
}
|
||||
}
|
||||
return { nextTurn: state['next-turn'].length, nextStep: state['next-step'].length }
|
||||
}
|
||||
/** The shape the routes pass since the fix: isSeeded + inheritedEventCount. */
|
||||
const forkMarkedSession = (id: string, seed: readonly SidebarSessionEvent[]): Session =>
|
||||
Session.create(
|
||||
id as SessionId,
|
||||
seed as never,
|
||||
{ version: 0, id: id as SessionId, createdAt: Date.now(), isSeeded: true },
|
||||
{ version: SESSION_FORMAT_VERSION, id: id as SessionId, createdAt: Date.now(), isSeeded: true },
|
||||
SessionLogOffset(seed.length),
|
||||
)
|
||||
/** Real loop order: inbox insert → turn/start → claim deletion → step/start
|
||||
@@ -152,7 +168,7 @@ describe('sidechat seed fork markers vs the real dsh-agent Inbox replay', () =>
|
||||
ev('agent/inbox/spliced', 2, { target: 'next-turn', start: 0, removedCount: 1, inserted: [] }),
|
||||
ev('step/start', 3, { turn: 1, step: 1 }),
|
||||
ev('user/message', 4, pendingMessage('m-q', 'user', 'q')),
|
||||
ev('assistant/message', 5, { turn: 1, step: 1, message: assistantMessage('a') }),
|
||||
ev('assistant/message', 5, { turn: 1, step: 1, message: assistantMessage('a'), stream: [] }),
|
||||
ev('step/end', 6, { turn: 1, step: 1 }),
|
||||
ev('turn/end', 7, { turn: 1, reason: { kind: 'completed' } }),
|
||||
]
|
||||
|
||||
@@ -80,15 +80,15 @@ describe('transcriptRows', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('accumulates chunk deltas per block and supersedes them on settle', () => {
|
||||
it('accumulates live deltas per block and supersedes them on settle', () => {
|
||||
const entries = [
|
||||
entry(ev('session/end-seed', 0)),
|
||||
entry(ev('user/message', 1, { content: textBlocks('q'), source: { kind: 'user' } })),
|
||||
entry(ev('turn/start', 2, { turn: 1 })),
|
||||
entry(ev('step/start', 3, { turn: 1, step: 1 })),
|
||||
entry(ev('assistant/chunk', 4, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'Hel' } })),
|
||||
entry(ev('assistant/chunk', 5, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'lo' } })),
|
||||
entry(ev('assistant/chunk', 6, { turn: 1, step: 1, chunk: { type: 'reasoning-delta', index: 1, text: 'think' } })),
|
||||
entry(ev('assistant/live-chunk', 4, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'Hel' } })),
|
||||
entry(ev('assistant/live-chunk', 5, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'lo' } })),
|
||||
entry(ev('assistant/live-chunk', 6, { turn: 1, step: 1, chunk: { type: 'reasoning-delta', index: 1, text: 'think' } })),
|
||||
]
|
||||
const rows = transcriptRows(entries)
|
||||
const assistant = rows.find(row => row.kind === 'assistant') as Extract<SidechatTranscriptRow, { kind: 'assistant' }>
|
||||
@@ -104,7 +104,7 @@ describe('transcriptRows', () => {
|
||||
entry(ev('user/message', 1, { content: textBlocks('q'), source: { kind: 'user' } })),
|
||||
entry(ev('turn/start', 2, { turn: 1 })),
|
||||
entry(ev('step/start', 3, { turn: 1, step: 1 })),
|
||||
entry(ev('assistant/chunk', 4, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'par' } })),
|
||||
entry(ev('assistant/live-chunk', 4, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'par' } })),
|
||||
entry(ev('assistant/message', 5, { turn: 1, step: 1, message: { content: textBlocks('final answer') } })),
|
||||
]
|
||||
const rows = transcriptRows(entries)
|
||||
|
||||
+19
-6
@@ -40,6 +40,8 @@ interface FakeContext {
|
||||
sessions: { get: (id: string) => { header: { cwd?: string } } | undefined }
|
||||
tools: { register: (tool: unknown) => () => void }
|
||||
effect: (fn: () => void | (() => void), label?: string) => void
|
||||
/** The session/agent event feeds: nothing emits in these tests. */
|
||||
on: (event: string, listener: (payload: never) => void) => () => void
|
||||
/** The settings service never appears in the smoke context: the inject
|
||||
* callback must never run (mirror of cordis' service-less inject). */
|
||||
inject: (deps: readonly string[], callback: (sctx: never) => void) => () => void
|
||||
@@ -100,6 +102,7 @@ describe('host plugin smoke', () => {
|
||||
// No settings service in the smoke context: the registration callback
|
||||
// never runs (cordis' service-less inject behaves the same).
|
||||
inject: () => () => {},
|
||||
on: () => () => {},
|
||||
// No jobs/agents services: the jobs routes degrade to a 503.
|
||||
get: () => undefined,
|
||||
}
|
||||
@@ -136,6 +139,7 @@ describe('host plugin smoke', () => {
|
||||
if (typeof cleanup === 'function') effects.push(cleanup)
|
||||
},
|
||||
inject: () => () => {},
|
||||
on: () => () => {},
|
||||
get: () => undefined,
|
||||
}
|
||||
try {
|
||||
@@ -492,7 +496,7 @@ describe('git destructive operations (scratch repository)', () => {
|
||||
describe('session cwd resolution over the API route', () => {
|
||||
interface CtxOverrides {
|
||||
sessions?: { get: (id: string) => { header: { cwd?: string } } | undefined }
|
||||
sessionPersistence?: { inspect: (id: string) => Promise<{ meta: { cwd?: string } }> }
|
||||
sessionPersistence?: { open: (id: string, access: 'read' | 'write') => Promise<{ header: { cwd?: string }; read: () => Promise<{ events: never[] }>; close: () => Promise<void> }> }
|
||||
}
|
||||
|
||||
const mountAll = (overrides: CtxOverrides = {}): SidebarWebRoute[] => {
|
||||
@@ -509,6 +513,8 @@ describe('session cwd resolution over the API route', () => {
|
||||
effect: (fn: () => void | (() => void)) => { fn() },
|
||||
// No settings service: the namespace registration never runs.
|
||||
inject: () => () => {},
|
||||
// The session/agent event feeds: nothing emits in these tests.
|
||||
on: () => () => {},
|
||||
// No jobs/agents services in the smoke context: the routes degrade.
|
||||
get: (key: string) => key === 'sessionPersistence' ? overrides.sessionPersistence : undefined,
|
||||
}
|
||||
@@ -575,8 +581,10 @@ describe('session cwd resolution over the API route', () => {
|
||||
const coldCwd = resolvePath('/cold-project-cwd')
|
||||
const route = mount({
|
||||
sessionPersistence: {
|
||||
inspect: async (id) => ({
|
||||
meta: id === 's-cold' ? { cwd: coldCwd } : {},
|
||||
open: async (id) => ({
|
||||
header: id === 's-cold' ? { cwd: coldCwd } : {},
|
||||
read: async () => ({ events: [] }),
|
||||
close: async () => {},
|
||||
}),
|
||||
},
|
||||
})
|
||||
@@ -592,7 +600,7 @@ describe('session cwd resolution over the API route', () => {
|
||||
// potentially recreate the original "outside workspace" misclassification.
|
||||
const route = mount({
|
||||
sessionPersistence: {
|
||||
inspect: async () => ({ meta: { cwd: 'relative/path' } }),
|
||||
open: async () => ({ header: { cwd: 'relative/path' }, read: async () => ({ events: [] }), close: async () => {} }),
|
||||
},
|
||||
})
|
||||
const result = await invoke(route, 'session.cwd', { sessionId: 's-bad' })
|
||||
@@ -603,7 +611,7 @@ describe('session cwd resolution over the API route', () => {
|
||||
it('falls back to the process cwd when persistence has no cwd for the session', async () => {
|
||||
const route = mount({
|
||||
sessionPersistence: {
|
||||
inspect: async () => ({ meta: {} }),
|
||||
open: async () => ({ header: {}, read: async () => ({ events: [] }), close: async () => {} }),
|
||||
},
|
||||
})
|
||||
const result = await invoke(route, 'session.cwd', { sessionId: 's-blank' })
|
||||
@@ -877,6 +885,8 @@ describe('side card settings routes', () => {
|
||||
if (deps.includes('settings') && settings !== undefined) callback({ settings })
|
||||
return () => {}
|
||||
},
|
||||
// The session/agent event feeds: nothing emits in these tests.
|
||||
on: () => () => {},
|
||||
// No jobs/agents services: the jobs routes degrade to a 503.
|
||||
get: () => undefined,
|
||||
}
|
||||
@@ -990,7 +1000,6 @@ describe('side card settings routes', () => {
|
||||
bottomPanelAutoTerminal: true,
|
||||
terminalFontFamily: '',
|
||||
terminalFontSize: 13,
|
||||
interceptOpenPath: true,
|
||||
editorExplorer: false,
|
||||
workspaceFence: true,
|
||||
terminalShell: '',
|
||||
@@ -1170,6 +1179,8 @@ describe('agent terminal tool gating', () => {
|
||||
if (deps.includes('settings')) callback({ settings })
|
||||
return () => {}
|
||||
},
|
||||
// The session/agent event feeds: nothing emits in these tests.
|
||||
on: () => () => {},
|
||||
// No jobs/agents services: the jobs routes degrade to a 503.
|
||||
get: () => undefined,
|
||||
}
|
||||
@@ -1227,6 +1238,8 @@ describe('agent sidebar-open tool gating', () => {
|
||||
if (deps.includes('settings')) callback({ settings })
|
||||
return () => {}
|
||||
},
|
||||
// The session/agent event feeds: nothing emits in these tests.
|
||||
on: () => () => {},
|
||||
get: () => undefined,
|
||||
}
|
||||
apply(ctx as never)
|
||||
|
||||
@@ -46,11 +46,11 @@ describe('subagent activity summary parser', () => {
|
||||
])).toEqual({ text: 'ok' })
|
||||
})
|
||||
|
||||
it('lastActivity ignores lifecycle events, chunks, and text-less messages', () => {
|
||||
it('lastActivity ignores lifecycle events, stream rows, and text-less messages', () => {
|
||||
const live = lastActivity([
|
||||
entry('turn/end', { turn: 1, reason: 'success' }),
|
||||
entry('step/start', { turn: 1, step: 1 }),
|
||||
entry('assistant/chunk', { turn: 1, step: 1, chunk: { type: 'text', delta: 'x' } }),
|
||||
entry('assistant/live-chunk', { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'x' } }),
|
||||
entry('assistant/message', { turn: 1, step: 1, message: { content: [{ type: 'tool_use', name: 'bash' }] } }),
|
||||
])
|
||||
expect(live).toEqual({})
|
||||
|
||||
@@ -77,6 +77,8 @@ function mount(initialTrustedHosts: readonly string[] = []): {
|
||||
if (typeof cleanup === 'function') effects.push(cleanup)
|
||||
},
|
||||
inject: () => () => {},
|
||||
// The session/agent event feeds: nothing emits in these tests.
|
||||
on: () => () => {},
|
||||
get: () => undefined,
|
||||
}
|
||||
apply(ctx as never)
|
||||
|
||||
@@ -14,6 +14,8 @@ import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// Bridge Node's `localStorage` accessor to jsdom's store (see the file).
|
||||
setupFiles: ['tests/setup.ts'],
|
||||
server: {
|
||||
deps: {
|
||||
inline: [/@deepseek-ai\/dsh-client-ui-primitives/],
|
||||
|
||||
Reference in New Issue
Block a user