dsh-mobile-plugin 0.4.1 → 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.
- package/README.md +71 -74
- package/dsh-mobile-credentials/lib/credentials.js +39 -16
- package/lib/impl.js +0 -4
- package/lib/index.js +1 -1
- package/lib/mobile/allowed-zone.js +100 -0
- package/lib/mobile/archive-clean.js +4 -3
- package/lib/mobile/attachments.js +5 -1
- package/lib/mobile/file-read.js +168 -0
- package/lib/mobile/history-derive.js +67 -98
- package/lib/mobile/local-images.js +38 -7
- package/lib/mobile/model-vendors.js +105 -0
- package/lib/mobile/plugin-market.js +475 -0
- package/lib/mobile/plugin-self.js +1 -1
- package/lib/mobile/plugin-update.js +1 -1
- package/lib/mobile/relay-control.js +131 -7
- package/lib/mobile/session-cwd.js +28 -0
- package/lib/mobile/session-file.js +1 -12
- package/lib/mobile/stats-collect.cjs +146 -15
- package/lib/mobile/stats-ledger.cjs +249 -59
- package/lib/mobile/stats-scheduler.cjs +41 -0
- package/lib/mobile/stream.js +30 -110
- package/lib/mobile/uploads.js +68 -5
- package/lib/mobile/vendor-logos.cjs +30 -0
- package/lib/mobile/wire-cut.js +212 -0
- package/lib/panel/index.html +2 -2
- package/lib/relay/apply-ledger.cjs +52 -0
- package/lib/relay/core.js +14 -3
- package/lib/relay/migrate.js +90 -0
- package/lib/relay/protocol.js +4 -0
- package/lib/relay/verify.js +52 -0
- package/lib/routes/control-plane.js +12 -3
- package/lib/routes/mobile-api.js +28 -2
- package/lib/session-cleanup.cjs +10 -0
- package/lib/shared/device-headers.js +12 -4
- package/lib/shell/updater.js +2 -2
- package/lib/sidecar-facade-worker.cjs +55 -8
- package/lib/sidecar-facade.cjs +11 -1
- package/lib/sidecar-lifecycle.cjs +14 -1
- package/lib/update-install.cjs +13 -6
- package/package.json +1 -1
- package/lib/auto-archive.cjs +0 -98
package/lib/update-install.cjs
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
* 别的插件刚发也会连累我们)。
|
|
14
14
|
*
|
|
15
15
|
* 包管理器优先用宿主发布的那个(profile 是宿主自带 pnpm 装的,PATH 上版本不同会
|
|
16
|
-
* ERR_PNPM_UNEXPECTED_STORE),找不到再退回 PATH。装之前先看有没有 agent 在跑;force
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* ERR_PNPM_UNEXPECTED_STORE),找不到再退回 PATH。装之前先看有没有 agent 在跑;force 与 auto
|
|
17
|
+
* 都跳过这道闸、也都不等发布晾置期(2026-10-26 用户要求自动更新一检测到新版就立刻装,与面板
|
|
18
|
+
* 上那颗「强制更新」同一个口径),两者的区别只剩「没新版时装不装」。顺序上先答「有没有新版」,
|
|
19
|
+
* 没东西可装时不该拿「有对话在跑」挡在前面。
|
|
19
20
|
*
|
|
20
21
|
* 只动 profile 的依赖树;找不到包管理器就如实失败;装完以盘上版本号为准,退出码 0 不算装上。
|
|
21
22
|
*/
|
|
@@ -243,6 +244,7 @@ function run(command, args, { cwd, env, timeoutMs, spawnImpl = spawn }) {
|
|
|
243
244
|
* @param {string} options.profileNodeModules profile 的 `node_modules` 目录
|
|
244
245
|
* @param {string} options.version 要装的版本(注册表给的)
|
|
245
246
|
* @param {boolean} [options.force] true = 跳过「新发布先晾一会儿」的门槛
|
|
247
|
+
* @param {boolean} [options.auto] 自动更新那一轮:同样跳过发布晾置期(见文件头)
|
|
246
248
|
* @param {() => any} [options.beforeInstall] 真正跑包管理器**之前**调一次(外壳用它把正在跑的
|
|
247
249
|
* 那一代复制进回退目录)。包管理器一跑就把旧的 store 目录清掉了,晚一步没得复制。
|
|
248
250
|
* @returns {Promise<{installed, skipped, beforeVersion, installedVersion, source, command, output, failure}>}
|
|
@@ -252,6 +254,7 @@ async function installLatest({
|
|
|
252
254
|
version,
|
|
253
255
|
name = DEFAULT_NAME,
|
|
254
256
|
force = false,
|
|
257
|
+
auto = false,
|
|
255
258
|
timeoutMs = INSTALL_TIMEOUT_MS,
|
|
256
259
|
env = process.env,
|
|
257
260
|
platform = process.platform,
|
|
@@ -331,8 +334,11 @@ async function installLatest({
|
|
|
331
334
|
|
|
332
335
|
const spec = `${name}@${version}`;
|
|
333
336
|
const releaseAgeFlag = '--config.minimum-release-age=0';
|
|
337
|
+
// `auto` 与 `force` 一样一开始就带上开关:自动更新要「一检测到就装」,不该先被那道策略挡回来
|
|
338
|
+
// 再重试一遍(多等一轮、日志里多一条失败)。
|
|
339
|
+
const skipReleaseAge = force || auto;
|
|
334
340
|
const args = picked.manager === 'pnpm'
|
|
335
|
-
? ['add', spec, '--config.auto-install-peers=false', ...(
|
|
341
|
+
? ['add', spec, '--config.auto-install-peers=false', ...(skipReleaseAge ? [releaseAgeFlag] : [])]
|
|
336
342
|
: ['install', spec, '--no-audit', '--no-fund'];
|
|
337
343
|
let command = [picked.manager, ...args].join(' ');
|
|
338
344
|
onEvent({ phase: 'install-started', command, manager: picked.kind });
|
|
@@ -356,7 +362,7 @@ async function installLatest({
|
|
|
356
362
|
* 重试一次只会把同一条错误再说一遍,还多花一次等待。
|
|
357
363
|
*/
|
|
358
364
|
const releaseAgeLike = /MINIMUM_RELEASE_AGE|minimumReleaseAge|minimum-release-age|release age/i;
|
|
359
|
-
const retried = !result.ok && picked.manager === 'pnpm' && !
|
|
365
|
+
const retried = !result.ok && picked.manager === 'pnpm' && !skipReleaseAge && releaseAgeLike.test(output);
|
|
360
366
|
if (retried) {
|
|
361
367
|
onEvent({ phase: 'release-age-retry' });
|
|
362
368
|
result = await spawnAdd([releaseAgeFlag]);
|
|
@@ -422,7 +428,8 @@ async function installLatest({
|
|
|
422
428
|
*
|
|
423
429
|
* `force` 是**用户明说「我知道,装吧」**的那一挡(面板上那颗「强制更新」):它同时跳过
|
|
424
430
|
* 这道闸和「新发布先晾一会儿」的门槛,而且不管有没有新版都装一遍。`auto`(宿主那一轮自动
|
|
425
|
-
*
|
|
431
|
+
* 更新)跳过同样的两道闸,只是仍然「没新版就不动盘」—— 2026-10-26 用户要求自动更新一检测到
|
|
432
|
+
* 新版就立刻装,所以它不再等发布晾置期,见文件头。
|
|
426
433
|
*/
|
|
427
434
|
async function installLatestIfNeeded(options = {}) {
|
|
428
435
|
const { profileNodeModules, force = false, auto = false, name = DEFAULT_NAME, agents = null } = options;
|
package/package.json
CHANGED
package/lib/auto-archive.cjs
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { unwrap } = require('./mobile/envelope.js')
|
|
4
|
-
|
|
5
|
-
const ARCHIVE_AFTER_MS = 24 * 60 * 60 * 1000
|
|
6
|
-
const SCAN_INTERVAL_MS = 12 * 60 * 60 * 1000
|
|
7
|
-
|
|
8
|
-
function projectCounts(items) {
|
|
9
|
-
const counts = new Map()
|
|
10
|
-
for (const item of items) {
|
|
11
|
-
const cwd = typeof item?.cwd === 'string' ? item.cwd.trim() : ''
|
|
12
|
-
if (cwd) counts.set(cwd, (counts.get(cwd) || 0) + 1)
|
|
13
|
-
}
|
|
14
|
-
return counts
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function archiveCandidates(items, now = Date.now()) {
|
|
18
|
-
const counts = projectCounts(items)
|
|
19
|
-
const cutoff = now - ARCHIVE_AFTER_MS
|
|
20
|
-
return items.filter(item => {
|
|
21
|
-
const cwd = typeof item?.cwd === 'string' ? item.cwd.trim() : ''
|
|
22
|
-
if (!cwd || (counts.get(cwd) || 0) <= 10) return false
|
|
23
|
-
if (item?.blank || item?.running || item?.sessionId === undefined) return false
|
|
24
|
-
return Number.isFinite(item.updatedAt) && item.updatedAt < cutoff
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function loggerOf(ctx) {
|
|
29
|
-
return ctx?.logger && typeof ctx.logger === 'object' ? ctx.logger : undefined
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function logFailure(ctx, sessionId, error) {
|
|
33
|
-
const logger = loggerOf(ctx)
|
|
34
|
-
const message = `automatic session archive failed for ${sessionId}: ${String(error?.message || error)}`
|
|
35
|
-
if (typeof logger?.warn === 'function') logger.warn(message)
|
|
36
|
-
else if (typeof logger?.error === 'function') logger.error(message)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function scanAndArchive(ctx, now = Date.now()) {
|
|
40
|
-
const api = ctx?.apiProxy
|
|
41
|
-
const list = api?.sessions?.list
|
|
42
|
-
const archive = api?.workspace?.archiveSession
|
|
43
|
-
if (typeof list !== 'function' || typeof archive !== 'function') return { scanned: 0, archived: 0, failed: 0 }
|
|
44
|
-
|
|
45
|
-
const response = await list({ rpcId: 'dsh-mobile-plugin-auto-archive', method: 'session.list', payload: {} })
|
|
46
|
-
// 回执是 { result: { ok, value } } 信封:早先这里直接读 response.value,
|
|
47
|
-
// 而 list() 的回执根本不在那一层——这个定时任务从来没真正扫到过会话。
|
|
48
|
-
const listed = unwrap(response)
|
|
49
|
-
const items = (listed.value && listed.value.items) || []
|
|
50
|
-
const candidates = archiveCandidates(items, now)
|
|
51
|
-
let archived = 0
|
|
52
|
-
let failed = 0
|
|
53
|
-
for (const item of candidates) {
|
|
54
|
-
try {
|
|
55
|
-
const result = await archive({
|
|
56
|
-
rpcId: 'dsh-mobile-plugin-auto-archive',
|
|
57
|
-
method: 'workspace.archiveSession',
|
|
58
|
-
payload: { sessionId: item.sessionId },
|
|
59
|
-
})
|
|
60
|
-
if (result?.ok === false) throw new Error(result.error?.message || 'archive rejected')
|
|
61
|
-
archived += 1
|
|
62
|
-
} catch (error) {
|
|
63
|
-
failed += 1
|
|
64
|
-
logFailure(ctx, item.sessionId, error)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return { scanned: items.length, archived, failed }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function installAutoArchive(ctx) {
|
|
71
|
-
let disposed = false
|
|
72
|
-
let inFlight
|
|
73
|
-
const scan = () => {
|
|
74
|
-
if (disposed || inFlight) return inFlight
|
|
75
|
-
inFlight = Promise.resolve().then(() => scanAndArchive(ctx)).catch(error => {
|
|
76
|
-
logFailure(ctx, 'scan', error)
|
|
77
|
-
return { scanned: 0, archived: 0, failed: 1 }
|
|
78
|
-
}).finally(() => { inFlight = undefined })
|
|
79
|
-
return inFlight
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
void scan()
|
|
83
|
-
const timer = setInterval(() => { void scan() }, SCAN_INTERVAL_MS)
|
|
84
|
-
return () => {
|
|
85
|
-
if (disposed) return
|
|
86
|
-
disposed = true
|
|
87
|
-
clearInterval(timer)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
module.exports = {
|
|
92
|
-
ARCHIVE_AFTER_MS,
|
|
93
|
-
SCAN_INTERVAL_MS,
|
|
94
|
-
archiveCandidates,
|
|
95
|
-
installAutoArchive,
|
|
96
|
-
projectCounts,
|
|
97
|
-
scanAndArchive,
|
|
98
|
-
}
|