dsh-multi-session 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,85 @@
1
+ # Changelog
2
+
3
+ 版本号遵循语义化版本;`0.x` 期间接口仍可能变。
4
+
5
+ ## 0.3.0
6
+
7
+ 首个公开发布版本。相对内部 0.2.0,**移除了整套完成提醒**,并把并排视图收敛到只显示对话正文。
8
+
9
+ ### 移除
10
+
11
+ - **完成提醒(全部三条通道)**:右下角浮标、标签页标题 `(N)` 角标、系统通知,全部删除。
12
+ 原因:完成标记存在 localStorage 里,提醒组件每次挂载都从 0 计数,于是**每刷新一次页面就把历史那批
13
+ 「已完成」重新当成新完成再提醒一遍**;开发期自动重载把这个问题放大成连续弹窗。
14
+ 现在只在面板里留一个被动的「待查看」蓝色标记,不弹任何东西。
15
+ - **并排视图里的工具调用行**:`tool/call`、`tool/result` 不再进入对话流。
16
+ 并排视图只保留「我 ↔ AI 的对话正文」,工具调用和它们的输出对「看清这段聊了什么」是纯噪音。
17
+
18
+ ### 新增
19
+
20
+ - **钉住的会话持久化**:`⊞` 钉住的会话存 localStorage,关掉面板再回来还是那几个。
21
+ - **并排输入框**:每列底部可直接发消息,`Enter` 排队(不打断当前回合)、
22
+ `Shift+Enter` 插话(立刻打断当前回合),运行中的列另有「停止」按钮。
23
+ 走 Host 侧 `sessionController.prompt` / `cancel`。
24
+ - **注入消息过滤**:DSH 把运行时上下文、时间戳、技能目录变更、压缩摘要等存成 `user/message`,
25
+ 现在这些整条丢弃,并剥掉 `<system-reminder>` 段;只留你真正打进去的话。
26
+ - **停下后继续跟**:运行中每 1.2 秒刷新,停下后降频到 8 秒继续跟而不是直接停,
27
+ 避免「刚跑完的那一刻停在旧结果上」。
28
+
29
+ ### 修复
30
+
31
+ - **点开「待查看」会话后标记不消失**:打开时同时清标记**并推进 `lastSeen` 基线**,
32
+ 否则刷新页面后冷启动补标会把它重新标成待查看。
33
+ - **会话跑完面板不更新**:停了之后不再建 `setInterval` 的旧逻辑改掉;缓存键补上 `live` 维度,
34
+ 否则运行中的短 TTL 条目会被停下瞬间的空闲请求命中,锁住 60 秒的旧 payload。
35
+ - **点击会话只能跳到新建会话**:`uiWorkspace.openSession` 的普通会话要传**裸 id 字符串**,
36
+ 传 `{ sessionId }` 对象会被静默当成无效目标落到新建会话,且不抛错。
37
+ - **诊断文件写到宿主磁盘的固定路径**:原来硬编码了开发机绝对路径,改成系统临时目录下的
38
+ `dsh-multi-session/` 子目录。
39
+
40
+ ### 变更
41
+
42
+ - 并排高度用视口百分比(`min(44vh, 420px)`)而不是 `100vh - 常数`,小窗口下不会算出负数。
43
+ - 每列默认 20 条,「载入更早」按 20 递增,Host 上限 600。
44
+ - Host 缓存键升到 `v4:` 并带上 `live` 维度;`live=1` 的 TTL 降到 800ms(比客户端 1.2 秒轮询短),
45
+ 这是「看着不实时」的真正原因——TTL 比轮询长就永远命中旧 payload。
46
+ - 开发期设施(诊断探针、`/stamp`、`/reload`、页面自动重载)默认关闭,
47
+ 只有 `dev: true` 或 `DSH_MULTI_SESSION_DEV=1` 时才注册。
48
+
49
+ ## 0.2.0
50
+
51
+ 内部版本,未公开发布。
52
+
53
+ ### 新增
54
+
55
+ - 冷启动补标:页面关着期间跑完的会话,下次打开自动标成「待查看」(启发式,最多补 10 个)。
56
+ - 会话操控(发消息 / 停止)。
57
+ - 标签页标题角标与系统通知 —— **已在 0.3.0 全部移除**,原因见上。
58
+
59
+ ### 变更
60
+
61
+ - 「全部已读」改为真正清盘:清空标记并落盘,刷新后不会又冒出来。
62
+ - 完成标记与基线裁剪按**最近更新时间**保留,不再按插入顺序随机丢。
63
+
64
+ ## 0.1.0
65
+
66
+ 首个跑得起来的版本。
67
+
68
+ ### 新增
69
+
70
+ - 多会话总览面板(会话视图里的「多会话」页签):按工作区(cwd)分组,
71
+ 组内按「运行中 → 刚完成 → 其余」排序;卡片显示状态点、标题、轮数、子代理数、
72
+ token、模型、相对时间;可勾选子代理 / 归档会话。
73
+ - 运行中与刚完成的卡片显示该会话**最后一句助手结论**。
74
+ - 并排对话流:`⊞` 钉住最多 3 个会话,显示对话正文,「载入更早」递增拉取。
75
+ - 完成提醒浮标 —— **已在 0.3.0 移除**。
76
+
77
+ ### 技术要点
78
+
79
+ - 对话取数走 Host 侧 `sessionQuery.observeSession`(不做 replay 校验):
80
+ 运行中会话约 5ms、139 轮历史会话约 72–318ms;
81
+ 同样内容用 `readSurface` 要 246–6176ms,`readSession` 要 5.6s。
82
+ - `observeSession` 拿不到事件时自动退回 `readSurface`。
83
+ - 客户端事件窗(`ctx.sessions.retain` + `eventSource`)只对**正被会话视图打开**的会话
84
+ 填充内容,拿不到任意会话的对话,所以必须由 Host 读事件。
85
+ - 面板载荷约 28–38KB / 20 条。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 good-saltfish
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,159 @@
1
- # Temporary Holding Version
1
+ # dsh-multi-session
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 在**一个会话里**同时看住所有会话:总览面板 + 并排对话流 + 直接发消息。
4
+
5
+ DSH 原生只给你当前这一个会话的界面。开了五六个会话并行跑的时候,谁跑完了、谁卡住了、
6
+ 另一个会话到底在干什么,都得一个个点开看。这个插件把这几个动作搬进同一个视图里。
7
+
8
+ ## 它解决什么
9
+
10
+ | 痛点 | 这个插件 |
11
+ | --- | --- |
12
+ | 想知道别的会话在聊什么,得点开再点回来 | 在面板里 `⊞` 钉住最多 3 个会话,并排看它们的**对话正文** |
13
+ | 分不清哪些在跑、哪些在等我 | 面板按「运行中 → 刚完成 → 其余」排序,带状态点、轮数、token、模型、相对时间 |
14
+ | 会话一大堆,找不到 | 按工作区(cwd)分组,子代理会话与归档会话默认折叠、可单独打开 |
15
+ | 不想点开就想知道结果 | 运行中/刚完成的卡片直接显示该会话最后一句助手结论 |
16
+ | 切来切去只为说一句话 | 并排列底部就是输入框,直接给那个会话发消息或打断它 |
17
+
18
+ ## 界面在哪
19
+
20
+ - **「多会话」页签**:任意会话顶部视图切换条里,点进去是总览面板。
21
+ - **钉住并排**:面板里每个会话卡片标题行右侧的 `⊞`,最多钉 3 个;再点 `⊟` 取消。钉住的选择存 localStorage,关掉面板再回来还在。
22
+ - **并排输入框**:每列底部一个输入框,`Enter` 发送(排进收件箱,不打断当前回合),`Shift+Enter` 插话(立刻打断当前回合);运行中的列另有「停止」按钮。
23
+
24
+ ## 架构
25
+
26
+ Host 半边和 Client 半边各管一段,边界很清晰:
27
+
28
+ ```
29
+ ┌─────────────────── Client(浏览器) ────────────────────┐
30
+ │ conversation.view ── MultiSessionPanel(列表 / 并排)│
31
+ │ session.header.actions ── ActiveSessionProbe(记录当前会话)│
32
+ │ shell.overlay(仅 dev) ── DiagOverlay(诊断层) │
33
+ │ │
34
+ │ 数据:插槽 standardProps 的 useWorkspaces / useSessions │
35
+ │ 对话:fetch /plugins/dsh-multi-session/digest │
36
+ │ 操控:fetch /plugins/dsh-multi-session/prompt │
37
+ └──────────────────────────┬───────────────────────────────┘
38
+ │ HTTP(同源,带 cookie,过 connection gate)
39
+ ┌──────────────────────────┴───────────────────────────────┐
40
+ │ Host(index.js) │
41
+ │ /digest ── sessionQuery.observeSession → foldDigest │
42
+ │ /prompt ── sessionController.prompt / .cancel │
43
+ │ /report ── 客户端异常落盘(仅诊断用) │
44
+ │ /probe ── 仅 dev:开发期探针 │
45
+ │ /stamp /reload ── 仅 dev:发布新 bundle + 页面自动重载 │
46
+ └──────────────────────────────────────────────────────────┘
47
+ ```
48
+
49
+ ### 为什么对话流必须走 Host
50
+
51
+ 客户端 `ctx.get('sessions').retain()` 能拿到 `SessionBinding`,`readiness` 也 resolved,
52
+ 但 `eventSource.snapshot.entries` **永远是 0 条**——客户端事件窗只对「正被会话视图打开」
53
+ 的那个会话填充。所以想看任意会话的对话,只能由 Host 读事件再喂给客户端。
54
+
55
+ ### 为什么取数用 observeSession
56
+
57
+ 同样读一个 139 轮 / 33513 事件的历史会话:
58
+
59
+ | 路径 | 运行中会话 | 历史会话 | 说明 |
60
+ | --- | --- | --- | --- |
61
+ | `sessionQuery.observeSession` | **5ms** | **72–318ms** | 直接给内存/已备好的事件快照,不做 replay 校验 |
62
+ | `sessionQuery.readSurface` | 246–1028ms | 2281–6176ms | 只给当前模型面,慢在 replay 校验 |
63
+ | `sessionQuery.readSession` | — | 5658–5740ms | 完整日志 + 校验,最慢 |
64
+ | `listEvents` + 逐条 `readEvent` | — | 每次 readEvent ≈ 1920ms | 完全不可用 |
65
+
66
+ `observeSession` 拿不到事件时自动退回 `readSurface`,响应里的 `source` 字段说明走了哪条路
67
+ (`live` / `prepared` / `surface`)。
68
+
69
+ **注意**:折叠完必须 `lease.dispose()`,不要 `lease.retain()`——后者会让会话被插件长期持有。
70
+
71
+ ### 为什么只显示你和 AI 的对话
72
+
73
+ DSH 把运行时上下文、时间戳、技能目录变更、压缩摘要等都以 `user/message` 事件存进日志。
74
+ Host 侧 `foldDigest` 做三层过滤后才进面板:
75
+
76
+ - `tool/call`、`tool/result` 完全不搬(工具调用和输出对「看清这段聊了什么」是噪音);
77
+ - `<system-reminder>` 整段剥掉;
78
+ - 以 `Current runtime context` / `Time sampled while` / `The available skill catalog changed` /
79
+ `[Compressed conversation section]` / `Browser time zone for this request` 等开头的注入消息整条丢弃。
80
+
81
+ 客户端 `renderDigestItem` 再兜一层:类型不是 `user` / `assistant` 的一律不渲染。
82
+
83
+ ### 数据从哪来(客户端)
84
+
85
+ | 需要的东西 | 来源 |
86
+ | --- | --- |
87
+ | `running` / `cwd` / `parentId` / `updatedAt` / `projectionValues` | `props.useSessions(identity)` 的 `SessionMeta` |
88
+ | 工作区分组 / 归档会话 | `props.useWorkspaces(identity)` |
89
+ | 「当前是哪个会话」 | `conversation.session.header.actions` 插槽收到的 `props.sessionId` |
90
+
91
+ 注意 `running` / `cwd` / `parentId` **只在 `useSessions` 里**,`useWorkspaces` 没有运行态;
92
+ 也没有 `running` 的类型定义暗示它在工作区快照里(这条曾经害我写错两版)。
93
+
94
+ selector 必须是**模块级稳定引用**(`const identity = (state) => state`),否则每帧重订阅。
95
+
96
+ ## 路由
97
+
98
+ | 路由 | 方法 | 说明 |
99
+ | --- | --- | --- |
100
+ | `/plugins/dsh-multi-session/digest` | GET | `?sessionId=&limit=&live=1&fresh=1&mode=` → `{ok, ms, source, turns, endReason, items}` |
101
+ | `/plugins/dsh-multi-session/prompt` | POST | `{sessionId, text, mode:'queue'\|'steer'}` 发消息;`{sessionId, action:'cancel'}` 停止 |
102
+ | `/plugins/dsh-multi-session/report` | GET/POST | 客户端异常上报,落盘到系统临时目录(保留最近 40 条) |
103
+ | `/plugins/dsh-multi-session/probe` | GET/POST | **仅 dev**:开发期探针 |
104
+ | `/plugins/dsh-multi-session/stamp` | GET | **仅 dev**:发布新 bundle 并回 `{rev}`,供页面自动重载轮询比对 |
105
+ | `/plugins/dsh-multi-session/reload` | GET | **仅 dev**:手动触发一次 bundle 重发布(只放行 loopback) |
106
+
107
+ 所有业务路由都过 `connection.requestRejection(req)` 鉴权闸门(缺失 → 503,未授权 → 401/403)。
108
+ `curl` 直接请求会得到 `{"error":"unauthorized"}`,这是正常的——只有浏览器同源 fetch 带 cookie 才过。
109
+
110
+ `digest` 的 Host 侧缓存:键 `v4:${mode}:${live?'live':'idle'}:${sessionId}:${limit}`,
111
+ `live=1` 用 800ms TTL、空闲用 60 秒,`fresh=1` 绕过缓存,最多 200 条。
112
+ `live` 必须进键——否则会话刚跑完那一刻,空闲请求会命中「助手最后一句话还没写进去」的旧 payload。
113
+
114
+ ## 安装
115
+
116
+ 从市场装,或本地装:
117
+
118
+ ```
119
+ plugin_manager action=install_bundle target=<插件目录或包名>
120
+ ```
121
+
122
+ ## 开发
123
+
124
+ 开发期设施(诊断探针、`/stamp`、`/reload`、页面自动重载、客户端 `__internals` 测试出口)
125
+ **默认关闭**。打开方式二选一:
126
+
127
+ - 插件配置 `dev: true`(在 profile 的 patch 里给本插件加这一项);
128
+ - 或环境变量 `DSH_MULTI_SESSION_DEV=1`(改完要重启 DSH 才生效)。
129
+
130
+ 打开后 Host 会在 `index.html` 注入 `window.__DSH_MULTI_SESSION_DEV__ = true`,
131
+ 客户端据此启用诊断层。
132
+
133
+ - **改 client.js**:`link:` 依赖是实时的,文件一变 `clientModules.rebuilt()` 就发布新 bundle,
134
+ index.html 里注入的轮询(每 2 秒比一次 `/stamp` 的 rev)会让页面自动重载(需 dev 打开)。
135
+ - **改 index.js(Host)**:ESM 模块缓存不会自动失效,必须让 Host 重新 import 一次。
136
+ 实测唯一可靠的手段是把 bundle 关一次再开:
137
+
138
+ ```
139
+ plugin_manager action=set_bundle target=dsh-multi-session enabled=false
140
+ plugin_manager action=set_bundle target=dsh-multi-session enabled=true
141
+ ```
142
+
143
+ (`remove_bundle` 对本插件返回 `not-removable`;`set_plugin` 不接受 `include:` 前缀的 entryId。)
144
+ 装完页面**不会**自动重载,得手动刷新。
145
+ - 语法自检:`node --check index.js`、`node --check client.js`。
146
+ - 图标是 SVG(`icon.svg`);市场收录要求位图,所以市场条目用 GitHub 托管的 png,不指向这个 SVG。
147
+
148
+ ## 已知限制
149
+
150
+ - 并排最多 3 列,每列默认最近 20 条(可点「载入更早」按 20 递增,Host 上限 600)。
151
+ - 单条正文上限:用户 1500 字、助手 3000 字——为了轮询载荷(约 28–38KB)。
152
+ - 运行中每 1.2 秒刷新一次;停下后降频到 8 秒继续跟(不会停),避免刚跑完那一刻停在旧结果上。
153
+ - 子代理会话默认折叠(实测 761 个会话里 639 个是子代理,全放出来会淹没面板)。
154
+ - 冷启动补标是启发式的:空闲会话若只是被改名(`updatedAt` 前移),也可能被误标成「待查看」。
155
+ - 纯 Web 插件,页面关掉就看不到任何东西;要关页面也收得到得另接 webhook。
156
+
157
+ ## License
158
+
159
+ MIT