dsh-mobile-plugin 0.1.96 → 0.1.98
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 +73 -50
- package/config/agent-presets/standard/agent.cordis.yml +13 -0
- package/lib/git/exec.js +129 -0
- package/lib/git/package.json +3 -0
- package/lib/git/parse.js +247 -0
- package/lib/git/read.js +617 -0
- package/lib/index.js +23 -29
- package/lib/mobile/archive-clean.js +31 -1
- package/lib/mobile/channels.js +583 -0
- package/lib/mobile/events.js +10 -8
- package/lib/mobile/git-repos.js +210 -0
- package/lib/mobile/git.js +603 -0
- package/lib/mobile/history-derive.js +79 -1
- package/lib/mobile/host-bridge/endpoints.js +3 -0
- package/lib/mobile/host-bridge/event-synthesis.js +109 -1
- package/lib/mobile/host-bridge/shims.js +9 -2
- package/lib/mobile/host-settings.js +41 -0
- package/lib/mobile/inbox-clean.js +337 -0
- package/lib/mobile/pairing.js +18 -1
- package/lib/mobile/questions.js +31 -9
- package/lib/mobile/schedule.js +440 -0
- package/lib/mobile/session-cache.js +18 -0
- package/lib/mobile/session-file.js +192 -0
- package/lib/mobile/session-model-options.js +184 -0
- package/lib/mobile/session-models.js +24 -2
- package/lib/mobile/session-schedule-mark.js +70 -0
- package/lib/mobile/session-snapshot.js +12 -1
- package/lib/mobile/sessions.js +8 -0
- package/lib/mobile/stats-collect.cjs +257 -0
- package/lib/mobile/stats-ledger.cjs +407 -0
- package/lib/mobile/stats.js +38 -0
- package/lib/mobile/stream.js +18 -10
- package/lib/mobile/uploads.js +24 -6
- package/lib/mobile/workspaces.js +30 -6
- package/lib/panel/index.html +4 -4
- package/lib/relay/lan-proxy.js +10 -1
- package/lib/routes/control-plane.js +38 -0
- package/lib/routes/mobile-api.js +70 -1
- package/lib/session-cleanup.cjs +8 -0
- package/lib/shared/sse-write.js +17 -6
- package/lib/shell/device-ota.js +52 -2
- package/lib/shell/manager.js +15 -1
- package/lib/shell/updater.js +153 -6
- package/lib/update-install.cjs +11 -0
- package/package.json +3 -2
package/lib/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import path from 'node:path'
|
|
|
26
26
|
import { fileURLToPath } from 'node:url'
|
|
27
27
|
import { createRuntime } from './shell/runtime.js'
|
|
28
28
|
import * as updater from './shell/updater.js'
|
|
29
|
-
import { installedPackageDir, installedVersion, profileNodeModules } from './shell/manager.js'
|
|
29
|
+
import { installedPackageDir, installedVersion, profileNodeModules, snapshotRunningGeneration } from './shell/manager.js'
|
|
30
30
|
import { createDeviceOta } from './shell/device-ota.js'
|
|
31
31
|
|
|
32
32
|
export const name = 'dsh-mobile-plugin'
|
|
@@ -81,33 +81,25 @@ export function apply(ctx) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* 手动与自动走的是同一条路:手动只是「现在就做一次」。装的那一步报的 `restartRequired` 一律
|
|
87
|
-
* 不照做 —— 外壳自己换代码,这正是这个文件存在的全部理由。
|
|
84
|
+
* 装之前把正在跑的那一代备份进回退目录:包管理器一跑就没得备份了。失败不挡更新,只记一句。
|
|
88
85
|
*/
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
86
|
+
const snapshotRunning = () => {
|
|
87
|
+
const status = runtime.status()
|
|
88
|
+
const result = snapshotRunningGeneration(status)
|
|
89
|
+
if (result?.created) log('info', 'dsh-mobile-plugin: 更新前已把 %s 复制进回退目录', status.version)
|
|
90
|
+
else if (result?.error) log('warn', 'dsh-mobile-plugin: 回退副本没做成:%s', result.error)
|
|
91
|
+
return result
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 装新版(实现自己装,见 `shell/updater.js`),装完**就地换代**。编排、落账与「换不过来
|
|
96
|
+
* 就重试」都在 `updater.performUpdate` 里 —— 入口只留装载逻辑,行数钉着。
|
|
97
|
+
*/
|
|
98
|
+
async function applyUpdate({ force = false, auto = false } = {}) {
|
|
99
|
+
return updater.performUpdate({
|
|
100
|
+
runtime, base, hostService, updateState, force, auto, log,
|
|
101
|
+
beforeInstall: snapshotRunning,
|
|
97
102
|
})
|
|
98
|
-
let swapped = false
|
|
99
|
-
let status = runtime.status()
|
|
100
|
-
if (outcome.installed) {
|
|
101
|
-
status = await runtime.reload()
|
|
102
|
-
swapped = true
|
|
103
|
-
log('info', 'dsh-mobile-plugin: 已就地换到 %s(无需重启 DSH)', status.version)
|
|
104
|
-
} else if (outcome.state && outcome.state !== 'succeeded') {
|
|
105
|
-
log('warn', 'dsh-mobile-plugin: 更新没有成功(%s)%s', outcome.state, outcome.failure?.message || '')
|
|
106
|
-
}
|
|
107
|
-
updateState.lastApply = { ...outcome, swapped }
|
|
108
|
-
updateState.appliedAt = new Date().toISOString()
|
|
109
|
-
if (outcome.installed) updateState.lastError = null
|
|
110
|
-
return { ...outcome, swapped, status }
|
|
111
103
|
}
|
|
112
104
|
|
|
113
105
|
// 换代前要知道有没有请求在飞(见 runtime.drain)。宿主 webServer 没有中间件机制,所以
|
|
@@ -165,10 +157,12 @@ export function apply(ctx) {
|
|
|
165
157
|
.start(ctx)
|
|
166
158
|
.then(() => {
|
|
167
159
|
log('info', 'dsh-mobile-plugin: 已装载 %s(来源 %s)', runtime.status().version, runtime.status().source)
|
|
168
|
-
// 每 10 分钟让已落盘的正式版接管 OTA
|
|
160
|
+
// 每 10 分钟让已落盘的正式版接管 OTA,并补上「盘上比运行中新」的那一次换代;
|
|
161
|
+
// 自动安装仍由开关控制。补换代不受开关管:它不下载任何东西,只是让已经在盘上的代码生效。
|
|
169
162
|
const timer = setInterval(() => {
|
|
170
163
|
deviceOta.reconcileOfficial()
|
|
171
|
-
.then(() => updater.
|
|
164
|
+
.then(() => updater.reconcileInstalledGeneration({ runtime, log }))
|
|
165
|
+
.then(() => updater.autoUpdateEnabled() ? applyUpdate({ auto: true }) : null)
|
|
172
166
|
.catch(error => {
|
|
173
167
|
updateState.lastError = String(error?.message || error)
|
|
174
168
|
log('warn', 'dsh-mobile-plugin: 更新检查失败:%s', updateState.lastError)
|
|
@@ -179,7 +173,7 @@ export function apply(ctx) {
|
|
|
179
173
|
// 启动后延后一次:别和绑端口、连隧道抢带宽。
|
|
180
174
|
if (updater.autoUpdateEnabled()) {
|
|
181
175
|
const kick = setTimeout(() => {
|
|
182
|
-
applyUpdate().catch(error => {
|
|
176
|
+
applyUpdate({ auto: true }).catch(error => {
|
|
183
177
|
updateState.lastError = String(error?.message || error)
|
|
184
178
|
log('warn', 'dsh-mobile-plugin: 启动时的自动更新失败:%s', updateState.lastError)
|
|
185
179
|
})
|
|
@@ -20,8 +20,25 @@ const { withIdempotency, requestIdOf } = require('./idempotency.js');
|
|
|
20
20
|
const { canonicalDeviceKey, rememberDeletedSessionIds } = require('./session-cache.js');
|
|
21
21
|
const { invalidateSessionList } = require('./sessions.js');
|
|
22
22
|
const { unwrap } = require('./envelope.js');
|
|
23
|
+
const { collectStats } = require('./stats-collect.cjs');
|
|
23
24
|
const { queueArchivedClearIds } = require('../session-cleanup.cjs');
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* 清理前这次补采的预算。
|
|
28
|
+
*
|
|
29
|
+
* 统计页那次只给 `DEFAULT_BUDGET_MS`(1.5 秒)是为了别让用户点开统计转好久;清理是
|
|
30
|
+
* 不可恢复的删除,值得多等几秒把账补全。上限卡在 App 单发请求的 8 秒总预算之内
|
|
31
|
+
* (`app/src/api/core.ts` 的 `REQUEST_TIMEOUT_MS`),否则手机先报超时、清理由这边照旧
|
|
32
|
+
* 执行,用户会以为没成功。
|
|
33
|
+
*
|
|
34
|
+
* 同时这次补采跳过「最后一句」预览(`skipPreview`):那一步要读会话日志,是整轮里
|
|
35
|
+
* 最重的一趟。删日志前先把数字记住,比给即将消失的卡片多留一行摘要重要。
|
|
36
|
+
*
|
|
37
|
+
* 极端多的归档(几千条)仍可能到点停手 —— 彻底兜住要把补采挪到请求之外,那是另一套
|
|
38
|
+
* 生命周期;这里先把「几百条」这个真实量级覆盖掉,不让它再沿用统计页的 1.5 秒。
|
|
39
|
+
*/
|
|
40
|
+
const CLEAR_ARCHIVED_COLLECT_BUDGET_MS = 6_000;
|
|
41
|
+
|
|
25
42
|
/**
|
|
26
43
|
* POST /api/mobile/v1/workspace/clear-archived(token)
|
|
27
44
|
* body: { confirm: true } → { ok: true, deleted: N }。confirm 不为 true 一律 400。
|
|
@@ -44,6 +61,19 @@ async function handleClearArchived(ctx, req, res) {
|
|
|
44
61
|
const archivedIds = Array.isArray(workspaceResponse.value?.archivedSessionIds) ? workspaceResponse.value.archivedSessionIds : [];
|
|
45
62
|
const selected = archivedIds.filter((id) => typeof id === 'string' && id);
|
|
46
63
|
if (selected.length === 0) return { status: 200, body: { ok: true, deleted: 0 } };
|
|
64
|
+
// 清理之前先把这批会话的用量最后采一次:之后再读它们的统计就没了(日志要删),
|
|
65
|
+
// 而统计账本要保留历史 —— 漏掉这一刀,最后一段用量会凭空消失。
|
|
66
|
+
// 采不上不挡清理:统计偏旧好过用户点不动这个按钮。
|
|
67
|
+
try {
|
|
68
|
+
await collectStats(ctx, {
|
|
69
|
+
sessionIds: selected,
|
|
70
|
+
force: true,
|
|
71
|
+
budgetMs: CLEAR_ARCHIVED_COLLECT_BUDGET_MS,
|
|
72
|
+
skipPreview: true,
|
|
73
|
+
});
|
|
74
|
+
} catch {
|
|
75
|
+
/* 旁路 */
|
|
76
|
+
}
|
|
47
77
|
// 排不进队列就整次失败(路由回 500):回「已清理」却没排上,重启之后它们原样回来。
|
|
48
78
|
queueArchivedClearIds(selected);
|
|
49
79
|
// 宿主那份快照可能还带着它们,先在本进程压住:手机列表与归档列表立刻看不到。
|
|
@@ -55,4 +85,4 @@ async function handleClearArchived(ctx, req, res) {
|
|
|
55
85
|
sendJson(res, outcome.status, outcome.body);
|
|
56
86
|
}
|
|
57
87
|
|
|
58
|
-
module.exports = { handleClearArchived };
|
|
88
|
+
module.exports = { handleClearArchived, CLEAR_ARCHIVED_COLLECT_BUDGET_MS };
|