dsh-session-manager 0.4.6 → 0.4.9
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 +216 -119
- package/README.md +107 -112
- package/README.zh.md +28 -35
- package/lib/client.js +143 -62
- package/lib/compat/dsh-adapter.js +200 -0
- package/lib/compat/zstd-frames.js +93 -0
- package/lib/index.js +185 -211
- package/package.json +1 -1
package/README.zh.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-session-manager)
|
|
6
6
|
[](https://github.com/hkkz9522/dsh-session-manager)
|
|
7
|
-
[](https://github.com/hkkz9522/dsh-session-manager/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/hkkz9522/dsh-session-manager/actions/workflows/ci.yml)
|
|
8
8
|
[](https://awesome-dsh-plugin.com)
|
|
9
9
|
|
|
10
10
|
用于在 DeepSeek Harness(DSH)Web 中进行会话管理,包括:删除会话、归档会话、跨工作区移动会话、迁移会话的 Agent 预设。欢迎至 GitHub 提意见。
|
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
- **归档 / 移出归档**会话。
|
|
15
15
|
- **删除会话**:带不可逆操作的二次确认。
|
|
16
|
-
-
|
|
17
|
-
- **迁移 Agent
|
|
18
|
-
- **会话管理窗口**:在侧边栏中浏览未归档和已归档会话,并对每一行执行打开、归档 /
|
|
16
|
+
- **移动至工作区**:保留历史、标题、归档状态和派生会话关系,同时把会话的 `cwd` 更新为目标工作区。
|
|
17
|
+
- **迁移 Agent 预设**:按需修改。典型工况:当原预设被改名或删除,导致会话无法恢复时,可修复该会话。
|
|
18
|
+
- **会话管理窗口**:在侧边栏中浏览未归档和已归档会话,并对每一行执行打开、归档 / 移出归档、移动、迁移预设、删除。
|
|
19
19
|
- 当前会话标题区域提供归档 / 移出归档、移动至工作区和红色的删除会话按钮。
|
|
20
|
+
- 弹窗按钮(移动、迁移预设、删除,以及"会话管理"入口)再次点击会关闭对应弹窗,与标题栏原生按钮行为一致。
|
|
20
21
|
|
|
21
22
|
## UI 入口
|
|
22
23
|
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
|
|
32
33
|
例如:当会话无法恢复,报错表明原 Agent 预设不存在时(例如删掉了 `router-standard`),可以使用迁移功能。
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
插件会读取最后一条 `agent-preset/selected` 事件中的有效预设(若不存在则读取会话 header),然后就地重写该事件(若会话从未记录过选择事件则追加新事件)——这一做法是持久的,旧事件保留在日志中作为历史。对 live session,新事件通过 `Session.append()` 追加到内存,再通过 `SessionStore.flush()` 刷到磁盘;api-gateway 的聊天面板在下次事件折叠时即可看到新预设。
|
|
35
36
|
|
|
36
37
|
> 迁移预设只会修改会话元数据,不会改写历史消息、文件或当前工作区。
|
|
37
38
|
|
|
@@ -59,46 +60,40 @@ dsh plugin --profile web add github:hkkz9522/dsh-session-manager
|
|
|
59
60
|
dev_inject_plugin {"dir": "<本仓库的绝对路径>"}
|
|
60
61
|
```
|
|
61
62
|
|
|
62
|
-
## HTTP API
|
|
63
|
-
|
|
64
|
-
以下本地接口供 Web UI 使用,也可用于集成和排查:
|
|
65
|
-
|
|
66
|
-
```text
|
|
67
|
-
POST /session-manager/api/delete { sessionId }
|
|
68
|
-
POST /session-manager/api/unarchive { sessionId }
|
|
69
|
-
GET /session-manager/api/workspaces
|
|
70
|
-
POST /session-manager/api/move { sessionId, targetWorkspaceId }
|
|
71
|
-
GET /session-manager/api/preset-scan?sessionId=<sessionId>
|
|
72
|
-
POST /session-manager/api/preset-migrate { sessionId, toPreset }
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
示例:将会话迁移到 `standard` 预设。
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
curl -s -X POST http://127.0.0.1:3080/session-manager/api/preset-migrate \
|
|
79
|
-
-H 'content-type: application/json' \
|
|
80
|
-
-d '{"sessionId":"session-...","toPreset":"standard"}'
|
|
81
|
-
```
|
|
82
|
-
|
|
83
63
|
## 安全与行为说明
|
|
84
64
|
|
|
85
65
|
- **删除不可恢复**,因此界面始终要求确认。
|
|
86
|
-
-
|
|
66
|
+
- 移动和迁移预设会先 quiesce 该会话的 live agent(取消运行 + 释放 scope + 移出 SessionStore),即使聊天标签页还开着也能成功;侧边栏会自动刷新。
|
|
87
67
|
- 移动会改写会话保存的 `cwd`,之后的工具调用将在目标工作区执行。
|
|
88
|
-
- subagent
|
|
89
|
-
-
|
|
68
|
+
- subagent 会话和临时空白会话占位不会参与删除、移动或迁移预设。
|
|
69
|
+
- 持久化改写走 DSH 自身的 `open/create/append/flush` 接口,编解码链在内部处理 v2 → v3 格式迁移,写出的工件对当前 DSH 版本始终合法。
|
|
70
|
+
|
|
71
|
+
## 兼容性
|
|
72
|
+
|
|
73
|
+
| 插件版本 | 已验证 DSH 版本 |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| 0.4.9 | v0.1.5-rc.1 |
|
|
76
|
+
| 0.4.7 | v0.1.5-rc.1 |
|
|
77
|
+
| 0.4.4 | 0.1.3-alpha.2 |
|
|
78
|
+
| 0.4.1 | 0.1.3-alpha.2 |
|
|
79
|
+
| 0.4.0 | v0.1.2-rc.1 |
|
|
80
|
+
| 0.1.2 | v0.1.0-rc.7 |
|
|
81
|
+
| 0.1.1 | v0.1.0-rc.7 |
|
|
82
|
+
| 0.1.0 | v0.1.0-rc.7 |
|
|
83
|
+
|
|
84
|
+
本插件是 Cordis 插件,peer dependency 为 `cordis: ">=4.0.0-rc <5"`。
|
|
90
85
|
|
|
91
|
-
##
|
|
86
|
+
## 开发
|
|
92
87
|
|
|
93
|
-
- 本插件是 Cordis 插件,声明的 peer dependency 为 `cordis >=4.0.0-rc <5`。
|
|
94
88
|
- `lib/index.js` 是 host 端 ESM 插件,`lib/client.js` 是 Web 客户端 bundle,无需构建步骤。
|
|
95
89
|
- 提交修改前请运行:
|
|
96
90
|
|
|
97
91
|
```powershell
|
|
98
92
|
node --check lib/client.js
|
|
99
93
|
node --check lib/index.js
|
|
100
|
-
|
|
94
|
+
node --test test/*.test.mjs
|
|
101
95
|
node scripts/smoke-test.mjs
|
|
96
|
+
git diff --check
|
|
102
97
|
npm pack --dry-run
|
|
103
98
|
```
|
|
104
99
|
|
|
@@ -108,10 +103,8 @@ npm pack --dry-run
|
|
|
108
103
|
|
|
109
104
|
感谢每一位安装和使用 dsh-session-manager 的用户,也感谢提交 Issue 与 Pull Request 帮助改进本插件的朋友们。
|
|
110
105
|
|
|
111
|
-
本插件已被 [dsh-market](https://github.com/dsh-market/dsh-market) 和 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
112
|
-
欢迎提出修改意见。
|
|
106
|
+
本插件已被 [dsh-market](https://github.com/dsh-market/dsh-market) 和 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) 收录。欢迎提出修改意见。
|
|
113
107
|
|
|
114
108
|
## 开源许可
|
|
115
109
|
|
|
116
110
|
[MIT](LICENSE)
|
|
117
|
-
|
package/lib/client.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @dsh-session-manager — Client half.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Renders the title-bar action cluster (Archive / Unarchive, Move to
|
|
5
|
+
* workspace, Delete session) and the sidebar-foot Session manager panel
|
|
6
|
+
* (Open / Archive / Unarchive / Move / Migrate preset / Delete per row,
|
|
7
|
+
* plus the panel's own filter pills and toggle-close behaviour). No build
|
|
8
|
+
* step: DSH loads the file as a Cordis client bundle via its inject
|
|
9
|
+
* declaration in package.json.
|
|
8
10
|
*/
|
|
9
11
|
window.__ModuleLoader__.load({
|
|
10
12
|
id: "dsh-session-manager",
|
|
@@ -190,6 +192,7 @@ window.__ModuleLoader__.load({
|
|
|
190
192
|
".sm-moveItemMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.sm-panelModal{width:760px;max-width:92vw}.sm-panelModal .sm-rowActions{flex-wrap:wrap;justify-content:flex-end}.sm-headerMenu{position:absolute;right:0;top:100%;z-index:50;display:flex;flex-direction:column;min-width:160px;padding:4px;background:var(--dsw-alias-surface-l1);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.12)}.sm-headerMenuItem{display:flex;align-items:center;width:100%;text-align:left;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-primary);cursor:pointer;padding:6px 10px;font-size:13px;line-height:18px}.sm-headerMenuItem:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-headerMenuItemDanger{color:var(--dsw-alias-state-error-primary)}.sm-headerBtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;height:auto;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-l2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;padding:4px 12px;font-size:13px;line-height:18px;white-space:nowrap;transition:background .15s,color .15s,border-color .15s}.sm-headerBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1)}.sm-headerBtn:focus-visible{outline:2px solid var(--dsw-alias-accent-primary);outline-offset:1px}.sm-headerBtn svg{width:16px;height:16px}.sm-migratePanel{display:flex;flex-direction:column;gap:14px;min-width:480px}.sm-migrateRow{display:flex;flex-direction:column;gap:8px}.sm-migrateRow>label{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px;font-weight:500}.sm-migrateChips{display:flex;flex-wrap:wrap;gap:6px}.sm-migrateChip{padding:6px 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:transparent;cursor:pointer;font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary);white-space:nowrap}.sm-migrateChip:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-migrateChip:disabled{opacity:.5;cursor:not-allowed}.sm-migrateChipOn{background:var(--dsw-alias-accent-primary);color:#fff;border-color:var(--dsw-alias-accent-primary)}.sm-migrateEmpty{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.sm-migratePreview{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px}.sm-migrateWarn{padding:10px 12px;border:1px solid rgba(217,119,6,.25);border-radius:8px;background:rgba(217,119,6,.08);color:var(--dsw-alias-label-secondary);font-size:13px;line-height:18px}.sm-migrateList{max-height:200px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:8px}.sm-headerBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1)}.sm-headerBtnDanger{background:var(--dsw-alias-state-error-tertiary);color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);font-weight:500}.sm-headerBtnDanger:hover:not(:disabled){background:#d92d20!important;color:#fff!important;border-color:#d92d20!important}.sm-header{display:inline-flex;align-items:center;gap:6px}.sm-headerBtnActive{background:var(--dsw-alias-fill-l3);color:var(--dsw-alias-label-primary)}",
|
|
191
193
|
".sm-panelDialog{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);width:560px;max-width:calc(100vw - 32px);max-height:540px;height:auto;box-sizing:border-box;display:flex;flex-direction:column;gap:0;background:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);overflow:hidden;z-index:9999;opacity:1}.sm-panelDialog .sm-nativeDialogHeader{padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none}.sm-panelDialog .sm-nativeDialogTitle{margin:0;font-size:14px;line-height:20px;font-weight:600}.sm-panelDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:flex-end;gap:6px;margin-top:0}.sm-panelDialog .sm-panel{display:flex;flex-direction:column;gap:6px;min-height:0;flex:1 1 auto;overflow:auto;padding:8px 12px}.sm-panelDialog .sm-list{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-height:0;overflow:auto}.sm-panelDialog .sm-row{display:flex;align-items:center;gap:8px;padding:0 10px;height:36px;min-height:36px;border:1px solid transparent;border-radius:8px;background:transparent}.sm-panelDialog .sm-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-panelDialog .sm-rowCurrent{background:var(--dsw-alias-fill-l2);border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-panelDialog .sm-rowMain{display:flex;align-items:stretch!important;gap:8px;min-width:0;flex:1 1 auto;overflow:hidden;text-align:left!important}.sm-panelDialog .sm-rowTitle{min-width:0;flex:1 1 0%;width:100%;align-self:stretch;display:flex;align-items:center;justify-content:flex-start;gap:6px;font-size:12px;line-height:16px;overflow:hidden;text-align:left!important;direction:ltr!important}.sm-confirmDialog{z-index:10000}.sm-confirmDialog.sm-nativeDialogLayer{position:fixed;left:calc(50vw + 184px);top:calc(50vh - 90px);width:300px;max-width:calc(100vw - 32px);max-height:none;display:block;align-items:initial;justify-content:initial;box-sizing:border-box;background:transparent;border:0;box-shadow:none;padding:0;inset:calc(50vh - 90px) auto auto calc(50vw + 308px)}.sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialogBackdrop{display:none}.sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialog{position:relative;left:auto;top:auto;transform:none;width:auto;box-sizing:border-box;background:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);padding:0;overflow:hidden;animation:sm-confirmPop .14s ease-out}.sm-confirmDialog .sm-nativeDialogHeader{padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee)}.sm-confirmDialog .sm-nativeDialogTitle{margin:0;font-size:14px;line-height:20px;font-weight:600}.sm-confirmDialog .sm-nativeDialogBody{padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}.sm-confirmDialog .sm-nativeDialogDescription{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap}.sm-confirmDialog .sm-warn{padding:6px 10px;font-size:12px;line-height:16px;margin-top:0}.sm-confirmDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:0}.sm-confirmDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary)}.sm-confirmDialog .sm-nativeDialogCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-confirmDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-error-primary);color:#fff;border-color:var(--dsw-alias-state-error-primary)}.sm-confirmDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}@keyframes sm-confirmPop{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.sm-confirmDialog.sm-nativeDialog{padding:0;width:auto;max-width:none}.sm-panelDialog .sm-rowTitle>span:first-child{display:block;min-width:0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;flex:1 1 auto;width:auto;text-align:left!important;direction:ltr!important;unicode-bidi:plaintext}.sm-panelDialog .sm-rowMeta{display:none}.sm-panelDialog .sm-rowActions{display:flex;gap:2px;flex:none;justify-content:flex-end}.sm-panelDialog .sm-rowActions .sm-rowBtn{min-height:22px;height:22px;padding:0 8px;font-size:11px;line-height:14px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}.sm-panelDialog .sm-rowActions .sm-rowBtn:hover{background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnDanger{color:var(--dsw-alias-state-error-primary)}.sm-panelDialog .sm-filter{flex:none;padding:6px 12px 4px;gap:4px;display:flex;flex-wrap:wrap}.sm-panelDialog .sm-filterBtn{min-height:24px;padding:2px 10px;font-size:11px;line-height:16px;border-radius:4px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}.sm-panelDialog .sm-filterBtn.sm-on{background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-badge{font-size:10px;line-height:14px;padding:0 5px;border-radius:6px;flex:none}.sm-panelDialog .sm-error{padding:6px 10px;margin:0;font-size:12px;line-height:16px}.sm-panelDialog .sm-nativeDialogButton{min-height:28px;padding:3px 12px;font-size:12px;line-height:18px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer}.sm-panelDialog .sm-nativeDialogCancel{background:transparent}.sm-panelDialog .sm-nativeDialogGhost{background:transparent;border-color:var(--dsw-alias-border-l2)}.sm-panelDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-accent-primary,#356ae6);color:#fff;border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-panelDialog .sm-empty{padding:18px 0;font-size:12px;color:var(--dsw-alias-label-tertiary);text-align:center}.sm-panelDialog .sm-footError{font-size:11px;color:var(--dsw-alias-state-error-primary);margin-right:auto;line-height:20px}.sm-panelDialog .sm-moveLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.sm-migrateDialog .sm-nativeDialogFooter{flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:14px}.sm-migrateDialog .sm-migrateChip{cursor:pointer;pointer-events:auto;position:relative;z-index:1}.sm-migrateDialog .sm-migrateChip[disabled]{opacity:.5;cursor:not-allowed}.sm-migrateDialog.sm-nativeDialogLayer{position:fixed;left:50vw;top:auto;bottom:48px;transform:translateX(-50%);width:min(560px,calc(100vw - 32px));max-height:none;display:block;align-items:initial;justify-content:initial;box-sizing:border-box;background:transparent;border:0;box-shadow:none;padding:0;inset:auto;z-index:10001}.sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialogBackdrop{display:none}.sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialog{position:relative;left:auto;top:auto;transform:none;width:auto;max-height:min(70vh,520px);box-sizing:border-box;background-color:#ffffff;background-color:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.45);overflow:hidden;padding:0;display:flex;flex-direction:column;gap:0;opacity:1}.sm-migrateDialog{position:fixed;left:50vw;top:auto;bottom:48px;transform:translateX(-50%);width:min(560px,calc(100vw - 32px));max-height:min(70vh,520px);box-sizing:border-box;display:flex;flex-direction:column;gap:0;background-color:#ffffff;background-color:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.45);overflow:hidden;opacity:1;z-index:10001;backdrop-filter:none;-webkit-backdrop-filter:none}.sm-migrateDialog .sm-nativeDialogHeader{padding:12px 18px;border-bottom:1px solid #eee;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;background-color:#fff;background-color:var(--dsw-alias-surface-l1,#fff)}.sm-migrateDialog .sm-nativeDialogTitle{margin:0;font-size:15px;line-height:22px;font-weight:600;color:#111;color:var(--dsw-alias-label-primary,#111)}.sm-migrateDialog .sm-migratePanel{padding:12px 18px;display:flex;flex-direction:column;gap:14px;background-color:#fff;background-color:var(--dsw-alias-surface-l1,#fff)}.sm-migrateDialog .sm-nativeDialogBody{padding:0;background-color:#fff;background-color:var(--dsw-alias-surface-l1,#fff)}.sm-migrateDialog .sm-nativeDialogFooter{padding:12px 18px;border-top:1px solid #eee;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:flex-end;gap:8px;background-color:#fff;background-color:var(--dsw-alias-surface-l1,#fff)}.sm-migrateDialog .sm-migrateRow>label{color:#444;color:var(--dsw-alias-label-secondary,#444)}.sm-migrateDialog .sm-migrateChip{padding:6px 12px;border-radius:999px;border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);background-color:#f5f5f5;background-color:var(--dsw-alias-fill-l2,#f5f5f5);color:#111;color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:18px;cursor:pointer;pointer-events:auto;position:relative;z-index:1;white-space:nowrap}.sm-migrateDialog .sm-migrateChip:hover:not(:disabled){background-color:#e8e8e8;background-color:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-migrateChipOn{background-color:#356ae6;background-color:var(--dsw-alias-accent-primary,#356ae6);color:#fff;border-color:#356ae6;border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-migrateDialog .sm-migrateChip.sm-migrateChipOn:hover:not(:disabled){background-color:#4576f0;background-color:var(--dsw-alias-accent-primary-hover,#4576f0);color:#fff;border-color:#4576f0;border-color:var(--dsw-alias-accent-primary-hover,#4576f0);filter:none}.sm-migrateDialog .sm-migrateChipOn:hover:not(:disabled){filter:brightness(.92)}.sm-migrateDialog .sm-migrateChip[disabled]{opacity:.5;cursor:not-allowed}.sm-migrateDialog .sm-migrateEmpty{color:#888;color:var(--dsw-alias-label-tertiary,#888)}.sm-migrateDialog .sm-migratePreview{padding:10px 12px;border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background-color:#f5f5f5;background-color:var(--dsw-alias-fill-l2,#f5f5f5);color:#111;color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:18px}.sm-migrateDialog .sm-migrateWarn{padding:10px 12px;border:1px solid rgba(217,119,6,.25);border-radius:8px;background-color:rgba(217,119,6,.08);color:#444;color:var(--dsw-alias-label-secondary,#444);font-size:13px;line-height:18px}.sm-migrateDialog .sm-nativeDialogButton{min-height:32px;padding:4px 14px;font-size:13px;line-height:20px;border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background-color:#fff;background-color:var(--dsw-alias-fill-l2,#fff);color:#111;color:var(--dsw-alias-label-primary,#111)}.sm-migrateDialog .sm-nativeDialogButton:hover:not(:disabled){background-color:#e8e8e8;background-color:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-nativeDialogConfirm{background-color:#356ae6;background-color:var(--dsw-alias-accent-primary,#356ae6);color:#fff;border-color:#356ae6;border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-migrateDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}",
|
|
192
194
|
".sm-panelDialog .sm-nativeDialogHeader{display:flex;align-items:center;gap:8px}.sm-panelHeaderClose{min-height:26px;padding:2px 9px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#555);cursor:pointer;font-size:12px;line-height:18px}.sm-panelHeaderClose:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderClose:disabled{opacity:.5;cursor:not-allowed}.sm-migrateCurrent{padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background:var(--dsw-alias-fill-l2,#f5f5f5);font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#111)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer{width:300px;inset:calc(50vh - 90px) auto auto calc(50vw + 308px)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer .sm-migrateDialog{position:relative;left:auto;top:auto;bottom:auto;transform:none;width:100%;max-width:none;max-height:min(70vh,520px);z-index:auto}.sm-migrateConfirmLayer .sm-migratePanel{min-width:0}.sm-migrateConfirmLayer .sm-nativeDialogButton.sm-nativeDialogConfirm:hover:not(:disabled){background-color:#4576f0;background-color:var(--dsw-alias-accent-primary-hover,#4576f0);color:#fff;border-color:#4576f0;border-color:var(--dsw-alias-accent-primary-hover,#4576f0);filter:none}",
|
|
195
|
+
|
|
193
196
|
// Theme colors are scoped to the plugin dialogs. DSH exposes its
|
|
194
197
|
// actual theme through `body[data-ds-dark-theme]` and color-scheme;
|
|
195
198
|
// keeping these values local avoids changing DSH's global variables.
|
|
@@ -352,6 +355,33 @@ window.__ModuleLoader__.load({
|
|
|
352
355
|
const warning = props.warning || "";
|
|
353
356
|
const confirmLabel = props.confirmLabel || "确认";
|
|
354
357
|
const cancelLabel = props.cancelLabel || "取消";
|
|
358
|
+
// Esc is handled at window level (issue #7): the layer-bound
|
|
359
|
+
// onKeyDown was unreachable when the row button that opened
|
|
360
|
+
// the dialog stayed focused. ConfirmDialog's portal to body
|
|
361
|
+
// makes it a separate subtree from the panel section, so
|
|
362
|
+
// keydown never bubbles through it. Enter was previously on
|
|
363
|
+
// the layer too, but that races with the focused Cancel /
|
|
364
|
+
// Confirm button's native Enter handler -- removing the layer
|
|
365
|
+
// Enter leaves Enter handling purely to the focused button.
|
|
366
|
+
const escStateRef = useRef({});
|
|
367
|
+
escStateRef.current = { busy, onCancel };
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
if (!open) return;
|
|
370
|
+
const handler = (e) => {
|
|
371
|
+
if (e.key !== "Escape") return;
|
|
372
|
+
if (e.defaultPrevented) return;
|
|
373
|
+
const s = escStateRef.current;
|
|
374
|
+
if (s.busy) return;
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
e.stopPropagation();
|
|
377
|
+
s.onCancel();
|
|
378
|
+
// See SessionManagerPanel for the rationale.
|
|
379
|
+
const ae = document.activeElement;
|
|
380
|
+
if (ae && typeof ae.blur === "function" && ae !== document.body) ae.blur();
|
|
381
|
+
};
|
|
382
|
+
window.addEventListener("keydown", handler);
|
|
383
|
+
return () => window.removeEventListener("keydown", handler);
|
|
384
|
+
}, [open]);
|
|
355
385
|
const R = (typeof window !== "undefined") ? window.__smReact : null;
|
|
356
386
|
const createPortal = (R && typeof R.createPortal === "function") ? R.createPortal : null;
|
|
357
387
|
const body = (typeof document === "undefined") ? null : document.body;
|
|
@@ -362,23 +392,14 @@ window.__ModuleLoader__.load({
|
|
|
362
392
|
style: props.anchor && typeof props.anchor.top === "number"
|
|
363
393
|
? { position: "fixed", top: props.anchor.top + "px", left: props.anchor.left + "px", zIndex: 10001 }
|
|
364
394
|
: null,
|
|
365
|
-
role: "presentation"
|
|
366
|
-
onKeyDown: (e) => {
|
|
367
|
-
if (e.key === "Escape" && !busy) { e.preventDefault(); onCancel(); }
|
|
368
|
-
else if (e.key === "Enter" && !busy) { e.preventDefault(); onConfirm(); }
|
|
369
|
-
},
|
|
370
|
-
tabIndex: -1
|
|
395
|
+
role: "presentation"
|
|
371
396
|
},
|
|
372
|
-
|
|
373
|
-
className: "sm-nativeDialogBackdrop",
|
|
374
|
-
onMouseDown: (e) => { if (e.target === e.currentTarget && !busy) onCancel(); }
|
|
375
|
-
}),
|
|
397
|
+
|
|
376
398
|
h("section", {
|
|
377
399
|
className: "sm-nativeDialog",
|
|
378
400
|
role: "dialog",
|
|
379
401
|
"aria-modal": "true",
|
|
380
|
-
"aria-labelledby": "sm-confirm-dialog-title"
|
|
381
|
-
ref: (el) => { if (el && typeof el.focus === "function") el.focus(); }
|
|
402
|
+
"aria-labelledby": "sm-confirm-dialog-title"
|
|
382
403
|
},
|
|
383
404
|
h("div", { className: "sm-nativeDialogHeader" },
|
|
384
405
|
h("h2", { id: "sm-confirm-dialog-title", className: "sm-nativeDialogTitle" }, title)
|
|
@@ -426,6 +447,33 @@ window.__ModuleLoader__.load({
|
|
|
426
447
|
const cancelLabel = props.cancelLabel || t("confirm.cancel");
|
|
427
448
|
const error = props.error || "";
|
|
428
449
|
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState("");
|
|
450
|
+
// Esc handled at window level (issue #7) -- Enter stays on the
|
|
451
|
+
// layer because (a) it gates on selectedWorkspaceId which is set
|
|
452
|
+
// only by clicking a listbox item, and (b) racing Enter on the
|
|
453
|
+
// Cancel / Confirm buttons is benign since the layer Enter
|
|
454
|
+
// handler only fires when focus is inside the dialog subtree,
|
|
455
|
+
// which is exactly when the focused button's native Enter has
|
|
456
|
+
// already triggered its own click -- and in MoveDialog that
|
|
457
|
+
// click just toggles selection, never cancels or confirms.
|
|
458
|
+
const escStateRef = useRef({});
|
|
459
|
+
escStateRef.current = { busy, onCancel };
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
if (!open) return;
|
|
462
|
+
const handler = (e) => {
|
|
463
|
+
if (e.key !== "Escape") return;
|
|
464
|
+
if (e.defaultPrevented) return;
|
|
465
|
+
const s = escStateRef.current;
|
|
466
|
+
if (s.busy) return;
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
e.stopPropagation();
|
|
469
|
+
s.onCancel();
|
|
470
|
+
// See SessionManagerPanel for the rationale.
|
|
471
|
+
const ae = document.activeElement;
|
|
472
|
+
if (ae && typeof ae.blur === "function" && ae !== document.body) ae.blur();
|
|
473
|
+
};
|
|
474
|
+
window.addEventListener("keydown", handler);
|
|
475
|
+
return () => window.removeEventListener("keydown", handler);
|
|
476
|
+
}, [open]);
|
|
429
477
|
if (!open) return null;
|
|
430
478
|
|
|
431
479
|
const list = (Array.isArray(props.workspaces) ? props.workspaces : [])
|
|
@@ -456,10 +504,8 @@ window.__ModuleLoader__.load({
|
|
|
456
504
|
// Single cancel button (footer) + ESC handler. No "x" close button so the
|
|
457
505
|
// user only sees one cancel entry point.
|
|
458
506
|
const onKeyDown = (e) => {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
onCancel();
|
|
462
|
-
} else if (e.key === "Enter" && !busy && selectedWorkspaceId && list.length > 0) {
|
|
507
|
+
// Enter only -- Esc moved to a window-level listener (see above).
|
|
508
|
+
if (e.key === "Enter" && !busy && selectedWorkspaceId && list.length > 0) {
|
|
463
509
|
e.preventDefault();
|
|
464
510
|
onConfirm(selectedWorkspaceId);
|
|
465
511
|
}
|
|
@@ -470,19 +516,14 @@ window.__ModuleLoader__.load({
|
|
|
470
516
|
? { position: "fixed", top: props.anchor.top + "px", left: props.anchor.left + "px", zIndex: 10001 }
|
|
471
517
|
: null,
|
|
472
518
|
role: "presentation",
|
|
473
|
-
onKeyDown
|
|
474
|
-
tabIndex: -1
|
|
519
|
+
onKeyDown
|
|
475
520
|
},
|
|
476
|
-
|
|
477
|
-
className: "sm-nativeDialogBackdrop",
|
|
478
|
-
onMouseDown: (e) => { if (e.target === e.currentTarget && !busy) onCancel(); }
|
|
479
|
-
}),
|
|
521
|
+
|
|
480
522
|
h("section", {
|
|
481
523
|
className: "sm-nativeDialog",
|
|
482
524
|
role: "dialog",
|
|
483
525
|
"aria-modal": "true",
|
|
484
|
-
"aria-labelledby": "sm-move-dialog-title"
|
|
485
|
-
ref: (el) => { if (el && typeof el.focus === "function") el.focus(); }
|
|
526
|
+
"aria-labelledby": "sm-move-dialog-title"
|
|
486
527
|
},
|
|
487
528
|
h("div", { className: "sm-nativeDialogHeader" },
|
|
488
529
|
h("h2", { id: "sm-move-dialog-title", className: "sm-nativeDialogTitle" }, title)
|
|
@@ -541,6 +582,30 @@ window.__ModuleLoader__.load({
|
|
|
541
582
|
});
|
|
542
583
|
return () => { alive = false; };
|
|
543
584
|
}, [open, sessionId]);
|
|
585
|
+
// Esc handled at window level (issue #7). MigratePresetDialog is
|
|
586
|
+
// a sibling of the panel layer in the panel's render tree, so
|
|
587
|
+
// the layer-bound onKeyDown (which the dialog relied on for
|
|
588
|
+
// Esc) was dead code -- focus never reached the dialog's own
|
|
589
|
+
// subtree because the row Migrate button stayed focused.
|
|
590
|
+
const escStateRef = useRef({});
|
|
591
|
+
escStateRef.current = { busy, onCancel };
|
|
592
|
+
useEffect(() => {
|
|
593
|
+
if (!open) return;
|
|
594
|
+
const handler = (e) => {
|
|
595
|
+
if (e.key !== "Escape") return;
|
|
596
|
+
if (e.defaultPrevented) return;
|
|
597
|
+
const s = escStateRef.current;
|
|
598
|
+
if (s.busy) return;
|
|
599
|
+
e.preventDefault();
|
|
600
|
+
e.stopPropagation();
|
|
601
|
+
s.onCancel();
|
|
602
|
+
// See SessionManagerPanel for the rationale.
|
|
603
|
+
const ae = document.activeElement;
|
|
604
|
+
if (ae && typeof ae.blur === "function" && ae !== document.body) ae.blur();
|
|
605
|
+
};
|
|
606
|
+
window.addEventListener("keydown", handler);
|
|
607
|
+
return () => window.removeEventListener("keydown", handler);
|
|
608
|
+
}, [open]);
|
|
544
609
|
if (!open) return null;
|
|
545
610
|
|
|
546
611
|
const currentPreset = (scan && scan.ok && scan.row && (scan.row.finalPreset || scan.row.headerPreset)) || fallbackPreset || "—";
|
|
@@ -576,13 +641,9 @@ window.__ModuleLoader__.load({
|
|
|
576
641
|
);
|
|
577
642
|
return h("div", {
|
|
578
643
|
className: "sm-nativeDialogLayer sm-confirmDialog sm-migrateConfirmLayer",
|
|
579
|
-
role: "presentation"
|
|
580
|
-
onKeyDown: (event) => { if (event.key === "Escape" && !busy) { event.preventDefault(); onCancel(); } }
|
|
644
|
+
role: "presentation"
|
|
581
645
|
},
|
|
582
|
-
|
|
583
|
-
className: "sm-nativeDialogBackdrop",
|
|
584
|
-
onMouseDown: (event) => { if (event.target === event.currentTarget && !busy) onCancel(); }
|
|
585
|
-
}),
|
|
646
|
+
|
|
586
647
|
h("section", {
|
|
587
648
|
className: "sm-nativeDialog sm-migrateDialog",
|
|
588
649
|
role: "dialog",
|
|
@@ -617,6 +678,7 @@ window.__ModuleLoader__.load({
|
|
|
617
678
|
getSnapshot() { return this.open; },
|
|
618
679
|
subscribe(cb) { this.listeners.add(cb); return () => this.listeners.delete(cb); },
|
|
619
680
|
set(v) { if (this.open === v) return; this.open = v; for (const fn of [...this.listeners]) fn(); },
|
|
681
|
+
toggle() { this.open = !this.open; for (const fn of [...this.listeners]) fn(); },
|
|
620
682
|
// Pending action queued by the title-bar buttons (e.g. 移动至工作区).
|
|
621
683
|
// The SessionManagerPanel consumes this on mount so it can pre-open
|
|
622
684
|
// the matching inner dialog (move / delete / migrate) next to itself.
|
|
@@ -727,9 +789,9 @@ window.__ModuleLoader__.load({
|
|
|
727
789
|
h("div", { className: "sm-rowActions" },
|
|
728
790
|
h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => onOpen(s.id) }, t("row.open")),
|
|
729
791
|
h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => runRow(s.id, () => isArchived ? onUnarchive(s.id) : onArchive(s.id)) }, t(isArchived ? "row.unarchive" : "row.archive")),
|
|
730
|
-
h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => setMoveFor(s) }, t("row.move")),
|
|
731
|
-
h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => setMigrateFor(s) }, t("row.migrate")),
|
|
732
|
-
h("button", { type: "button", className: "sm-rowBtn sm-rowBtnDanger", disabled: busy, onClick: () => setConfirmFor(s) }, t("row.delete"))
|
|
792
|
+
h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (moveFor && moveFor.id === s.id) setMoveFor(null); else setMoveFor(s); } }, t("row.move")),
|
|
793
|
+
h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (migrateFor && migrateFor.id === s.id) setMigrateFor(null); else setMigrateFor(s); } }, t("row.migrate")),
|
|
794
|
+
h("button", { type: "button", className: "sm-rowBtn sm-rowBtnDanger", disabled: busy, onClick: () => { if (confirmFor && confirmFor.id === s.id) setConfirmFor(null); else setConfirmFor(s); } }, t("row.delete"))
|
|
733
795
|
)
|
|
734
796
|
);
|
|
735
797
|
};
|
|
@@ -743,30 +805,48 @@ window.__ModuleLoader__.load({
|
|
|
743
805
|
setMigrateFor(null);
|
|
744
806
|
onClose();
|
|
745
807
|
};
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
808
|
+
// Esc is handled at window level (issue #7): the original layer-
|
|
809
|
+
// bound onKeyDown was unreachable when focus stayed on the sidebar
|
|
810
|
+
// toggle button that opened the panel -- that button is a sibling
|
|
811
|
+
// of the panel layer, so keydown never bubbles into the panel
|
|
812
|
+
// subtree. The state ref lets the listener see the latest values
|
|
813
|
+
// without re-subscribing on every render. The panel listener
|
|
814
|
+
// returns early when any inner dialog is open so the child
|
|
815
|
+
// dialog's own window listener gets the first shot at Esc.
|
|
816
|
+
const escStateRef = useRef({});
|
|
817
|
+
escStateRef.current = { busyId, confirmFor, moveFor, migrateFor, onPanelClose };
|
|
818
|
+
useEffect(() => {
|
|
819
|
+
if (!open) return;
|
|
820
|
+
const handler = (e) => {
|
|
821
|
+
if (e.key !== "Escape") return;
|
|
822
|
+
if (e.defaultPrevented) return;
|
|
823
|
+
const s = escStateRef.current;
|
|
824
|
+
if (s.busyId !== null) return;
|
|
825
|
+
if (s.confirmFor || s.moveFor || s.migrateFor) return;
|
|
750
826
|
e.preventDefault();
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
827
|
+
e.stopPropagation();
|
|
828
|
+
s.onPanelClose();
|
|
829
|
+
// Esc after a keyboard-driven close leaves the originally-
|
|
830
|
+
// focused trigger button (sidebar toggle / row button) with
|
|
831
|
+
// :focus-visible still active -- blur it so no focus ring
|
|
832
|
+
// lingers after the panel closes. Mouse-driven closes are
|
|
833
|
+
// not affected: :focus-visible only activates for keyboard
|
|
834
|
+
// focus, so the ring never shows in that path.
|
|
835
|
+
const ae = document.activeElement;
|
|
836
|
+
if (ae && typeof ae.blur === "function" && ae !== document.body) ae.blur();
|
|
837
|
+
};
|
|
838
|
+
window.addEventListener("keydown", handler);
|
|
839
|
+
return () => window.removeEventListener("keydown", handler);
|
|
840
|
+
}, [open]);
|
|
754
841
|
const panelModal = h("div", {
|
|
755
842
|
className: "sm-nativeDialogLayer",
|
|
756
|
-
role: "presentation"
|
|
757
|
-
onKeyDown: onPanelKey,
|
|
758
|
-
tabIndex: -1
|
|
843
|
+
role: "presentation"
|
|
759
844
|
},
|
|
760
|
-
h("div", {
|
|
761
|
-
className: "sm-nativeDialogBackdrop",
|
|
762
|
-
onMouseDown: (e) => { if (e.target === e.currentTarget && busyId === null) onPanelClose(); }
|
|
763
|
-
}),
|
|
764
845
|
h("section", {
|
|
765
846
|
className: "sm-nativeDialog sm-panelDialog",
|
|
766
847
|
role: "dialog",
|
|
767
848
|
"aria-modal": "true",
|
|
768
|
-
"aria-labelledby": "sm-panel-dialog-title"
|
|
769
|
-
ref: (el) => { if (el && typeof el.focus === "function") el.focus(); }
|
|
849
|
+
"aria-labelledby": "sm-panel-dialog-title"
|
|
770
850
|
},
|
|
771
851
|
h("div", { className: "sm-nativeDialogHeader" },
|
|
772
852
|
h("h2", { id: "sm-panel-dialog-title", className: "sm-nativeDialogTitle" }, t("panel.title")),
|
|
@@ -1062,10 +1142,11 @@ window.__ModuleLoader__.load({
|
|
|
1062
1142
|
ctx.sessions.open(sessionId);
|
|
1063
1143
|
}
|
|
1064
1144
|
};
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1067
|
-
//
|
|
1068
|
-
//
|
|
1145
|
+
// The 0.4.6+ move path keeps the live agent/session intact (no
|
|
1146
|
+
// session/disposed, no half-resumed Session object), so the chat
|
|
1147
|
+
// panel keeps the in-memory session as active. We still refresh
|
|
1148
|
+
// the workspace + session-list projections so the destination
|
|
1149
|
+
// workspace and the moved row appear without a manual refresh.
|
|
1069
1150
|
await refreshMovedSession();
|
|
1070
1151
|
setTimeout(() => { void refreshMovedSession(); }, 250);
|
|
1071
1152
|
setTimeout(() => { void refreshMovedSession(); }, 900);
|
|
@@ -1079,16 +1160,16 @@ window.__ModuleLoader__.load({
|
|
|
1079
1160
|
// but calling it unconditionally breaks migration on newer builds.
|
|
1080
1161
|
await ctx.sessions.refresh();
|
|
1081
1162
|
if (wasCurrent) ctx.sessions.open(sessionId);
|
|
1082
|
-
//
|
|
1083
|
-
//
|
|
1084
|
-
//
|
|
1163
|
+
// The migrate rewrite happens in-process on the live session, so
|
|
1164
|
+
// we just need a delayed baseline to let the api-gateway's projection
|
|
1165
|
+
// settle before re-activating the in-memory session.
|
|
1085
1166
|
setTimeout(() => {
|
|
1086
1167
|
void ctx.sessions.refresh().then(() => {
|
|
1087
1168
|
if (wasCurrent) ctx.sessions.open(sessionId);
|
|
1088
1169
|
}).catch(() => {});
|
|
1089
1170
|
}, 250);
|
|
1090
1171
|
},
|
|
1091
|
-
onOpenPanel: () => panelStore.
|
|
1172
|
+
onOpenPanel: () => panelStore.toggle()
|
|
1092
1173
|
});
|
|
1093
1174
|
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
1094
1175
|
name: "conversation.session.header.actions",
|