dsh-mobile-plugin 0.4.0 → 0.4.4

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.
Files changed (111) hide show
  1. package/README.md +71 -74
  2. package/dsh-mobile-credentials/lib/credentials.js +47 -24
  3. package/host.js +22 -36
  4. package/lib/client.js +29 -82
  5. package/lib/desktop-view.cjs +5 -10
  6. package/lib/git/read.js +6 -8
  7. package/lib/host-compat.cjs +13 -25
  8. package/lib/host-guard.cjs +9 -39
  9. package/lib/host-patches.cjs +5 -16
  10. package/lib/http/routing.js +7 -27
  11. package/lib/impl.js +6 -12
  12. package/lib/index.js +1 -1
  13. package/lib/mirror/pusher.cjs +33 -85
  14. package/lib/mirror-snapshot.cjs +6 -24
  15. package/lib/mobile/agent-presets.js +6 -26
  16. package/lib/mobile/allowed-zone.js +100 -0
  17. package/lib/mobile/approvals.js +18 -44
  18. package/lib/mobile/archive-clean.js +13 -21
  19. package/lib/mobile/attachments.js +16 -37
  20. package/lib/mobile/channels.js +15 -41
  21. package/lib/mobile/credential-upgrade.js +7 -16
  22. package/lib/mobile/credentials.js +5 -21
  23. package/lib/mobile/directory-browse.js +8 -27
  24. package/lib/mobile/directory-listing.js +10 -23
  25. package/lib/mobile/drive-roots.js +59 -0
  26. package/lib/mobile/envelope.js +4 -15
  27. package/lib/mobile/events.js +35 -65
  28. package/lib/mobile/extensions.js +5 -8
  29. package/lib/mobile/file-read.js +168 -0
  30. package/lib/mobile/git-repos.js +6 -15
  31. package/lib/mobile/git.js +11 -19
  32. package/lib/mobile/history-derive.js +85 -134
  33. package/lib/mobile/host-bridge/endpoints.js +4 -6
  34. package/lib/mobile/host-bridge/event-synthesis.js +21 -35
  35. package/lib/mobile/host-bridge/shims.js +12 -22
  36. package/lib/mobile/host-bridge.js +8 -20
  37. package/lib/mobile/host-settings.js +9 -19
  38. package/lib/mobile/inbox-clean.js +7 -24
  39. package/lib/mobile/local-images.js +48 -23
  40. package/lib/mobile/model-vendors.js +105 -0
  41. package/lib/mobile/monitor.js +8 -17
  42. package/lib/mobile/pairing.js +7 -19
  43. package/lib/mobile/plugin-market.js +475 -0
  44. package/lib/mobile/plugin-self.js +6 -23
  45. package/lib/mobile/plugin-update.js +8 -27
  46. package/lib/mobile/plugins.js +6 -24
  47. package/lib/mobile/questions.js +15 -40
  48. package/lib/mobile/references.js +11 -31
  49. package/lib/mobile/relay-control.js +136 -33
  50. package/lib/mobile/rpc.js +5 -13
  51. package/lib/mobile/schedule.js +8 -33
  52. package/lib/mobile/self-check.cjs +9 -36
  53. package/lib/mobile/session-cache.js +13 -19
  54. package/lib/mobile/session-cwd.js +28 -0
  55. package/lib/mobile/session-file.js +9 -33
  56. package/lib/mobile/session-flags.js +7 -13
  57. package/lib/mobile/session-mirror.js +8 -15
  58. package/lib/mobile/session-models.js +3 -3
  59. package/lib/mobile/session-schedule-mark.js +8 -14
  60. package/lib/mobile/session-send.js +13 -21
  61. package/lib/mobile/session-snapshot.js +4 -6
  62. package/lib/mobile/sessions.js +7 -23
  63. package/lib/mobile/stats-collect.cjs +154 -37
  64. package/lib/mobile/stats-ledger.cjs +258 -75
  65. package/lib/mobile/stats-scheduler.cjs +41 -0
  66. package/lib/mobile/stream.js +39 -130
  67. package/lib/mobile/subagents.js +5 -18
  68. package/lib/mobile/uploads.js +75 -21
  69. package/lib/mobile/vendor-logos.cjs +30 -0
  70. package/lib/mobile/wire-cut.js +212 -0
  71. package/lib/mobile/workspaces.js +20 -8
  72. package/lib/panel/index.html +7 -7
  73. package/lib/read-receipt.cjs +10 -21
  74. package/lib/relay/apply-ledger.cjs +52 -0
  75. package/lib/relay/core.js +14 -3
  76. package/lib/relay/lan-network.cjs +14 -28
  77. package/lib/relay/lan-proxy.js +14 -21
  78. package/lib/relay/migrate.js +90 -0
  79. package/lib/relay/pairing-policy.js +6 -9
  80. package/lib/relay/preview.js +8 -25
  81. package/lib/relay/protocol.js +9 -12
  82. package/lib/relay/status.js +5 -8
  83. package/lib/relay/tunnel.js +9 -14
  84. package/lib/relay/verify.js +52 -0
  85. package/lib/routes/control-plane.js +39 -47
  86. package/lib/routes/mobile-api.js +47 -34
  87. package/lib/session-cleanup.cjs +26 -27
  88. package/lib/shared/credentials-entry.js +4 -11
  89. package/lib/shared/device-headers.js +12 -4
  90. package/lib/shared/dshtoken.js +4 -4
  91. package/lib/shared/e2ee-request.js +1 -1
  92. package/lib/shared/e2ee.js +8 -14
  93. package/lib/shared/fresh-request.js +6 -22
  94. package/lib/shared/http-util.js +3 -4
  95. package/lib/shared/list-revision.js +5 -14
  96. package/lib/shared/session-signal.js +7 -16
  97. package/lib/shared/sse-write.js +14 -20
  98. package/lib/shared/workspace-guard.js +7 -15
  99. package/lib/shell/device-ota.js +4 -6
  100. package/lib/shell/manager.js +12 -27
  101. package/lib/shell/ota-package.js +7 -12
  102. package/lib/shell/runtime.js +8 -16
  103. package/lib/shell/self-check-after-swap.js +9 -23
  104. package/lib/shell/updater.js +15 -28
  105. package/lib/sidecar-facade-worker.cjs +75 -41
  106. package/lib/sidecar-facade.cjs +18 -8
  107. package/lib/sidecar-lifecycle.cjs +21 -10
  108. package/lib/update-install.cjs +24 -62
  109. package/lib/update-registry.cjs +6 -18
  110. package/package.json +1 -1
  111. package/lib/auto-archive.cjs +0 -98
