dsh-in-hand 1.6.0 → 1.7.1
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 +60 -15
- package/lib/index.js +552 -7
- package/lib/page.html +1539 -287
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,8 +36,9 @@ metrics row above the composer.
|
|
|
36
36
|
渲染成手机上看得懂的样子。目的很朴素——不用坐到电脑前,也能知道它现在在干什么、有没有
|
|
37
37
|
事在等你。
|
|
38
38
|
|
|
39
|
-
后来发现同一组窄接口能接上 DSH **自己的**会话运行时,于是「写」只用了
|
|
40
|
-
|
|
39
|
+
后来发现同一组窄接口能接上 DSH **自己的**会话运行时,于是「写」只用了 6 条路由就够:
|
|
40
|
+
附件导入、对话(含插队与硬打断)、逐字收回复、随时停止、建会话与切换模型、调整排队顺序。
|
|
41
|
+
**读是监控,写是对话**——两者共用一套令牌和
|
|
41
42
|
一条反向隧道,始终不碰 GUI。
|
|
42
43
|
|
|
43
44
|
所以这里没有 App 可装:没有原生代码、没有打包产物、没有应用商店。手机上进一次「添加到主
|
|
@@ -72,7 +73,7 @@ DSH 的 Web GUI 没有内置鉴权,而默认 preset 往往是 `danger-full-acc
|
|
|
72
73
|
|
|
73
74
|
- **读**:把状态渲染成一份手机看得懂的页面,只放行这一条路径就够。
|
|
74
75
|
- **写**:`/taskwatch/chat/*` 把 DSH **自己的**会话运行时接到手机上 —— 直接对话、
|
|
75
|
-
|
|
76
|
+
逐字收回复、随时停止、切换会话与模型、调整排队顺序。写入口**只有 6 条**,全部列在下面。
|
|
76
77
|
|
|
77
78
|
采集面完全通过 `ctx.get(...)` 软读取服务,任何一个服务不存在都只会让对应的那一块
|
|
78
79
|
留空,不会让插件挂载失败。对话那一组同理:拿不到 `sessionController` 时只让这一个
|
|
@@ -107,25 +108,49 @@ DSH 的 Web GUI 没有内置鉴权,而默认 preset 往往是 `danger-full-acc
|
|
|
107
108
|
| `/taskwatch/chat/titles?ids=` | JSON | 批量补会话标题(最多 20 个,宿主缓存 10 分钟) |
|
|
108
109
|
| `/taskwatch/chat/page?...` | JSON | 往更早翻会话历史 |
|
|
109
110
|
| `/taskwatch/chat/stream?sessionId=` | SSE | 跟随一个会话:开场快照 + 增量事件 + 逐字输出 |
|
|
111
|
+
| `/taskwatch/chat/queue?sessionId=` | JSON | 该会话未领取的消息:`items`(下一轮队列,可重排)与 `steering`(已插到本回合下一步) |
|
|
112
|
+
| `/taskwatch/entries` | JSON | 双入口清单:`{entries:[{kind:'remote'\|'lan',label,url,current}]}`;没配 `publicUrl` 时只有局域网一条 |
|
|
110
113
|
| `/taskwatch/manifest.webmanifest` | JSON | PWA manifest |
|
|
111
114
|
| `/taskwatch/sw.js` | JS | service worker |
|
|
112
115
|
| `/taskwatch/icon-192.png` · `/taskwatch/icon-512.png` | PNG | 图标 |
|
|
113
116
|
|
|
114
|
-
写(`POST`)——
|
|
117
|
+
写(`POST`)—— 只有这 6 条对话入口,外加一条不写宿主状态的换路票据:
|
|
115
118
|
|
|
116
119
|
| 路径 | 请求体 | 说明 |
|
|
117
120
|
|---|---|---|
|
|
118
121
|
| `/taskwatch/chat/upload` | 原始二进制(`sessionId/kind/name/mime` 查询参数) | 导入文件或图片;图片由宿主校验,文件按会话签发 receipt |
|
|
119
|
-
| `/taskwatch/chat/send` | `{sessionId,text,attachments?,mode?,timeZone?}` |
|
|
122
|
+
| `/taskwatch/chat/send` | `{sessionId,text,attachments?,mode?,now?,interrupt?,timeZone?}` | 给会话发文字和已上传附件;`now:true` 走宿主原生 steer 插队(见下) |
|
|
120
123
|
| `/taskwatch/chat/cancel` | `{sessionId}` | 停止当前这一轮 |
|
|
121
124
|
| `/taskwatch/chat/create` | `{}` 或 `{workspace}` | 新建会话;`workspace` 必须命中 `/taskwatch/chat/workspaces` 列出的路径 |
|
|
122
125
|
| `/taskwatch/chat/model` | `{sessionId,provider,model}` | 切换该会话使用的模型 |
|
|
126
|
+
| `/taskwatch/chat/queue/reorder` | `{sessionId,ids:[...]}` | 重排下一轮队列;`ids` 必须是当前队列的完整 id 列表 |
|
|
127
|
+
| `/taskwatch/ticket` | `{to:'remote'\|'lan'}` | 签发 60 秒、一次性的换路票据;只在内存里放一张纸条,不碰会话 / 文件 / 配置。没配 `publicUrl` 时远端请求回 `409 no-public-entry` |
|
|
123
128
|
|
|
124
129
|
全部注册在 `ctx.webServer` 上,不代理 DSH 自身的 `/api`,也不转发任意路径。
|
|
125
|
-
写入口就只有上面
|
|
130
|
+
写入口就只有上面 6 条对话入口 + 1 条换路票据 —— 契约测试会**正面清点**路由表,
|
|
131
|
+
新增路由必须在只读 / 对话 / 非对话写三张白名单里显式归类,漏一个就让构建失败
|
|
126
132
|
(早先那条断言写的是"源码里不出现 `method === 'POST'`"。加了 5 个写路由之后它
|
|
127
133
|
照样通过,是条假绿灯:它证明的是"没这么写",不是"没有这个能力")。
|
|
128
134
|
|
|
135
|
+
### 排队、重排与「立即发送」的真实语义
|
|
136
|
+
|
|
137
|
+
宿主本来就有两条投递通道:`mode:'queue'`(进 `nextTurn`,等下一轮)与
|
|
138
|
+
`mode:'steer'`(进 `nextStep`,在本回合下一个步骤边界被领取)。手机端:
|
|
139
|
+
|
|
140
|
+
- **排队** = 默认的 `queue`;`GET /taskwatch/chat/queue` 读 `nextTurn` 的顺序,
|
|
141
|
+
`POST /taskwatch/chat/queue/reorder` 用同一批 Message 整体重新 `splice`,
|
|
142
|
+
不伪造消息、不改内容。
|
|
143
|
+
- **立即发送** = `send` 带 `now:true`,即宿主原生 `steer`。它进入 steering lane,
|
|
144
|
+
排在本轮所有 `nextTurn` 排队消息之前,**但不会中止**正在执行的工具调用或模型
|
|
145
|
+
请求 —— 消息在下一个 step 边界才被模型看到;会话空闲时会立刻唤醒并起一轮。
|
|
146
|
+
同一 lane 里更早的 steering 消息仍按先来后到排在它前面。
|
|
147
|
+
- **硬打断** = 再带 `interrupt:true`:先 `cancel({keepInbox:true})`(队列保留),
|
|
148
|
+
等 agent 真正 idle(最多 2 秒)再 steer;成了就先于保留的 `nextTurn` 队列执行
|
|
149
|
+
(cancel 前已有的 steering 仍按原顺序先跑)。2 秒内停不下来返回
|
|
150
|
+
`409 interrupt-timeout`,消息不发出、队列不动。
|
|
151
|
+
- **不会**做的事:不把队列顺序存在插件内存里(那会和宿主真实队列漂移),
|
|
152
|
+
也不支持把 `steering` 消息重新排进 `nextTurn`。
|
|
153
|
+
|
|
129
154
|
### 手机能选工作区,但选不了任意目录
|
|
130
155
|
|
|
131
156
|
「在手机上新开一个对话」得能指定在哪个目录开工,否则只能落在部署默认值上。但
|
|
@@ -437,21 +462,41 @@ PWA 外壳由中继本地提供(而不是转发上游),这样它不随上
|
|
|
437
462
|
换路等于换了一个**源**(`https://…` ↔ `http://192.168.x.x:3099`),而浏览器的 cookie 是
|
|
438
463
|
**按源隔离**的 —— 在远端那条路上换到的 cookie,到了局域网这条路等于不存在。
|
|
439
464
|
|
|
440
|
-
|
|
465
|
+
所以页面点切换时,先向当前这条路的**宿主**(中继,或插件宿主本身)要一张凭据:
|
|
441
466
|
|
|
442
|
-
1. `POST /taskwatch/ticket
|
|
467
|
+
1. `POST /taskwatch/ticket`(中继上要带令牌)→ 返回另一条路的 URL,末尾带一次性 `?t=…`;
|
|
443
468
|
2. 手机跳过去,中继兑换这张票:**60 秒有效、兑换即作废、重放无效**,同时给那条路种下
|
|
444
469
|
cookie;
|
|
445
470
|
3. 于是不用重新输那 6 位配对码。
|
|
446
471
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
472
|
+
**v1.7.1 起,前两条插件宿主也自己实现了**(同一套语义):`GET /taskwatch/entries`
|
|
473
|
+
回答「现在有哪几条路」,`POST /taskwatch/ticket` 给一张 60 秒、用后即焚的票据。
|
|
474
|
+
所以从宿主页面(例如 `http://127.0.0.1:52676/taskwatch`)点「去远端」不再拿到 405 / 404。
|
|
475
|
+
`GET /taskwatch/upstream-health` 仍只有中继实现;宿主页面拿到它的 404 时按「未知」处理,
|
|
476
|
+
并停掉探测 —— **不会**把「没有体检接口」误判成「这条路断了」。
|
|
477
|
+
|
|
478
|
+
公网地址只来自插件配置项 `publicUrl`(也支持环境变量 `DSH_TASKWATCH_PUBLIC_URL`),
|
|
479
|
+
代码里不写死任何域名:
|
|
480
|
+
|
|
481
|
+
```yaml
|
|
482
|
+
- insert:
|
|
483
|
+
- id: dsh-in-hand
|
|
484
|
+
name: dsh-in-hand
|
|
485
|
+
config:
|
|
486
|
+
publicUrl: https://dsh.example.com
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
没配就只报局域网入口;`ticket` 的远端请求明确回 `409 no-public-entry`。宿主这层仍然
|
|
490
|
+
不做令牌校验(401 由中继负责),但票据是写路由,所以和中继的写路由同一套门禁:
|
|
491
|
+
`POST` + `application/json` + 同源(`Origin` / `Sec-Fetch-Site`)校验。
|
|
492
|
+
|
|
493
|
+
页面侧遇到这两条接口的 **404 / 405** 会进入**静态降级**:不重试、不弹错,胶囊按
|
|
494
|
+
「只有一个入口」隐藏;设置 / 任务详情里留一行说明与一个手动「去远端」链接
|
|
495
|
+
(上次成功的地址记在 `localStorage['tw-remote-url']`)。500 / 502 / 网络错误只提示一次
|
|
496
|
+
(同一句 30 秒内不重复弹),不会变成重试风暴。
|
|
451
497
|
|
|
452
|
-
>
|
|
453
|
-
>
|
|
454
|
-
> 不在本仓库。
|
|
498
|
+
> 中继那边另有独立实现,见中继仓库 `dsh-in-hand-relay`(该仓库尚未创建,此处只写仓库名)。
|
|
499
|
+
> 中继没升级时,页面在宿主上仍能正常用,只是没有远端切换。
|
|
455
500
|
|
|
456
501
|
### 502 排查
|
|
457
502
|
|