dsh-autorestart 0.8.2 → 0.8.3

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/docs/README.zh.md CHANGED
@@ -20,7 +20,7 @@
20
20
  - 多个会话同时要求重启时合并成一次,且每个请求者都会被唤醒;
21
21
  - 带 goal 的会话不会把重启卡死:重启前自动**解除(disarm)**它的 goal(当前那一轮照常跑完,什么都不丢),重启之后自动重新上链;
22
22
  - 重启前有两道检查:宿主侧查一次,独立 helper 在杀进程前再查一次;
23
- - 界面上能看到状态:输入框工具行左侧的"待重启 · 等待 N 个任务结束"小胶囊、左侧栏会话标题旁的圆点;快重启时圆点呼吸,点胶囊看详情;
23
+ - 界面上能看到状态:输入框工具行左侧的"待重启 · 等待 N 个会话"小胶囊、左侧栏会话标题旁的圆点;快重启时圆点呼吸,点胶囊看详情;
24
24
  - 可配置:静默窗口(默认 2 秒)、杀进程前等待(默认 2 秒)、忙碌时是否允许强制重启等。
25
25
  - 同一套"等到大家都安静"的想法还做成了**收工关机**:左侧栏最底部(设置按钮旁边)一个月亮按钮 → 等所有会话收工 → 屏幕上倒数(一张浮动卡片:大字 + 鲸鱼娘)→ **关掉整台电脑**(见「收工关机」)。
26
26
 
