dsh-url-router 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -99
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,120 +1,114 @@
|
|
|
1
1
|
# dsh-url-router
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
the surface it names. **Browser half only** — no dependencies, no host route, no telemetry.
|
|
3
|
+
**DSH Web GUI 地址栏路由插件 —— 每个界面都有自己的链接:地址栏写着屏幕上的东西,链接点开就是那个界面。** 给 DeepSeek Harness (dsh) Web GUI 补上"可寻址":点侧栏会话,地址栏变成 `#/session/<id>`;点插件面板,变成 `#/panel/plugins`;点进某个插件、某个组件、某个任务,地址栏跟着一起走。把这条链接贴到新标签页、或者对着侧栏任意一行右键,就是浏览器自己的行为——无需复制按钮、无需自定义菜单。
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
它是**地址栏的唯一写者**:只改 URL 的 fragment,路径与查询串(例如启动时的 `?token=…`)原样保留;只用 `replaceState`,浏览面板不会往后退栈里塞历史。**只有浏览器半区**——零依赖、无宿主路由、无遥测。
|
|
6
|
+
|
|
7
|
+
[中文](#中文) · [English](#english)
|
|
8
|
+
|
|
9
|
+
---
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
click a conversation and it becomes `#/session/<id>`. Paste either link into another
|
|
14
|
-
tab (same machine, same `DSH_HOME`) and that surface opens.
|
|
11
|
+
## 中文
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
### 安装
|
|
17
14
|
|
|
18
15
|
```sh
|
|
16
|
+
# 通过插件管理器(推荐)
|
|
19
17
|
dsh plugin --profile web add dsh-url-router
|
|
20
|
-
|
|
18
|
+
|
|
19
|
+
# 重启 dsh web,之后硬刷新浏览器
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| Route | Surface | Opened by |
|
|
26
|
-
|---|---|---|
|
|
27
|
-
| `#/session/<id>` | the conversation the main view shows | `uiWorkspace.openSession(id)` |
|
|
28
|
-
| `#/panel/<panelId>` | a main-column panel (`plugins`, `task-board`, `skill-explorer`, `ssh`, …) | `layout.selectPanel(id)` |
|
|
29
|
-
| `#/panel/plugins/<package>` | an installed plugin's page | the panel's own list row control |
|
|
30
|
-
| `#/panel/plugins/<item>` | a built-in plugin's page (`shell`, `agent-loop`, `subagent`, `web-search`) | the panel's own list row control |
|
|
31
|
-
| `#/panel/plugins/<package>/<component>` | one component page inside that plugin's page | the panel's own component row control |
|
|
32
|
-
| `#/panel/task-board/task/<taskId>` | one task's detail on the task board | the task board's own `dsh-taskboard-open-task` window event |
|
|
33
|
-
| `#/panel/task-board/new` | the task board's new-task form | the task board's own `dsh-taskboard-new-task` window event |
|
|
34
|
-
|
|
35
|
-
Route kinds are owned by this plugin; a kind it does not know (`#/settings/…`) is
|
|
36
|
-
**left alone** with a single diagnostic, so another router can own it later.
|
|
37
|
-
|
|
38
|
-
A panel's inner pages are the panel's own vocabulary, so this plugin never invents one:
|
|
39
|
-
the plugin manager publishes `data-plugin-detail` / `data-plugin-item-detail` /
|
|
40
|
-
`data-plugin-row-detail`, and the task board publishes `data-dsh-taskboard-open-task` /
|
|
41
|
-
`data-dsh-taskboard-new-task` while asking for a task on a window event of the same name.
|
|
42
|
-
|
|
43
|
-
## Row links
|
|
44
|
-
|
|
45
|
-
Every conversation row in the sidebar is exposed as a **real link** — a transparent
|
|
46
|
-
`<a href="#/session/<id>" target="_blank">` covering the row, so the browser's own link
|
|
47
|
-
behavior applies with no custom menu:
|
|
48
|
-
|
|
49
|
-
- **right-click** → the native menu (open link in new tab / copy link / save link as)
|
|
50
|
-
- **middle click, Cmd/Ctrl+click** → a new tab, natively
|
|
51
|
-
- **plain left click** → opens the conversation *here* (`preventDefault` + the official
|
|
52
|
-
navigation), so the address bar keeps using `replaceState` and no history entry is added
|
|
53
|
-
- the row's own controls (⋯, buttons) are lifted above the overlay and keep working
|
|
54
|
-
|
|
55
|
-
The same applies to the **main-panel rows** (Plugins, Task Board, …): each becomes
|
|
56
|
-
`#/panel/<id>`. A panel row carries no identity in the DOM, so its id comes from the panel
|
|
57
|
-
registry (`ctx.slots.entries('sidebar.panellist')`), matched by accessible name and aligned
|
|
58
|
-
by registration order; a row that cannot be matched is left untouched.
|
|
59
|
-
|
|
60
|
-
It supersedes the earlier `dsh-session-url` plugin: install one, not both (both write the
|
|
61
|
-
fragment). The link shape `#/session/<id>` is identical, so existing links keep working.
|
|
62
|
-
|
|
63
|
-
## Rules it keeps
|
|
64
|
-
|
|
65
|
-
- **Fragment only.** Path and query string are never touched (a launch `?token=…` survives).
|
|
66
|
-
- **One writer.** This plugin owns the fragment; run it *instead of* any other plugin
|
|
67
|
-
that writes the address bar (`dsh-session-url` mirrors conversations into the same
|
|
68
|
-
fragment — the two would overwrite each other).
|
|
69
|
-
- **`replaceState` only**, so browsing panels does not fill the Back stack.
|
|
70
|
-
- **Scopes are declared, not guessed.** A main panel is application-level (it lists this
|
|
71
|
-
machine's plugins, not a conversation), so its link carries no session id.
|
|
72
|
-
- **A link stays authoritative until it lands** (at most one boot, 8 s): once the named
|
|
73
|
-
surface is on screen the link stops owning the URL, so walking deeper inside a panel
|
|
74
|
-
updates the address bar instead of being pulled back to the link.
|
|
75
|
-
- **A link wins for one boot.** Both owners restore their own remembered state while the
|
|
76
|
-
page loads, so a linked surface stays authoritative for 8 s; after that the view owns
|
|
77
|
-
the URL again.
|
|
78
|
-
- **Never throws.** Refused navigations and broken services are contained, reported once,
|
|
79
|
-
and fall back to the view actually on screen — never a blank page.
|
|
80
|
-
|
|
81
|
-
## Limits
|
|
82
|
-
|
|
83
|
-
- Depends on the official client faces `ctx.layout.selectPanel` / `ctx.layout.panelInfo`
|
|
84
|
-
and the session/workspace controllers. If their shape changes, the plugin disables
|
|
85
|
-
itself with one diagnostic rather than touching official behavior.
|
|
86
|
-
- Row links depend on the official row markup (`div[data-row-key="session:<id>"]`); if
|
|
87
|
-
that changes the plugin degrades to doing nothing rather than touching official rows.
|
|
88
|
-
- Settings sections are **not** routed yet: the settings package exposes no
|
|
89
|
-
"open section" seam (they are keyed list slots). Right-hand panes are openable
|
|
90
|
-
(`ISidebarRight.openTab(kind)`) but not yet routed.
|
|
91
|
-
- An unregistered panel id is refused by the layout by *throwing*; the link is dropped at
|
|
92
|
-
the end of its window with one diagnostic. A panel that registers within the window is
|
|
93
|
-
still reached.
|
|
22
|
+
开发时也可以直接挂工作树:
|
|
94
23
|
|
|
95
|
-
|
|
24
|
+
```sh
|
|
25
|
+
dsh plugin --profile web add "link:/path/to/dsh-url-router"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 路由表
|
|
29
|
+
|
|
30
|
+
| 地址 | 界面 | 由谁打开 |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `#/session/<id>` | 主视图里的那个会话 | `uiWorkspace.openSession(id)` |
|
|
33
|
+
| `#/panel/<panelId>` | 主面板(`plugins`、`task-board`、`skill-explorer`、`ssh` …) | `layout.selectPanel(id)` |
|
|
34
|
+
| `#/panel/plugins/<包名>` | 某个已安装插件的页面 | 面板自己的列表行控件 |
|
|
35
|
+
| `#/panel/plugins/<官方项 id>` | 某个官方内置插件的页面(`shell`、`agent-loop`、`subagent`、`web-search`) | 面板自己的列表行控件 |
|
|
36
|
+
| `#/panel/plugins/<包名>/<组件 id>` | 插件页面里的某个组件页 | 面板自己的组件行控件 |
|
|
37
|
+
| `#/panel/task-board/task/<任务 id>` | 任务看板里某个任务的详情 | 任务看板自己的 `dsh-taskboard-open-task` 窗口事件 |
|
|
38
|
+
| `#/panel/task-board/new` | 任务看板的新建任务表单 | 任务看板自己的 `dsh-taskboard-new-task` 窗口事件 |
|
|
39
|
+
|
|
40
|
+
输入侧宽容:`sessions`/`panels` 复数、百分号编码都接受,一律规范化成上面的形式。**不认识的段位不碰**(例如 `#/settings/…`),只留一条诊断,留给以后的路由器。
|
|
41
|
+
|
|
42
|
+
面板内部页是**面板自己的词汇表**,本插件不发明:插件管理器发布 `data-plugin-detail` / `data-plugin-item-detail` / `data-plugin-row-detail`,任务看板发布 `data-dsh-taskboard-open-task` / `data-dsh-taskboard-new-task`,并接受同名窗口事件。
|
|
43
|
+
|
|
44
|
+
### 每一行都是真链接
|
|
45
|
+
|
|
46
|
+
侧栏的会话行、插件面板的列表行(已安装组与官方组)都是**整行覆盖的透明真锚点**:
|
|
96
47
|
|
|
97
|
-
|
|
48
|
+
- **右键** → 浏览器原生菜单(在新标签页中打开 / 复制链接 / 另存为)
|
|
49
|
+
- **中键、Cmd/Ctrl + 单击** → 原生新标签页
|
|
50
|
+
- **左键单击** → 在本页打开(`preventDefault` 后调官方导航,不新增历史记录)
|
|
98
51
|
|
|
99
|
-
|
|
52
|
+
### 它守的规矩
|
|
100
53
|
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
54
|
+
- **只动 fragment**:路径与查询串从不改写(启动 `?token=…` 能活下来)。
|
|
55
|
+
- **唯一写者**:本插件拥有 fragment,请**不要**和别的写 fragment 的插件同时启用(例如 `dsh-session-url`),两者会互相覆盖。
|
|
56
|
+
- **只用 `replaceState`**:浏览面板不污染后退栈。
|
|
57
|
+
- **作用域由视图决定,不靠猜**:主面板是应用级界面(列的是本机插件,不是某个对话),所以它的链接不带会话 id。
|
|
58
|
+
- **链接落地即交权**:视图一旦变成链接描述的那个界面,链接立刻失去权威——这样你在面板里继续点进去时,地址栏不会被拽回上一层。
|
|
59
|
+
- **绝不抛错**:官方 `selectPanel` 对未注册的面板 id 是抛异常的;所有宿主面调用都被容纳,失败只留一条诊断并回落到屏幕上真实的视图。
|
|
107
60
|
|
|
108
|
-
|
|
61
|
+
### 已知限制
|
|
62
|
+
|
|
63
|
+
- **设置页分区暂未纳入**:设置包没有"打开某个分区"的接缝(它们是 keyed list slot),只能等它自己发布类似契约。
|
|
64
|
+
- **右栏 pane 与会话内轮次/消息锚点暂未纳入**。
|
|
65
|
+
- **技能中心 / SSH 的内部页**:需要它们各自发布"当前实体 id + 按 id 打开"的契约(任务看板就是这么做的)。
|
|
66
|
+
- 链接**同名不跨机**:会话 id 只在同一台机器、同一个 `DSH_HOME` 下有意义。
|
|
67
|
+
|
|
68
|
+
### 开发
|
|
69
|
+
|
|
70
|
+
手写 JS 直接进 `lib/`(无构建步骤),`npm test` 直接跑交付产物(35→45 条用例,`node --test`)。改之前请读 [AGENTS.md](AGENTS.md) 里的路由与行链接契约。
|
|
71
|
+
|
|
72
|
+
## English
|
|
73
|
+
|
|
74
|
+
**A URL router for the DSH Web GUI — every surface gets its own link.** Click a conversation and the address bar becomes `#/session/<id>`; click a panel (Plugins, Task Board, …) and it becomes `#/panel/<panelId>`; walk into a plugin, a component, or a task and the bar follows. Paste the link into another tab, or right-click any sidebar row for the browser's own "open in new tab / copy link".
|
|
75
|
+
|
|
76
|
+
It is the **single writer of the fragment**: path and query string (a launch `?token=…`) are never touched, and it only ever calls `replaceState`. **Browser half only** — no dependencies, no host route, no telemetry.
|
|
77
|
+
|
|
78
|
+
### Install
|
|
109
79
|
|
|
110
80
|
```sh
|
|
111
81
|
dsh plugin --profile web add dsh-url-router
|
|
112
|
-
#
|
|
82
|
+
# restart `dsh web`, then hard-refresh the browser
|
|
113
83
|
```
|
|
114
84
|
|
|
115
|
-
|
|
85
|
+
### Routes
|
|
86
|
+
|
|
87
|
+
| Route | Surface |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `#/session/<id>` | the conversation the main view shows |
|
|
90
|
+
| `#/panel/<panelId>` | a main-column panel |
|
|
91
|
+
| `#/panel/plugins/<package>` | an installed plugin's page |
|
|
92
|
+
| `#/panel/plugins/<item>` | a built-in plugin's page (`shell`, `agent-loop`, `subagent`, `web-search`) |
|
|
93
|
+
| `#/panel/plugins/<package>/<component>` | one component page inside that plugin's page |
|
|
94
|
+
| `#/panel/task-board/task/<taskId>` | one task's detail on the task board |
|
|
95
|
+
| `#/panel/task-board/new` | the task board's new-task form |
|
|
96
|
+
|
|
97
|
+
A kind it does not know (`#/settings/…`) is left alone with a single diagnostic. Panel inner pages are the panel's own vocabulary: the plugin manager publishes `data-plugin-detail` / `data-plugin-item-detail` / `data-plugin-row-detail`, the task board publishes `data-dsh-taskboard-open-task` / `data-dsh-taskboard-new-task`.
|
|
98
|
+
|
|
99
|
+
### Every row is a real link
|
|
100
|
+
|
|
101
|
+
Conversation rows and both plugin-list groups carry a transparent full-row anchor: right-click gives the native menu, middle/Cmd-click opens a native new tab, and a plain left click navigates in place without adding a history entry.
|
|
102
|
+
|
|
103
|
+
### Rules it keeps
|
|
104
|
+
|
|
105
|
+
- **Fragment only.** Path and query are never rewritten.
|
|
106
|
+
- **One writer.** Do not run another fragment writer (`dsh-session-url`) beside it.
|
|
107
|
+
- **`replaceState` only**, so panels never fill the Back stack.
|
|
108
|
+
- **Scopes are declared, not guessed.** A panel is application-level and carries no session id.
|
|
109
|
+
- **A link stays authoritative until it lands**, then hands the address bar back.
|
|
110
|
+
- **Never throws.** Refused navigations are contained with one diagnostic.
|
|
111
|
+
|
|
112
|
+
### Limits
|
|
116
113
|
|
|
117
|
-
-
|
|
118
|
-
- **任务看板内部页已纳入**:`#/panel/task-board/task/<任务id>`(任务详情弹层)、`#/panel/task-board/new`(新建任务表单)。这两条靠任务看板自己发布的契约(`data-dsh-taskboard-open-task` / `data-dsh-taskboard-new-task` + 同名窗口事件),本插件不猜它的弹层。
|
|
119
|
-
- **设置页分区暂未纳入**(设置包没有"打开某分区"的接缝;它们是 keyed list slot)。
|
|
120
|
-
- 右栏 pane 能打开(`ISidebarRight.openTab(kind)`),但尚未纳入路由。
|
|
114
|
+
Settings sections (no open seam yet), the right-hand panes, and in-conversation turn anchors are not routed. Skill Center / SSH inner pages need their own "publish the current id + accept an open request" contract, the way the task board did. Session links are meaningful on the same machine and `DSH_HOME` only.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-url-router",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "DSH Web GUI
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "DSH Web GUI 地址栏路由插件:每个界面都有自己的链接(会话 / 主面板 / 插件页与组件页 / 任务看板),整行即真链接,只写 fragment。",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/DDDMUC/dsh-url-router.git"
|