@@ -0,0 +1,212 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 移动线路的减法:宿主事件里哪些字段不下发给手机。
5
+ *
6
+ * App 的对话页不看「过程」(跑了哪些命令、读了哪些文件),所以推理、工具参数、工具结果的
7
+ * 正文与 write/edit 的 diff 都不上线路。工具参数这一笔两头都要收:落定的 tool-call 块只剩
8
+ * 骨架,流式 `tool-call-delta` 里逐帧推的那份 `argumentsDelta`(`write` / `edit` 就是整份
9
+ * 文件内容)也只留帧、不留内容。留下三样:工具结果里的图片(渲染层要画),以及三张轮尾卡的
10
+ * 料 —— 计划卡读 `exit_plan_mode` 的 `plan`、扩展卡读 `extension_present` 的 `extension`、
11
+ * 定时任务卡解析 `schedule_create` 结果的 JSON。
12
+ *
13
+ * 直播帧循环(stream.js)与历史派生(history-derive.js)调的是同一份实现。同一条规则写两份,
14
+ * 迟早漂成「直播里还带着推理、拉一次历史又没有了」。
15
+ */
16
+
17
+ /**
18
+ * 三张轮尾卡要用的工具:只有它们的 `arguments` 原文留着。
19
+ *
20
+ * 与 `history-derive.js` 的 `ENTRY_OUTCOME_TOOLS`(薄入口给哪些出卡调用留一行)不是同一张
21
+ * 表:那张管「一轮只留几条调用」,这张管「参数发不发」,只是成员恰好一样。新增轮尾卡时
22
+ * 两边都要看一眼。
23
+ */
24
+ const ARGUMENT_KEEP_TOOLS = new Set(['exit_plan_mode', 'extension_present', 'schedule_create']);
25
+
26
+ /**
27
+ * 结果正文唯一保留的一项:定时任务卡解析它上面的 JSON,解不出来就不出卡(见 App 的
28
+ * `turn-tail-cards`),所以这份正文一个字都不能少、也不能截断。
29
+ */
30
+ const OUTPUT_KEEP_TOOLS = new Set(['schedule_create']);
31
+
32
+ /** 这一块内容是不是图片(附件引用、内联字节、还原失败三种形状都算)。 */
33
+ function isImagePart(part) {
34
+ return Boolean(part) && typeof part === 'object' && part.type === 'image';
35
+ }
36
+
37
+ /** 工具结果的内容块:只留图片块,正文(text 与其它一切)整条不要。 */
38
+ function cutToolResultParts(parts) {
39
+ if (!Array.isArray(parts)) return parts;
40
+ const kept = parts.filter(isImagePart);
41
+ return kept.length === parts.length ? parts : kept;
42
+ }
43
+
44
+ /**
45
+ * 工具结果的内容:两种形状都认(当前是块数组、老日志是 `{ isError, content }` 信封),
46
+ * 信封壳必须原样留着 —— 结果级失败态挂在它上面(与 `history-derive.js` 的 `toolResultParts`
47
+ * 同一口径),剥掉壳子失败的工具就会显示成成功。
48
+ */
49
+ function cutToolResultContent(content) {
50
+ if (!Array.isArray(content)) return content;
51
+ const first = content[0];
52
+ const envelope = content.length === 1 && first && typeof first === 'object' && typeof first.type !== 'string' ? first : null;
53
+ if (!envelope) return cutToolResultParts(content);
54
+ const inner = Array.isArray(envelope.content) ? envelope.content : [];
55
+ const kept = cutToolResultParts(inner);
56
+ return kept === inner ? content : [{ ...envelope, content: kept }];
57
+ }
58
+
59
+ /**
60
+ * 一条 `tool/result` 事件:只留 callId、状态与图片(`state` 由 `message.isError` 与
61
+ * `data.error.code` 派生,两者都要留着),正文只给 `OUTPUT_KEEP_TOOLS` 里的工具留着,
62
+ * `data.meta`(write/edit 的已应用 diff)整条丢掉。
63
+ */
64
+ function cutToolResultEvent(event, toolName) {
65
+ const data = event && event.data;
66
+ if (!data || typeof data !== 'object') return event;
67
+ const message = data.message;
68
+ if (!message || typeof message !== 'object') return event;
69
+ const content = OUTPUT_KEEP_TOOLS.has(toolName) ? message.content : cutToolResultContent(message.content);
70
+ if (content === message.content && data.meta === undefined) return event;
71
+ const { meta: _droppedDiffs, ...rest } = data;
72
+ return { ...event, data: { ...rest, message: content === message.content ? message : { ...message, content } } };
73
+ }
74
+
75
+ /**
76
+ * 一块 assistant 内容:推理整块不要,tool-call 收成骨架 `{ type, id, name }`。
77
+ * 返回 `null` 表示这一块不下发;返回原对象表示原样下发。
78
+ */
79
+ function cutAssistantBlock(block) {
80
+ if (!block || typeof block !== 'object') return block;
81
+ if (block.type === 'reasoning') return null;
82
+ if (block.type !== 'tool-call') return block;
83
+ if (ARGUMENT_KEEP_TOOLS.has(block.name)) return block;
84
+ const skeleton = { type: 'tool-call' };
85
+ if (block.id !== undefined) skeleton.id = block.id;
86
+ if (block.name !== undefined) skeleton.name = block.name;
87
+ return skeleton;
88
+ }
89
+
90
+ /** assistant/message 的内容块:逐块过上面那一条规则(没改动的帧原样返回,省一次复制)。 */
91
+ function cutAssistantContent(content) {
92
+ if (!Array.isArray(content)) return content;
93
+ let changed = false;
94
+ const out = [];
95
+ for (const block of content) {
96
+ const kept = cutAssistantBlock(block);
97
+ if (kept !== block) changed = true;
98
+ if (kept !== null) out.push(kept);
99
+ }
100
+ return changed ? out : content;
101
+ }
102
+
103
+ /** 一条 `assistant/message` 事件:content 过上面那一条规则。 */
104
+ function cutAssistantEvent(event) {
105
+ const data = event && event.data;
106
+ const message = data && data.message;
107
+ if (!message || typeof message !== 'object' || !Array.isArray(message.content)) return event;
108
+ const content = cutAssistantContent(message.content);
109
+ if (content === message.content) return event;
110
+ return { ...event, data: { ...data, message: { ...message, content } } };
111
+ }
112
+
113
+ /**
114
+ * 一条 `tool/call` 事件:与 tool-call 块同一条规则 —— 只为三张轮尾卡的工具留 `arguments`
115
+ * (其余一律只留 `callId` / `name`)。宿主把这条事件原样追进会话日志,手机今天收得到每一次
116
+ * 调用的完整参数(bash 命令行、文件路径都在里面),而 App 对这条事件没有任何消费者。
117
+ * 认不出工具名时按「不留」处理:宁可少一份参数,也不把别的调用原样发出去。
118
+ */
119
+ function cutToolCallEvent(event) {
120
+ const data = event && event.data;
121
+ if (!data || typeof data !== 'object') return event;
122
+ if (typeof data.name === 'string' && ARGUMENT_KEEP_TOOLS.has(data.name)) return event;
123
+ if (data.arguments === undefined) return event;
124
+ const { arguments: _droppedArguments, ...rest } = data;
125
+ return { ...event, data: rest };
126
+ }
127
+
128
+ /**
129
+ * 一块流式增量:`tool-call-delta` 的参数正文不下发,帧本身照留(App 靠它知道有工具开始了,
130
+ * `index` / `id` / `name` 也就是配对要用的那几个字段)。这条是整个减法里最贵的一笔 ——
131
+ * `write` / `edit` 的参数就是整份文件内容,按 token 一帧地推给手机。
132
+ */
133
+ function cutAssistantChunkEvent(event) {
134
+ const data = event && event.data;
135
+ const chunk = data && data.chunk;
136
+ if (!chunk || typeof chunk !== 'object' || chunk.type !== 'tool-call-delta') return event;
137
+ if (chunk.argumentsDelta === undefined) return event;
138
+ const { argumentsDelta: _droppedArguments, ...rest } = chunk;
139
+ return { ...event, data: { ...data, chunk: rest } };
140
+ }
141
+
142
+ /**
143
+ * 事件类型 → 这条事件该怎么减:能减的类型走对应的一条规则,其余原样透传。
144
+ * 直播帧循环与回放都调它,一次判完,避免两处各判一半。
145
+ */
146
+ function cutEvent(event, names) {
147
+ if (!event || typeof event !== 'object') return event;
148
+ if (event.type === 'assistant/message') return cutAssistantEvent(event);
149
+ if (event.type === 'assistant/chunk') return cutAssistantChunkEvent(event);
150
+ if (event.type === 'tool/result') {
151
+ const source = event.data && event.data.message && event.data.message.source;
152
+ return cutToolResultEvent(event, names && source ? names.nameOf(source.callId) : undefined);
153
+ }
154
+ if (event.type === 'tool/call') return cutToolCallEvent(event);
155
+ return event;
156
+ }
157
+
158
+ /**
159
+ * callId → 工具名。
160
+ *
161
+ * `tool/result` 自己不带动工具名(只有 `source.callId`),而「这条结果的正文要不要留」
162
+ * 取决于工具名,所以要在事件流里把配对攒起来。三个来源:`tool/call` 事件、assistant 消息
163
+ * 里的 tool-call 块、流式 tool-call 增量。攒不到名字就按「不留正文」处理 —— 宁可少一张
164
+ * 定时任务卡(权威历史那条路会补上),也不能把别的工具输出当成卡的料发出去。
165
+ */
166
+ function createToolNameIndex() {
167
+ const byCallId = new Map();
168
+ const remember = (rawId, rawName) => {
169
+ if (rawId === undefined || rawId === null) return;
170
+ const callId = typeof rawId === 'string' ? rawId : String(rawId);
171
+ if (!callId || typeof rawName !== 'string' || rawName === '') return;
172
+ byCallId.set(callId, rawName);
173
+ };
174
+ const absorb = (event) => {
175
+ const data = event && event.data;
176
+ if (!data || typeof data !== 'object') return;
177
+ if (event.type === 'tool/call') {
178
+ remember(data.callId, data.name);
179
+ return;
180
+ }
181
+ if (event.type === 'assistant/chunk') {
182
+ const chunk = data.chunk;
183
+ if (chunk && typeof chunk === 'object') remember(chunk.id, chunk.name);
184
+ return;
185
+ }
186
+ if (event.type !== 'assistant/message') return;
187
+ const content = data.message && Array.isArray(data.message.content) ? data.message.content : null;
188
+ if (!content) return;
189
+ for (const block of content) {
190
+ if (block && block.type === 'tool-call') remember(block.id, block.name);
191
+ }
192
+ };
193
+ return {
194
+ absorb,
195
+ /** 一次把一批事件(回放取回的历史窗口)吸进来。 */
196
+ absorbAll: (events) => { for (const entry of events || []) absorb(entry && entry.event ? entry.event : entry); },
197
+ nameOf: (callId) => (callId === undefined || callId === null ? undefined : byCallId.get(typeof callId === 'string' ? callId : String(callId))),
198
+ };
199
+ }
200
+
201
+ module.exports = {
202
+ ARGUMENT_KEEP_TOOLS,
203
+ OUTPUT_KEEP_TOOLS,
204
+ cutEvent,
205
+ cutAssistantBlock,
206
+ cutAssistantContent,
207
+ cutAssistantEvent,
208
+ cutToolCallEvent,
209
+ cutToolResultContent,
210
+ cutToolResultEvent,
211
+ createToolNameIndex,
212
+ };
@@ -3,15 +3,12 @@
3
3
  /**
4
4
  * dsh-mobile 工作区/目录浏览:透传宿主 apiProxy 的目录浏览与工作区采纳 RPC。
5
5
  *
6
- * 移动端是**远程遥控器**——它不能像桌面端那样弹被控端的原生目录对话框。
6
+ * 移动端是远程遥控器,不能像桌面端那样弹被控端的原生目录对话框。
7
7
  *
8
- * 这里曾经写着一个**错的**前提:以为 directory-picker-auto 会在「宿主绑定局域网(非
9
- * 127.0.0.1)」时自动启用 `browse` 能力,于是 `apiProxy.host.listDirectory` /
10
- * `host.createDirectory` 在任何被控端都可用。实际不是:auto 只在「web 服务绑到非回环」
11
- * 或「平台不是 darwin/win32」时挂 browse,而**桌面 profile 宿主**(手机经 sidecar/中转
12
- * 连上的那种)两者都不满足,于是恒解析成 native —— 手机端一个条目都列不出来,只能登记
13
- * 一个恰好已存在的目录。现在这两个动词在宿主不服务 browse 时由插件自带实现兜底,见
14
- * lib/mobile/directory-browse.js。本模块仍只做与 sessions.js 相同的透传包装。
8
+ * directory-picker-auto 只在「web 服务绑到非回环」或「平台不是 darwin/win32」时挂 browse,
9
+ * 桌面 profile 宿主两者都不满足、恒解析成 native。所以宿主不服务 browse 时,两个目录动词由
10
+ * 插件自带实现兜底(见 lib/mobile/directory-browse.js);本模块只做与 sessions.js 相同的
11
+ * 透传包装。
15
12
  */