@@ -330,7 +330,7 @@ agent/status(running) ──► 写 checkpoint(<DSH_HOME>\storages\dsh-autores
330
330
 
331
331
  ## 安装
332
332
 
333
- 当前版本:**0.8.2**。
333
+ 当前版本:**0.8.3**。
334
334
 
335
335
  ```powershell
336
336
  # 在 dsh 里(或对 Agent 说):把工作区里的这个包安装为 profile bundle
@@ -533,7 +533,7 @@ restart gate: action=wait reason=agents-busy-within-soft-window busyCount=1 quie
533
533
  不放名字,也不放任何"双击可以取消"之类的提示(没地方放)。**单击**它(或聚焦后按 Enter / 空格)会在上方
534
534
  弹出一个小面板:
535
535
 
536
- - 这次重启还在等几个任务结束;
536
+ - 这次重启还在等几个会话;
537
537
  - 还有几个**别的**会话也在等同一次重启;
538
538
  - 一个按钮:「取消我的这次重启」。
539
539
 
@@ -933,8 +933,8 @@ or by requesting the health path with ?gate=1.
933
933
 
934
934
  - 胶囊渲染在**输入框工具行左侧**(`conversation.input.left` 槽位),是一个高度约 22px 的小胶囊,**不额外占一行**(不再是输入框上方的一整行)。
935
935
  - 只有当健康路由 `/__dsh_autorestart/health?state=1` 返回 `pendingRestart: true` **且**本会话 id 出现在 `waiters` 列表里时才会出现;别的会话等待重启时你看不到它。
936
- - 文案有多种状态:还有别的**任务**占着时显示「待重启 · 等待 N 个任务结束」;都空闲时显示「待重启 · 空闲后自动重启」;被某个**自动续跑的 goal** 占着时显示「待重启 · 被某个自动续跑的 goal 占着」(见下面的升级 3)。
937
- - 悬停提示(原生 `title`):「等待其他任务结束(计数含"排队但还没开始"的任务)后自动重启;重启后会自动继续本会话进度」。
936
+ - 文案有多种状态:还有别的**会话**占着时显示「待重启 · 等待 N 个会话」;都空闲时显示「待重启 · 空闲后自动重启」;被某个**自动续跑的 goal** 占着时显示「待重启 · 被某个自动续跑的 goal 占着」(见下面的升级 3)。
937
+ - 悬停提示(原生 `title`):「等待其他会话结束(计数含"排队但还没开始"的会话)后自动重启;重启后会自动继续本会话进度」。
938
938
  - **每个占用者都说清自己是什么**:普通会话用它自己的标题;**子智能体**用"创建它的那个会话"的标题来称呼(显示成「子智能体(那个会话)」)。**原始的 `session-xxxx` 永远不会推给界面** —— 取不到标题时用一个通用说法。
939
939
  - 颜色走**品牌色(蓝紫)** `var(--dsw-alias-brand-primary, #5b8def)`,**不再用告警黄(warning)色**:胶囊里的小圆点和侧边栏圆点都是品牌色,胶囊边框保持中性(`--dsw-alias-border-l1`),文字保持中性。
940
940
  - 左侧边栏会话行的**行首 16px 状态格**里也会出现一个同款 8px 圆点(`sidebar.session.row.leading` 槽位,id `autorestart-restart-dot`,order 20):纯图标,完整说明放在悬停 `title` 里,aria-label 为「待重启」/「Pending restart」。
package/index.js CHANGED
@@ -387,7 +387,7 @@ function snapGraceChoice(value, fallback = DEFAULT_RESTART_GRACE_MS) {
387
387
  * `pluginVersion()`), but a bundled installation may not ship that file — so
388
388
  * the constant below is the last resort. A test keeps the two in sync.
389
389
  */
390
- const PLUGIN_VERSION_FALLBACK = '0.8.2'
390
+ const PLUGIN_VERSION_FALLBACK = '0.8.3'
391
391
 
392
392
  /**
393
393
  * The version of the graceful-shutdown FEATURE, not the version of the package.
@@ -2714,6 +2714,9 @@ const titleOfSummary = (item) => {
2714
2714
  at: record && typeof record === 'object' ? record.at : null,
2715
2715
  progress: record && typeof record === 'object' ? record.progress : null,
2716
2716
  blocking: record && typeof record === 'object' && Number.isFinite(record.blocked) ? record.blocked : blocked,
2717
+ // The measured size of the pre-restart snapshot: it survives the stage
2718
+ // change, so a page that missed the copy can still report it.
2719
+ snapshotBytes: record && typeof record === 'object' ? record.snapshotBytes : null,
2717
2720
  now: Date.now(),
2718
2721
  })
2719
2722
  }
@@ -4373,7 +4376,7 @@ const titleOfSummary = (item) => {
4373
4376
  /**
4374
4377
  * The stage of a restart, cut down for the health route (no long texts).
4375
4378
  *
4376
- * @returns {object} `{ phase, at, progress, slow, cancellable }`
4379
+ * @returns {object} `{ phase, at, blocking, progress, slow, cancellable }`
4377
4380
  */
4378
4381
  restartStageBrief() {
4379
4382
  const info = restartStageInfo()
@@ -4381,6 +4384,11 @@ const titleOfSummary = (item) => {
4381
4384
  phase: info.phase,
4382
4385
  at: info.at,
4383
4386
  elapsedMs: info.elapsedMs,
4387
+ // How many tasks still hold the gate: the centred overlay prints the
4388
+ // number, so it travels as a number and not only inside `text`.
4389
+ blocking: info.blocking,
4390
+ // The size of the pre-restart snapshot, on every stage (bytes).
4391
+ snapshotBytes: info.snapshotBytes,
4384
4392
  progress: info.progress === null
4385
4393
  ? null
4386
4394
  : { files: info.progress.files, bytes: info.progress.bytes, total: info.progress.total, percent: info.progress.percent, ageMs: info.progress.ageMs },
@@ -7879,11 +7887,16 @@ function registerHealthRoute(ctx, runtime, config, sleep) {
7879
7887
  // without one is sent exactly as the gate produced it. The goal
7880
7888
  // reason (`goal-active` / `goal-exhausted`) is attached as well, so
7881
7889
  // the client can tell a goal-driven blocker from a busy session.
7882
- const blockedBy = goalAwareBlockingDetails(runtime, verdict.blockedBy).map((entry) => {
7883
- if (!entry || typeof entry !== 'object') return entry
7884
- const title = runtime.sessionTitle(entry.sessionId)
7885
- return title ? { ...entry, title } : entry
7886
- })
7890
+ // A blocker that is not an object (an empty string still reported by
7891
+ // an older route, a null) is not a blocker: it has no session and no
7892
+ // name, and rendering one produced the bare ",," the user saw. It is
7893
+ // dropped HERE, so the interface never receives one.
7894
+ const blockedBy = goalAwareBlockingDetails(runtime, verdict.blockedBy)
7895
+ .filter((entry) => entry && typeof entry === 'object')
7896
+ .map((entry) => {
7897
+ const title = runtime.sessionTitle(entry.sessionId)
7898
+ return title ? { ...entry, title } : entry
7899
+ })
7887
7900
  stateBody = JSON.stringify({
7888
7901
  ok: true,
7889
7902
  pendingRestart,
@@ -7891,6 +7904,12 @@ function registerHealthRoute(ctx, runtime, config, sleep) {
7891
7904
  generation: live.generation,
7892
7905
  waiters,
7893
7906
  blockedBy,
7907
+ // THE ONE NUMBER both client surfaces read: the capsule and the
7908
+ // panel it opens. Counting the blockers in the browser is what made
7909
+ // "1 个会话在忙" sit next to "还在等 0 个任务" — the two surfaces
7910
+ // counted different filtered copies of the same list (user report,
7911
+ // 2026-10-29). A blocker without a readable name is still a task.
7912
+ busyCount: countBlockerTasks(verdict.blockedBy),
7894
7913
  allowRestart: verdict.allow === true,
7895
7914
  // WHY the gate says what it says. Without this, "allowRestart is
7896
7915
  // false and blockedBy is empty" looks like a contradiction: the
package/lib/checkpoint.js CHANGED
@@ -223,6 +223,11 @@ function normalizeRestartPhase(raw, now) {
223
223
  startedAt,
224
224
  at,
225
225
  progress,
226
+ // HOW BIG THE PRE-RESTART BACKUP WAS. It is kept across the stage change on
227
+ // purpose: a hosted profile can be 92 KB and the copy takes ~5 ms, so a page
228
+ // that polls every 2 s would otherwise never learn that a backup happened
229
+ // (user report, 2026-10-29). null = nothing was measured.
230
+ snapshotBytes: isFiniteNumber(raw.snapshotBytes) ? Math.max(0, Math.trunc(raw.snapshotBytes)) : null,
226
231
  blocked: isFiniteNumber(raw.blocked) ? Math.max(0, Math.trunc(raw.blocked)) : 0,
227
232
  };
228
233
  return { record, changed };
@@ -246,10 +251,18 @@ export function setRestartPhase(state, { phase, now = Date.now(), blocked = 0, r
246
251
  const previous = isObject(state.restartPhase) ? state.restartPhase : null;
247
252
  // A new restart starts its own clock; a stage change inside the same restart
248
253
  // keeps the start time, so "elapsed" is the whole restart and not one stage.
249
- const startedAt = restartStage && previous !== null && RESTART_PHASES.includes(previous.phase)
250
- && !(phase === 'waiting' && previous.phase !== 'waiting')
251
- ? previous.startedAt
252
- : moment;
254
+ const sameRestart = restartStage && previous !== null && RESTART_PHASES.includes(previous.phase)
255
+ && !(phase === 'waiting' && previous.phase !== 'waiting');
256
+ const startedAt = sameRestart ? previous.startedAt : moment;
257
+ // THE SIZE OF THE SNAPSHOT, measured once and then CARRIED: the live byte
258
+ // counter while the copy runs, the final size afterwards. A 92 KB profile is
259
+ // copied in ~5 ms, so this number is the only thing a polling interface can
260
+ // still say about a backup that really happened (user report, 2026-10-29).
261
+ const measuredBytes = previous !== null && isFiniteNumber(previous.snapshotBytes)
262
+ ? Math.max(0, Math.trunc(previous.snapshotBytes))
263
+ : (previous !== null && isObject(previous.progress) && isFiniteNumber(previous.progress.bytes)
264
+ ? Math.max(0, Math.trunc(previous.progress.bytes))
265
+ : null);
253
266
  const record = {
254
267
  phase,
255
268
  startedAt,
@@ -259,6 +272,9 @@ export function setRestartPhase(state, { phase, now = Date.now(), blocked = 0, r
259
272
  progress: phase === 'snapshotting' && previous !== null && previous.phase === 'snapshotting'
260
273
  ? previous.progress
261
274
  : null,
275
+ // A NEW restart starts with nothing measured; a stage change inside one
276
+ // restart keeps the size it already has.
277
+ snapshotBytes: sameRestart ? measuredBytes : null,
262
278
  blocked: isFiniteNumber(blocked) ? Math.max(0, Math.trunc(blocked)) : 0,
263
279
  };
264
280
  state.restartPhase = record;
@@ -289,6 +305,9 @@ export function setRestartProgress(state, { files = 0, bytes = 0, total = 0, fil
289
305
  filesTotal: number(filesTotal),
290
306
  at: moment,
291
307
  },
308
+ // The live byte counter IS the size of the snapshot so far: keeping it here
309
+ // means the number survives the moment the copy ends.
310
+ snapshotBytes: number(bytes),
292
311
  blocked: previous !== null && isFiniteNumber(previous.blocked) ? previous.blocked : 0,
293
312
  };
294
313
  state.updatedAt = moment;
@@ -152,6 +152,7 @@ export function estimateRemainingSeconds({ bytes = null, total = null, elapsedMs
152
152
  * cancellable: boolean,
153
153
  * cancelReason: string|null,
154
154
  * estimateSeconds: number|null,
155
+ * blocking: number|null,
155
156
  * text: { zh: string, en: string },
156
157
  * }}
157
158
  */
@@ -161,6 +162,7 @@ export function describeRestartPhase({
161
162
  at = null,
162
163
  progress = null,
163
164
  blocking = null,
165
+ snapshotBytes = null,
164
166
  now = Date.now(),
165
167
  } = {}) {
166
168
  const stage = isRestartPhase(phase) ? phase : 'idle';
@@ -206,10 +208,21 @@ export function describeRestartPhase({
206
208
 
207
209
  const estimateSeconds = stage === 'snapshotting' ? seconds : null;
208
210
  const done = measured === null ? null : formatBytes(measured.bytes);
211
+ // HOW MANY TASKS STILL HOLD THE GATE. The centred overlay says
212
+ // "还有 N 个会话" as a number of its own, so the count travels as a NUMBER
213
+ // and not only inside the sentence: a surface that has room for two words
214
+ // must not have to parse the long text.
215
+ //
216
+ // NULL means "the caller did not say", which is not the same as zero:
217
+ // Number(null) is 0, and a stage that was never given a count must not
218
+ // pretend the gate is free.
219
+ const blockingCount = blocking === null || blocking === undefined || blocking === ''
220
+ ? null
221
+ : nonNegative(blocking);
209
222
 
210
223
  let text;
211
224
  if (stage === 'waiting') {
212
- const count = nonNegative(blocking);
225
+ const count = blockingCount;
213
226
  const suffixZh = count && count > 0 ? '(还有 ' + count + ' 个会话在忙)' : '';
214
227
  const suffixEn = count && count > 0 ? ' (' + count + (count === 1 ? ' conversation' : ' conversations') + ' busy)' : '';
215
228
  text = {
@@ -217,20 +230,36 @@ export function describeRestartPhase({
217
230
  en: 'Waiting for every conversation to be idle…' + suffixEn,
218
231
  };
219
232
  } else if (stage === 'snapshotting') {
220
- // The number appears ONLY when it was measured. "预计 X 秒" needs a real
221
- // speed behind it, otherwise the text says what it knows and nothing more.
222
- const etaZh = estimateSeconds === null ? '' : ',预计 ' + estimateSeconds + ' 秒';
223
- const etaEn = estimateSeconds === null ? '' : ', about ' + estimateSeconds + 's left';
224
- const doneZh = done === null ? '' : ',已完成 ' + done;
225
- const doneEn = done === null ? '' : ', ' + done + ' done';
233
+ // ONE VOICE (user report, 2026-10-29): "正在备份" did not say WHAT was being
234
+ // backed up — the card says "DSH 重启前备份中", and this sentence (the
235
+ // settings page and the tooltip) says the same words, so no surface can
236
+ // disagree about what is running.
237
+ //
238
+ // The numbers appear ONLY when they were measured: "预计 X 秒" needs a real
239
+ // speed behind it, and "已完成 Y MB" a real byte count. With nothing
240
+ // measured yet the sentence claims nothing at all.
241
+ const measuredZh = [];
242
+ const measuredEn = [];
243
+ if (estimateSeconds !== null) {
244
+ measuredZh.push('预计 ' + estimateSeconds + ' 秒');
245
+ measuredEn.push('about ' + estimateSeconds + 's');
246
+ }
247
+ if (done !== null) {
248
+ measuredZh.push('已完成 ' + done);
249
+ measuredEn.push(done + ' done');
250
+ }
251
+ const detailZh = measuredZh.length === 0 ? '' : '(' + measuredZh.join(',') + ')';
252
+ const detailEn = measuredEn.length === 0 ? '' : ' (' + measuredEn.join(', ') + ')';
226
253
  text = {
227
- zh: '正在备份配置和插件' + etaZh + doneZh + '…',
228
- en: 'Backing up the configuration and the plugins' + etaEn + doneEn + '…',
254
+ zh: 'DSH 重启前备份中' + detailZh + '…',
255
+ en: 'Backing up before DSH restart' + detailEn + '…',
229
256
  };
230
257
  if (slow !== null) {
258
+ // Still the same subject when it is slow: the user must know WHICH backup
259
+ // is slow, not only that "the backup" is.
231
260
  text = {
232
- zh: '备份比预期慢(可能磁盘很忙),还在继续…' + (done === null ? '' : '(已完成 ' + done + ')'),
233
- en: 'The backup is slower than expected (the disk may be busy); it is still running…'
261
+ zh: 'DSH 重启前备份比预期慢(可能磁盘很忙),还在继续…' + (done === null ? '' : '(已完成 ' + done + ')'),
262
+ en: 'The DSH pre-restart backup is slower than expected (the disk may be busy); it is still running…'
234
263
  + (done === null ? '' : ' (' + done + ' done)'),
235
264
  };
236
265
  }
@@ -258,6 +287,18 @@ export function describeRestartPhase({
258
287
  cancellable: isCancellable(stage),
259
288
  cancelReason: cancelRefusalReason(stage),
260
289
  estimateSeconds,
290
+ // The number of tasks the gate still waits for, on its own: the centred
291
+ // overlay prints it, and a surface with room for two words must not have
292
+ // to parse the long sentence of `text`.
293
+ blocking: blockingCount,
294
+ // HOW BIG THE PRE-RESTART BACKUP IS (bytes). It is reported on EVERY stage
295
+ // of the restart, not only while the copy runs, so a card that appears a
296
+ // moment late can still say "backup done (92 KB)" instead of nothing.
297
+ // null means "nothing was measured", which is NOT the same as zero bytes:
298
+ // Number(null) is 0, and a receipt for a 0-byte backup would be a lie.
299
+ snapshotBytes: snapshotBytes === null || snapshotBytes === undefined || snapshotBytes === ''
300
+ ? null
301
+ : nonNegative(snapshotBytes),
261
302
  text,
262
303
  };
263
304
  }
package/locale/en.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "label": "Pending restart",
3
3
  "busyPrefix": " · waiting for ",
4
- "busySuffix": " task(s) to finish",
4
+ "busySuffix": " session(s)",
5
5
  "idle": " · restarts once idle",
6
6
  "goalActive": " · held by a self-continuing goal",
7
7
  "goalExhausted": " · a goal ran out of rounds",
@@ -12,7 +12,7 @@
12
12
  "parkBusy": "Parking…",
13
13
  "parkError": "Park failed: {message}",
14
14
  "tooltip": "Waits for the other sessions to become idle, then restarts automatically; this session resumes its progress after the restart.",
15
- "tooltipBusy": "Waits for the other tasks to finish (the count includes work that is queued but not started yet), then restarts automatically; this session resumes its progress after the restart.",
15
+ "tooltipBusy": "Waits for the other sessions to finish (the count includes work that is queued but not started yet), then restarts automatically; this session resumes its progress after the restart.",
16
16
  "tooltipWithError": "Waits for the other sessions to become idle, then restarts automatically; this session resumes its progress after the restart. {message}",
17
17
  "dotAria": "Pending restart",
18
18
  "panelHeading": "Pending restart",
@@ -48,7 +48,7 @@
48
48
  "durationMinutesShort": "{n} min",
49
49
  "durationHoursShort": "{n}h",
50
50
  "durationHoursMinutesShort": "{n}h {m}m",
51
- "cancelPanelTasks": "This restart is still waiting for {n} task(s) to finish",
51
+ "cancelPanelTasks": "Still waiting for {n} session(s)",
52
52
  "cancelPanelOthers": "{n} other session(s) are waiting for this restart too",
53
53
  "cancelPanelOthersAfter": "{n} session(s) are still waiting",
54
54
  "cancelPanelAria": "Pending restart details",
@@ -201,8 +201,10 @@
201
201
  "copyPath": "Copy the path",
202
202
  "copied": "The path is on the clipboard",
203
203
  "copyFailed": "The copy failed — please select the path above by hand",
204
- "withPlugins": "includes the installed plugins",
205
- "withoutPlugins": "configuration only"
204
+ "time": "Backup time",
205
+ "size": "Backup size",
206
+ "withPlugins": "(plugins included)",
207
+ "withoutPlugins": "(plugins excluded)"
206
208
  },
207
209
  "backupPlugins": {
208
210
  "label": "Include the installed plugins in the backup",
@@ -299,6 +301,11 @@
299
301
  "tierNone": "(this machine has no way to run it)"
300
302
  },
301
303
  "error": "Could not save the settings: {message}",
304
+ "saveNotice": {
305
+ "saved": "Saved",
306
+ "failed": "Not saved",
307
+ "dismiss": "Dismiss"
308
+ },
302
309
  "autostartKind": {
303
310
  "other": "something else",
304
311
  "windows-task": "Windows scheduled task",
@@ -321,6 +328,20 @@
321
328
  "self-supervised": "the supervisor of this plugin started it"
322
329
  }
323
330
  },
331
+ "restart": {
332
+ "stage": {
333
+ "snapshotting": "Backing up…",
334
+ "restarting": "Restarting…",
335
+ "back": "Back"
336
+ },
337
+ "overlay": {
338
+ "snapshotting": "Backing up before DSH restart",
339
+ "size": "{done} / {total}",
340
+ "sizeDone": "{done}",
341
+ "restarting": "Restarting…",
342
+ "back": "Back again"
343
+ }
344
+ },
324
345
  "meta": {
325
346
  "title": "dsh-autorestart — restart continuation",
326
347
  "description": "Keeps agent work alive across a DeepSeek Harness restart: a durable checkpoint, goal re-arm or a continuation message on boot, and an idle-aware graceful restart the agent can request itself."
package/locale/zh.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "label": "待重启",
3
3
  "busyPrefix": " · 等待 ",
4
- "busySuffix": " 个任务结束",
4
+ "busySuffix": " 个会话",
5
5
  "idle": " · 空闲后自动重启",
6
6
  "goalActive": " · 被某个自动续跑的 goal 占着",
7
7
  "goalExhausted": " · 某个任务的 goal 轮次额度已用完,无法续跑",
@@ -12,7 +12,7 @@
12
12
  "parkBusy": "停泊中…",
13
13
  "parkError": "停泊失败:{message}",
14
14
  "tooltip": "等待其他会话空闲后自动重启;重启后会自动继续本会话进度",
15
- "tooltipBusy": "等待其他任务结束(计数含“排队但还没开始”的任务)后自动重启;重启后本会话会自动继续进度",
15
+ "tooltipBusy": "等待其他会话结束(计数含“排队但还没开始”的会话)后自动重启;重启后本会话会自动继续进度",
16
16
  "tooltipWithError": "等待其他会话空闲后自动重启;重启后本会话会自动继续进度。{message}",
17
17
  "dotAria": "待重启",
18
18
  "panelHeading": "待重启",
@@ -48,7 +48,7 @@
48
48
  "durationMinutesShort": "{n} 分钟",
49
49
  "durationHoursShort": "{n} 小时",
50
50
  "durationHoursMinutesShort": "{n} 小时 {m} 分",
51
- "cancelPanelTasks": "这次重启还在等 {n} 个任务结束",
51
+ "cancelPanelTasks": "还在等 {n} 个会话",
52
52
  "cancelPanelOthers": "还有 {n} 个会话也在等这次重启",
53
53
  "cancelPanelOthersAfter": "还剩 {n} 个会话在等",
54
54
  "cancelPanelAria": "待重启详情",
@@ -201,8 +201,10 @@
201
201
  "copyPath": "复制路径",
202
202
  "copied": "路径已复制",
203
203
  "copyFailed": "复制失败,请手动选中上面的路径",
204
- "withPlugins": "含已装插件",
205
- "withoutPlugins": "只有配置"
204
+ "time": "备份时间",
205
+ "size": "备份大小",
206
+ "withPlugins": "(含已装插件)",
207
+ "withoutPlugins": "(不含已装插件)"
206
208
  },
207
209
  "backupPlugins": {
208
210
  "label": "备份包含已安装插件",
@@ -299,6 +301,11 @@
299
301
  "tierNone": "(这台机器没有可用机制)"
300
302
  },
301
303
  "error": "没能保存设置:{message}",
304
+ "saveNotice": {
305
+ "saved": "已生效",
306
+ "failed": "没保存成功",
307
+ "dismiss": "关闭"
308
+ },
302
309
  "autostartKind": {
303
310
  "other": "其他方式",
304
311
  "windows-task": "Windows 计划任务",
@@ -321,6 +328,20 @@
321
328
  "self-supervised": "它由本插件自己的守护在管"
322
329
  }
323
330
  },
331
+ "restart": {
332
+ "stage": {
333
+ "snapshotting": "重启前备份中",
334
+ "restarting": "正在重启…",
335
+ "back": "已回来"
336
+ },
337
+ "overlay": {
338
+ "snapshotting": "DSH 重启前备份中",
339
+ "size": "{done} / {total}",
340
+ "sizeDone": "{done}",
341
+ "restarting": "正在重启…",
342
+ "back": "已回来"
343
+ }
344
+ },
324
345
  "meta": {
325
346
  "title": "dsh-autorestart — 重启不断线",
326
347
  "description": "让 DeepSeek Harness 重启后接着干活:落盘的检查点、启动时重新武装 goal 或给一条续接提示,以及一次等人闲下来的优雅重启。"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-autorestart",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "type": "module",
5
5
  "description": "DeepSeek Harness plugin: restart without losing work. Waits until every conversation is idle, restarts the host, then automatically resumes the interrupted sessions. AI-generated, not human-reviewed: use with caution.",
6
6
  "license": "MIT",