dsh-plugin-mobile-gateway 0.6.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROTOCOL.md +103 -13
- package/README.md +3 -1
- package/lib/index.mjs +107 -3
- package/package.json +2 -2
package/PROTOCOL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh Mobile Gateway — WebSocket 协议参考
|
|
2
2
|
|
|
3
|
-
移动端通过一个经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送文字和图片、处理 Human-in-the-loop 提问与操作审批、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现(v0.
|
|
3
|
+
移动端通过一个经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送文字和图片、处理 Human-in-the-loop 提问与操作审批、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现(v0.7.0)。
|
|
4
4
|
|
|
5
5
|
- **本机端点**:`ws://127.0.0.1:3080/ws/mobile`(与 dsh web GUI 同端口)
|
|
6
6
|
- **局域网端点**:`ws://<电脑的私有局域网 IP>:3081/ws/mobile`(插件独立监听,只提供经过鉴权的 WebSocket)
|
|
@@ -77,7 +77,7 @@ const pairingText = Buffer.from(JSON.stringify(payload), 'utf8').toString('base6
|
|
|
77
77
|
```json
|
|
78
78
|
{ "kind": "paired", "token": "<长期设备 token>",
|
|
79
79
|
"device": { "id": "...", "name": "iPhone", "createdAt": 1787111700000 } }
|
|
80
|
-
{ "kind": "hello", "protocol": 3, "capabilities": ["images", "file-downloads"], "authenticated": true,
|
|
80
|
+
{ "kind": "hello", "protocol": 3, "capabilities": ["images", "commands", "tasks", "goals", "file-downloads"], "authenticated": true,
|
|
81
81
|
"device": { "id": "...", "name": "iPhone" }, "port": 3080, "clients": 1 }
|
|
82
82
|
```
|
|
83
83
|
|
|
@@ -546,6 +546,8 @@ let image = [
|
|
|
546
546
|
| `search` | `query` | 会话全文搜索 |
|
|
547
547
|
| `session-stats` | `sessionId` | 执行统计投影(输入框统计条数据源) |
|
|
548
548
|
| `context-usage` | `sessionId` | token 用量 + 上下文占用投影 |
|
|
549
|
+
| `tasks` | `sessionId` | 当前任务列表(`todos` projection) |
|
|
550
|
+
| `goal` | `sessionId` | 当前目标及其 CAS 版本(`goal` projection) |
|
|
549
551
|
|
|
550
552
|
### `history` 详细
|
|
551
553
|
```json
|
|
@@ -659,7 +661,93 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
659
661
|
|
|
660
662
|
---
|
|
661
663
|
|
|
662
|
-
## 6.
|
|
664
|
+
## 6. 任务列表与目标
|
|
665
|
+
|
|
666
|
+
WebUI 中的“任务”与“进行中的目标”分别对应 DSH 的 `todos` 和 `goal` session projection。移动端进入会话后应请求 `tasks` 与 `goal` 取得基线;随后以 `tasks-updated` / `goal-updated` 实时更新 UI。
|
|
667
|
+
|
|
668
|
+
### 任务列表
|
|
669
|
+
|
|
670
|
+
```json
|
|
671
|
+
{ "type": "tasks", "sessionId": "session-abc" }
|
|
672
|
+
→ {
|
|
673
|
+
"kind": "tasks",
|
|
674
|
+
"sessionId": "session-abc",
|
|
675
|
+
"asOfSeq": 42,
|
|
676
|
+
"todos": [
|
|
677
|
+
{ "content": "检查 Android SDK", "status": "completed" },
|
|
678
|
+
{ "content": "创建项目", "status": "in_progress" },
|
|
679
|
+
{ "content": "构建 APK", "status": "pending" }
|
|
680
|
+
]
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
- `todos: null` 表示该会话尚未写入过任务列表;客户端可隐藏任务卡片。
|
|
685
|
+
- 任务由 Agent 的 `todo_write` 更新;移动端只读展示,不能直接改写。
|
|
686
|
+
|
|
687
|
+
### 当前目标
|
|
688
|
+
|
|
689
|
+
```json
|
|
690
|
+
{ "type": "goal", "sessionId": "session-abc" }
|
|
691
|
+
→ {
|
|
692
|
+
"kind": "goal",
|
|
693
|
+
"sessionId": "session-abc",
|
|
694
|
+
"asOfSeq": 42,
|
|
695
|
+
"goal": {
|
|
696
|
+
"goal": {
|
|
697
|
+
"id": "goal-opaque-id",
|
|
698
|
+
"revision": 7,
|
|
699
|
+
"objective": "初始化一个 Android app",
|
|
700
|
+
"phase": "active",
|
|
701
|
+
"maxGoalRounds": 12
|
|
702
|
+
},
|
|
703
|
+
"roundsStarted": 3,
|
|
704
|
+
"createdAt": 1787111700000,
|
|
705
|
+
"updatedAt": 1787111800000
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
`goal: null` 表示没有当前目标。所有目标写操作必须携带刚读取到的 `{id, revision}`。这是 DSH 的 compare-and-set 保护:当 WebUI 或另一台设备已经修改目标时,宿主拒绝陈旧 revision,移动端应重新请求 `goal` 后再提示用户重试。
|
|
711
|
+
|
|
712
|
+
| type | 参数 | 说明 |
|
|
713
|
+
|---|---|---|
|
|
714
|
+
| `goal-edit` | `sessionId`, `ref`, `objective?`, `maxGoalRounds?` | 修改目标名称(`objective`)或轮数上限,至少提供一项 |
|
|
715
|
+
| `goal-pause` | `sessionId`, `ref` | 暂停当前目标 |
|
|
716
|
+
| `goal-resume` | `sessionId`, `ref` | 继续已暂停/阻塞的目标 |
|
|
717
|
+
| `goal-clear` | `sessionId`, `ref` | 删除当前目标 |
|
|
718
|
+
|
|
719
|
+
更改目标名称:
|
|
720
|
+
|
|
721
|
+
```json
|
|
722
|
+
{ "type": "goal-edit", "sessionId": "session-abc",
|
|
723
|
+
"ref": { "id": "goal-opaque-id", "revision": 7 },
|
|
724
|
+
"objective": "完成 Android app 初始化" }
|
|
725
|
+
→ { "kind": "goal-edit", "sessionId": "session-abc",
|
|
726
|
+
"ref": { "id": "goal-opaque-id", "revision": 8 } }
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
暂停、继续和删除只替换 `type`:
|
|
730
|
+
|
|
731
|
+
```json
|
|
732
|
+
{ "type": "goal-pause", "sessionId": "session-abc", "ref": { "id": "goal-opaque-id", "revision": 8 } }
|
|
733
|
+
→ { "kind": "goal-pause", "sessionId": "session-abc", "ref": { "id": "goal-opaque-id", "revision": 9 } }
|
|
734
|
+
|
|
735
|
+
{ "type": "goal-clear", "sessionId": "session-abc", "ref": { "id": "goal-opaque-id", "revision": 9 } }
|
|
736
|
+
→ { "kind": "goal-clear", "sessionId": "session-abc", "cleared": true }
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### 实时更新
|
|
740
|
+
|
|
741
|
+
```json
|
|
742
|
+
{ "kind": "tasks-updated", "sessionId": "session-abc", "asOfSeq": 43, "todos": [ ... ] }
|
|
743
|
+
{ "kind": "goal-updated", "sessionId": "session-abc", "asOfSeq": 44, "goal": { ... } }
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
服务端只转发 `todos` 与 `goal` projection;客户端按 `asOfSeq` 做高序号覆盖,避免较早推送回写较新的查询结果。
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## 7. 工作区与目录
|
|
663
751
|
|
|
664
752
|
| type | 参数 | 说明 |
|
|
665
753
|
|---|---|---|
|
|
@@ -689,7 +777,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
689
777
|
|
|
690
778
|
---
|
|
691
779
|
|
|
692
|
-
##
|
|
780
|
+
## 8. 模型与思考等级
|
|
693
781
|
|
|
694
782
|
| type | 参数 | 说明 |
|
|
695
783
|
|---|---|---|
|
|
@@ -721,7 +809,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
721
809
|
|
|
722
810
|
---
|
|
723
811
|
|
|
724
|
-
##
|
|
812
|
+
## 9. 权限控制
|
|
725
813
|
|
|
726
814
|
| type | 参数 | 说明 |
|
|
727
815
|
|---|---|---|
|
|
@@ -736,7 +824,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
736
824
|
|
|
737
825
|
---
|
|
738
826
|
|
|
739
|
-
##
|
|
827
|
+
## 10. 新会话默认配置
|
|
740
828
|
|
|
741
829
|
| type | 参数 | 说明 |
|
|
742
830
|
|---|---|---|
|
|
@@ -754,7 +842,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
754
842
|
|
|
755
843
|
---
|
|
756
844
|
|
|
757
|
-
##
|
|
845
|
+
## 11. 分支(fork)
|
|
758
846
|
|
|
759
847
|
```json
|
|
760
848
|
{ "type": "fork", "sessionId": "session-abc", "atSeq": 42 }
|
|
@@ -765,7 +853,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
765
853
|
|
|
766
854
|
---
|
|
767
855
|
|
|
768
|
-
##
|
|
856
|
+
## 12. 宿主信息
|
|
769
857
|
|
|
770
858
|
| type | 返回 |
|
|
771
859
|
|---|---|
|
|
@@ -779,13 +867,14 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
779
867
|
|
|
780
868
|
---
|
|
781
869
|
|
|
782
|
-
##
|
|
870
|
+
## 13. 服务端主动推送
|
|
783
871
|
|
|
784
872
|
| kind | 触发时机 |
|
|
785
873
|
|---|---|
|
|
786
874
|
| `paired` | 首次配对成功;仅此一次返回长期设备 token |
|
|
787
|
-
| `hello` | 连接成功:`{ "kind":"hello", "protocol":3, "capabilities":["images","file-downloads"], "authenticated":true, "port":3080, "clients":1 }` |
|
|
875
|
+
| `hello` | 连接成功:`{ "kind":"hello", "protocol":3, "capabilities":["images","commands","tasks","goals","file-downloads"], "authenticated":true, "port":3080, "clients":1 }` |
|
|
788
876
|
| `event` | 任意会话的 agent 输出(见下) |
|
|
877
|
+
| `tasks-updated` / `goal-updated` | 当前会话的任务列表或目标 projection 发生变化 |
|
|
789
878
|
| `question-requested` / `question-resolved` | Human-in-the-loop 问题请求与最终状态 |
|
|
790
879
|
| `approval-requested` / `approval-resolved` | Human-in-the-loop 操作审批请求与最终状态 |
|
|
791
880
|
| `pong` / `subscribed` / `sent` | 对应请求的回复 |
|
|
@@ -805,7 +894,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
805
894
|
|
|
806
895
|
---
|
|
807
896
|
|
|
808
|
-
##
|
|
897
|
+
## 14. 端到端示例(Postman)
|
|
809
898
|
|
|
810
899
|
1. Connect → 收到 `hello`
|
|
811
900
|
2. `{"type":"sessions"}` → 挑 `sessionId`(或直接下一步自动建)
|
|
@@ -818,7 +907,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
818
907
|
|
|
819
908
|
---
|
|
820
909
|
|
|
821
|
-
##
|
|
910
|
+
## 15. 安全注意
|
|
822
911
|
|
|
823
912
|
- `/ws/mobile` 的移动网关默认关闭;本机 WebUI 手动开启后,若 5 分钟内没有设备成功连接会自动关闭
|
|
824
913
|
- 网关开启后仍要求已配对设备凭证;不要把 `requireAuth` 设为 `false` 后暴露到网络
|
|
@@ -833,7 +922,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
833
922
|
|
|
834
923
|
---
|
|
835
924
|
|
|
836
|
-
##
|
|
925
|
+
## 16. 版本历史(插件)
|
|
837
926
|
|
|
838
927
|
| 版本 | 新增 |
|
|
839
928
|
|---|---|
|
|
@@ -859,6 +948,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
859
948
|
| v0.6.7 | 订阅已有 Session 时重放待处理 Human-in-the-loop 请求,并增加 Approval 端到端诊断日志与安装版本标记 |
|
|
860
949
|
| v0.6.8 | 会话工作目录受限的文件列表与分块下载:支持图片、文档、IPA、APK 等普通文件,含连接归属、路径越界防护、取消、超时和 SHA-256 完整性校验 |
|
|
861
950
|
| v0.6.9 | 服务端驱动的命令与技能目录:支持本地化 Hint、通用二级选项、专用命令执行,以及 command/compaction 生命周期事件;Host 命令不再作为用户 Prompt 发送 |
|
|
951
|
+
| v0.7.0 | 任务与 Goal 对齐:任务/Goal 基线查询、`todos`/`goal` 实时投影、Goal 改名、暂停、继续与删除 |
|
|
862
952
|
|
|
863
953
|
---
|
|
864
954
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
# dsh-plugin-mobile-gateway
|
|
6
6
|
|
|
7
|
-
DeepSeek Harness
|
|
7
|
+
DeepSeek Harness 的设备鉴权移动网关,支持会话与实时事件、任务列表和当前 Goal 同步及管理、服务端驱动的命令和技能菜单、Human-in-the-loop、图片及文件传输。安装后,Harness WebUI 左侧边栏会出现“移动设备”入口,可直接开启网关、生成配对二维码和管理可信设备。
|
|
8
|
+
|
|
9
|
+
> v0.7.0:移动端现可与 WebUI 对齐展示任务列表和当前 Goal;支持 Goal 改名、暂停、继续和删除,并通过实时 projection 与 revision 校验处理多设备并发更新。
|
|
8
10
|
|
|
9
11
|
- WebSocket:`/ws/mobile`
|
|
10
12
|
- 局域网:`ws://<局域网 IP>:3081/ws/mobile`
|
package/lib/index.mjs
CHANGED
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
// { "type": "context-usage", "sessionId" } -> tokenUsage + contextPressure projections
|
|
58
58
|
// { "type": "session-stats", "sessionId" } -> sessionStats + tokenUsage projections
|
|
59
59
|
// (the input-box stats strip source)
|
|
60
|
+
// { "type": "tasks", "sessionId" } -> current todo-list projection
|
|
61
|
+
// { "type": "goal", "sessionId" } -> current goal projection
|
|
62
|
+
// { "type": "goal-edit", "sessionId", "ref", "objective"?, "maxGoalRounds"? }
|
|
63
|
+
// { "type": "goal-pause"|"goal-resume"|"goal-clear", "sessionId", "ref" }
|
|
60
64
|
// { "type": "agent-presets" } -> preset roster (+ isDefault)
|
|
61
65
|
// { "type": "defaults" } -> default agent preset + default permission
|
|
62
66
|
// { "type": "set-default", "target": "agent-preset"|"permission", "value" }
|
|
@@ -64,7 +68,7 @@
|
|
|
64
68
|
// { "type": "question-cancel", "rpcId", "sessionId" }
|
|
65
69
|
// { "type": "approval-response", "rpcId", "sessionId", "approvalId",
|
|
66
70
|
// "outcome": "allowed-once"|"rejected" }
|
|
67
|
-
// server -> client: { "kind": "hello", "protocol": 3, "capabilities": ["images", "commands", "file-downloads"],
|
|
71
|
+
// server -> client: { "kind": "hello", "protocol": 3, "capabilities": ["images", "commands", "tasks", "goals", "file-downloads"],
|
|
68
72
|
// "authenticated", "device"?, "port", "clients" }
|
|
69
73
|
// { "kind": "pong", "at" }
|
|
70
74
|
// { "kind": "subscribed", "sessionId" }
|
|
@@ -86,6 +90,12 @@
|
|
|
86
90
|
// { "kind": "file-download-cancelled", "transferId" }
|
|
87
91
|
// { "kind": "error", "code", "message", "requestType"?, "sessionId"? }
|
|
88
92
|
// { "kind": "event", "sessionId", "seq", "time", "event": { ... } }
|
|
93
|
+
// { "kind": "tasks", "sessionId", "asOfSeq", "todos" }
|
|
94
|
+
// { "kind": "goal", "sessionId", "asOfSeq", "goal" }
|
|
95
|
+
// { "kind": "goal-edit"|"goal-pause"|"goal-resume", "sessionId", "ref" }
|
|
96
|
+
// { "kind": "goal-clear", "sessionId", "cleared": true }
|
|
97
|
+
// { "kind": "tasks-updated", "sessionId", "asOfSeq", "todos" }
|
|
98
|
+
// { "kind": "goal-updated", "sessionId", "asOfSeq", "goal" }
|
|
89
99
|
// { "kind": "question-requested", "rpcId", "sessionId", "questions", "replay"? }
|
|
90
100
|
// { "kind": "question-response", "rpcId", "sessionId", "action", "accepted", "reason"? }
|
|
91
101
|
// { "kind": "question-resolved", "rpcId", "sessionId", "outcome" }
|
|
@@ -958,6 +968,54 @@ function requireSessionId(msg) {
|
|
|
958
968
|
return { value: sessionId }
|
|
959
969
|
}
|
|
960
970
|
|
|
971
|
+
// Goal mutations use DSH's compare-and-set reference. Returning a stale
|
|
972
|
+
// revision is intentionally rejected by DSH instead of overwriting a newer
|
|
973
|
+
// WebUI/mobile update.
|
|
974
|
+
function requireGoalRef(msg, sessionId) {
|
|
975
|
+
const ref = msg.ref
|
|
976
|
+
const id = ref && typeof ref.id === 'string' && ref.id.trim() !== '' ? ref.id.trim() : null
|
|
977
|
+
const revision = ref && typeof ref.revision === 'number' && Number.isSafeInteger(ref.revision) && ref.revision > 0
|
|
978
|
+
? ref.revision
|
|
979
|
+
: null
|
|
980
|
+
if (!id || revision === null) {
|
|
981
|
+
return {
|
|
982
|
+
error: {
|
|
983
|
+
kind: 'error',
|
|
984
|
+
code: 'bad-request',
|
|
985
|
+
message: `${typeof msg.type === 'string' ? msg.type : 'goal mutation'} requires ref.id and a positive integer ref.revision`,
|
|
986
|
+
requestType: typeof msg.type === 'string' ? msg.type : 'goal',
|
|
987
|
+
sessionId,
|
|
988
|
+
},
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return { value: { id, revision } }
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
async function readSessionProjection(api, type, sessionId, key) {
|
|
995
|
+
const history = await proxyQuery(api, 'history', api.sessions.history.bind(api.sessions), { sessionId })
|
|
996
|
+
if (history.kind !== 'history') return history
|
|
997
|
+
const values = (history.projections && history.projections.values) || {}
|
|
998
|
+
return {
|
|
999
|
+
kind: type,
|
|
1000
|
+
sessionId,
|
|
1001
|
+
asOfSeq: history.projections ? history.projections.asOfSeq : undefined,
|
|
1002
|
+
[key]: Object.prototype.hasOwnProperty.call(values, key) ? values[key] : null,
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
async function mutateGoal(api, type, method, payload) {
|
|
1007
|
+
if (!api.goals || typeof api.goals[method] !== 'function') {
|
|
1008
|
+
return {
|
|
1009
|
+
kind: 'error',
|
|
1010
|
+
code: 'unsupported',
|
|
1011
|
+
message: 'goal management is unavailable in this DSH host',
|
|
1012
|
+
requestType: type,
|
|
1013
|
+
sessionId: payload.sessionId,
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return proxyQuery(api, type, api.goals[method].bind(api.goals), payload)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
961
1019
|
// ---------------------------------------------------------------------------
|
|
962
1020
|
// History frame sizing: raw session logs can be huge (trajectory, tool output,
|
|
963
1021
|
// context), so a single history response must never blow the client's
|
|
@@ -1515,6 +1573,39 @@ async function handleQuery(api, typertGateway, agentDefaultModel, msg) {
|
|
|
1515
1573
|
contextPressure: values.contextPressure || null,
|
|
1516
1574
|
}
|
|
1517
1575
|
}
|
|
1576
|
+
if (msg.type === 'tasks') {
|
|
1577
|
+
const sessionId = requireSessionId(msg)
|
|
1578
|
+
if (sessionId.error) return sessionId.error
|
|
1579
|
+
return readSessionProjection(api, 'tasks', sessionId.value, 'todos')
|
|
1580
|
+
}
|
|
1581
|
+
if (msg.type === 'goal') {
|
|
1582
|
+
const sessionId = requireSessionId(msg)
|
|
1583
|
+
if (sessionId.error) return sessionId.error
|
|
1584
|
+
return readSessionProjection(api, 'goal', sessionId.value, 'goal')
|
|
1585
|
+
}
|
|
1586
|
+
if (msg.type === 'goal-edit') {
|
|
1587
|
+
const sessionId = requireSessionId(msg)
|
|
1588
|
+
if (sessionId.error) return sessionId.error
|
|
1589
|
+
const ref = requireGoalRef(msg, sessionId.value)
|
|
1590
|
+
if (ref.error) return ref.error
|
|
1591
|
+
const payload = { sessionId: sessionId.value, ref: ref.value }
|
|
1592
|
+
if (typeof msg.objective === 'string' && msg.objective.trim() !== '') payload.objective = msg.objective.trim()
|
|
1593
|
+
if (typeof msg.maxGoalRounds === 'number' && Number.isSafeInteger(msg.maxGoalRounds) && msg.maxGoalRounds > 0) {
|
|
1594
|
+
payload.maxGoalRounds = msg.maxGoalRounds
|
|
1595
|
+
}
|
|
1596
|
+
if (payload.objective === undefined && payload.maxGoalRounds === undefined) {
|
|
1597
|
+
return { kind: 'error', code: 'bad-request', message: 'goal-edit requires a non-empty objective or a positive maxGoalRounds', requestType: 'goal-edit', sessionId: sessionId.value }
|
|
1598
|
+
}
|
|
1599
|
+
return mutateGoal(api, 'goal-edit', 'edit', payload)
|
|
1600
|
+
}
|
|
1601
|
+
if (msg.type === 'goal-pause' || msg.type === 'goal-resume' || msg.type === 'goal-clear') {
|
|
1602
|
+
const sessionId = requireSessionId(msg)
|
|
1603
|
+
if (sessionId.error) return sessionId.error
|
|
1604
|
+
const ref = requireGoalRef(msg, sessionId.value)
|
|
1605
|
+
if (ref.error) return ref.error
|
|
1606
|
+
const method = msg.type === 'goal-pause' ? 'pause' : msg.type === 'goal-resume' ? 'resume' : 'clear'
|
|
1607
|
+
return mutateGoal(api, msg.type, method, { sessionId: sessionId.value, ref: ref.value })
|
|
1608
|
+
}
|
|
1518
1609
|
if (msg.type === 'agent-presets') {
|
|
1519
1610
|
return proxyQuery(api, 'agent-presets', api.agentPresets.list.bind(api.agentPresets), {})
|
|
1520
1611
|
}
|
|
@@ -2263,7 +2354,9 @@ const plugin = {
|
|
|
2263
2354
|
msg.type === 'permission-options' || msg.type === 'permission' || msg.type === 'context-usage' ||
|
|
2264
2355
|
msg.type === 'agent-presets' || msg.type === 'defaults' || msg.type === 'set-default' ||
|
|
2265
2356
|
msg.type === 'session-stats' || msg.type === 'default-model' ||
|
|
2266
|
-
msg.type === 'save-default-model' || msg.type === 'fork' || msg.type === 'providers'
|
|
2357
|
+
msg.type === 'save-default-model' || msg.type === 'fork' || msg.type === 'providers' ||
|
|
2358
|
+
msg.type === 'tasks' || msg.type === 'goal' || msg.type === 'goal-edit' ||
|
|
2359
|
+
msg.type === 'goal-pause' || msg.type === 'goal-resume' || msg.type === 'goal-clear') {
|
|
2267
2360
|
handleQuery(api, typertGateway, agentDefaultModel, msg).then((frame) => {
|
|
2268
2361
|
if (frame && ws.readyState === 1) ws.send(JSON.stringify(frame))
|
|
2269
2362
|
})
|
|
@@ -2292,7 +2385,7 @@ const plugin = {
|
|
|
2292
2385
|
ws.send(JSON.stringify({
|
|
2293
2386
|
kind: 'hello',
|
|
2294
2387
|
protocol: 3,
|
|
2295
|
-
capabilities: ['images', 'commands', ...(options.fileDownloadsEnabled ? ['file-downloads'] : [])],
|
|
2388
|
+
capabilities: ['images', 'commands', 'tasks', 'goals', ...(options.fileDownloadsEnabled ? ['file-downloads'] : [])],
|
|
2296
2389
|
port: transport.port || webServer.port,
|
|
2297
2390
|
clients: clients.size,
|
|
2298
2391
|
authenticated: !!device,
|
|
@@ -2424,6 +2517,17 @@ const plugin = {
|
|
|
2424
2517
|
outcome: payload.outcome,
|
|
2425
2518
|
})
|
|
2426
2519
|
log(`approval resolved: rpcId=${rpcId} approvalId=${approvalId} session=${sessionId} outcome=${payload.outcome}`)
|
|
2520
|
+
} else if (payload.type === 'session/projection' && (payload.key === 'todos' || payload.key === 'goal')) {
|
|
2521
|
+
const sessionId = String(payload.sessionId)
|
|
2522
|
+
const kind = payload.key === 'todos' ? 'tasks-updated' : 'goal-updated'
|
|
2523
|
+
const valueKey = payload.key === 'todos' ? 'todos' : 'goal'
|
|
2524
|
+
broadcastInteractionFrame({
|
|
2525
|
+
kind,
|
|
2526
|
+
sessionId,
|
|
2527
|
+
asOfSeq: payload.seq,
|
|
2528
|
+
[valueKey]: payload.value,
|
|
2529
|
+
})
|
|
2530
|
+
log(`projection forwarded: key=${payload.key} session=${sessionId} seq=${payload.seq}`)
|
|
2427
2531
|
} else {
|
|
2428
2532
|
log(`interaction ignored: type=${payload.type}`)
|
|
2429
2533
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-mobile-gateway",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Secure mobile gateway for DeepSeek Harness with realtime sessions, tasks, goals, commands, approvals, and file transfers",
|
|
5
5
|
"main": "lib/index.mjs",
|
|
6
6
|
"files": [
|
|
7
7
|
"bin",
|