dsh-mobile-plugin 0.4.0 → 0.4.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/dsh-mobile-credentials/lib/credentials.js +8 -8
- package/host.js +22 -36
- package/lib/client.js +29 -82
- package/lib/desktop-view.cjs +5 -10
- package/lib/git/read.js +6 -8
- package/lib/host-compat.cjs +13 -25
- package/lib/host-guard.cjs +9 -39
- package/lib/host-patches.cjs +5 -16
- package/lib/http/routing.js +7 -27
- package/lib/impl.js +6 -8
- package/lib/mirror/pusher.cjs +33 -85
- package/lib/mirror-snapshot.cjs +6 -24
- package/lib/mobile/agent-presets.js +6 -26
- package/lib/mobile/approvals.js +18 -44
- package/lib/mobile/archive-clean.js +10 -19
- package/lib/mobile/attachments.js +11 -36
- package/lib/mobile/channels.js +15 -41
- package/lib/mobile/credential-upgrade.js +7 -16
- package/lib/mobile/credentials.js +5 -21
- package/lib/mobile/directory-browse.js +8 -27
- package/lib/mobile/directory-listing.js +10 -23
- package/lib/mobile/drive-roots.js +59 -0
- package/lib/mobile/envelope.js +4 -15
- package/lib/mobile/events.js +35 -65
- package/lib/mobile/extensions.js +5 -8
- package/lib/mobile/git-repos.js +6 -15
- package/lib/mobile/git.js +11 -19
- package/lib/mobile/history-derive.js +23 -41
- package/lib/mobile/host-bridge/endpoints.js +4 -6
- package/lib/mobile/host-bridge/event-synthesis.js +21 -35
- package/lib/mobile/host-bridge/shims.js +12 -22
- package/lib/mobile/host-bridge.js +8 -20
- package/lib/mobile/host-settings.js +9 -19
- package/lib/mobile/inbox-clean.js +7 -24
- package/lib/mobile/local-images.js +11 -17
- package/lib/mobile/monitor.js +8 -17
- package/lib/mobile/pairing.js +7 -19
- package/lib/mobile/plugin-self.js +5 -22
- package/lib/mobile/plugin-update.js +8 -27
- package/lib/mobile/plugins.js +6 -24
- package/lib/mobile/questions.js +15 -40
- package/lib/mobile/references.js +11 -31
- package/lib/mobile/relay-control.js +6 -27
- package/lib/mobile/rpc.js +5 -13
- package/lib/mobile/schedule.js +8 -33
- package/lib/mobile/self-check.cjs +9 -36
- package/lib/mobile/session-cache.js +13 -19
- package/lib/mobile/session-file.js +8 -21
- package/lib/mobile/session-flags.js +7 -13
- package/lib/mobile/session-mirror.js +8 -15
- package/lib/mobile/session-models.js +3 -3
- package/lib/mobile/session-schedule-mark.js +8 -14
- package/lib/mobile/session-send.js +13 -21
- package/lib/mobile/session-snapshot.js +4 -6
- package/lib/mobile/sessions.js +7 -23
- package/lib/mobile/stats-collect.cjs +10 -24
- package/lib/mobile/stats-ledger.cjs +13 -20
- package/lib/mobile/stream.js +12 -23
- package/lib/mobile/subagents.js +5 -18
- package/lib/mobile/uploads.js +8 -17
- package/lib/mobile/workspaces.js +20 -8
- package/lib/panel/index.html +7 -7
- package/lib/read-receipt.cjs +10 -21
- package/lib/relay/lan-network.cjs +14 -28
- package/lib/relay/lan-proxy.js +14 -21
- package/lib/relay/pairing-policy.js +6 -9
- package/lib/relay/preview.js +8 -25
- package/lib/relay/protocol.js +6 -13
- package/lib/relay/status.js +5 -8
- package/lib/relay/tunnel.js +9 -14
- package/lib/routes/control-plane.js +28 -45
- package/lib/routes/mobile-api.js +19 -32
- package/lib/session-cleanup.cjs +16 -27
- package/lib/shared/credentials-entry.js +4 -11
- package/lib/shared/dshtoken.js +4 -4
- package/lib/shared/e2ee-request.js +1 -1
- package/lib/shared/e2ee.js +8 -14
- package/lib/shared/fresh-request.js +6 -22
- package/lib/shared/http-util.js +3 -4
- package/lib/shared/list-revision.js +5 -14
- package/lib/shared/session-signal.js +7 -16
- package/lib/shared/sse-write.js +14 -20
- package/lib/shared/workspace-guard.js +7 -15
- package/lib/shell/device-ota.js +4 -6
- package/lib/shell/manager.js +12 -27
- package/lib/shell/ota-package.js +7 -12
- package/lib/shell/runtime.js +8 -16
- package/lib/shell/self-check-after-swap.js +9 -23
- package/lib/shell/updater.js +13 -26
- package/lib/sidecar-facade-worker.cjs +20 -33
- package/lib/sidecar-facade.cjs +7 -7
- package/lib/sidecar-lifecycle.cjs +7 -9
- package/lib/update-install.cjs +14 -59
- package/lib/update-registry.cjs +6 -18
- package/package.json +1 -1
|
@@ -77,12 +77,10 @@ async function buildStatsSnapshot(ctx, sessionId) {
|
|
|
77
77
|
/**
|
|
78
78
|
* 统计投影快照(handleStats 与 buildStatsSnapshot 共用)。
|
|
79
79
|
*
|
|
80
|
-
* 0.2.0 把整帧投影从
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* pending),排队条与计划条读的是 planMode。历史轮询是每 5 秒一次的热路径,多背一次
|
|
85
|
-
* 投影 RPC 划不来,所以宁可让统计单独走投影端点。
|
|
80
|
+
* 0.2.0 把整帧投影从 history/page 的返回值里拆走了,「拉一条历史顺带取投影」这条路断了;
|
|
81
|
+
* 改走 `apiProxy.sessions.projections({ request: { sessionId } })`,返回值是
|
|
82
|
+
* `{ asOfSeq, values }`。顺带把 plan 归一化成 planMode(排队条与计划条读它)。
|
|
83
|
+
* 历史轮询是每 5 秒一次的热路径,多背一次投影 RPC 划不来,所以统计单独走投影端点。
|
|
86
84
|
*/
|
|
87
85
|
async function statsProjections(ctx, sessionId) {
|
|
88
86
|
// 显式给 signal:宿主部分方法内部直接读 signal.aborted,不做 undefined 兜底。
|
package/lib/mobile/sessions.js
CHANGED
|
@@ -1,32 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* 移动端会话 API
|
|
4
|
+
* 移动端会话 API 的会话列表与新建会话。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* envelope.js 信封拆解(unwrap / envelopeValue),唯一的实现
|
|
12
|
-
* rpc.js 对宿主 apiProxy 的窄接口封装(callSessions / callAgentPresets)
|
|
13
|
-
* session-cache.js 按 ctx 隔离的状态表,全部 WeakMap 集中在这里
|
|
14
|
-
* attachments.js 图片附件还原
|
|
15
|
-
* completion.js 完成提醒状态机
|
|
16
|
-
* session-models.js 模型目录与切模型
|
|
17
|
-
* session-search.js 正文检索
|
|
18
|
-
* session-send.js 发消息 / slash 命令 / 读 / 附件重取 / 停 / 改名 / 分叉
|
|
19
|
-
* session-snapshot.js 消息与统计快照(镜像与分页共用)
|
|
20
|
-
* session-mirror.js 镜像整帧构建
|
|
21
|
-
* session-goal.js 目标条动作
|
|
22
|
-
* session-presets.js Agent 预设目录
|
|
23
|
-
* session-preview.js 列表行的 lastMessage 预览(按会话指纹复用 + 运行中会话的节流)
|
|
24
|
-
* session-list-shortcut.js `?rev=` 条件刷新记住的那份清单是谁
|
|
6
|
+
* 这个文件一度是一千九百多行的单体,HTTP 路由、宿主 RPC、缓存、事件派生全混在一起;现在
|
|
7
|
+
* 按域拆开:envelope(信封拆解)、rpc(窄接口封装)、session-cache(按 ctx 隔离的状态表)、
|
|
8
|
+
* attachments、completion、session-models、session-search、session-send、session-snapshot、
|
|
9
|
+
* session-mirror、session-goal、session-presets、session-preview、session-list-shortcut。
|
|
25
10
|
*
|
|
26
11
|
* 剩下的就是列表:GET /sessions(10 秒缓存 + fresh 绕过 + 完成态确认 + `?rev=` 条件刷新)、
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* 所以 lib/mobile 下所有 import 路径一行都不用改。
|
|
12
|
+
* 单会话摘要、正文水位探针、统计、新建会话、排队消息动作。本文件继续转出各子模块的导出,
|
|
13
|
+
* 所以 lib/mobile 下的 import 路径一行都不用改。
|
|
30
14
|
*/
|
|
31
15
|
|
|
32
16
|
const { sendJson, readJsonBody } = require('../shared/http-util');
|
|
@@ -3,26 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* 统计采集:把宿主每条会话的累计用量读出来,和水位一比,增量记进账本。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* 不重放会话事件:手机要的是「按天看个大概」,重放整条日志是每秒都在烧的开销。所以走水位差
|
|
7
|
+
* (宿主累计值 − 账本水位 = 这次入账的量),零依赖,代价是入账日期取「插件看到增量的那一天」,
|
|
8
|
+
* 跨零点的一轮会落在后一天。首采会把历史全量入账,日期取它最后一次活动的日期,算一次性回填。
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* 宿主累计值 − 账本记下的水位 = 这次该入账的量。
|
|
12
|
-
* 好处是零依赖(只用现有的会话投影接口,不需要宿主新能力,也不用装别家插件),
|
|
13
|
-
* 代价如实说:入账日期取「插件看到增量的那一天」,跨零点的一轮会落在后一天。
|
|
14
|
-
* 这是刻意的近似 —— 为一个几十分钟的边界去重放事件流不值得。
|
|
10
|
+
* 采集是按需的(打开统计页 / 下拉刷新 / 清理已归档前补一刀),没有后台定时器。
|
|
15
11
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 而不是从今天零点重新计数。
|
|
19
|
-
*
|
|
20
|
-
* 采集是**按需**的(打开统计页 / 下拉刷新 / 清理已归档前补一刀),没有后台定时器:
|
|
21
|
-
* 手机上没人看统计的时候不该让宿主一直读盘。
|
|
22
|
-
*
|
|
23
|
-
* 清理前那次补采(`skipPreview`)刻意不读「最后一句」:那一步要读会话日志,是整轮里
|
|
24
|
-
* 最重的一趟,而删除不可恢复、时间又卡在手机那条请求的预算里。为了多把几十上百条会话的
|
|
25
|
-
* 数字记进账本,宁可让首次见到的会话卡片少一行摘要 —— 标题与项目名还认得出来。
|
|
12
|
+
* 清理前那次补采(`skipPreview`)不读「最后一句」:那一步要读会话日志,是整轮最重的一趟,
|
|
13
|
+
* 而删除不可恢复、时间又卡在请求预算里。宁可让首次见到的会话卡片少一行摘要。
|
|
26
14
|
*/
|
|
27
15
|
|
|
28
16
|
const { unwrap } = require('./envelope.js');
|
|
@@ -39,11 +27,9 @@ const CONCURRENCY = 4;
|
|
|
39
27
|
/**
|
|
40
28
|
* 这一轮先扫「最近还在动」的会话,再按「最久没采过」补老账。
|
|
41
29
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* 采过的会顺手把时间推到当下、自动排到队尾,于是每一轮的预算都接着上一轮的地方往下走,
|
|
46
|
-
* 不需要额外持久化一个游标。
|
|
30
|
+
* 只按最后活动时间倒序扫会让最老的那批永远排在队尾、永远采不到。所以队列分两段:近 7 天有
|
|
31
|
+
* 动作的优先(它们才是数字在变的那批),其余按 `collectedAt` 升序 —— 采过的会把时间推到当下、
|
|
32
|
+
* 自动排到队尾,每一轮的预算接着上一轮往下走,不需要额外持久化游标。
|
|
47
33
|
*/
|
|
48
34
|
const RECENT_WINDOW_MS = 7 * 86400_000;
|
|
49
35
|
|
|
@@ -51,7 +37,7 @@ const RECENT_WINDOW_MS = 7 * 86400_000;
|
|
|
51
37
|
* 一次采集的时间预算。
|
|
52
38
|
*
|
|
53
39
|
* 手机上点开统计页时,插件会当场把每条会话的累计用量读一遍;一百多条会话串起来能拖到
|
|
54
|
-
*
|
|
40
|
+
* 十几秒(每条两趟宿主调用),用户看到的就是「点统计,转好久」。
|
|
55
41
|
* 所以给这一趟设上限:到点就停,把**已经采到的**那部分记进账本先回给手机,剩下的等
|
|
56
42
|
* 下一次打开 / 下拉刷新接着采 —— 水位差是累积的,采一半不会算错,只是数字暂时偏小。
|
|
57
43
|
*/
|
|
@@ -3,21 +3,15 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* 统计账本:按天累计的用量 + 每条会话的累计与身份(`$DSH_HOME/mobile-stats.json`)。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 表现为「今天的使用量突然掉了一半」)。所以采集一旦读到增量就落进这个文件,之后会话
|
|
9
|
-
* 在不在都不影响历史数字 —— 那张会话卡片只会被标成「已清理」。
|
|
6
|
+
* 它不寄在会话日志上:用户会清理已归档的会话,删的就是日志本身;统计每次现算的话,清理之后
|
|
7
|
+
* 那部分用量会凭空消失。一旦读到增量就落进这个文件,之后会话在不在都不影响历史数字。
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - 会话卡片保留最近 `MAX_SESSIONS` 条(按最后活动时间),更老的会话从卡片里消失,
|
|
16
|
-
* 但它们的量**仍然留在按天的账上**(总量不会因为裁剪而缩水)。卡片退场只是不再展示
|
|
17
|
-
* (`retired`),水位留着 —— 水位一丢,这条会话下次再有动作就会被当成首采、把整段
|
|
18
|
-
* 历史重记一遍,按天的账凭空多出一截。
|
|
9
|
+
* 口径(与桌面 dsh-context 的每日账本一致,只保留手机用得着的部分):一天的账落在「观测到
|
|
10
|
+
* 增量」的宿主本地日期上;计费 token = 未命中输入 + 缓存读 + 缓存写 + 输出,四个桶分开存;
|
|
11
|
+
* 会话卡片只留最近 `MAX_SESSIONS` 条,更老的退场(`retired`)但量仍留在按天账上,水位也留着
|
|
12
|
+
* —— 水位一丢,那条会话下次有动作会被当成首采、把整段历史重记一遍。
|
|
19
13
|
*
|
|
20
|
-
*
|
|
14
|
+
* 只做纯数据(读写 + 折算),不碰网络;采集在 stats-collect.cjs,路由在 stats.js。
|
|
21
15
|
*/
|
|
22
16
|
|
|
23
17
|
const fs = require('node:fs');
|
|
@@ -170,8 +164,8 @@ function sanitizeLedger(raw) {
|
|
|
170
164
|
}
|
|
171
165
|
}
|
|
172
166
|
const billed = sanitizeUsage(value.billed);
|
|
173
|
-
//
|
|
174
|
-
//
|
|
167
|
+
// 更早的账本没有每条会话的按天用量:把它的历史量记在它最后一次活动那天。
|
|
168
|
+
// 这跟「首采回填」是同一条近似,也是一次性回填 —— 之后就只记增量。
|
|
175
169
|
if (Object.keys(daily).length === 0 && billedOf(billed) > 0) {
|
|
176
170
|
daily[dayKeyOf(num(value.lastAt) || Date.now())] = { ...billed };
|
|
177
171
|
}
|
|
@@ -364,12 +358,11 @@ function usageInRange(record, startDay, today) {
|
|
|
364
358
|
/**
|
|
365
359
|
* 按范围折算成 App 要的总览(纯函数)。
|
|
366
360
|
*
|
|
367
|
-
*
|
|
368
|
-
* 由页面决定怎么省略日期标签。
|
|
361
|
+
* 逐日序列会把范围内每一天都列出来(没有活动的补 0),柱状图靠它保持等距。
|
|
369
362
|
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
363
|
+
* 三块口径必须对得上:`kpi` 是范围内总量,`sessions` 里的每张卡片是这条会话在同一个范围里
|
|
364
|
+
* 的用量,`retired` / `untracked` 把不在卡片上的那部分明说出来 —— 卡片加起来解释不了总量,
|
|
365
|
+
* 是页面被读成「数据对不上」的根源。
|
|
373
366
|
*/
|
|
374
367
|
function summarize(ledger, range, now) {
|
|
375
368
|
const today = dayKeyOf(now);
|
package/lib/mobile/stream.js
CHANGED
|
@@ -3,32 +3,21 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* dsh-mobile 流式:SSE 转发目标会话的增量事件。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* 订阅宿主事件流,只转发目标会话的 session/event 帧,每 15s 发一行注释加一帧
|
|
7
|
+
* `session/heartbeat`;客户端断开即取消订阅。心跳那帧不能省:注释行只是保活,客户端一个
|
|
8
|
+
* 事件都收不到,而「流被正常收尾」在 App 那边看起来和「还开着」一样,它需要一个自己数得到
|
|
9
|
+
* 的活口。心跳不带 seq、不进消息模型、不写缓存。
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* App 需要一个**自己数得到**的活口,两拍没到就主动断开重连(见 app 侧
|
|
13
|
-
* services/viewmodels/stream-liveness.ts)。这帧不带 seq、不进消息模型、不写缓存。
|
|
11
|
+
* 断线恢复:客户端重连带 `Last-Event-ID: <sessionId>:<seq>`(或 `lastSeq`),服务端先回放
|
|
12
|
+
* `seq > lastSeq` 的事件再接入实时流,并用 seq 去重。回放受窗口与字节数上限约束,超出的
|
|
13
|
+
* 缺口让客户端去拉权威快照。
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
* `
|
|
17
|
-
* 再无缝接入实时流,并用 seq 去重避免回放/实时交界处重复交付。由于宿主 history
|
|
18
|
-
* 只支持窗口回放,若断线窗口超过回放上限,客户端仍应在其 SSRD 重连补偿刷新
|
|
19
|
-
* 权威快照(/session/messages、/workspaces)兜底。
|
|
15
|
+
* 生命周期:所有失败路径都必须经 logStream 留痕(守卫逐条钉着字面量),收尾一定给客户端一帧
|
|
16
|
+
* `session/resync-required`;keepAlive 定时器要 unref。
|
|
20
17
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* 出流量的三个口子(2026-10-25,都在这一层收):
|
|
26
|
-
* - 草稿增量按 200ms 合并成一帧(`DRAFT_FLUSH_MS`):一帧固定成本两三百字节,而一个
|
|
27
|
-
* token 只有十几字节,逐帧发等于按帧收费;
|
|
28
|
-
* - 草稿的填充窗口 250ms(`DRAFT_PAD_INTERVAL_MS`):填充是按时间补的,120ms 就是
|
|
29
|
-
* 16KB/s 的底线,与正文多少无关;
|
|
30
|
-
* - 推理只推每一步的开头 600 字、工具结果只推前 2000 字:运行中用户不读它们,完整内容
|
|
31
|
-
* 仍在 `assistant/message` 与 `/session/messages` 里,一个字都不丢。
|
|
18
|
+
* 出流量收在三处:草稿增量按 200ms 合并成一帧(一帧固定成本两三百字节,token 才十几字节);
|
|
19
|
+
* 草稿填充窗口 250ms;推理只推每一步开头 600 字、工具结果只推前 2000 字,完整内容仍在
|
|
20
|
+
* `assistant/message` 与 `/session/messages` 里。
|
|
32
21
|
*/
|
|
33
22
|
|
|
34
23
|
const { sendJson } = require('../shared/http-util');
|
package/lib/mobile/subagents.js
CHANGED
|
@@ -3,25 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* 移动端「子代理」API:把某个会话派出去的子代理目录转给手机。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `origin === 'subagent'` 的行在侧栏里直接 continue 掉(app/src/drawerGroups.ts:122),
|
|
10
|
-
* 于是用户既不知道这个会话派过子代理,也没有任何入口能打开它 —— 子代理在手机上
|
|
11
|
-
* 等于不存在。
|
|
6
|
+
* 手机侧栏把 `origin === 'subagent'` 的行直接跳过,于是用户既不知道会话派过子代理,也没有
|
|
7
|
+
* 入口打开它。子会话在普通消息接口里只有带 durable parent address(parentSessionId + mode)
|
|
8
|
+
* 才能读,而 /sessions 只给 origin/parentSessionId,mode 来自这里的子代理目录。
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* 「这个会话有哪些子代理」——
|
|
16
|
-
* 那正是 subagent.list 的返回值(entries + parentAvailable),这里只做形状收敛、
|
|
17
|
-
* 限长与限条数,避免异常宿主把手机端列表撑爆。
|
|
18
|
-
*
|
|
19
|
-
* 宿主 subagent.list 的第二个形参是取消信号(内部 listChildren 会读 signal),
|
|
20
|
-
* 故与 session.search 一样显式传一个 AbortController 的 signal,不裸传 undefined。
|
|
21
|
-
*
|
|
22
|
-
* DSH 0.2.0 没有 subagent.list 这个 RPC 了,目录改由 host-bridge.js 从会话投影的
|
|
23
|
-
* subagentCatalog 重建(形状仍是 entries + parentAvailable),本文件对它的形状收敛、
|
|
24
|
-
* 限长与限条数一字未动。
|
|
10
|
+
* 0.2.0 没有 subagent.list 这个 RPC,目录由 host-bridge.js 从会话投影的 subagentCatalog
|
|
11
|
+
* 重建(形状仍是 entries + parentAvailable);本文件只做形状收敛、限长与限条数。
|
|
25
12
|
*/
|
|
26
13
|
|
|
27
14
|
const { sendJson, readJsonBody } = require('../shared/http-util');
|
package/lib/mobile/uploads.js
CHANGED
|
@@ -3,26 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* 移动端文件分片上传:把手机分享进来的文件落到宿主机固定收件目录。
|
|
5
5
|
*
|
|
6
|
-
* 四步:begin(登记文件名,拿 uploadId 与片大小)→ chunk(按 index 顺序追加)→
|
|
7
|
-
*
|
|
6
|
+
* 四步:begin(登记文件名,拿 uploadId 与片大小)→ chunk(按 index 顺序追加)→ finish
|
|
7
|
+
* (落盘成 `<收件根>/<日期>/<文件名>`,重名自动加后缀)→ abort(放弃)。
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* readJsonBody 默认只有 256KB。分片后单请求最大只有 2MB,与这些上限无关。
|
|
13
|
-
* - 手机侧网络不稳,分片能只重传坏掉的那一片,而不是整个文件。
|
|
9
|
+
* 分片而不是一次传完:单请求因此与中转的 `MAX_BODY_BYTES`、readJsonBody 的 256KB 上限无关,
|
|
10
|
+
* 手机网络不稳时也能只重传坏掉的那一片。片内容走 base64 而不是原始字节:App 侧没有发二进制
|
|
11
|
+
* body 的先例,图片通道本来就以 base64 走 JSON;代价是 33% 膨胀,仍远低于各层上限。
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
* (lib/relay/protocol.js 的 httpBody 帧),但 App 侧没有任何「发二进制 body」的
|
|
17
|
-
* 先例,RN 的 fetch 对 ArrayBuffer body 的支持也随版本变;图片通道本来就以 base64
|
|
18
|
-
* 走 JSON,这里沿用同一条确定能用的路。代价是 33% 膨胀(单片 2MB → 约 2.73MB),
|
|
19
|
-
* 仍远低于任何一层上限。
|
|
13
|
+
* 顺序与幂等:chunk 只接受下一片,重复送达同一片按重传处理,跳号直接 409。
|
|
20
14
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* 落盘根目录固定 `~/.dsh/mobile-inbox`(收件专目录,与工作区解耦,工作区被删也不
|
|
25
|
-
* 影响已收到的文件)。`DSH_MOBILE_INBOX` 可覆盖,只给测试用。
|
|
15
|
+
* 收件根固定 `~/.dsh/mobile-inbox`,与工作区解耦(工作区被删不影响已收到的文件);
|
|
16
|
+
* `DSH_MOBILE_INBOX` 可覆盖,只给测试用。
|
|
26
17
|
*/
|
|
27
18
|
|
|
28
19
|
const fsp = require('node:fs/promises');
|
package/lib/mobile/workspaces.js
CHANGED
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* dsh-mobile 工作区/目录浏览:透传宿主 apiProxy 的目录浏览与工作区采纳 RPC。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 移动端是远程遥控器,不能像桌面端那样弹被控端的原生目录对话框。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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,
|