dsh-recall-plugin 2.3.0 → 2.3.2
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/CHANGELOG.md +45 -0
- package/README.en.md +4 -4
- package/README.md +14 -18
- package/lib/client.js +641 -484
- package/lib/config.js +53 -96
- package/lib/diagnostics.js +27 -52
- package/lib/dump-parse.js +79 -61
- package/lib/errors.js +41 -55
- package/lib/index.js +345 -446
- package/lib/maintenance.js +204 -275
- package/lib/routes-core.js +89 -140
- package/lib/routes-manage.js +571 -624
- package/lib/scripts.posix.js +452 -638
- package/lib/scripts.pwsh.js +537 -778
- package/lib/session-info.js +42 -57
- package/lib/snapshots.js +400 -588
- package/lib/store.js +364 -563
- package/package.json +8 -3
package/lib/routes-manage.js
CHANGED
|
@@ -1,624 +1,571 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
for (const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
let
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (!
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (
|
|
461
|
-
return
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
return {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
: await enqueue(() => maint.runGcAll())
|
|
573
|
-
// gc 后占用显著下降:立即失效占用缓存,设置页 refresh 必须看到新值
|
|
574
|
-
usageCache.payload = null
|
|
575
|
-
return { ok: true, gc: Boolean(done) }
|
|
576
|
-
}
|
|
577
|
-
if (op === 'lineage') {
|
|
578
|
-
// F1 / PF-4:返回全部已知工作区的 fork lineage(childId ↔ parentId
|
|
579
|
-
// 撤回链),供快照管理树聚族。原实现对每个 root 串行 loadLineage
|
|
580
|
-
// (每 root 一条进程,20 工作区 ≈ 10s,版本家族标记最后才亮)——
|
|
581
|
-
// LINEAGE 段并入 storesDump 后一次 dump 全拿,零新增进程。dump 的
|
|
582
|
-
// ==DIR 就是磁盘 store 目录(比 roots 全集更全,还免去对未知 root
|
|
583
|
-
// resolveStore 建目录的副作用);无 LINEAGE 段的旧输出按空 lineage
|
|
584
|
-
// 处理(parseStoresDump 容错)。
|
|
585
|
-
const hints = new Map()
|
|
586
|
-
for (const [root, st] of state.stores.entries()) {
|
|
587
|
-
if (st && st.dir) hints.set(st.dir, root)
|
|
588
|
-
}
|
|
589
|
-
let dump
|
|
590
|
-
try { dump = await dumpStores() } catch (error) { dump = new Map() }
|
|
591
|
-
const out = []
|
|
592
|
-
for (const info of dump.values()) {
|
|
593
|
-
for (const e of info.lineage || []) out.push(e)
|
|
594
|
-
}
|
|
595
|
-
return { ok: true, lineage: out }
|
|
596
|
-
}
|
|
597
|
-
return { ok: false, code: E.RECALL_UNKNOWN_OP, message: '未知的管理操作: ' + op }
|
|
598
|
-
},
|
|
599
|
-
|
|
600
|
-
// 设置页「插件配置」卡片恢复默认:整段清空 user 层回组合 base——官方
|
|
601
|
-
// settings RPC 的 replace 明确是「restoration/reset 路径」。老版本服务
|
|
602
|
-
// 没有 replace 时降级 settings.update 写 DEFAULTS。
|
|
603
|
-
'config-reset': async () => {
|
|
604
|
-
let settings = null
|
|
605
|
-
try { settings = ctx.get('settings') } catch (error) { settings = null }
|
|
606
|
-
if (!settings || typeof settings.update !== 'function') {
|
|
607
|
-
return { ok: false, code: E.RECALL_SETTINGS_UNAVAILABLE, message: '设置服务不可用:请在 profile 的 cordis.patch.yml 按 id: recall 覆盖配置' }
|
|
608
|
-
}
|
|
609
|
-
try {
|
|
610
|
-
if (typeof settings.replace === 'function') {
|
|
611
|
-
await settings.replace('dsh-recall', {})
|
|
612
|
-
} else {
|
|
613
|
-
await settings.update('dsh-recall', Object.assign({}, DEFAULTS, { baseExcludes: DEFAULTS.baseExcludes.slice() }))
|
|
614
|
-
}
|
|
615
|
-
} catch (error) {
|
|
616
|
-
return { ok: false, code: E.RECALL_SETTINGS_WRITE_FAILED, message: '恢复默认失败:' + String(error && error.message ? error.message : error) }
|
|
617
|
-
}
|
|
618
|
-
// 重置后热更运行中的 cfg(与 config-set 同链路的 watch 触发,这里做
|
|
619
|
-
// 双保险:descriptor 已变更,applyResolvedConfig 立即落地)
|
|
620
|
-
applyResolvedConfig(readSettings())
|
|
621
|
-
return { ok: true }
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
1
|
+
import { isSafetySnapshotId } from "./snapshots.js";
|
|
2
|
+
import { parseExcludeDump } from "./dump-parse.js";
|
|
3
|
+
function createRoutesManage(deps) {
|
|
4
|
+
const {
|
|
5
|
+
ctx,
|
|
6
|
+
rt,
|
|
7
|
+
snaps,
|
|
8
|
+
maint,
|
|
9
|
+
state,
|
|
10
|
+
cfg,
|
|
11
|
+
supported,
|
|
12
|
+
enqueue,
|
|
13
|
+
runLimited,
|
|
14
|
+
listExcludeFiles,
|
|
15
|
+
dumpStores,
|
|
16
|
+
locateSnapshotOnDisk,
|
|
17
|
+
collectAllSnapshotRecords,
|
|
18
|
+
listCache,
|
|
19
|
+
excludeCache,
|
|
20
|
+
usageCache,
|
|
21
|
+
sessionInfo,
|
|
22
|
+
titleFromEvents,
|
|
23
|
+
messageTextFromEvents,
|
|
24
|
+
applyResolvedConfig,
|
|
25
|
+
readSettings,
|
|
26
|
+
DEFAULTS,
|
|
27
|
+
E
|
|
28
|
+
} = deps;
|
|
29
|
+
const { sessionTitles, messageTexts, liveTitleFast, liveMessageTextFast } = sessionInfo;
|
|
30
|
+
async function buildListItems() {
|
|
31
|
+
const allItems = [];
|
|
32
|
+
const dump = await dumpStores();
|
|
33
|
+
const hints = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const [root, st] of state.stores.entries()) {
|
|
35
|
+
if (st && st.dir) hints.set(st.dir, root);
|
|
36
|
+
}
|
|
37
|
+
const byId = /* @__PURE__ */ new Map();
|
|
38
|
+
function push(id, time, root, sessionId) {
|
|
39
|
+
if (!id || typeof id !== "string") return;
|
|
40
|
+
if (isSafetySnapshotId(id)) return;
|
|
41
|
+
const old = byId.get(id);
|
|
42
|
+
if (!old) {
|
|
43
|
+
const rec = {
|
|
44
|
+
id,
|
|
45
|
+
time: typeof time === "number" ? time : 0,
|
|
46
|
+
root: root || null,
|
|
47
|
+
workspace: root ? root.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? null : null,
|
|
48
|
+
sessionId: sessionId || null,
|
|
49
|
+
sessionTitle: liveTitleFast(sessionId || "")
|
|
50
|
+
};
|
|
51
|
+
const liveText = liveMessageTextFast(sessionId || "", id);
|
|
52
|
+
if (liveText) rec.messageText = liveText;
|
|
53
|
+
byId.set(id, rec);
|
|
54
|
+
allItems.push(rec);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!old.root && root) {
|
|
58
|
+
old.root = root;
|
|
59
|
+
old.workspace = root.replace(/[\\/]+$/, "").split(/[\\/]/).pop() || null;
|
|
60
|
+
}
|
|
61
|
+
if (!old.sessionId && sessionId) {
|
|
62
|
+
old.sessionId = sessionId;
|
|
63
|
+
old.sessionTitle = liveTitleFast(sessionId);
|
|
64
|
+
}
|
|
65
|
+
if (!old.messageText && id) {
|
|
66
|
+
const t = liveMessageTextFast(sessionId, id);
|
|
67
|
+
if (t) old.messageText = t;
|
|
68
|
+
}
|
|
69
|
+
if (!old.time && time) old.time = time;
|
|
70
|
+
}
|
|
71
|
+
for (const [dir, info] of dump) {
|
|
72
|
+
const baseRoot = info.root || hints.get(dir) || null;
|
|
73
|
+
for (const e of info.entries || []) {
|
|
74
|
+
if (!e || typeof e.id !== "string") continue;
|
|
75
|
+
push(e.id, e.time, baseRoot || typeof e.root === "string" && e.root || null, e.sessionId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const [id, s] of state.snapshots.entries()) {
|
|
79
|
+
push(id, s.time, s.root, s.sessionId);
|
|
80
|
+
}
|
|
81
|
+
allItems.sort((a, b) => (b.time || 0) - (a.time || 0));
|
|
82
|
+
return allItems;
|
|
83
|
+
}
|
|
84
|
+
function refreshListCacheInBackground() {
|
|
85
|
+
if (listCache.refreshing) return listCache.refreshing;
|
|
86
|
+
listCache.refreshing = buildListItems().then((allItems) => {
|
|
87
|
+
listCache.items = allItems;
|
|
88
|
+
listCache.at = Date.now();
|
|
89
|
+
listCache.stale = false;
|
|
90
|
+
}).catch((error) => {
|
|
91
|
+
console.error("recall list refresh failed:", String(error && error.stack || error));
|
|
92
|
+
}).finally(() => {
|
|
93
|
+
listCache.refreshing = null;
|
|
94
|
+
});
|
|
95
|
+
return listCache.refreshing;
|
|
96
|
+
}
|
|
97
|
+
async function deleteSnapshotsByFilter(match, sessionId) {
|
|
98
|
+
let records;
|
|
99
|
+
if (Array.isArray(listCache.items) && listCache.items.length) {
|
|
100
|
+
records = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const it of listCache.items) {
|
|
102
|
+
if (!it || typeof it.id !== "string") continue;
|
|
103
|
+
records.set(it.id, { id: it.id, root: it.root || null, sessionId: it.sessionId || null, time: typeof it.time === "number" ? it.time : 0 });
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
records = await collectAllSnapshotRecords();
|
|
107
|
+
}
|
|
108
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
109
|
+
for (const rec of records.values()) {
|
|
110
|
+
if (!match(rec) || !rec.root) continue;
|
|
111
|
+
if (!byRoot.has(rec.root)) byRoot.set(rec.root, []);
|
|
112
|
+
byRoot.get(rec.root).push(rec.id);
|
|
113
|
+
}
|
|
114
|
+
let deleted = 0;
|
|
115
|
+
await enqueue(async () => {
|
|
116
|
+
for (const [root, rootIds] of byRoot) {
|
|
117
|
+
let store = state.stores.get(root) || null;
|
|
118
|
+
if (!store) {
|
|
119
|
+
try {
|
|
120
|
+
store = await rt.resolveStore(root);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
store = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!store) continue;
|
|
126
|
+
try {
|
|
127
|
+
if (state.gitExe) {
|
|
128
|
+
const tags = rootIds.map((id) => "snap-" + id);
|
|
129
|
+
for (let i = 0; i < tags.length; i += 100) {
|
|
130
|
+
await rt.runShell(rt.scripts.purgeTagsScript(store, state.gitExe, tags.slice(i, i + 100)), { timeoutMs: 12e4, stdoutMaxBytes: 4096 });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!state.indexLoaded.has(root)) {
|
|
134
|
+
try {
|
|
135
|
+
await snaps.loadIndex(root, sessionId);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const id of rootIds) state.snapshots.delete(id);
|
|
140
|
+
await snaps.saveIndex(root, sessionId);
|
|
141
|
+
deleted += rootIds.length;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
rt.recordError("recall batch delete failed for " + root + ": " + String(error));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
listCache.items = null;
|
|
147
|
+
usageCache.payload = null;
|
|
148
|
+
});
|
|
149
|
+
return deleted;
|
|
150
|
+
}
|
|
151
|
+
async function deleteAllSnapshots() {
|
|
152
|
+
return enqueue(async () => {
|
|
153
|
+
const stores = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const [root, store] of state.stores.entries()) {
|
|
155
|
+
if (store && store.dir) stores.set(store.dir, { store, root });
|
|
156
|
+
}
|
|
157
|
+
const dump = await dumpStores();
|
|
158
|
+
for (const [dir, info] of dump.entries()) {
|
|
159
|
+
const known = stores.get(dir);
|
|
160
|
+
if (known) {
|
|
161
|
+
if (!known.root && info.root) known.root = info.root;
|
|
162
|
+
known.entries = info.entries || [];
|
|
163
|
+
} else {
|
|
164
|
+
stores.set(dir, {
|
|
165
|
+
// 全局删除只动该目录下的 git/index;不必、也不能依赖可反解的 root。
|
|
166
|
+
store: rt.storeFromDir(dir, false),
|
|
167
|
+
root: info.root || null,
|
|
168
|
+
entries: info.entries || []
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (stores.size === 0) return { deleted: 0, stores: 0, failed: 0 };
|
|
173
|
+
const gitExe = await rt.resolveGit();
|
|
174
|
+
if (!gitExe) {
|
|
175
|
+
const message = "\u672A\u68C0\u6D4B\u5230 git CLI\uFF0C\u65E0\u6CD5\u9A8C\u8BC1\u5E76\u5220\u9664\u5FEB\u7167 tag";
|
|
176
|
+
rt.recordError("recall delete all failed: " + message);
|
|
177
|
+
return { deleted: 0, stores: 0, failed: stores.size || 1, message };
|
|
178
|
+
}
|
|
179
|
+
let deleted = 0;
|
|
180
|
+
let clearedStores = 0;
|
|
181
|
+
let failed = 0;
|
|
182
|
+
for (const { store, root } of stores.values()) {
|
|
183
|
+
try {
|
|
184
|
+
const output = await rt.runShell(rt.scripts.listTagsScript(store, gitExe), { timeoutMs: 12e4, stdoutMaxBytes: 4194304 });
|
|
185
|
+
const tags = rt.scripts.stripBom(output).split(/\r?\n/).map((tag) => tag.trim()).filter((tag) => tag.indexOf("snap-") === 0);
|
|
186
|
+
for (let i = 0; i < tags.length; i += 100) {
|
|
187
|
+
await rt.runShell(rt.scripts.purgeTagsScript(store, gitExe, tags.slice(i, i + 100)), { timeoutMs: 12e4, stdoutMaxBytes: 4096 });
|
|
188
|
+
}
|
|
189
|
+
const remainedOutput = await rt.runShell(rt.scripts.listTagsScript(store, gitExe), { timeoutMs: 12e4, stdoutMaxBytes: 4194304 });
|
|
190
|
+
const remained = rt.scripts.stripBom(remainedOutput).split(/\r?\n/).map((tag) => tag.trim()).filter((tag) => tag.indexOf("snap-") === 0);
|
|
191
|
+
if (remained.length) throw new Error("\u4ECD\u6709 " + remained.length + " \u4E2A\u5FEB\u7167 tag \u672A\u5220\u9664");
|
|
192
|
+
await rt.writeTextViaShell(store.dir + (rt.isWin ? "\\" : "/") + "index.json", "[]");
|
|
193
|
+
for (const tag of tags) state.snapshots.delete(tag.slice("snap-".length));
|
|
194
|
+
if (root) {
|
|
195
|
+
for (const [id, snap] of state.snapshots.entries()) {
|
|
196
|
+
if (snap && snap.root === root) state.snapshots.delete(id);
|
|
197
|
+
}
|
|
198
|
+
state.indexLoaded.add(root);
|
|
199
|
+
}
|
|
200
|
+
deleted += tags.length;
|
|
201
|
+
clearedStores += 1;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
failed += 1;
|
|
204
|
+
rt.recordError("recall delete all failed for " + store.dir + ": " + String(error));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
listCache.items = null;
|
|
208
|
+
usageCache.payload = null;
|
|
209
|
+
return { deleted, stores: clearedStores, failed };
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
"exclude-get": async () => {
|
|
214
|
+
if (!supported) return { ok: false, unsupported: true };
|
|
215
|
+
if (excludeCache.payload && Date.now() - excludeCache.at < 3e4) return excludeCache.payload;
|
|
216
|
+
const byFile = await listExcludeFiles();
|
|
217
|
+
let contents = /* @__PURE__ */ new Map();
|
|
218
|
+
try {
|
|
219
|
+
const text = rt.scripts.stripBom(await rt.runShell(rt.scripts.excludeDumpScript(Array.from(byFile.keys())), { stdoutMaxBytes: 1048576 }));
|
|
220
|
+
contents = parseExcludeDump(text);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
}
|
|
223
|
+
const payload = {
|
|
224
|
+
ok: true,
|
|
225
|
+
files: Array.from(byFile.entries()).map(([path, info]) => ({
|
|
226
|
+
path,
|
|
227
|
+
home: Boolean(info.store.home),
|
|
228
|
+
roots: info.roots,
|
|
229
|
+
content: contents.get(path) || ""
|
|
230
|
+
}))
|
|
231
|
+
};
|
|
232
|
+
excludeCache.at = Date.now();
|
|
233
|
+
excludeCache.payload = payload;
|
|
234
|
+
return payload;
|
|
235
|
+
},
|
|
236
|
+
"exclude-set": async (args) => {
|
|
237
|
+
if (!supported) return { ok: false, unsupported: true };
|
|
238
|
+
const path = args && args.path ? String(args.path) : "";
|
|
239
|
+
const content = args && typeof args.content === "string" ? args.content : "";
|
|
240
|
+
const byFile = await listExcludeFiles();
|
|
241
|
+
const info = byFile.get(path);
|
|
242
|
+
if (!info) return { ok: false, code: E.RECALL_UNKNOWN_PATH, message: "\u672A\u77E5\u7684\u6392\u9664\u6587\u4EF6\u8DEF\u5F84" };
|
|
243
|
+
await snaps.writeExclude(info.store, content);
|
|
244
|
+
excludeCache.payload = null;
|
|
245
|
+
return { ok: true };
|
|
246
|
+
},
|
|
247
|
+
// 设置页「插件配置」卡片读配置:resolved 全量值 + 用户已覆盖字段 + env
|
|
248
|
+
// 锁定字段(环境变量优先级最高)+ 可写性(只读 provider 禁存)。
|
|
249
|
+
"config-get": async () => {
|
|
250
|
+
const envLocks = {
|
|
251
|
+
gcSnaps: Boolean(process.env && process.env.DSH_RECALL_GC_SNAPS),
|
|
252
|
+
gcHours: Boolean(process.env && process.env.DSH_RECALL_GC_HOURS)
|
|
253
|
+
};
|
|
254
|
+
let overridden = {};
|
|
255
|
+
let writable = false;
|
|
256
|
+
try {
|
|
257
|
+
const settings = ctx.get("settings");
|
|
258
|
+
if (settings && typeof settings.describe === "function") {
|
|
259
|
+
const list = settings.describe();
|
|
260
|
+
const ours = (Array.isArray(list) ? list : []).find((d) => d && d.ns === "dsh-recall");
|
|
261
|
+
if (ours && ours.user && typeof ours.user === "object") overridden = ours.user;
|
|
262
|
+
writable = settings.writable !== false;
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
ok: true,
|
|
268
|
+
values: {
|
|
269
|
+
gcSnaps: cfg.gcSnaps,
|
|
270
|
+
gcHours: cfg.gcHours,
|
|
271
|
+
maxFileBytes: cfg.maxFileBytes,
|
|
272
|
+
maxSnapshotsPerWorkspace: cfg.maxSnapshotsPerWorkspace,
|
|
273
|
+
baseExcludes: cfg.baseExcludes.slice(),
|
|
274
|
+
refillDraft: cfg.refillDraft,
|
|
275
|
+
snapshotEnabled: cfg.snapshotEnabled,
|
|
276
|
+
archiveOriginal: cfg.archiveOriginal,
|
|
277
|
+
retentionDays: cfg.retentionDays
|
|
278
|
+
},
|
|
279
|
+
overridden,
|
|
280
|
+
envLocks,
|
|
281
|
+
writable
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
// 设置页「插件配置」卡片存配置:白名单字段 + 类型清洗后经 settings.update
|
|
285
|
+
// 写进用户层,watch 链路把新值热更新进 cfg,无需重启。
|
|
286
|
+
"config-set": async (args) => {
|
|
287
|
+
const patch = args && args.patch && typeof args.patch === "object" ? args.patch : {};
|
|
288
|
+
const clean = {};
|
|
289
|
+
if (patch.gcSnaps !== void 0) clean.gcSnaps = Number(patch.gcSnaps);
|
|
290
|
+
if (patch.gcHours !== void 0) clean.gcHours = Number(patch.gcHours);
|
|
291
|
+
if (patch.maxFileBytes !== void 0) clean.maxFileBytes = Number(patch.maxFileBytes);
|
|
292
|
+
if (patch.maxSnapshotsPerWorkspace !== void 0) {
|
|
293
|
+
const n = Number(patch.maxSnapshotsPerWorkspace);
|
|
294
|
+
if (!Number.isFinite(n)) return { ok: false, code: E.RECALL_BAD_TYPE, message: "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650\u5FC5\u987B\u662F\u6570\u5B57" };
|
|
295
|
+
clean.maxSnapshotsPerWorkspace = Math.max(0, n);
|
|
296
|
+
}
|
|
297
|
+
if (patch.refillDraft !== void 0) clean.refillDraft = Boolean(patch.refillDraft);
|
|
298
|
+
if (patch.snapshotEnabled !== void 0) clean.snapshotEnabled = Boolean(patch.snapshotEnabled);
|
|
299
|
+
if (patch.archiveOriginal !== void 0) clean.archiveOriginal = Boolean(patch.archiveOriginal);
|
|
300
|
+
if (patch.retentionDays !== void 0) {
|
|
301
|
+
const n = Number(patch.retentionDays);
|
|
302
|
+
if (!Number.isFinite(n) || n < 0) return { ok: false, code: E.RECALL_BAD_TYPE, message: "\u4FDD\u7559\u5929\u6570\u5FC5\u987B\u662F >= 0 \u7684\u6570\u5B57\uFF080 \u8868\u793A\u4E0D\u542F\u7528\uFF09" };
|
|
303
|
+
clean.retentionDays = Math.trunc(n);
|
|
304
|
+
}
|
|
305
|
+
if (patch.baseExcludes !== void 0) {
|
|
306
|
+
if (!Array.isArray(patch.baseExcludes)) return { ok: false, code: E.RECALL_BAD_TYPE, message: "baseExcludes \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4" };
|
|
307
|
+
clean.baseExcludes = patch.baseExcludes.filter((p) => typeof p === "string" && p.trim());
|
|
308
|
+
}
|
|
309
|
+
if (!Object.keys(clean).length) return { ok: false, code: E.RECALL_EMPTY_PATCH, message: "\u6CA1\u6709\u53EF\u5199\u5165\u7684\u914D\u7F6E\u5B57\u6BB5" };
|
|
310
|
+
let settings = null;
|
|
311
|
+
try {
|
|
312
|
+
settings = ctx.get("settings");
|
|
313
|
+
} catch (error) {
|
|
314
|
+
settings = null;
|
|
315
|
+
}
|
|
316
|
+
if (!settings || typeof settings.update !== "function") {
|
|
317
|
+
return { ok: false, code: E.RECALL_SETTINGS_UNAVAILABLE, message: "\u8BBE\u7F6E\u670D\u52A1\u4E0D\u53EF\u7528\uFF1A\u8BF7\u5728 profile \u7684 cordis.patch.yml \u6309 id: recall \u8986\u76D6\u914D\u7F6E" };
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
await settings.update("dsh-recall", clean);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
const se = error;
|
|
323
|
+
return { ok: false, code: E.RECALL_SETTINGS_WRITE_FAILED, message: "\u914D\u7F6E\u5199\u5165\u5931\u8D25\uFF1A" + String(se && se.message ? se.message : error) };
|
|
324
|
+
}
|
|
325
|
+
return { ok: true };
|
|
326
|
+
},
|
|
327
|
+
// 设置页「快照管理」卡片:列表 / 磁盘占用 / 单条删除 / 手动 gc。
|
|
328
|
+
// 全部走串行队列——删除 tag 与 gc 与快照争的是同一个 git 仓库。
|
|
329
|
+
"manage": async (args) => {
|
|
330
|
+
if (!supported) return { ok: false, unsupported: true };
|
|
331
|
+
const op = args && args.op ? String(args.op) : "list";
|
|
332
|
+
const sessionId = args && args.sessionId ? String(args.sessionId) : null;
|
|
333
|
+
if (op === "list") {
|
|
334
|
+
const limitRaw = args && args.limit !== void 0 ? Number(args.limit) : 200;
|
|
335
|
+
const safeLimit = Math.min(Math.max(Number.isFinite(limitRaw) ? Math.trunc(limitRaw) : 200, 1), 2e3);
|
|
336
|
+
if (listCache.items && (Date.now() - listCache.at < 3e4 || listCache.stale)) {
|
|
337
|
+
const stale = Boolean(listCache.stale);
|
|
338
|
+
if (stale) refreshListCacheInBackground();
|
|
339
|
+
return { ok: true, items: listCache.items.slice(0, safeLimit), total: listCache.items.length, stale };
|
|
340
|
+
}
|
|
341
|
+
const allItems = await buildListItems();
|
|
342
|
+
listCache.at = Date.now();
|
|
343
|
+
listCache.items = allItems;
|
|
344
|
+
listCache.stale = false;
|
|
345
|
+
return { ok: true, items: allItems.slice(0, safeLimit), total: allItems.length };
|
|
346
|
+
}
|
|
347
|
+
if (op === "titles") {
|
|
348
|
+
const ids = Array.from(new Set(
|
|
349
|
+
(Array.isArray(args && args.sessionIds) ? args.sessionIds : []).map(String).filter(Boolean)
|
|
350
|
+
)).slice(0, 100);
|
|
351
|
+
const out = {};
|
|
352
|
+
await runLimited(ids.map((sid) => async () => {
|
|
353
|
+
if (out[sid] !== void 0) return;
|
|
354
|
+
let title = liveTitleFast(sid);
|
|
355
|
+
if (title === null) {
|
|
356
|
+
const query = ctx.get("sessionQuery");
|
|
357
|
+
if (query && typeof query.readSession === "function") {
|
|
358
|
+
try {
|
|
359
|
+
const log = await query.readSession(sid);
|
|
360
|
+
title = titleFromEvents(log && log.events);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
title = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
sessionTitles.set(sid, title);
|
|
367
|
+
out[sid] = title;
|
|
368
|
+
}), 4);
|
|
369
|
+
return { ok: true, titles: out };
|
|
370
|
+
}
|
|
371
|
+
if (op === "messages") {
|
|
372
|
+
const reqs = Array.isArray(args && args.requests) ? args.requests.slice(0, 200) : [];
|
|
373
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
374
|
+
for (const r of reqs) {
|
|
375
|
+
const sid = r && r.sessionId ? String(r.sessionId) : null;
|
|
376
|
+
const mid = r && r.messageId ? String(r.messageId) : null;
|
|
377
|
+
if (!sid || !mid) continue;
|
|
378
|
+
if (!bySession.has(sid)) bySession.set(sid, []);
|
|
379
|
+
bySession.get(sid).push(mid);
|
|
380
|
+
}
|
|
381
|
+
const texts = {};
|
|
382
|
+
await runLimited(Array.from(bySession.entries()).map(([sid, mids]) => async () => {
|
|
383
|
+
const allCached = mids.every((mid) => messageTexts.has(String(sid) + "\0" + String(mid)));
|
|
384
|
+
let log = null;
|
|
385
|
+
if (!allCached) {
|
|
386
|
+
const query = ctx.get("sessionQuery");
|
|
387
|
+
if (query && typeof query.readSession === "function") {
|
|
388
|
+
try {
|
|
389
|
+
log = await query.readSession(sid);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
log = null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
for (const mid of mids) {
|
|
396
|
+
const key = String(sid) + "\0" + String(mid);
|
|
397
|
+
if (messageTexts.has(key)) {
|
|
398
|
+
texts[mid] = messageTexts.get(key) ?? null;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
let text = liveMessageTextFast(sid, mid);
|
|
402
|
+
if (text === null && log && Array.isArray(log.events)) {
|
|
403
|
+
text = messageTextFromEvents(log.events, mid);
|
|
404
|
+
}
|
|
405
|
+
messageTexts.set(key, text);
|
|
406
|
+
texts[mid] = text;
|
|
407
|
+
}
|
|
408
|
+
}), 4);
|
|
409
|
+
return { ok: true, messageTexts: texts };
|
|
410
|
+
}
|
|
411
|
+
if (op === "usage") {
|
|
412
|
+
if (!sessionId && usageCache.payload && Date.now() - usageCache.at < 3e4) {
|
|
413
|
+
return usageCache.payload;
|
|
414
|
+
}
|
|
415
|
+
let bytes = 0;
|
|
416
|
+
let homeStores = 0;
|
|
417
|
+
let fallbackStores = 0;
|
|
418
|
+
if (sessionId) {
|
|
419
|
+
const root = await rt.resolveRoot(sessionId);
|
|
420
|
+
if (!root) return { ok: false, code: E.RECALL_NO_ROOT, message: "\u65E0\u6CD5\u89E3\u6790\u5F53\u524D\u5DE5\u4F5C\u533A" };
|
|
421
|
+
const store = state.stores.get(root);
|
|
422
|
+
if (!store) return { ok: false, code: E.RECALL_NO_STORE, message: "\u5F53\u524D\u5DE5\u4F5C\u533A\u5C1A\u672A\u521B\u5EFA\u5FEB\u7167\u5B58\u50A8" };
|
|
423
|
+
if (store.home) homeStores++;
|
|
424
|
+
else fallbackStores++;
|
|
425
|
+
const out = await rt.runShell(rt.scripts.diskUsageScript(store.dir), { stdoutMaxBytes: 4096 });
|
|
426
|
+
bytes = parseInt(rt.scripts.stripBom(out).trim(), 10) || 0;
|
|
427
|
+
} else {
|
|
428
|
+
const knownStores = Array.from(state.stores.values()).filter((s) => s && s.dir);
|
|
429
|
+
const perStore = /* @__PURE__ */ new Map();
|
|
430
|
+
await runLimited(knownStores.map((store) => async () => {
|
|
431
|
+
try {
|
|
432
|
+
const out = await rt.runShell(rt.scripts.diskUsageScript(store.dir), { stdoutMaxBytes: 4096 });
|
|
433
|
+
perStore.set(store.dir, parseInt(rt.scripts.stripBom(out).trim(), 10) || 0);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
}
|
|
436
|
+
}), 4);
|
|
437
|
+
for (const store of knownStores) {
|
|
438
|
+
if (store.home) homeStores++;
|
|
439
|
+
else fallbackStores++;
|
|
440
|
+
bytes += perStore.get(store.dir) || 0;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const payload = { ok: true, bytes, gitAvailable: state.gitExe !== "", homeStores, fallbackStores };
|
|
444
|
+
if (!sessionId) {
|
|
445
|
+
usageCache.at = Date.now();
|
|
446
|
+
usageCache.payload = payload;
|
|
447
|
+
}
|
|
448
|
+
return payload;
|
|
449
|
+
}
|
|
450
|
+
if (op === "delete") {
|
|
451
|
+
const scope = args && args.scope ? String(args.scope) : "snapshot";
|
|
452
|
+
const root = args && args.root ? String(args.root) : null;
|
|
453
|
+
const targetSessionId = args && args.sessionId ? String(args.sessionId) : null;
|
|
454
|
+
const id = args && args.messageId ? String(args.messageId) : "";
|
|
455
|
+
if (scope === "workspace") {
|
|
456
|
+
if (!root) return { ok: false, code: E.RECALL_NO_ROOT, message: "\u7F3A\u5C11\u5DE5\u4F5C\u533A\u8DEF\u5F84" };
|
|
457
|
+
const deleted = await deleteSnapshotsByFilter((rec) => rec.root === root, sessionId);
|
|
458
|
+
return { ok: true, deleted };
|
|
459
|
+
}
|
|
460
|
+
if (scope === "session") {
|
|
461
|
+
if (!targetSessionId) return { ok: false, code: E.RECALL_NO_SESSION, message: "\u7F3A\u5C11\u4F1A\u8BDD ID" };
|
|
462
|
+
const deleted = await deleteSnapshotsByFilter(
|
|
463
|
+
(rec) => rec.sessionId === targetSessionId && (!root || rec.root === root),
|
|
464
|
+
sessionId
|
|
465
|
+
);
|
|
466
|
+
return { ok: true, deleted };
|
|
467
|
+
}
|
|
468
|
+
let snap = state.snapshots.get(id) || null;
|
|
469
|
+
let snapRoot = snap ? snap.root : root;
|
|
470
|
+
let store = null;
|
|
471
|
+
if (snapRoot) {
|
|
472
|
+
try {
|
|
473
|
+
store = await rt.resolveStore(snapRoot);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
store = null;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (!store) {
|
|
479
|
+
const found = await locateSnapshotOnDisk(id);
|
|
480
|
+
if (found) {
|
|
481
|
+
store = found.store;
|
|
482
|
+
snapRoot = found.root;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!store) return { ok: false, code: E.RECALL_NO_SNAPSHOT, message: "\u8BE5\u5FEB\u7167\u4E0D\u5B58\u5728" };
|
|
486
|
+
const finalStore = store;
|
|
487
|
+
const finalRoot = snapRoot;
|
|
488
|
+
await enqueue(async () => {
|
|
489
|
+
if (state.gitExe) {
|
|
490
|
+
await rt.runShell(rt.scripts.purgeTagsScript(finalStore, state.gitExe, ["snap-" + id]), { timeoutMs: 12e4, stdoutMaxBytes: 4096 });
|
|
491
|
+
}
|
|
492
|
+
if (!state.indexLoaded.has(finalRoot)) {
|
|
493
|
+
try {
|
|
494
|
+
await snaps.loadIndex(finalRoot, sessionId);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
state.snapshots.delete(id);
|
|
499
|
+
await snaps.saveIndex(finalRoot, sessionId);
|
|
500
|
+
listCache.items = null;
|
|
501
|
+
usageCache.payload = null;
|
|
502
|
+
});
|
|
503
|
+
return { ok: true };
|
|
504
|
+
}
|
|
505
|
+
if (op === "deleteAll") {
|
|
506
|
+
const result = await deleteAllSnapshots();
|
|
507
|
+
if (result.failed > 0) {
|
|
508
|
+
return {
|
|
509
|
+
ok: false,
|
|
510
|
+
code: E.RECALL_PARTIAL_DELETE,
|
|
511
|
+
deleted: result.deleted,
|
|
512
|
+
message: result.message || "\u5DF2\u5220\u9664 " + result.deleted + " \u6761\u5FEB\u7167\uFF0C\u4F46\u6709 " + result.failed + " \u4E2A\u5B58\u50A8\u672A\u5B8C\u6210\uFF1B\u8BF7\u67E5\u770B\u6700\u8FD1\u9519\u8BEF\u540E\u91CD\u8BD5"
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return { ok: true, deleted: result.deleted, stores: result.stores };
|
|
516
|
+
}
|
|
517
|
+
if (op === "gc") {
|
|
518
|
+
const done = sessionId ? await enqueue(() => maint.runGc(sessionId, true)) : await enqueue(() => maint.runGcAll());
|
|
519
|
+
usageCache.payload = null;
|
|
520
|
+
return { ok: true, gc: Boolean(done) };
|
|
521
|
+
}
|
|
522
|
+
if (op === "lineage") {
|
|
523
|
+
const hints = /* @__PURE__ */ new Map();
|
|
524
|
+
for (const [root, st] of state.stores.entries()) {
|
|
525
|
+
if (st && st.dir) hints.set(st.dir, root);
|
|
526
|
+
}
|
|
527
|
+
let dump;
|
|
528
|
+
try {
|
|
529
|
+
dump = await dumpStores();
|
|
530
|
+
} catch (error) {
|
|
531
|
+
dump = /* @__PURE__ */ new Map();
|
|
532
|
+
}
|
|
533
|
+
const out = [];
|
|
534
|
+
for (const info of dump.values()) {
|
|
535
|
+
for (const e of info.lineage || []) out.push(e);
|
|
536
|
+
}
|
|
537
|
+
return { ok: true, lineage: out };
|
|
538
|
+
}
|
|
539
|
+
return { ok: false, code: E.RECALL_UNKNOWN_OP, message: "\u672A\u77E5\u7684\u7BA1\u7406\u64CD\u4F5C: " + op };
|
|
540
|
+
},
|
|
541
|
+
// 设置页「插件配置」卡片恢复默认:整段清空 user 层回组合 base——官方
|
|
542
|
+
// settings RPC 的 replace 明确是「restoration/reset 路径」。老版本服务
|
|
543
|
+
// 没有 replace 时降级 settings.update 写 DEFAULTS。
|
|
544
|
+
"config-reset": async () => {
|
|
545
|
+
let settings = null;
|
|
546
|
+
try {
|
|
547
|
+
settings = ctx.get("settings");
|
|
548
|
+
} catch (error) {
|
|
549
|
+
settings = null;
|
|
550
|
+
}
|
|
551
|
+
if (!settings || typeof settings.update !== "function") {
|
|
552
|
+
return { ok: false, code: E.RECALL_SETTINGS_UNAVAILABLE, message: "\u8BBE\u7F6E\u670D\u52A1\u4E0D\u53EF\u7528\uFF1A\u8BF7\u5728 profile \u7684 cordis.patch.yml \u6309 id: recall \u8986\u76D6\u914D\u7F6E" };
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
if (typeof settings.replace === "function") {
|
|
556
|
+
await settings.replace("dsh-recall", {});
|
|
557
|
+
} else {
|
|
558
|
+
await settings.update("dsh-recall", Object.assign({}, DEFAULTS, { baseExcludes: DEFAULTS.baseExcludes.slice() }));
|
|
559
|
+
}
|
|
560
|
+
} catch (error) {
|
|
561
|
+
const re = error;
|
|
562
|
+
return { ok: false, code: E.RECALL_SETTINGS_WRITE_FAILED, message: "\u6062\u590D\u9ED8\u8BA4\u5931\u8D25\uFF1A" + String(re && re.message ? re.message : error) };
|
|
563
|
+
}
|
|
564
|
+
applyResolvedConfig(readSettings());
|
|
565
|
+
return { ok: true };
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
export {
|
|
570
|
+
createRoutesManage
|
|
571
|
+
};
|