dsh-session-manager 0.7.4 → 0.7.5
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 +9 -0
- package/README.md +5 -4
- package/README.zh.md +4 -3
- package/lib/client.js +225 -27
- package/lib/index.js +226 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.7.5 — 2026-10-22
|
|
6
|
+
|
|
7
|
+
- **fix(security)**: Enforce centralized API authentication on all `/session-manager/api/*` routes using DSH official `ctx.connection.admit(req)`. Enforce strict Owner-only authorization (`peer.principal.kind === "owner"`), reject untrusted, unauthenticated, empty, or non-owner requests before body reading and database queries, and fail closed with HTTP 503 (`auth-unavailable`) if connection service is missing or unready, preventing unauthorized access across public tunnels, reverse proxies, and local networks (#43).
|
|
8
|
+
- **fix(move)**: Synchronize the effective `workingDirectory` projection upon moving sessions across workspaces. For active sessions, call official `workingDirectory.set(agent, path)` when active or fall back to appending `working-directory/change` events to fold the projection in real time, and flush to disk via projection cache; for cold sessions, rewrite headers when un-diverged or append change events for sequential logs. For unmigrated packed rows with conflicting historical working-directory events, safely refuse moves with HTTP 409 (`working-directory-sync-unsafe`) before any file write to prevent destination directory drift upon restart (#42).
|
|
9
|
+
- **fix(ui)**: Align session header action buttons with native DSH session title bar style: unify height to 28px, remove idle border and background (transparent ghost buttons), add standard 6px border-radius, use official hover and active tokens, style danger buttons cleanly, and replace raw Unicode emojis with crisp 14×14 SVG vector icons (#44).
|
|
10
|
+
- **fix(ui)**: Optimize session header and manager panel layout: set header slot order to 0 to sit immediately after static session preset and team entries and before dynamic subagent and task status indicators; switch header action buttons to a fixed-width (flex-shrink: 0, 28px square) icon-only layout with corner priority badge; in session manager panel row actions, convert buttons matching header (archive, favorite, review, edit, move, delete) to clean icon-only buttons with hover tooltips while keeping non-header ("Open", "Migrate") and bulk action buttons with visible text labels (#46).
|
|
11
|
+
- **test**: Add comprehensive test suites for API authentication gating (`issue-43-api-auth.test.mjs`), cross-workspace working directory synchronization (`issue-42-working-directory-after-move.test.mjs`), header action button styling with SVG vector icons (`issue-44-header-styles.test.mjs`), and header/panel row layout optimization (`issue-46-header-layout.test.mjs`).
|
|
12
|
+
- **compat**: Compatible with DSH Desktop v0.2.1-rc.2 (and v0.2.0-rc.2).
|
|
13
|
+
|
|
5
14
|
## 0.7.4 — 2026-10-20
|
|
6
15
|
|
|
7
16
|
- **fix(workspace)**: Repair workspace session membership on DSH 0.2.x. New and forked sessions are now attributed to the workspace owning their `cwd` as soon as the session is announced (runtime repair, no restart needed), and historical sessions whose `cwd` matches a registered workspace are re-registered on startup — all through the official DSH workspace entity API (#41).
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Verified with the official DSH Desktop client; see [5 Compatibility](#5-compatib
|
|
|
20
20
|
|
|
21
21
|
- **Delete** is irreversible and always asks for confirmation. Subagent sessions and transient blank placeholders (no persisted artifact) cannot be deleted.
|
|
22
22
|
- **Archive / Unarchive** moves a session in and out of the active list without touching its disk content.
|
|
23
|
-
- **Move to workspace** keeps history, title, archive state and derived-session relationships intact, rewrites the session's `cwd` to the target workspace, and updates the live writer's header in place so any pending tool calls keep landing on the new path. When the session's `cwd` already equals the target workspace path but its membership was never recorded, only the missing membership is repaired: no `cwd` rewrite, no artifact rewrite or move, and no session restart.
|
|
23
|
+
- **Move to workspace** keeps history, title, archive state and derived-session relationships intact, rewrites the session's `cwd` to the target workspace, synchronizes the runtime `workingDirectory` projection for active agents, and updates the live writer's header in place so any pending tool calls keep landing on the new path. When the session's `cwd` already equals the target workspace path but its membership was never recorded, only the missing membership is repaired: no `cwd` rewrite, no artifact rewrite or move, and no session restart.
|
|
24
24
|
- **Migrate Agent preset** rewrites the latest `agent-preset/selected` event (or the session header if no such event exists) so a session whose preset was renamed or removed can resume. Message history is never altered.
|
|
25
25
|
|
|
26
26
|
### 1.2 Session shortcuts
|
|
@@ -66,7 +66,7 @@ Verified with the official DSH Desktop client; see [5 Compatibility](#5-compatib
|
|
|
66
66
|
|
|
67
67
|
### 2.1 Title bar
|
|
68
68
|
|
|
69
|
-
The right side of the title area exposes actions for the **current session**: **Archive / Unarchive**, **Tags / Notes**, **Move to workspace**, **Delete session
|
|
69
|
+
The right side of the title area exposes actions for the **current session**: **Archive / Unarchive**, **Favorites**, **Review-later**, **Tags / Notes**, **Move to workspace**, **Delete session**, and **More actions** (`⋯`), matching native DSH title-bar button sizing (28px height, transparent ghost style) and vector SVG icon design.
|
|
70
70
|
|
|
71
71
|
### 2.2 Sessions and workspaces management entry and interface
|
|
72
72
|
|
|
@@ -137,7 +137,8 @@ After changing plugin dependencies, `package.json`, bundle configuration, or oth
|
|
|
137
137
|
- Preset migration separates backup, publication and rollback. If rollback fails, recovery files are retained and their paths are included in the error; do not remove them.
|
|
138
138
|
- **Workspace archiving rollback protection**: archiving is refused if active sessions are present, and batch session archiving automatically rolls back on partial failures to preserve state consistency.
|
|
139
139
|
- On startup, sessions whose `cwd` names a registered workspace but whose membership record is missing are re-registered through the official DSH API (idempotent). Incomplete scans skip pruning only: existing membership is never dropped, and live sessions or memberships added during the scan are always preserved.
|
|
140
|
-
- Plugin mutations are serialized per session and request bodies are limited to 64 KiB. This queue supplements, rather than replaces, DSH's own persistence coordination.
|
|
140
|
+
- Plugin mutations are serialized per session and request bodies are limited to 64 KiB. This queue supplements, rather than replaces, DSH's own persistence coordination.
|
|
141
|
+
- **Centralized API authentication**: All `/session-manager/api/*` routes are protected by DSH official `connection.admit()` and `requestRejection()` gates, strictly enforcing Owner-only privileges (`peer.principal.kind === "owner"`) and failing closed (HTTP 503) if connection services are absent, ensuring that exposed network tunnels or local proxies cannot bypass access control.
|
|
141
142
|
|
|
142
143
|
## 5 Compatibility
|
|
143
144
|
|
|
@@ -147,7 +148,7 @@ The table below lists verified version combinations between DeepSeek Harness (DS
|
|
|
147
148
|
|
|
148
149
|
| DeepSeek Harness (DSH) | dsh-session-manager | Verified Environment |
|
|
149
150
|
| :--- | :--- | :--- |
|
|
150
|
-
| **v0.2.1-rc.2** | `0.7.4`, `0.7.3`, `0.7.2` | Desktop |
|
|
151
|
+
| **v0.2.1-rc.2** | `0.7.5`, `0.7.4`, `0.7.3`, `0.7.2` | Desktop |
|
|
151
152
|
| **v0.2.0-rc.2** | `0.7.1`, `0.7.0`, `0.6.4`, `0.6.3`, `0.6.2`, `0.5.4` | Desktop |
|
|
152
153
|
| **v0.1.7-rc.2** | `0.5.3` | Desktop / Web UI |
|
|
153
154
|
| **v0.1.7-rc.1** | `0.5.2` | Desktop / Web UI |
|
package/README.zh.md
CHANGED
|
@@ -20,7 +20,7 @@ DSH 会话与工作区管理——工作区:归档与显示。会话:跨工
|
|
|
20
20
|
|
|
21
21
|
- **删除**:不可逆操作,UI 始终要求二次确认。subagent 会话和尚未落盘的空白会话占位不能删除。
|
|
22
22
|
- **归档 / 移出归档**:把会话移出或移回主列表,不删除磁盘内容。
|
|
23
|
-
- **移动至工作区**:跨工作区移动时保留历史、标题、归档状态和派生会话关系,同时把 `cwd`
|
|
23
|
+
- **移动至工作区**:跨工作区移动时保留历史、标题、归档状态和派生会话关系,同时把 `cwd` 重写为目标工作区,实时同步活动 Agent 的运行时 `workingDirectory` 投影,并就地更新 live writer 的 header,使挂起的工具调用继续落到新路径。若会话的 `cwd` 本来就等于目标工作区、只是归属未登记,则只补全归属记录,不改写 `cwd`、不重写或移动工件、不重启会话。
|
|
24
24
|
- **迁移 Agent 预设**:原预设被改名或删除导致会话无法恢复时,可修复该会话。迁移会就地重写最后一条 `agent-preset/selected` 事件(若从未记录则修改会话 header),不改动历史消息。
|
|
25
25
|
|
|
26
26
|
### 1.2 会话快捷管理
|
|
@@ -64,7 +64,7 @@ DSH 会话与工作区管理——工作区:归档与显示。会话:跨工
|
|
|
64
64
|
|
|
65
65
|
### 2.1 标题栏入口
|
|
66
66
|
|
|
67
|
-
标题栏右侧对**当前会话**提供:**归档 /
|
|
67
|
+
标题栏右侧对**当前会话**提供:**归档 / 移出归档**、**收藏**、**待回看**、**标签 / 备注**、**移动至工作区**、**删除会话**以及**更多操作**(`⋯`),与 DSH 原生顶栏按钮规范完全对齐(28px 高度、无边框幽灵透明样式、SVG 矢量描边图标)。
|
|
68
68
|
|
|
69
69
|
### 2.2 会话与工作区管理入口与界面
|
|
70
70
|
|
|
@@ -136,6 +136,7 @@ dsh plugin --profile web add <本仓库路径>
|
|
|
136
136
|
- **工作区归档事务保护**:若工作区内存在运行中会话则拒绝归档;批量归档会话若中途遇到异常会自动回滚已变更状态,避免部分成功导致状态不一致。
|
|
137
137
|
- 启动时若发现 `cwd` 对应已注册工作区但归属记录遗漏的会话,会通过 DSH 官方接口补全登记(幂等);扫描不完整时只跳过清理,不会误删现有归属,也不会清除仍在内存中或扫描期间新加入的会话。
|
|
138
138
|
- 同一会话的插件写操作按顺序执行;请求体限制为 64 KiB。该队列不替代 DSH 自身的持久化写入协调。
|
|
139
|
+
- **集中 API 鉴权与安全门禁**:所有 `/session-manager/api/*` 接口统一接入 DSH 官方 `connection.admit()` / `requestRejection()` 校验,严格限制仅 Owner 权限主体可调用,服务未就绪时默认拒绝(HTTP 503 Fail-Closed),有效阻断公网隧道或反向代理暴露下的未授权访问。
|
|
139
140
|
|
|
140
141
|
## 5 兼容性
|
|
141
142
|
|
|
@@ -145,7 +146,7 @@ dsh plugin --profile web add <本仓库路径>
|
|
|
145
146
|
|
|
146
147
|
| DeepSeek Harness (DSH) | dsh-session-manager | 验证环境 |
|
|
147
148
|
| :--- | :--- | :--- |
|
|
148
|
-
| **v0.2.1-rc.2** | `0.7.4`, `0.7.3`, `0.7.2` | Desktop |
|
|
149
|
+
| **v0.2.1-rc.2** | `0.7.5`, `0.7.4`, `0.7.3`, `0.7.2` | Desktop |
|
|
149
150
|
| **v0.2.0-rc.2** | `0.7.1`, `0.7.0`, `0.6.4`, `0.6.3`, `0.6.2`, `0.5.4` | Desktop |
|
|
150
151
|
| **v0.1.7-rc.2** | `0.5.3` | Desktop / Web UI |
|
|
151
152
|
| **v0.1.7-rc.1** | `0.5.2` | Desktop / Web UI |
|
package/lib/client.js
CHANGED
|
@@ -289,6 +289,7 @@ function isTopmostDialog(z) {
|
|
|
289
289
|
"confirm.move.current": "当前工作区:{name}",
|
|
290
290
|
"confirm.move.same": "请选择与当前工作区不同的目标工作区",
|
|
291
291
|
"confirm.move.confirm": "确认移动",
|
|
292
|
+
"move.restartRequired": "会话已成功移动至目标工作区,但当前工作目录需要在重启 DSH 后生效。",
|
|
292
293
|
"confirm.cancel": "取消",
|
|
293
294
|
"busy.processing": "处理中…",
|
|
294
295
|
"error.operation": "操作失败:{message}",
|
|
@@ -640,6 +641,7 @@ function isTopmostDialog(z) {
|
|
|
640
641
|
"confirm.move.current": "Current workspace: {name}",
|
|
641
642
|
"confirm.move.same": "Choose a target workspace different from the current workspace",
|
|
642
643
|
"confirm.move.confirm": "Move",
|
|
644
|
+
"move.restartRequired": "Session moved to target workspace successfully, but working directory will take effect after restarting DSH.",
|
|
643
645
|
"confirm.cancel": "Cancel",
|
|
644
646
|
"busy.processing": "Working…",
|
|
645
647
|
"error.operation": "Operation failed: {message}",
|
|
@@ -693,11 +695,18 @@ function isTopmostDialog(z) {
|
|
|
693
695
|
};
|
|
694
696
|
|
|
695
697
|
const css = [
|
|
696
|
-
".sm-header{display:flex;align-items:center;gap:6px}",
|
|
697
|
-
".sm-
|
|
698
|
-
".sm-headerBtn:
|
|
698
|
+
".sm-header{display:inline-flex;align-items:center;gap:6px;flex-wrap:nowrap}",
|
|
699
|
+
".sm-header{flex-shrink:0}",
|
|
700
|
+
".sm-headerBtn{display:inline-flex;align-items:center;justify-content:center;gap:4px;min-height:28px;height:28px;box-sizing:border-box;color:var(--dsw-alias-label-secondary);background:transparent;border:0;border-radius:6px;cursor:pointer;padding:0 6px;font-size:12px;line-height:18px;white-space:nowrap;transition:background .12s,color .12s;flex-shrink:0}",
|
|
701
|
+
".sm-header .sm-headerBtn{width:28px;min-width:28px;padding:0;justify-content:center;position:relative}",
|
|
702
|
+
".sm-header .sm-headerBtnLabel{display:none}",
|
|
703
|
+
".sm-header .sm-priorityBadge{position:absolute;top:0;right:0;font-size:8px;line-height:9px;padding:0 2px;border-radius:3px;pointer-events:none}",
|
|
704
|
+
".sm-headerBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
|
|
705
|
+
".sm-headerBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}",
|
|
699
706
|
".sm-headerBtn:disabled{opacity:.5;cursor:not-allowed}",
|
|
700
|
-
".sm-headerBtn
|
|
707
|
+
".sm-headerBtn.sm-headerBtnActive{background:var(--dsw-alias-button-ghost-active-fill,rgba(0,0,0,.06));color:var(--dsw-alias-label-primary)}",
|
|
708
|
+
".sm-headerBtn svg{width:14px;height:14px;display:block;margin:auto;flex-shrink:0}",
|
|
709
|
+
".sm-headerBtn:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
701
710
|
".sm-busy{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:28px;margin-left:4px}",
|
|
702
711
|
".sm-footer{display:flex}",
|
|
703
712
|
".sm-footerBtn{min-height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;align-items:center;gap:6px;padding:3px 8px;font-size:12px;line-height:18px;display:inline-flex}",
|
|
@@ -745,8 +754,8 @@ function isTopmostDialog(z) {
|
|
|
745
754
|
".sm-moveItem:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
746
755
|
".sm-moveItemSelected{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}",
|
|
747
756
|
".sm-moveItemName{color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px}",
|
|
748
|
-
".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%;margin-top:4px;z-index:100005;display:flex;flex-direction:column;min-width:140px;padding:4px;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.15)}.sm-headerMenuItem{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-primary,#111);cursor:pointer;padding:6px 10px;font-size:13px;line-height:18px;white-space:nowrap}.sm-headerMenuItem:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-headerMenuItem:disabled{opacity:.5;cursor:not-allowed}.sm-headerMenuItem .sm-headerBtnIcon{width:
|
|
749
|
-
".sm-panelDialog{position:fixed;inset:0;margin:auto;transform:none;width:680px;max-width:calc(100vw - 32px);height:560px;max-height:calc(100vh - 32px);box-sizing:border-box;display:flex;flex-direction:column;gap:0;background:var(--sm-dialog-fill);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:1020}.sm-panelDialog .sm-nativeDialogHeader{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;position:relative;min-height:46px;box-sizing:border-box}.sm-panelDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;min-width:0;max-width:calc(100% - 140px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;pointer-events:auto;z-index:0}.sm-panelDialog .sm-nativeDialogHeaderActions{display:flex;align-items:center;gap:6px;flex:none;position:relative;z-index:1;margin-left:auto}.sm-nativeDialogFooter,.sm-bulkDialog .sm-nativeDialogFooter,.sm-confirmDialog .sm-nativeDialogFooter,.sm-migrateDialog .sm-nativeDialogFooter{display:flex;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;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;scrollbar-gutter:stable}.sm-panelDialog .sm-row{display:flex;align-items:center;gap:8px;padding:6px 10px;min-height:36px;border:1px solid transparent;border-radius:8px;background:transparent;flex-shrink:0;transition:background .12s ease,border-color .12s ease}.sm-panelDialog .sm-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-panelDialog .sm-rowCurrent{background:var(--dsw-alias-bg-module-platform);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-panelDialog .sm-rowMain{display:flex;align-items:stretch!important;gap:8px;min-width:0;flex:1 1 auto;overflow:visible;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;unicode-bidi:plaintext}.sm-confirmDialog{z-index:10000}.sm-confirmDialog.sm-nativeDialogLayer{position:fixed;left:calc(50vw + 184px);top:calc(50vh - 90px);width:360px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);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;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);display:flex;flex-direction:column;box-sizing:border-box;background:var(--sm-dialog-fill);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{position:relative;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);display:flex;align-items:center;justify-content:center;min-height:44px;box-sizing:border-box}.sm-confirmDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;text-align:center;width:auto;max-width:calc(100% - 32px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm-confirmDialog .sm-nativeDialogBody{padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0;max-height:calc(100vh - 120px);overflow-y:auto}.sm-confirmDialog .sm-nativeDialogDescription{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap}.sm-confirmDialog .sm-warn{display:flex;align-items:flex-start;gap:8px;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 8%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 25%,transparent);border-radius:8px;padding:8px 10px;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:space-between;gap:6px;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);text-align:center}.sm-confirmDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-migrateDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-updateDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-bulkDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-panelDialog .sm-nativeDialogCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-confirmDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.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;padding:0 7px;font-size:11px;line-height:18px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap}.sm-rowBtn.sm-markIcon{padding:0 6px;font-size:13px;line-height:18px}.sm-panelDialog .sm-rowActions .sm-rowBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnDanger{color:var(--dsw-alias-state-error-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnDanger:hover{background:var(--dsw-alias-interactive-bg-hover-danger);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{height:28px;min-height:28px;padding:3px 10px;font-size:12px;line-height:18px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;box-sizing:border-box;white-space:nowrap;flex-shrink:0;transition:all .15s ease}.sm-panelDialog .sm-filterBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-panelDialog .sm-filterBtn:disabled{opacity:.4;cursor:not-allowed}.sm-panelDialog .sm-filterBtn.sm-on{background:var(--dsw-alias-bg-module-platform);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 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:center}.sm-panelDialog .sm-nativeDialogCancel{background:transparent}.sm-panelDialog .sm-nativeDialogConfirm,.sm-annotationDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-panelDialog .sm-nativeDialogConfirm:hover:not(:disabled),.sm-annotationDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}.sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.sm-confirmDialog .sm-nativeDialogDanger,.sm-migrateDialog .sm-nativeDialogDanger,.sm-bulkDialog .sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-confirmDialog .sm-nativeDialogDanger:hover:not(:disabled),.sm-migrateDialog .sm-nativeDialogDanger:hover:not(:disabled),.sm-bulkDialog .sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.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:space-between;gap:6px;margin-top:0}.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:360px;max-width:calc(100vw - 32px);max-height:min(70vh,520px);box-sizing:border-box;background-color:var(--sm-dialog-fill);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,.45);overflow:hidden;padding:0;display:flex;flex-direction:column;gap:0}.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:var(--sm-dialog-fill);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,.45);overflow:hidden;z-index:10001}.sm-migrateDialog .sm-nativeDialogHeader{padding:12px 18px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;position:relative;display:flex;align-items:center;justify-content:center;min-height:44px;box-sizing:border-box}.sm-migrateDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;color:var(--dsw-alias-label-primary,#111);text-align:center;width:auto;max-width:calc(100% - 36px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm-migrateDialog .sm-migratePanel{padding:12px 18px;display:flex;flex-direction:column;gap:14px}.sm-migrateDialog .sm-nativeDialogBody{padding:0}.sm-migrateDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0;gap:8px}.sm-migrateDialog .sm-migrateRow>label{color:var(--dsw-alias-label-secondary,#444)}.sm-migrateDialog .sm-migrateChip{padding:6px 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#ddd);background-color:var(--dsw-alias-markdown-tag,#f1f3f5);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:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-migrateChipOn{background-color:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-migrateDialog .sm-migrateChip.sm-migrateChipOn:hover:not(:disabled){background-color:var(--dsw-alias-button-info-hover,#4576f0);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-button-info-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:var(--dsw-alias-label-tertiary,#888)}.sm-migrateDialog .sm-migratePreview{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background-color:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:18px}.sm-migrateDialog .sm-migrateWarn{padding:10px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 30%,transparent);border-radius:8px;background-color:var(--dsw-alias-state-warn-tertiary,#fef5e7);color:var(--dsw-alias-label-secondary,#444);font-size:13px;line-height:18px}.sm-migrateDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background-color:transparent;color:var(--dsw-alias-label-primary,#111);text-align:center}.sm-migrateDialog .sm-nativeDialogButton:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-nativeDialogConfirm{background-color:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-migrateDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}",
|
|
757
|
+
".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%;margin-top:4px;z-index:100005;display:flex;flex-direction:column;min-width:140px;padding:4px;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.15)}.sm-headerMenuItem{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-primary,#111);cursor:pointer;padding:6px 10px;font-size:13px;line-height:18px;white-space:nowrap}.sm-headerMenuItem:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-headerMenuItem:disabled{opacity:.5;cursor:not-allowed}.sm-headerMenuItem .sm-headerBtnIcon{width:14px;min-width:14px;text-align:center;flex-shrink:0}.sm-headerMenuItemDanger{color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-headerMenuItemDanger:hover{background:var(--dsw-alias-interactive-bg-hover-danger)}.sm-headerMenuUltraOnly{display:none!important}.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-state-business-primary);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-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-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px}.sm-migrateWarn{padding:10px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 30%,transparent);border-radius:8px;background:var(--dsw-alias-state-warn-tertiary,#fef5e7);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-headerBtnDanger{background:transparent;color:var(--dsw-alias-state-error-primary,#d92d20);border:0;font-weight:500}.sm-headerBtnDanger:hover:not(:disabled){background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-headerSecondaryActions{display:inline-flex;align-items:center;gap:6px;flex-wrap:nowrap}.sm-headerCompactMenu{display:none;position:relative}.sm-headerCompactMenu.sm-headerCompactMenuOpen{position:relative;z-index:100004}.sm-headerCompactMenuBtn{width:28px;min-width:28px;height:28px;padding:0;justify-content:center;font-size:14px;line-height:1}.sm-headerBtnIcon{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;font-size:14px;line-height:1;vertical-align:middle;flex-shrink:0}.sm-headerBtn.sm-markIcon{width:28px;min-width:28px;height:28px;padding:0;justify-content:center}.sm-headerBtn.sm-markIcon .sm-headerBtnIcon{width:14px;height:14px;font-size:14px;line-height:1}.sm-headerCompactMenuBtn .sm-headerBtnIcon{width:14px;height:14px;font-size:14px;line-height:1}.sm-annotationToggleOpen{position:relative;z-index:100004}@container (max-width:1000px){.sm-headerBtn{width:28px;min-width:28px;height:28px;padding:0;justify-content:center}.sm-headerBtnLabel,.sm-priorityText,.sm-priorityBadge{display:none}.sm-headerBtn.sm-headerBtn-withBadge{width:auto;min-width:28px;padding:0 6px}}@container (max-width:520px){.sm-headerSecondaryActions,.sm-headerSecondaryAction{display:none!important}.sm-headerCompactMenu{display:inline-flex}}@container (max-width:420px){.sm-header>:not(.sm-headerCompactMenu){display:none!important}.sm-header .sm-headerBtn:not(.sm-headerCompactMenuBtn){display:none!important}.sm-headerCompactMenu{display:inline-flex!important}.sm-headerMenu{right:auto!important;left:0!important}.sm-headerMenuUltraOnly{display:flex!important}}",
|
|
758
|
+
".sm-panelDialog{position:fixed;inset:0;margin:auto;transform:none;width:680px;max-width:calc(100vw - 32px);height:560px;max-height:calc(100vh - 32px);box-sizing:border-box;display:flex;flex-direction:column;gap:0;background:var(--sm-dialog-fill);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:1020}.sm-panelDialog .sm-nativeDialogHeader{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;position:relative;min-height:46px;box-sizing:border-box}.sm-panelDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;min-width:0;max-width:calc(100% - 140px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;pointer-events:auto;z-index:0}.sm-panelDialog .sm-nativeDialogHeaderActions{display:flex;align-items:center;gap:6px;flex:none;position:relative;z-index:1;margin-left:auto}.sm-nativeDialogFooter,.sm-bulkDialog .sm-nativeDialogFooter,.sm-confirmDialog .sm-nativeDialogFooter,.sm-migrateDialog .sm-nativeDialogFooter{display:flex;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;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;scrollbar-gutter:stable}.sm-panelDialog .sm-row{display:flex;align-items:center;gap:8px;padding:6px 10px;min-height:36px;border:1px solid transparent;border-radius:8px;background:transparent;flex-shrink:0;transition:background .12s ease,border-color .12s ease}.sm-panelDialog .sm-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-panelDialog .sm-rowCurrent{background:var(--dsw-alias-bg-module-platform);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-panelDialog .sm-rowMain{display:flex;align-items:stretch!important;gap:8px;min-width:0;flex:1 1 auto;overflow:visible;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;unicode-bidi:plaintext}.sm-confirmDialog{z-index:10000}.sm-confirmDialog.sm-nativeDialogLayer{position:fixed;left:calc(50vw + 184px);top:calc(50vh - 90px);width:360px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);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;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);display:flex;flex-direction:column;box-sizing:border-box;background:var(--sm-dialog-fill);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{position:relative;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);display:flex;align-items:center;justify-content:center;min-height:44px;box-sizing:border-box}.sm-confirmDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;text-align:center;width:auto;max-width:calc(100% - 32px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm-confirmDialog .sm-nativeDialogBody{padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0;max-height:calc(100vh - 120px);overflow-y:auto}.sm-confirmDialog .sm-nativeDialogDescription{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap}.sm-confirmDialog .sm-warn{display:flex;align-items:flex-start;gap:8px;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 8%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 25%,transparent);border-radius:8px;padding:8px 10px;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:space-between;gap:6px;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);text-align:center}.sm-confirmDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-migrateDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-updateDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-bulkDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-panelDialog .sm-nativeDialogCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-confirmDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.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:4px;flex:none;align-items:center;justify-content:flex-end}.sm-panelDialog .sm-rowActions .sm-rowBtn{display:inline-flex;align-items:center;justify-content:center;min-height:22px;height:22px;box-sizing:border-box;padding:0 7px;font-size:11px;line-height:18px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;transition:background .12s,color .12s}.sm-panelDialog .sm-rowActions .sm-rowBtn.sm-rowBtnIcon,.sm-panelDialog .sm-rowActions .sm-rowBtn.sm-markIcon{width:22px;min-width:22px;padding:0;justify-content:center}.sm-panelDialog .sm-rowActions .sm-rowBtn.sm-markIcon .sm-headerBtnIcon,.sm-panelDialog .sm-rowActions .sm-rowBtn.sm-rowBtnIcon .sm-headerBtnIcon{width:14px;height:14px;font-size:14px;line-height:1}.sm-panelDialog .sm-rowActions .sm-rowBtn.sm-rowBtnIcon svg,.sm-panelDialog .sm-rowActions .sm-rowBtn.sm-markIcon svg{width:14px;height:14px;display:block;margin:auto;flex-shrink:0}.sm-rowActions .sm-rowBtnLabel{display:none}.sm-rowBtn.sm-markIcon{padding:0 6px;font-size:13px;line-height:18px}.sm-panelDialog .sm-rowActions .sm-rowBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnActive{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnDanger{color:var(--dsw-alias-state-error-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnDanger:hover{background:var(--dsw-alias-interactive-bg-hover-danger);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{height:28px;min-height:28px;padding:3px 10px;font-size:12px;line-height:18px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;box-sizing:border-box;white-space:nowrap;flex-shrink:0;transition:all .15s ease}.sm-panelDialog .sm-filterBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-panelDialog .sm-filterBtn:disabled{opacity:.4;cursor:not-allowed}.sm-panelDialog .sm-filterBtn.sm-on{background:var(--dsw-alias-bg-module-platform);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 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:center}.sm-panelDialog .sm-nativeDialogCancel{background:transparent}.sm-panelDialog .sm-nativeDialogConfirm,.sm-annotationDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-panelDialog .sm-nativeDialogConfirm:hover:not(:disabled),.sm-annotationDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}.sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.sm-confirmDialog .sm-nativeDialogDanger,.sm-migrateDialog .sm-nativeDialogDanger,.sm-bulkDialog .sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-confirmDialog .sm-nativeDialogDanger:hover:not(:disabled),.sm-migrateDialog .sm-nativeDialogDanger:hover:not(:disabled),.sm-bulkDialog .sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.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:space-between;gap:6px;margin-top:0}.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:360px;max-width:calc(100vw - 32px);max-height:min(70vh,520px);box-sizing:border-box;background-color:var(--sm-dialog-fill);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,.45);overflow:hidden;padding:0;display:flex;flex-direction:column;gap:0}.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:var(--sm-dialog-fill);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,.45);overflow:hidden;z-index:10001}.sm-migrateDialog .sm-nativeDialogHeader{padding:12px 18px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;position:relative;display:flex;align-items:center;justify-content:center;min-height:44px;box-sizing:border-box}.sm-migrateDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;color:var(--dsw-alias-label-primary,#111);text-align:center;width:auto;max-width:calc(100% - 36px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm-migrateDialog .sm-migratePanel{padding:12px 18px;display:flex;flex-direction:column;gap:14px}.sm-migrateDialog .sm-nativeDialogBody{padding:0}.sm-migrateDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0;gap:8px}.sm-migrateDialog .sm-migrateRow>label{color:var(--dsw-alias-label-secondary,#444)}.sm-migrateDialog .sm-migrateChip{padding:6px 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#ddd);background-color:var(--dsw-alias-markdown-tag,#f1f3f5);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:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-migrateChipOn{background-color:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-migrateDialog .sm-migrateChip.sm-migrateChipOn:hover:not(:disabled){background-color:var(--dsw-alias-button-info-hover,#4576f0);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-button-info-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:var(--dsw-alias-label-tertiary,#888)}.sm-migrateDialog .sm-migratePreview{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background-color:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:18px}.sm-migrateDialog .sm-migrateWarn{padding:10px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 30%,transparent);border-radius:8px;background-color:var(--dsw-alias-state-warn-tertiary,#fef5e7);color:var(--dsw-alias-label-secondary,#444);font-size:13px;line-height:18px}.sm-migrateDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background-color:transparent;color:var(--dsw-alias-label-primary,#111);text-align:center}.sm-migrateDialog .sm-nativeDialogButton:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-nativeDialogConfirm{background-color:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-migrateDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}",
|
|
750
759
|
// The .sm-tooltip rule below deliberately keeps !important: it targets
|
|
751
760
|
// DSH's Tooltip primitive (used through renderTip), which ships its own
|
|
752
761
|
// inline styles, so a plugin rule cannot win on specificity alone
|
|
@@ -2449,6 +2458,150 @@ function isTopmostDialog(z) {
|
|
|
2449
2458
|
useEffect(() => { void annotationStore.load(refreshOnMount); }, [refreshOnMount]);
|
|
2450
2459
|
return state;
|
|
2451
2460
|
};
|
|
2461
|
+
|
|
2462
|
+
function HeaderStarIcon(props = {}) {
|
|
2463
|
+
const size = props.size || 14;
|
|
2464
|
+
const filled = !!props.filled;
|
|
2465
|
+
return h("svg", {
|
|
2466
|
+
viewBox: "0 0 24 24",
|
|
2467
|
+
width: size,
|
|
2468
|
+
height: size,
|
|
2469
|
+
fill: filled ? "var(--dsw-alias-state-warn-primary, #eac54f)" : "none",
|
|
2470
|
+
stroke: filled ? "var(--dsw-alias-state-warn-primary, #eac54f)" : "currentColor",
|
|
2471
|
+
strokeWidth: "1.5",
|
|
2472
|
+
strokeLinecap: "round",
|
|
2473
|
+
strokeLinejoin: "round",
|
|
2474
|
+
"aria-hidden": "true",
|
|
2475
|
+
style: { display: "block" }
|
|
2476
|
+
},
|
|
2477
|
+
h("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" })
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
function HeaderClockIcon(props = {}) {
|
|
2482
|
+
const size = props.size || 14;
|
|
2483
|
+
return h("svg", {
|
|
2484
|
+
viewBox: "0 0 24 24",
|
|
2485
|
+
width: size,
|
|
2486
|
+
height: size,
|
|
2487
|
+
fill: "none",
|
|
2488
|
+
stroke: "currentColor",
|
|
2489
|
+
strokeWidth: "1.5",
|
|
2490
|
+
strokeLinecap: "round",
|
|
2491
|
+
strokeLinejoin: "round",
|
|
2492
|
+
"aria-hidden": "true",
|
|
2493
|
+
style: { display: "block" }
|
|
2494
|
+
},
|
|
2495
|
+
h("circle", { cx: 12, cy: 12, r: 9 }),
|
|
2496
|
+
h("polyline", { points: "12 7 12 12 15 14" })
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
function HeaderTagIcon(props = {}) {
|
|
2501
|
+
const size = props.size || 14;
|
|
2502
|
+
return h("svg", {
|
|
2503
|
+
viewBox: "0 0 24 24",
|
|
2504
|
+
width: size,
|
|
2505
|
+
height: size,
|
|
2506
|
+
fill: "none",
|
|
2507
|
+
stroke: "currentColor",
|
|
2508
|
+
strokeWidth: "1.5",
|
|
2509
|
+
strokeLinecap: "round",
|
|
2510
|
+
strokeLinejoin: "round",
|
|
2511
|
+
"aria-hidden": "true",
|
|
2512
|
+
style: { display: "block" }
|
|
2513
|
+
},
|
|
2514
|
+
h("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }),
|
|
2515
|
+
h("line", { x1: 7, y1: 7, x2: 7.01, y2: 7 })
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
function HeaderArchiveIcon(props = {}) {
|
|
2520
|
+
const size = props.size || 14;
|
|
2521
|
+
const isUnarchive = !!props.unarchive;
|
|
2522
|
+
return h("svg", {
|
|
2523
|
+
viewBox: "0 0 24 24",
|
|
2524
|
+
width: size,
|
|
2525
|
+
height: size,
|
|
2526
|
+
fill: "none",
|
|
2527
|
+
stroke: "currentColor",
|
|
2528
|
+
strokeWidth: "1.5",
|
|
2529
|
+
strokeLinecap: "round",
|
|
2530
|
+
strokeLinejoin: "round",
|
|
2531
|
+
"aria-hidden": "true",
|
|
2532
|
+
style: { display: "block" }
|
|
2533
|
+
},
|
|
2534
|
+
h("polyline", { points: "21 8 21 21 3 21 3 8" }),
|
|
2535
|
+
h("rect", { x: 1, y: 3, width: 22, height: 5, rx: 1 }),
|
|
2536
|
+
isUnarchive
|
|
2537
|
+
? h(React.Fragment, null,
|
|
2538
|
+
h("polyline", { points: "10 12 12 10 14 12" }),
|
|
2539
|
+
h("line", { x1: 12, y1: 10, x2: 12, y2: 16 })
|
|
2540
|
+
)
|
|
2541
|
+
: h(React.Fragment, null,
|
|
2542
|
+
h("polyline", { points: "10 14 12 16 14 14" }),
|
|
2543
|
+
h("line", { x1: 12, y1: 16, x2: 12, y2: 10 })
|
|
2544
|
+
)
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function HeaderMoveIcon(props = {}) {
|
|
2549
|
+
const size = props.size || 14;
|
|
2550
|
+
return h("svg", {
|
|
2551
|
+
viewBox: "0 0 24 24",
|
|
2552
|
+
width: size,
|
|
2553
|
+
height: size,
|
|
2554
|
+
fill: "none",
|
|
2555
|
+
stroke: "currentColor",
|
|
2556
|
+
strokeWidth: "1.5",
|
|
2557
|
+
strokeLinecap: "round",
|
|
2558
|
+
strokeLinejoin: "round",
|
|
2559
|
+
"aria-hidden": "true",
|
|
2560
|
+
style: { display: "block" }
|
|
2561
|
+
},
|
|
2562
|
+
h("polyline", { points: "17 4 21 8 17 12" }),
|
|
2563
|
+
h("line", { x1: 3, y1: 8, x2: 21, y2: 8 }),
|
|
2564
|
+
h("polyline", { points: "7 20 3 16 7 12" }),
|
|
2565
|
+
h("line", { x1: 21, y1: 16, x2: 3, y2: 16 })
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
function HeaderTrashIcon(props = {}) {
|
|
2570
|
+
const size = props.size || 14;
|
|
2571
|
+
return h("svg", {
|
|
2572
|
+
viewBox: "0 0 24 24",
|
|
2573
|
+
width: size,
|
|
2574
|
+
height: size,
|
|
2575
|
+
fill: "none",
|
|
2576
|
+
stroke: "currentColor",
|
|
2577
|
+
strokeWidth: "1.5",
|
|
2578
|
+
strokeLinecap: "round",
|
|
2579
|
+
strokeLinejoin: "round",
|
|
2580
|
+
"aria-hidden": "true",
|
|
2581
|
+
style: { display: "block" }
|
|
2582
|
+
},
|
|
2583
|
+
h("polyline", { points: "3 6 5 6 21 6" }),
|
|
2584
|
+
h("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
function HeaderEllipsisIcon(props = {}) {
|
|
2589
|
+
const size = props.size || 14;
|
|
2590
|
+
return h("svg", {
|
|
2591
|
+
viewBox: "0 0 24 24",
|
|
2592
|
+
width: size,
|
|
2593
|
+
height: size,
|
|
2594
|
+
fill: "currentColor",
|
|
2595
|
+
stroke: "none",
|
|
2596
|
+
"aria-hidden": "true",
|
|
2597
|
+
style: { display: "block" }
|
|
2598
|
+
},
|
|
2599
|
+
h("circle", { cx: 12, cy: 12, r: 2 }),
|
|
2600
|
+
h("circle", { cx: 19, cy: 12, r: 2 }),
|
|
2601
|
+
h("circle", { cx: 5, cy: 12, r: 2 })
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2452
2605
|
const annotationButtons = (id, title, t, state, className, onToggle, onEdit, disabled = false, editorOpen = false) => {
|
|
2453
2606
|
const value = annotationOf(state, id);
|
|
2454
2607
|
const busy = disabled || !state.loaded || !!state.error || state.pending.has(id);
|
|
@@ -2457,23 +2610,24 @@ function isTopmostDialog(z) {
|
|
|
2457
2610
|
const favoriteLabel = favoriteDesc + ": " + title;
|
|
2458
2611
|
const reviewLabel = reviewDesc + ": " + title;
|
|
2459
2612
|
const isHeader = className === "sm-headerBtn";
|
|
2613
|
+
const priorityInfo = value.priority ? ` (P${value.priority})` : "";
|
|
2460
2614
|
return h(React.Fragment, null,
|
|
2461
2615
|
renderTip(favoriteDesc, h("button", { type: "button", className: className + " sm-markIcon" + (value.favorite ? " sm-markOn" : ""), disabled: busy, "aria-label": favoriteLabel, "aria-pressed": value.favorite, onClick: () => onToggle({ favorite: !value.favorite }) },
|
|
2462
|
-
h("span", { className: "sm-headerBtnIcon" }, value.favorite
|
|
2616
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderStarIcon({ filled: value.favorite }))
|
|
2463
2617
|
)),
|
|
2464
2618
|
renderTip(reviewDesc, h("button", { type: "button", className: className + " sm-markIcon" + (value.reviewLater ? " sm-markOn" : ""), disabled: busy, "aria-label": reviewLabel, "aria-pressed": value.reviewLater, onClick: () => onToggle({ reviewLater: !value.reviewLater }) },
|
|
2465
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
2619
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderClockIcon())
|
|
2466
2620
|
)),
|
|
2467
|
-
renderTip(t("marks.edit.aria"), h("button", {
|
|
2621
|
+
renderTip(t("marks.edit.aria") + priorityInfo, h("button", {
|
|
2468
2622
|
type: "button",
|
|
2469
|
-
className: className + (isHeader ? " sm-headerSecondaryAction" : "") + (editorOpen ? " sm-annotationToggleOpen sm-headerBtnActive" : ""),
|
|
2623
|
+
className: className + (isHeader ? " sm-headerSecondaryAction" : " sm-rowBtnIcon") + (editorOpen ? " sm-annotationToggleOpen sm-headerBtnActive sm-rowBtnActive" : ""),
|
|
2470
2624
|
disabled: busy,
|
|
2471
2625
|
"aria-label": t("marks.edit.aria") + ": " + title,
|
|
2472
2626
|
...(isHeader ? { "aria-expanded": editorOpen } : {}),
|
|
2473
2627
|
onClick: e => onEdit(e)
|
|
2474
2628
|
},
|
|
2475
|
-
|
|
2476
|
-
|
|
2629
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderTagIcon()),
|
|
2630
|
+
h("span", { className: isHeader ? "sm-headerBtnLabel" : "sm-rowBtnLabel" }, isHeader ? t("marks.header.edit") : t("marks.edit")),
|
|
2477
2631
|
isHeader ? h("span", { className: "sm-priorityBadge sm-priority-" + (value.priority ?? 3) }, "P" + (value.priority ?? 3)) : null
|
|
2478
2632
|
))
|
|
2479
2633
|
);
|
|
@@ -3773,7 +3927,7 @@ function isTopmostDialog(z) {
|
|
|
3773
3927
|
));
|
|
3774
3928
|
}
|
|
3775
3929
|
|
|
3776
|
-
const CURRENT_VERSION = "0.7.
|
|
3930
|
+
const CURRENT_VERSION = "0.7.5";
|
|
3777
3931
|
|
|
3778
3932
|
function compareSemver(v1, v2) {
|
|
3779
3933
|
if (typeof v1 !== "string" || typeof v2 !== "string") return 0;
|
|
@@ -5006,11 +5160,49 @@ function isTopmostDialog(z) {
|
|
|
5006
5160
|
h("div", { className: "sm-rowMeta" }, shortPath(s.cwd) + (s.cwd ? " · " : "") + formatRelative(s.updatedAt, now)),
|
|
5007
5161
|
h("div", { className: "sm-rowActions" },
|
|
5008
5162
|
h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (isArchived) { if (isWsArchived) { if (typeof window !== "undefined" && typeof window.alert === "function") { window.alert(t("error.workspaceArchived")); } return; } /* DSH refuses to surface an archived session as the current view: clicking "Open" on an archived row flashes an empty chat panel and bounces back to "no session selected". Unarchive first, then open. (issue #17.4) */ runRow(s.id, async () => { await onUnarchive(s.id); await onOpen(s.id); }); } else { onOpen(s.id); } } }, t("row.open")),
|
|
5009
|
-
|
|
5163
|
+
renderTip(t(isArchived ? "row.unarchive" : "row.archive"), h("button", {
|
|
5164
|
+
type: "button",
|
|
5165
|
+
className: "sm-rowBtn sm-rowBtnIcon" + (isArchived ? " sm-rowBtnActive" : ""),
|
|
5166
|
+
disabled: busy,
|
|
5167
|
+
"aria-label": t(isArchived ? "row.unarchive" : "row.archive") + ": " + (s.displayTitle || s.id),
|
|
5168
|
+
onClick: () => runRow(s.id, () => {
|
|
5169
|
+
if (isArchived) {
|
|
5170
|
+
if (isWsArchived) {
|
|
5171
|
+
if (typeof window !== "undefined" && typeof window.alert === "function") {
|
|
5172
|
+
window.alert(t("error.workspaceArchived"));
|
|
5173
|
+
}
|
|
5174
|
+
return;
|
|
5175
|
+
}
|
|
5176
|
+
return onUnarchive(s.id);
|
|
5177
|
+
}
|
|
5178
|
+
return onArchive(s.id);
|
|
5179
|
+
})
|
|
5180
|
+
},
|
|
5181
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderArchiveIcon({ unarchive: isArchived })),
|
|
5182
|
+
h("span", { className: "sm-rowBtnLabel" }, t(isArchived ? "row.unarchive" : "row.archive"))
|
|
5183
|
+
)),
|
|
5010
5184
|
annotationButtons(s.id, s.displayTitle || s.id, t, annotations, "sm-rowBtn", patch => runRow(s.id, () => annotationStore.save(s.id, patch)), () => { setAnnotationAnchor(null); setAnnotationFor(current => current?.id === s.id ? null : s); }, busy),
|
|
5011
|
-
|
|
5185
|
+
renderTip(t("row.move"), h("button", {
|
|
5186
|
+
type: "button",
|
|
5187
|
+
className: "sm-rowBtn sm-rowBtnIcon" + (moveFor && moveFor.id === s.id ? " sm-rowBtnActive" : ""),
|
|
5188
|
+
disabled: busy,
|
|
5189
|
+
"aria-label": t("row.move") + ": " + (s.displayTitle || s.id),
|
|
5190
|
+
onClick: () => { if (moveFor && moveFor.id === s.id) setMoveFor(null); else setMoveFor(s); }
|
|
5191
|
+
},
|
|
5192
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderMoveIcon()),
|
|
5193
|
+
h("span", { className: "sm-rowBtnLabel" }, t("row.move"))
|
|
5194
|
+
)),
|
|
5012
5195
|
h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (migrateFor && migrateFor.id === s.id) setMigrateFor(null); else setMigrateFor(s); } }, t("row.migrate")),
|
|
5013
|
-
|
|
5196
|
+
renderTip(t("row.delete"), h("button", {
|
|
5197
|
+
type: "button",
|
|
5198
|
+
className: "sm-rowBtn sm-rowBtnDanger sm-rowBtnIcon" + (confirmFor && confirmFor.id === s.id ? " sm-rowBtnActive" : ""),
|
|
5199
|
+
disabled: busy,
|
|
5200
|
+
"aria-label": t("row.delete") + ": " + (s.displayTitle || s.id),
|
|
5201
|
+
onClick: () => { if (confirmFor && confirmFor.id === s.id) setConfirmFor(null); else setConfirmFor(s); }
|
|
5202
|
+
},
|
|
5203
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderTrashIcon()),
|
|
5204
|
+
h("span", { className: "sm-rowBtnLabel" }, t("row.delete"))
|
|
5205
|
+
))
|
|
5014
5206
|
)
|
|
5015
5207
|
)
|
|
5016
5208
|
)
|
|
@@ -6096,7 +6288,7 @@ function isTopmostDialog(z) {
|
|
|
6096
6288
|
isArchived ? onUnarchive(sessionId) : onArchive(sessionId)
|
|
6097
6289
|
)
|
|
6098
6290
|
},
|
|
6099
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6291
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderArchiveIcon({ unarchive: isArchived })),
|
|
6100
6292
|
h("span", { className: "sm-headerBtnLabel" }, isArchived ? (t ? t("row.unarchive") : "移出归档") : (t ? t("row.archive") : "归档"))
|
|
6101
6293
|
)) : null,
|
|
6102
6294
|
annotationButtons(sessionId, props.displayTitle || sessionId, t, annotations, "sm-headerBtn", patch => runWithAlert("annotations", () => annotationStore.save(sessionId, patch)), (e) => {
|
|
@@ -6132,7 +6324,7 @@ function isTopmostDialog(z) {
|
|
|
6132
6324
|
}
|
|
6133
6325
|
}
|
|
6134
6326
|
},
|
|
6135
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6327
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderMoveIcon()),
|
|
6136
6328
|
h("span", { className: "sm-headerBtnLabel" }, t ? t("row.move") : "移动至工作区")
|
|
6137
6329
|
)),
|
|
6138
6330
|
renderTip(t ? t("row.delete") : "删除会话", h("button", {
|
|
@@ -6154,7 +6346,7 @@ function isTopmostDialog(z) {
|
|
|
6154
6346
|
}
|
|
6155
6347
|
}
|
|
6156
6348
|
},
|
|
6157
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6349
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderTrashIcon()),
|
|
6158
6350
|
h("span", { className: "sm-headerBtnLabel" }, t ? t("row.delete") : "删除会话")
|
|
6159
6351
|
))
|
|
6160
6352
|
),
|
|
@@ -6171,7 +6363,7 @@ function isTopmostDialog(z) {
|
|
|
6171
6363
|
setMoreOpen(v => !v);
|
|
6172
6364
|
}
|
|
6173
6365
|
},
|
|
6174
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6366
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderEllipsisIcon())
|
|
6175
6367
|
)),
|
|
6176
6368
|
moreOpen ? h("div", { className: "sm-headerMenu", role: "menu", style: menuStyle },
|
|
6177
6369
|
(onArchive || onUnarchive) ? h("button", {
|
|
@@ -6183,7 +6375,7 @@ function isTopmostDialog(z) {
|
|
|
6183
6375
|
runWithAlert("archive", () => isArchived ? onUnarchive(sessionId) : onArchive(sessionId));
|
|
6184
6376
|
}
|
|
6185
6377
|
},
|
|
6186
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6378
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderArchiveIcon({ unarchive: isArchived })),
|
|
6187
6379
|
h("span", null, isArchived ? (t ? t("row.unarchive") : "移出归档") : (t ? t("row.archive") : "归档"))
|
|
6188
6380
|
) : null,
|
|
6189
6381
|
h("button", {
|
|
@@ -6196,7 +6388,7 @@ function isTopmostDialog(z) {
|
|
|
6196
6388
|
runWithAlert("annotations", () => annotationStore.save(sessionId, { favorite: !currentAnnotation.favorite }));
|
|
6197
6389
|
}
|
|
6198
6390
|
},
|
|
6199
|
-
h("span", { className: "sm-headerBtnIcon" }, currentAnnotation.favorite
|
|
6391
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderStarIcon({ filled: currentAnnotation.favorite })),
|
|
6200
6392
|
h("span", null, t ? t(currentAnnotation.favorite ? "marks.favorite.remove" : "marks.favorite.add") : (currentAnnotation.favorite ? "取消收藏" : "收藏会话"))
|
|
6201
6393
|
),
|
|
6202
6394
|
h("button", {
|
|
@@ -6209,7 +6401,7 @@ function isTopmostDialog(z) {
|
|
|
6209
6401
|
runWithAlert("annotations", () => annotationStore.save(sessionId, { reviewLater: !currentAnnotation.reviewLater }));
|
|
6210
6402
|
}
|
|
6211
6403
|
},
|
|
6212
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6404
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderClockIcon()),
|
|
6213
6405
|
h("span", null, t ? t(currentAnnotation.reviewLater ? "marks.review.remove" : "marks.review.add") : (currentAnnotation.reviewLater ? "取消稍后回看" : "稍后回看"))
|
|
6214
6406
|
),
|
|
6215
6407
|
h("button", {
|
|
@@ -6226,7 +6418,7 @@ function isTopmostDialog(z) {
|
|
|
6226
6418
|
setAnnotationFor(curr => (curr?.id === sessionId ? null : { id: sessionId, displayTitle }));
|
|
6227
6419
|
}
|
|
6228
6420
|
},
|
|
6229
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6421
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderTagIcon()),
|
|
6230
6422
|
h("span", null, t ? t("marks.header.edit") : "标签/备注")
|
|
6231
6423
|
),
|
|
6232
6424
|
h("button", {
|
|
@@ -6245,7 +6437,7 @@ function isTopmostDialog(z) {
|
|
|
6245
6437
|
}
|
|
6246
6438
|
}
|
|
6247
6439
|
},
|
|
6248
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6440
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderMoveIcon()),
|
|
6249
6441
|
h("span", null, t ? t("row.move") : "移动至工作区")
|
|
6250
6442
|
),
|
|
6251
6443
|
h("button", {
|
|
@@ -6264,7 +6456,7 @@ function isTopmostDialog(z) {
|
|
|
6264
6456
|
}
|
|
6265
6457
|
}
|
|
6266
6458
|
},
|
|
6267
|
-
h("span", { className: "sm-headerBtnIcon" },
|
|
6459
|
+
h("span", { className: "sm-headerBtnIcon" }, HeaderTrashIcon()),
|
|
6268
6460
|
h("span", null, t ? t("row.delete") : "删除会话")
|
|
6269
6461
|
)
|
|
6270
6462
|
) : null
|
|
@@ -6976,6 +7168,12 @@ function isTopmostDialog(z) {
|
|
|
6976
7168
|
const wasCurrent = ctx.sessions.list.getSnapshot().current === sessionId;
|
|
6977
7169
|
const response = await callApi("/move", { sessionId, targetWorkspaceId });
|
|
6978
7170
|
if (!response.ok) throw new Error(response.error || "move failed");
|
|
7171
|
+
if (response.result?.restartRequired) {
|
|
7172
|
+
try {
|
|
7173
|
+
const msg = (typeof t === "function" ? t("move.restartRequired") : null) || "会话已成功移动至目标工作区,但当前工作目录需要在重启 DSH 后生效。";
|
|
7174
|
+
window.alert(msg);
|
|
7175
|
+
} catch (_) {}
|
|
7176
|
+
}
|
|
6979
7177
|
const refreshMovedSession = async () => {
|
|
6980
7178
|
await Promise.allSettled([
|
|
6981
7179
|
typeof ctx.workspaces?.refresh === "function" ? ctx.workspaces.refresh() : Promise.resolve(),
|
|
@@ -7050,7 +7248,7 @@ function isTopmostDialog(z) {
|
|
|
7050
7248
|
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
7051
7249
|
name: "conversation.session.header.actions",
|
|
7052
7250
|
id: "session-manager-header",
|
|
7053
|
-
order:
|
|
7251
|
+
order: 0,
|
|
7054
7252
|
locale: NS,
|
|
7055
7253
|
inject: () => actions()
|
|
7056
7254
|
}, HeaderAction));
|
package/lib/index.js
CHANGED
|
@@ -28,7 +28,7 @@ import { basename, dirname, join } from "node:path";
|
|
|
28
28
|
import { randomBytes } from "node:crypto";
|
|
29
29
|
import { createAnnotationStore } from "./annotation-store.js";
|
|
30
30
|
import { createWorkspaceArchiveStore, assertWorkspaceId } from "./workspace-archive-store.js";
|
|
31
|
-
import { createDshAdapter, normalizeSessionHeader, compareSemver, detectPluginSource, isHmrActive, resolveProfileDir } from "./compat/dsh-adapter.js";
|
|
31
|
+
import { createDshAdapter, getService, normalizeSessionHeader, compareSemver, detectPluginSource, isHmrActive, resolveProfileDir } from "./compat/dsh-adapter.js";
|
|
32
32
|
import { ARTIFACT_NAMES, assertSessionId, assertSessionDirectory, candidateSessionDirs, encodeFsSessionSegment, decodeFsSessionSegment, encodeProjectSegment, encodeSessionSegment, parseSessionGenerationFromFilename, readSessionHeader, readSessionFile, writeTempFile, replaceSessionFile } from "./session-files.js";
|
|
33
33
|
|
|
34
34
|
export const name = "dsh-session-manager";
|
|
@@ -39,7 +39,8 @@ export const inject = [
|
|
|
39
39
|
"sessions",
|
|
40
40
|
"agents",
|
|
41
41
|
"sessionPersistence",
|
|
42
|
-
"agentPresets"
|
|
42
|
+
"agentPresets",
|
|
43
|
+
"connection"
|
|
43
44
|
];
|
|
44
45
|
|
|
45
46
|
/** Like JSON.stringify but swallows circular-reference/BigInt errors so the
|
|
@@ -385,7 +386,7 @@ function verifyInstalledPackage(profileDir, expectedVersion) {
|
|
|
385
386
|
|
|
386
387
|
export function apply(ctx) {
|
|
387
388
|
try {
|
|
388
|
-
const buildId = "dsh-session-manager v0.7.
|
|
389
|
+
const buildId = "dsh-session-manager v0.7.5 (workspace-archive)";
|
|
389
390
|
if (typeof ctx?.logger?.info === "function") ctx.logger.info(buildId);
|
|
390
391
|
else if (typeof console !== "undefined" && console?.info) console.info(buildId);
|
|
391
392
|
} catch (_) { /* ignore logging failures */ }
|
|
@@ -1044,6 +1045,115 @@ ${rest}`, "utf8");
|
|
|
1044
1045
|
return await run();
|
|
1045
1046
|
};
|
|
1046
1047
|
|
|
1048
|
+
/**
|
|
1049
|
+
* Issue #42: record the working-directory change a move implies.
|
|
1050
|
+
*
|
|
1051
|
+
* A cross-workspace move rewrites the artifact header's `cwd`, but DSH
|
|
1052
|
+
* folds the `workingDirectory` projection from the session log: the unit
|
|
1053
|
+
* initializes from `header.cwd` only while it has no checkpoint, and both
|
|
1054
|
+
* the projection cache and every `working-directory/change` event
|
|
1055
|
+
* supersede that initial value. A live session therefore keeps reporting
|
|
1056
|
+
* its pre-move directory -- the projection state is never re-initialized --
|
|
1057
|
+
* and the cache checkpoints that stale state under the NEW header identity,
|
|
1058
|
+
* so a rebuild cannot recover the target either. Route the change through
|
|
1059
|
+
* the host's own path (the `workingDirectory` service is what a user `cd`
|
|
1060
|
+
* uses) so the log gains the event the projection folds from; without that
|
|
1061
|
+
* service, append the equivalent event directly, which is the same shape
|
|
1062
|
+
* the host's `commit()` writes.
|
|
1063
|
+
*
|
|
1064
|
+
* @param agent - the live Agent owning the moved session, when one exists.
|
|
1065
|
+
* @param session - the live Session owning the moved log.
|
|
1066
|
+
* @param targetPath - the destination workspace directory.
|
|
1067
|
+
* @returns `{status}`: "synced" (host service committed the change),
|
|
1068
|
+
* "recorded" (event appended directly) or "restart-required" (the host
|
|
1069
|
+
* rejected the change; the move itself has already succeeded).
|
|
1070
|
+
*/
|
|
1071
|
+
const rehomeLiveWorkingDirectory = async ({ agent, session, targetPath }) => {
|
|
1072
|
+
const service = getService(ctx, "workingDirectory");
|
|
1073
|
+
if (service !== undefined && typeof service.set === "function" && agent !== undefined) {
|
|
1074
|
+
try {
|
|
1075
|
+
const cwd = await service.set(agent, targetPath);
|
|
1076
|
+
return { status: "synced", cwd: typeof cwd === "string" && cwd !== "" ? cwd : targetPath };
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
const isFiberError = error?.code === "INACTIVE_EFFECT" ||
|
|
1079
|
+
String(error?.message || error).toLowerCase().includes("inactive") ||
|
|
1080
|
+
String(error?.message || error).toLowerCase().includes("fiber");
|
|
1081
|
+
if (!isFiberError) {
|
|
1082
|
+
ctx.logger.warn(`session-manager: moved "${safeJson(session?.id)}" but the host rejected the working-directory change: ${safeErrorMessage(error)}`);
|
|
1083
|
+
return { status: "restart-required", reason: safeErrorMessage(error) };
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (session === undefined || session === null || typeof session.append !== "function") {
|
|
1088
|
+
return { status: "restart-required", reason: "no live session to record the working-directory change" };
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
session.append("working-directory/change", { cwd: targetPath });
|
|
1092
|
+
return { status: "recorded", cwd: targetPath };
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
ctx.logger.warn(`session-manager: moved "${safeJson(session.id)}" but could not record the working-directory change: ${safeErrorMessage(error)}`);
|
|
1095
|
+
return { status: "restart-required", reason: safeErrorMessage(error) };
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Issue #42 (cold sessions): append the `working-directory/change` event a
|
|
1101
|
+
* live move records, but only when the stored log already carries one --
|
|
1102
|
+
* otherwise the rewritten `header.cwd` alone initializes the projection on
|
|
1103
|
+
* the next resume and touching the log would be gratuitous. A stale change
|
|
1104
|
+
* event would otherwise fold the pre-move directory back in. Rows whose
|
|
1105
|
+
* `seq` does not match their position (packed physical rows) cannot be
|
|
1106
|
+
* extended without the persistence codec, so the body is left untouched and
|
|
1107
|
+
* the caller reports that a restart is required.
|
|
1108
|
+
*
|
|
1109
|
+
* @param rest - the artifact body (everything after the header line).
|
|
1110
|
+
* @param targetPath - the destination workspace directory.
|
|
1111
|
+
* @returns `{rest, status}` with the body to publish.
|
|
1112
|
+
*/
|
|
1113
|
+
const rehomeColdWorkingDirectory = (rest, targetPath) => {
|
|
1114
|
+
const nl = String.fromCharCode(10);
|
|
1115
|
+
const terminated = rest.endsWith(nl);
|
|
1116
|
+
const body = terminated ? rest.slice(0, -1) : rest;
|
|
1117
|
+
if (body === "") return { rest, status: "header-only" };
|
|
1118
|
+
const lines = body.split(nl);
|
|
1119
|
+
let lastChange;
|
|
1120
|
+
let hasPackedOrUnparseableRows = false;
|
|
1121
|
+
let hasNonSequentialSeq = false;
|
|
1122
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1123
|
+
let event;
|
|
1124
|
+
try {
|
|
1125
|
+
event = JSON.parse(lines[index]);
|
|
1126
|
+
} catch {
|
|
1127
|
+
hasPackedOrUnparseableRows = true;
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (event === null || typeof event !== "object") {
|
|
1131
|
+
hasPackedOrUnparseableRows = true;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
if (event.seq !== index) {
|
|
1135
|
+
hasNonSequentialSeq = true;
|
|
1136
|
+
}
|
|
1137
|
+
if (event.type === "working-directory/change") {
|
|
1138
|
+
lastChange = event.data?.cwd;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (lastChange === undefined) return { rest, status: "header-only" };
|
|
1142
|
+
if (lastChange === targetPath) return { rest, status: "already-target" };
|
|
1143
|
+
if (hasPackedOrUnparseableRows || hasNonSequentialSeq) {
|
|
1144
|
+
const error = new Error("无法安全同步会话工作目录(会话日志包含压缩行且已有工作目录变更历史),为防止工作目录不一致,已取消移动。原始会话保持原状");
|
|
1145
|
+
error.code = "working-directory-sync-unsafe";
|
|
1146
|
+
throw error;
|
|
1147
|
+
}
|
|
1148
|
+
const appended = JSON.stringify({
|
|
1149
|
+
type: "working-directory/change",
|
|
1150
|
+
seq: lines.length,
|
|
1151
|
+
time: Date.now(),
|
|
1152
|
+
data: { cwd: targetPath }
|
|
1153
|
+
});
|
|
1154
|
+
return { rest: `${body}${nl}${appended}${nl}`, status: "recorded" };
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1047
1157
|
/**
|
|
1048
1158
|
* TRUE cross-workspace move. See the module docstring for the full design:
|
|
1049
1159
|
* re-home the stored cwd, migrate the artifact, then swap accounting.
|
|
@@ -1196,7 +1306,14 @@ ${rest}`, "utf8");
|
|
|
1196
1306
|
throw new Error("目标位置已有会话工件,拒绝覆盖");
|
|
1197
1307
|
} catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
1198
1308
|
if (oldPath === newPath) throw new Error("会话源路径与目标路径相同,但 cwd 不一致,拒绝覆盖");
|
|
1199
|
-
|
|
1309
|
+
// Issue #42: a cold log is the only authority its working-directory
|
|
1310
|
+
// projection folds from on the next resume, so re-home it together
|
|
1311
|
+
// with the artifact header. Live sessions get the equivalent event
|
|
1312
|
+
// through the host path once the artifact has been published.
|
|
1313
|
+
const bodyRehome = liveSession === void 0
|
|
1314
|
+
? rehomeColdWorkingDirectory(rest, targetPath)
|
|
1315
|
+
: { rest, status: "live" };
|
|
1316
|
+
const bytes = await encodeArtifact(JSON.stringify(newHeader), bodyRehome.rest, newPath.endsWith(".zstd"));
|
|
1200
1317
|
await mkdir(dirname(newPath), { recursive: true });
|
|
1201
1318
|
const tempNew = await writeTempFile(newPath, bytes);
|
|
1202
1319
|
const oldHidden = `${oldPath}.${randomBytes(6).toString("hex")}.tmp`;
|
|
@@ -1297,7 +1414,7 @@ ${rest}`, "utf8");
|
|
|
1297
1414
|
await rm(dirname(oldPath), { recursive: true, force: true });
|
|
1298
1415
|
} catch (error) { ctx.logger.warn(`session-manager: old directory retained: ${String(error)}`); }
|
|
1299
1416
|
|
|
1300
|
-
movedArtifact = { header: newHeader, rest };
|
|
1417
|
+
movedArtifact = { header: newHeader, rest: bodyRehome.rest, workingDirectory: bodyRehome.status };
|
|
1301
1418
|
|
|
1302
1419
|
return {
|
|
1303
1420
|
ok: true,
|
|
@@ -1329,7 +1446,34 @@ ${rest}`, "utf8");
|
|
|
1329
1446
|
ctx.logger.warn(`session-manager: post-move cache refresh failed for "${sessionId}": ${String(error)}`);
|
|
1330
1447
|
}
|
|
1331
1448
|
}
|
|
1332
|
-
|
|
1449
|
+
// Issue #42: the artifact header was rewritten, but the folded
|
|
1450
|
+
// `workingDirectory` projection was not. Align it now -- after the
|
|
1451
|
+
// artifact has been published (a live session's change event must land in
|
|
1452
|
+
// the moved log) and after the registry swap.
|
|
1453
|
+
let workingDirectory = movedArtifact?.workingDirectory ?? "not-needed";
|
|
1454
|
+
let restartRequired = false;
|
|
1455
|
+
if (liveSession !== void 0) {
|
|
1456
|
+
const rehomed = await rehomeLiveWorkingDirectory({ agent: liveAgent, session: liveSession, targetPath });
|
|
1457
|
+
workingDirectory = rehomed.status;
|
|
1458
|
+
if (rehomed.status === "restart-required") restartRequired = true;
|
|
1459
|
+
if (rehomed.status === "synced" || rehomed.status === "recorded") {
|
|
1460
|
+
// The refold above seeds from the projection cache, so it cannot see
|
|
1461
|
+
// a change committed after the move: checkpoint what the live
|
|
1462
|
+
// session now actually folds (this flushes the new event first).
|
|
1463
|
+
if (cache !== void 0 && typeof cache.write === "function") {
|
|
1464
|
+
try {
|
|
1465
|
+
await cache.write(liveSession);
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
ctx.logger.warn(`session-manager: post-move working-directory checkpoint failed for "${sessionId}": ${safeErrorMessage(error)}`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
} else if (workingDirectory === "restart-required") {
|
|
1472
|
+
restartRequired = true;
|
|
1473
|
+
}
|
|
1474
|
+
result.workingDirectory = workingDirectory;
|
|
1475
|
+
result.restartRequired = restartRequired;
|
|
1476
|
+
// The agent is still live (no teardown). The session's in-memory
|
|
1333
1477
|
// header was rewritten above and the workspace registry updated;
|
|
1334
1478
|
// future writes go through persistence.locate() against the new
|
|
1335
1479
|
// cwd, so the file at the new location accumulates new events.
|
|
@@ -2295,9 +2439,80 @@ ${rest}`, "utf8");
|
|
|
2295
2439
|
if (isBlank) { const e = new Error("blank"); e.code = "blank-skip"; throw e; }
|
|
2296
2440
|
};
|
|
2297
2441
|
|
|
2298
|
-
|
|
2442
|
+
/**
|
|
2443
|
+
* Issue #43: Validate incoming request with DSH connection authentication.
|
|
2444
|
+
* Reuses DSH official Connection/Gateway authentication and authorization mechanisms.
|
|
2445
|
+
* Strict fail-closed: requires connection.admit() to succeed with a valid Owner peer.
|
|
2446
|
+
* Must execute before request body reading, JSON parsing, or business operations.
|
|
2447
|
+
*/
|
|
2448
|
+
const authenticateApiRequest = (req) => {
|
|
2449
|
+
const connection = getService(ctx, "connection");
|
|
2450
|
+
if (!connection || typeof connection.admit !== "function") {
|
|
2451
|
+
return {
|
|
2452
|
+
ok: false,
|
|
2453
|
+
status: 503,
|
|
2454
|
+
code: "auth-unavailable",
|
|
2455
|
+
error: "DSH 认证服务未就绪或不可用,为保护数据安全已拒绝请求"
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
try {
|
|
2459
|
+
const admission = connection.admit(req);
|
|
2460
|
+
if (!admission || typeof admission !== "object") {
|
|
2461
|
+
return {
|
|
2462
|
+
ok: false,
|
|
2463
|
+
status: 401,
|
|
2464
|
+
code: "unauthorized",
|
|
2465
|
+
error: "未授权:需要有效的 DSH 会话凭证"
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
if ("rejection" in admission) {
|
|
2469
|
+
const status = typeof admission.rejection === "number" ? admission.rejection : 401;
|
|
2470
|
+
const error = status === 403
|
|
2471
|
+
? "禁止访问:请求未通过 DSH 信任检查或跨站防护"
|
|
2472
|
+
: "未授权:需要有效的 DSH 会话凭证";
|
|
2473
|
+
return { ok: false, status, code: status === 403 ? "forbidden" : "unauthorized", error };
|
|
2474
|
+
}
|
|
2475
|
+
const peer = admission.peer;
|
|
2476
|
+
if (!peer || typeof peer !== "object") {
|
|
2477
|
+
return {
|
|
2478
|
+
ok: false,
|
|
2479
|
+
status: 401,
|
|
2480
|
+
code: "unauthorized",
|
|
2481
|
+
error: "未授权:缺少有效 Peer 凭证"
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
const principalKind = peer.principal?.kind;
|
|
2485
|
+
if (principalKind !== "owner") {
|
|
2486
|
+
return {
|
|
2487
|
+
ok: false,
|
|
2488
|
+
status: 403,
|
|
2489
|
+
code: "forbidden",
|
|
2490
|
+
error: "禁止访问:会话管理属于特权操作,需要 owner 权限"
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
return { ok: true, peer };
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
return {
|
|
2496
|
+
ok: false,
|
|
2497
|
+
status: 403,
|
|
2498
|
+
code: "auth-error",
|
|
2499
|
+
error: "认证检查失败: " + safeErrorMessage(err)
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
|
|
2504
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2505
|
+
kind: "prefix",
|
|
2299
2506
|
path: API_PREFIX,
|
|
2300
2507
|
handler: async (req, res) => {
|
|
2508
|
+
const auth = authenticateApiRequest(req);
|
|
2509
|
+
if (!auth.ok) {
|
|
2510
|
+
return send(res, auth.status, {
|
|
2511
|
+
ok: false,
|
|
2512
|
+
code: auth.code,
|
|
2513
|
+
error: auth.error
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2301
2516
|
try {
|
|
2302
2517
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
2303
2518
|
const path = url.pathname.startsWith(API_PREFIX) ? url.pathname.slice(API_PREFIX.length) || "/" : "/";
|
|
@@ -2528,7 +2743,7 @@ ${rest}`, "utf8");
|
|
|
2528
2743
|
return send(res, 200, { ok: true, result });
|
|
2529
2744
|
}
|
|
2530
2745
|
if (req.method === "GET" && path === "/update/check") {
|
|
2531
|
-
const currentVersion = adapter.pluginVersion() || "0.7.
|
|
2746
|
+
const currentVersion = adapter.pluginVersion() || "0.7.5";
|
|
2532
2747
|
const runningVersion = currentVersion;
|
|
2533
2748
|
let installedVersion = runningVersion;
|
|
2534
2749
|
const profileDir = resolveProfileDir(adapter, ctx);
|
|
@@ -2712,7 +2927,7 @@ ${rest}`, "utf8");
|
|
|
2712
2927
|
};
|
|
2713
2928
|
setActiveTask(taskRecord);
|
|
2714
2929
|
|
|
2715
|
-
const currentVersion = adapter.pluginVersion() || "0.7.
|
|
2930
|
+
const currentVersion = adapter.pluginVersion() || "0.7.5";
|
|
2716
2931
|
let installedVersion = currentVersion;
|
|
2717
2932
|
if (profileDir) {
|
|
2718
2933
|
const pkgFile = join(profileDir, "node_modules", "dsh-session-manager", "package.json");
|
|
@@ -3242,7 +3457,7 @@ ${rest}`, "utf8");
|
|
|
3242
3457
|
}
|
|
3243
3458
|
if (req.method === "GET" && path === "/update/status") {
|
|
3244
3459
|
const profileDir = resolveProfileDir(adapter, ctx);
|
|
3245
|
-
const currentVersion = adapter.pluginVersion() || "0.7.
|
|
3460
|
+
const currentVersion = adapter.pluginVersion() || "0.7.5";
|
|
3246
3461
|
let installedVersion = currentVersion;
|
|
3247
3462
|
if (profileDir) {
|
|
3248
3463
|
const diskCheck = verifyInstalledPackage(profileDir);
|
|
@@ -3317,6 +3532,7 @@ ${rest}`, "utf8");
|
|
|
3317
3532
|
ctx.logger.warn("session-manager: api error: " + safeErrorMessage(error) + "; json=" + safeJson(error));
|
|
3318
3533
|
const status = error?.code === "bad-request" ? 400 : error?.code === "body-too-large" ? 413
|
|
3319
3534
|
: error?.code === "annotation-conflict" || error?.code === "annotations-busy" ? 409
|
|
3535
|
+
: error?.code === "working-directory-sync-unsafe" ? 409
|
|
3320
3536
|
: error?.code === "session-not-found" ? 404 : error?.code?.startsWith("annotations-") ? 503
|
|
3321
3537
|
: error?.code === "workspace-membership-conflict" ? 409
|
|
3322
3538
|
: error?.code === "workspace-attach-unavailable" ? 501 : 500;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-manager",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "DeepSeek Harness session and workspace manager —— Workspaces: archive and visibility. Sessions: move, migrate presets, delete, favorite, review later, prioritize, tag, and annotate. Batch operations. | DSH 会话与工作区管理——工作区:归档与显示。会话:跨工作区移动、预设迁移、删除;收藏、待回看、优先级、标签、备注。批量操作。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|