16
13
 
17
14
  const { sendJson, readJsonBody } = require('../shared/http-util');
@@ -27,6 +24,7 @@ const { withIdempotency, requestIdOf } = require('./idempotency.js');
27
24
  const { canonicalDeviceKey } = require('./session-cache.js');
28
25
  const { unwrap } = require('./envelope.js');
29
26
  const { describeProjectGit } = require('./git-repos.js');
27
+ const { listRoots } = require('./drive-roots.js');
30
28
 
31
29
  /** 调宿主 apiProxy.host.*(directory-picker 的 browse 能力)。 */
32
30
  function callHost(ctx, method, payload) {
@@ -167,6 +165,19 @@ async function handleListDirectory(ctx, req, res) {
167
165
  sendJson(res, 200, { ok: true, listing: value });
168
166
  }
169
167
 
168
+ /**
169
+ * GET /api/mobile/v1/directory/roots(token)
170
+ * 「添加工作区」的盘符层:宿主 browse 后端只列某一层目录的内容、从不枚举盘符,而手机端的
171
+ * 目录浏览器一打开就落在被控端主目录 —— Windows 上它永远在 C 盘,别的盘没有任何入口。
172
+ * Windows 回真实存在的盘符,其余平台回文件系统根;只有一项时 App 不显示这一层。
173
+ *
174
+ * 不走宿主:host.listDirectory 只认路径、给不出一份根清单,而枚举本身与目录选择器的部署
175
+ * 形态无关(browse / native 两种宿主下答案一样)。
176
+ */
177
+ async function handleDirectoryRoots(_ctx, _req, res) {
178
+ sendJson(res, 200, { ok: true, roots: await listRoots() });
179
+ }
180
+
170
181
  /**
171
182
  * POST /api/mobile/v1/directory/create(token){ path, name }
172
183
  * 透传宿主 host.createDirectory:在被控端 `path` 下新建名为 `name` 的文件夹。
@@ -339,6 +350,7 @@ module.exports = {
339
350
  handleMoveWorkspace,
340
351
  handleWorkspaces,
341
352
  handleListDirectory,
353
+ handleDirectoryRoots,
342
354
  handleCreateDirectory,
343
355
  handleCreateWorkspace,
344
356
  handleRenameWorkspace,