dsh-as-aistudio 0.5.3 → 0.5.5
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 +9 -0
- package/package.json +1 -1
- package/src/client.js +13 -6
- package/src/components.js +1 -1
- package/src/vendor/host/dsh-delete-turn.js +1 -1
- package/src/vendor/host/dsh-delete-turn.logic.js +1 -1
- package/src/vendor/host/dsh-log-trim.flatten.js +1 -1
- package/src/vendor/host/dsh-log-trim.host-libs.mjs +1 -1
- package/src/vendor/host/dsh-log-trim.js +69 -4
- package/src/vendor/host/dsh-log-trim.ledger.js +131 -0
- package/src/vendor/host/dsh-log-trim.lossless.js +11 -2
- package/src/vendor/host/dsh-log-trim.roots.js +1 -1
- package/src/vendor/host/dsh-log-trim.vendor.compact-session.mjs +1 -1
- package/src/vendor/host/dsh-log-trim.verify-session-log.mjs +1 -1
- package/src/vendor/versions.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
## 中文
|
|
6
6
|
|
|
7
|
+
**0.5.5** —— 跟进 dsh-delete-turn 0.2.3(多选工具条重排)。
|
|
8
|
+
|
|
9
|
+
- 内嵌 delete 半部更新:多选条从输入框正中挪到上方右侧、体量缩小。
|
|
10
|
+
|
|
11
|
+
**0.5.4** —— 跟进 dsh-log-trim 0.6.0(裁剪统计账本)。
|
|
12
|
+
|
|
13
|
+
- 内嵌的 log-trim 升到 0.6.0:每次改写记一笔窗口/事件/字节账,写在 `~/.dsh/dsh-log-trim/ledger.json`,
|
|
14
|
+
并暴露 `GET /api/dsh-log-trim/ledger` 给其它插件读。
|
|
15
|
+
|
|
7
16
|
**0.5.3** —— 跟进四个组件:设置卡只挂在包自己的页面上。
|
|
8
17
|
|
|
9
18
|
- edit 0.5.16 / rerun 0.2.9 / delete 0.2.2 / log-trim 0.5.2:去掉 `plugins.row.config`,只保留 `plugins.bundle.config`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-as-aistudio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Google-AI-Studio-style turn controls for DeepSeek Harness, in one install: edit a prompt and re-run it, re-run a reply in place, delete a turn from the model context, and render sent prompts as Markdown. Self-contained: the plugin list shows this one row, and the four components are vendored inside it - installing the studio installs nothing else.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/client.js
CHANGED
|
@@ -7273,8 +7273,8 @@ window.__ModuleLoader__.load({
|
|
|
7273
7273
|
'.dshdt-header-select:disabled{color:var(--dsw-alias-label-dimmed,#9aa0aa);cursor:wait}',
|
|
7274
7274
|
'.dshdt-header-select svg{flex:none}',
|
|
7275
7275
|
'.dshdt-header-select-on{color:var(--dsw-alias-button-primary-fill,#4d6bfe);border-color:var(--dsw-alias-button-primary-fill,#4d6bfe);background:var(--dsw-alias-button-primary-ghost-fill,rgba(77,107,254,.08))}',
|
|
7276
|
-
'.dshdt-selectbar{position:fixed;
|
|
7277
|
-
'.dshdt-selectbar-count{
|
|
7276
|
+
'.dshdt-selectbar{position:fixed;right:24px;bottom:150px;z-index:60;display:flex;align-items:center;gap:8px;padding:5px 10px;border-radius:999px;background:var(--dsw-alias-bg-elevated,#fff);color:var(--dsw-alias-label-primary,inherit);border:1px solid var(--dsw-alias-border-secondary,rgba(127,127,127,.2));box-shadow:0 4px 14px rgba(0,0,0,.12);font-size:12px}',
|
|
7277
|
+
'.dshdt-selectbar-count{color:var(--dsw-alias-label-secondary,#5c6270);font-size:12px;white-space:nowrap}',
|
|
7278
7278
|
'.dshdt-dialog-text{margin:0;color:var(--dsw-alias-label-primary,inherit);font-size:14px;line-height:22px}',
|
|
7279
7279
|
'.dshdt-dialog-note{margin:10px 0 0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:13px;line-height:20px}',
|
|
7280
7280
|
'.dshdt-dialog-warn{margin:10px 0 0;color:var(--dsw-alias-state-warn-primary,#a15c00);font-size:13px;line-height:20px}',
|
|
@@ -8410,14 +8410,14 @@ window.__ModuleLoader__.load({
|
|
|
8410
8410
|
}),
|
|
8411
8411
|
jsx(primitives.Button, {
|
|
8412
8412
|
variant: 'ghost',
|
|
8413
|
-
size: '
|
|
8413
|
+
size: 'sm',
|
|
8414
8414
|
disabled: view.pending,
|
|
8415
8415
|
onClick: () => controller.setSelecting(false),
|
|
8416
8416
|
children: t('select.cancel'),
|
|
8417
8417
|
}),
|
|
8418
8418
|
jsx(primitives.Button, {
|
|
8419
8419
|
variant: 'primary',
|
|
8420
|
-
size: '
|
|
8420
|
+
size: 'sm',
|
|
8421
8421
|
className: 'dshdt-danger',
|
|
8422
8422
|
disabled: view.pending || count === 0,
|
|
8423
8423
|
onClick: () => controller.requestBatch(),
|
|
@@ -8605,6 +8605,7 @@ window.__ModuleLoader__.load({
|
|
|
8605
8605
|
),
|
|
8606
8606
|
)
|
|
8607
8607
|
|
|
8608
|
+
|
|
8608
8609
|
ctx.slots.inject('conversation.input.overlay', () =>
|
|
8609
8610
|
ctx.slots.register(
|
|
8610
8611
|
{
|
|
@@ -9577,7 +9578,7 @@ window.__ModuleLoader__.load({
|
|
|
9577
9578
|
const react = require('react')
|
|
9578
9579
|
const { jsx, jsxs } = require('react/jsx-runtime')
|
|
9579
9580
|
|
|
9580
|
-
const PLUGIN_VERSION = '0.
|
|
9581
|
+
const PLUGIN_VERSION = '0.6.0'
|
|
9581
9582
|
const ROUTE_PREFIX = '/api/dsh-log-trim'
|
|
9582
9583
|
|
|
9583
9584
|
/** The knobs this card edits, in the order they are drawn. */
|
|
@@ -9619,6 +9620,12 @@ window.__ModuleLoader__.load({
|
|
|
9619
9620
|
suffix: '条',
|
|
9620
9621
|
},
|
|
9621
9622
|
{ key: 'minRatio', label: '最少可丢占比', hint: '占整份日志的比例,默认 0.2(20%)', min: 0, max: 1, step: 0.05, kind: 'ratio' },
|
|
9623
|
+
{
|
|
9624
|
+
key: 'recordLedger',
|
|
9625
|
+
label: '记录裁剪统计',
|
|
9626
|
+
hint: '每次改写日志记一笔(窗口数 / 事件数 / 字节),写到 ~/.dsh/dsh-log-trim/ledger.json;dsh-session-pages 会读它显示,其它插件也能读。关掉就不记',
|
|
9627
|
+
kind: 'toggle',
|
|
9628
|
+
},
|
|
9622
9629
|
{ key: 'minBytes', label: '最小日志体积', hint: '小于它就根本不读,默认 1 MB', min: 0, max: 200, step: 1, kind: 'mb' },
|
|
9623
9630
|
{
|
|
9624
9631
|
key: 'backupKeep',
|
|
@@ -10082,7 +10089,7 @@ window.__ModuleLoader__.load({
|
|
|
10082
10089
|
const NS = 'dsh-as-aistudio'
|
|
10083
10090
|
|
|
10084
10091
|
/** Keep in sync with package.json and src/components.js. */
|
|
10085
|
-
const PLUGIN_VERSION = '0.5.
|
|
10092
|
+
const PLUGIN_VERSION = '0.5.5'
|
|
10086
10093
|
|
|
10087
10094
|
/** Presence marker the live verifier reads. */
|
|
10088
10095
|
const DEBUG_KEY = '__DSH_AS_AISTUDIO__'
|
package/src/components.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
export const STUDIO_ID = 'dsh-as-aistudio'
|
|
27
27
|
|
|
28
28
|
/** Keep in sync with package.json and src/client.js. */
|
|
29
|
-
export const STUDIO_VERSION = '0.5.
|
|
29
|
+
export const STUDIO_VERSION = '0.5.5'
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* The AI Studio action strip this studio composes, in the order the user meets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-delete-turn host half (dsh-delete-turn.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-delete-turn/src/index.js (v0.2.
|
|
3
|
+
// Source: ../dsh-delete-turn/src/index.js (v0.2.3, sha256 5dfc53e5c47750f5)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-delete-turn host half (dsh-delete-turn.logic.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-delete-turn/src/logic.js (v0.2.
|
|
3
|
+
// Source: ../dsh-delete-turn/src/logic.js (v0.2.3, sha256 6da16435446356ca)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-log-trim host half (dsh-log-trim.flatten.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-log-trim/lib/flatten.js (v0.
|
|
3
|
+
// Source: ../dsh-log-trim/lib/flatten.js (v0.6.0, sha256 3aa1fdcbb120ab06)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-log-trim host half (dsh-log-trim.host-libs.mjs), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-log-trim/lib/host-libs.mjs (v0.
|
|
3
|
+
// Source: ../dsh-log-trim/lib/host-libs.mjs (v0.6.0, sha256 5ebed01988b5bfcf)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-log-trim host half (dsh-log-trim.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-log-trim/lib/index.js (v0.
|
|
3
|
+
// Source: ../dsh-log-trim/lib/index.js (v0.6.0, sha256 1ef6710b85f69330)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -37,12 +37,13 @@ import { discoverRoots, isCurrentLog, listLegacy } from './dsh-log-trim.roots.js
|
|
|
37
37
|
import fs from 'node:fs'
|
|
38
38
|
import os from 'node:os'
|
|
39
39
|
import path from 'node:path'
|
|
40
|
-
import { applyTrim, foldSurface, isSessionLocked, keepLastWindows, readLog, renumber } from './dsh-log-trim.vendor.compact-session.mjs'
|
|
40
|
+
import { applyTrim, foldSurface, isSessionLocked, keepLastWindows, readLog, renumber, windowsOf } from './dsh-log-trim.vendor.compact-session.mjs'
|
|
41
41
|
import { applyFlatten, applyLossless, applyStreamOnly, planLossless, applyRebuild } from './dsh-log-trim.lossless.js'
|
|
42
|
+
import { readLedger, recordFor } from './dsh-log-trim.ledger.js'
|
|
42
43
|
|
|
43
44
|
export const name = 'dsh-log-trim'
|
|
44
45
|
export const PLUGIN_ID = name
|
|
45
|
-
export const PLUGIN_VERSION = '0.
|
|
46
|
+
export const PLUGIN_VERSION = '0.6.0'
|
|
46
47
|
export const ROUTE_PREFIX = '/api/dsh-log-trim'
|
|
47
48
|
|
|
48
49
|
/**
|
|
@@ -118,6 +119,36 @@ export function writeSavedConfig(config) {
|
|
|
118
119
|
* @param {number} backupKeep - how many backups to keep afterwards.
|
|
119
120
|
* @returns {Promise<object>} what happened.
|
|
120
121
|
*/
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 窗口裁剪写完之后记一笔账:窗口数、事件数、字节数都来自这次操作本身。
|
|
125
|
+
*
|
|
126
|
+
* @param {string} logFile - 日志路径。
|
|
127
|
+
* @param {object} result - `applyTrim` 的返回。
|
|
128
|
+
* @param {boolean} record - 是否记录(配置项)。
|
|
129
|
+
*/
|
|
130
|
+
function noteWindowTrim(logFile, result, record) {
|
|
131
|
+
if (record === false) return
|
|
132
|
+
try {
|
|
133
|
+
let windowsAfter = null
|
|
134
|
+
try {
|
|
135
|
+
windowsAfter = windowsOf(readLog(logFile).events).length
|
|
136
|
+
} catch {
|
|
137
|
+
windowsAfter = null
|
|
138
|
+
}
|
|
139
|
+
recordFor(logFile, 'window', [], [], {
|
|
140
|
+
windowsBefore: result?.before?.windows,
|
|
141
|
+
windowsAfter: windowsAfter === null ? undefined : windowsAfter,
|
|
142
|
+
eventsBefore: result?.before?.events,
|
|
143
|
+
eventsAfter: result?.after?.events,
|
|
144
|
+
bytesBefore: result?.before?.bytes,
|
|
145
|
+
bytesAfter: result?.after?.bytes,
|
|
146
|
+
})
|
|
147
|
+
} catch {
|
|
148
|
+
// 记账失败不影响裁剪本身。
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
121
152
|
export async function trimOne(logFile, config) {
|
|
122
153
|
if (isSessionLocked(logFile)) {
|
|
123
154
|
throw new Error('这个会话正被宿主打开着(session.lock)。先切到别的对话或重启 DSH,再压缩它')
|
|
@@ -130,6 +161,7 @@ export async function trimOne(logFile, config) {
|
|
|
130
161
|
result = await applyFlatten(logFile, config)
|
|
131
162
|
} else if (config.mode === 'window') {
|
|
132
163
|
result = await applyTrim(logFile, config.keepWindows, { streamKeepTurns: 0 })
|
|
164
|
+
noteWindowTrim(logFile, result, config.recordLedger)
|
|
133
165
|
} else {
|
|
134
166
|
const plan = planLossless(logFile, config)
|
|
135
167
|
if (!plan.should) throw new Error(plan.reason)
|
|
@@ -419,6 +451,8 @@ export const DEFAULTS = {
|
|
|
419
451
|
streamOnBoot: true,
|
|
420
452
|
// 每次扫描先把 edit/rerun/delete 的载体折叠掉(重启即自动执行)。
|
|
421
453
|
flattenOnBoot: false,
|
|
454
|
+
// 每次改写过日志就记一笔统计(账本文件,别的插件可读);关掉就不记。
|
|
455
|
+
recordLedger: true,
|
|
422
456
|
// 启动时是否裁剪窗口(关掉就只剩折叠/清理)。
|
|
423
457
|
trimOnBoot: true,
|
|
424
458
|
// 只在开启时清理“已被 replace 盖住、界面早就不显示”的事件(要重编号,默认关)。
|
|
@@ -496,6 +530,10 @@ export function resolveConfig(raw = {}) {
|
|
|
496
530
|
if (typeof value === 'boolean') merged.streamOnBoot = value
|
|
497
531
|
continue
|
|
498
532
|
}
|
|
533
|
+
if (key === 'recordLedger') {
|
|
534
|
+
if (typeof value === 'boolean') merged.recordLedger = value
|
|
535
|
+
continue
|
|
536
|
+
}
|
|
499
537
|
if (key === 'losslessOnBoot') {
|
|
500
538
|
if (typeof value === 'boolean') merged.losslessOnBoot = value
|
|
501
539
|
continue
|
|
@@ -992,6 +1030,7 @@ export async function runScan(config, logger = {}) {
|
|
|
992
1030
|
}
|
|
993
1031
|
try {
|
|
994
1032
|
const result = config.mode === 'window' ? await applyTrim(logFile, config.keepWindows) : await applyLossless(logFile, config, lossless)
|
|
1033
|
+
if (config.mode === 'window') noteWindowTrim(logFile, result, config.recordLedger)
|
|
995
1034
|
const pruned = pruneBackups(logFile, config.backupKeep)
|
|
996
1035
|
summary.savedBytes += Math.max(0, result.before.bytes - result.after.bytes)
|
|
997
1036
|
summary.trimmed.push({
|
|
@@ -1095,7 +1134,10 @@ export function apply(ctx, config = {}) {
|
|
|
1095
1134
|
}
|
|
1096
1135
|
if (live.mode === 'window') {
|
|
1097
1136
|
const plan = planTrim(report, live)
|
|
1098
|
-
if (plan.should)
|
|
1137
|
+
if (plan.should) {
|
|
1138
|
+
const done = await applyTrim(logFile, live.keepWindows, { streamKeepTurns: 0 })
|
|
1139
|
+
noteWindowTrim(logFile, done, live.recordLedger)
|
|
1140
|
+
}
|
|
1099
1141
|
else await applyRebuild(logFile, live)
|
|
1100
1142
|
return
|
|
1101
1143
|
}
|
|
@@ -1363,6 +1405,29 @@ export function apply(ctx, config = {}) {
|
|
|
1363
1405
|
|
|
1364
1406
|
// Where the backups are, and a way to open the folder holding them. The card
|
|
1365
1407
|
// shows the paths; the button hands them to the system file manager.
|
|
1408
|
+
// 裁剪统计账本:别的插件(如 dsh-session-pages)直接读这个接口,
|
|
1409
|
+
// 不必依赖本包;不带 sessionId 时返回全量,带时只返回那一个会话。
|
|
1410
|
+
fiber.effect(() =>
|
|
1411
|
+
webServer.register({
|
|
1412
|
+
kind: 'exact',
|
|
1413
|
+
path: `${ROUTE_PREFIX}/ledger`,
|
|
1414
|
+
handler: (req, res) => {
|
|
1415
|
+
if (!isLoopback(req)) return sendJson(res, 403, { ok: false, error: 'loopback only' })
|
|
1416
|
+
if (req.method !== 'GET') return sendJson(res, 405, { ok: false, error: 'GET only' })
|
|
1417
|
+
let sessionId = null
|
|
1418
|
+
try {
|
|
1419
|
+
sessionId = new URL(req.url, 'http://127.0.0.1').searchParams.get('sessionId')
|
|
1420
|
+
} catch {
|
|
1421
|
+
sessionId = null
|
|
1422
|
+
}
|
|
1423
|
+
const ledger = readLedger()
|
|
1424
|
+
if (sessionId === null || sessionId === '') return sendJson(res, 200, { ok: true, ...ledger })
|
|
1425
|
+
const entry = ledger.sessions[sessionId] ?? null
|
|
1426
|
+
return sendJson(res, 200, { ok: true, sessionId, trim: entry })
|
|
1427
|
+
},
|
|
1428
|
+
}),
|
|
1429
|
+
)
|
|
1430
|
+
|
|
1366
1431
|
fiber.effect(() =>
|
|
1367
1432
|
webServer.register({
|
|
1368
1433
|
kind: 'exact',
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// dsh-log-trim host half (dsh-log-trim.ledger.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
|
+
//
|
|
3
|
+
// Source: ../dsh-log-trim/lib/ledger.js (v0.6.0, sha256 1b391592e0cc0fbf)
|
|
4
|
+
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
|
+
//
|
|
6
|
+
// src/shell.js imports the copy named after the component, which is why the
|
|
7
|
+
// studio installs with no runtime dependency on the component package.
|
|
8
|
+
/**
|
|
9
|
+
* 裁剪统计账本:每改写过一次日志就记一条,按会话累计。
|
|
10
|
+
*
|
|
11
|
+
* 放在 `~/.dsh/dsh-log-trim/ledger.json`——**谁能读谁就能显示**:
|
|
12
|
+
* `dsh-session-pages` 之类的插件直接读文件即可,不需要依赖本包;
|
|
13
|
+
* 本插件另外用 `/api/dsh-log-trim/ledger` 把它暴露出来。
|
|
14
|
+
*
|
|
15
|
+
* 账本只增不减(除了用户显式清空):裁剪本身会删日志内容,统计不能跟着丢。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from 'node:fs'
|
|
19
|
+
import os from 'node:os'
|
|
20
|
+
import path from 'node:path'
|
|
21
|
+
import { windowsOf } from './dsh-log-trim.vendor.compact-session.mjs'
|
|
22
|
+
|
|
23
|
+
/** 账本版本;结构变了就加一,读者据此决定要不要认。 */
|
|
24
|
+
export const LEDGER_VERSION = 1
|
|
25
|
+
|
|
26
|
+
/** 账本文件路径。 */
|
|
27
|
+
export function ledgerPath(home = os.homedir()) {
|
|
28
|
+
return path.join(home, '.dsh', 'dsh-log-trim', 'ledger.json')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 读账本;不存在或坏掉时返回一个空账本(读取永远不该让插件起不来)。
|
|
33
|
+
*
|
|
34
|
+
* @param {string} [home] - 家目录(测试用)。
|
|
35
|
+
* @returns {{ version: number, sessions: Record<string, object> }} 账本。
|
|
36
|
+
*/
|
|
37
|
+
export function readLedger(home = os.homedir()) {
|
|
38
|
+
const empty = { version: LEDGER_VERSION, sessions: {} }
|
|
39
|
+
let raw
|
|
40
|
+
try {
|
|
41
|
+
raw = fs.readFileSync(ledgerPath(home), 'utf8')
|
|
42
|
+
} catch {
|
|
43
|
+
return empty
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(raw)
|
|
47
|
+
if (parsed === null || typeof parsed !== 'object') return empty
|
|
48
|
+
const sessions = parsed.sessions !== null && typeof parsed.sessions === 'object' ? parsed.sessions : {}
|
|
49
|
+
return { version: LEDGER_VERSION, sessions }
|
|
50
|
+
} catch {
|
|
51
|
+
return empty
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 把一次改写记进账本。
|
|
57
|
+
*
|
|
58
|
+
* @param {object} entry - 记录。
|
|
59
|
+
* @param {string} entry.sessionId - 会话 id。
|
|
60
|
+
* @param {'window'|'flatten'|'stream'|'lossless'|'restore'} entry.mode - 做了什么。
|
|
61
|
+
* @param {object[]} entry.before - 改写前的事件。
|
|
62
|
+
* @param {object[]} entry.after - 改写后的事件。
|
|
63
|
+
* @param {number} [entry.bytesBefore] - 改写前字节数。
|
|
64
|
+
* @param {number} [entry.bytesAfter] - 改写后字节数。
|
|
65
|
+
* @param {string} [home] - 家目录(测试用)。
|
|
66
|
+
* @returns {object|null} 该会话的新累计;写不进去时返回 null。
|
|
67
|
+
*/
|
|
68
|
+
export function recordTrim(entry, home = os.homedir()) {
|
|
69
|
+
const ledger = readLedger(home)
|
|
70
|
+
const before = Array.isArray(entry.before) ? entry.before : []
|
|
71
|
+
const after = Array.isArray(entry.after) ? entry.after : []
|
|
72
|
+
// 窗口模式不一定留得下事件数组(裁剪是整份重写),允许直接传数字。
|
|
73
|
+
const windowsBefore = Number.isSafeInteger(entry.windowsBefore) ? entry.windowsBefore : windowsOf(before).length
|
|
74
|
+
const windowsAfter = Number.isSafeInteger(entry.windowsAfter) ? entry.windowsAfter : windowsOf(after).length
|
|
75
|
+
const record = {
|
|
76
|
+
at: new Date().toISOString(),
|
|
77
|
+
mode: entry.mode,
|
|
78
|
+
windowsBefore,
|
|
79
|
+
windowsAfter,
|
|
80
|
+
windowsRemoved: Math.max(0, windowsBefore - windowsAfter),
|
|
81
|
+
eventsBefore: Number.isSafeInteger(entry.eventsBefore) ? entry.eventsBefore : before.length,
|
|
82
|
+
eventsAfter: Number.isSafeInteger(entry.eventsAfter) ? entry.eventsAfter : after.length,
|
|
83
|
+
eventsRemoved: Math.max(0, (Number.isSafeInteger(entry.eventsBefore) ? entry.eventsBefore : before.length) - (Number.isSafeInteger(entry.eventsAfter) ? entry.eventsAfter : after.length)),
|
|
84
|
+
bytesBefore: typeof entry.bytesBefore === 'number' ? entry.bytesBefore : null,
|
|
85
|
+
bytesAfter: typeof entry.bytesAfter === 'number' ? entry.bytesAfter : null,
|
|
86
|
+
}
|
|
87
|
+
const current = ledger.sessions[entry.sessionId] ?? { trims: [], totals: { trims: 0, windowsRemoved: 0, eventsRemoved: 0, bytesSaved: 0, lastAt: null } }
|
|
88
|
+
const trims = [...(Array.isArray(current.trims) ? current.trims : []), record].slice(-200)
|
|
89
|
+
const totals = {
|
|
90
|
+
trims: (current.totals?.trims ?? 0) + 1,
|
|
91
|
+
windowsRemoved: (current.totals?.windowsRemoved ?? 0) + record.windowsRemoved,
|
|
92
|
+
eventsRemoved: (current.totals?.eventsRemoved ?? 0) + record.eventsRemoved,
|
|
93
|
+
bytesSaved: (current.totals?.bytesSaved ?? 0) + Math.max(0, (record.bytesBefore ?? 0) - (record.bytesAfter ?? 0)),
|
|
94
|
+
lastAt: record.at,
|
|
95
|
+
}
|
|
96
|
+
ledger.sessions[entry.sessionId] = { trims, totals }
|
|
97
|
+
try {
|
|
98
|
+
const file = ledgerPath(home)
|
|
99
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
100
|
+
fs.writeFileSync(file, JSON.stringify(ledger))
|
|
101
|
+
} catch {
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
return totals
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 清空账本(用户显式要求时)。 */
|
|
108
|
+
export function clearLedger(home = os.homedir()) {
|
|
109
|
+
try {
|
|
110
|
+
fs.rmSync(ledgerPath(home), { force: true })
|
|
111
|
+
return true
|
|
112
|
+
} catch {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 按日志路径记一笔:sessionId 就是日志所在目录名。
|
|
119
|
+
*
|
|
120
|
+
* @param {string} logFile - 日志路径。
|
|
121
|
+
* @param {string} mode - window / flatten / stream / lossless / rebuild。
|
|
122
|
+
* @param {object[]} before - 改写前事件。
|
|
123
|
+
* @param {object[]} after - 改写后事件。
|
|
124
|
+
* @param {{ bytesBefore?: number, bytesAfter?: number, record?: boolean, home?: string }} [options] - 附加信息。
|
|
125
|
+
* @returns {object|null} 新累计;没记或记不进去返回 null。
|
|
126
|
+
*/
|
|
127
|
+
export function recordFor(logFile, mode, before, after, options = {}) {
|
|
128
|
+
if (options.record === false) return null
|
|
129
|
+
const sessionId = path.basename(path.dirname(logFile))
|
|
130
|
+
return recordTrim({ sessionId, mode, before, after, bytesBefore: options.bytesBefore, bytesAfter: options.bytesAfter }, options.home)
|
|
131
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-log-trim host half (dsh-log-trim.lossless.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-log-trim/lib/lossless.js (v0.
|
|
3
|
+
// Source: ../dsh-log-trim/lib/lossless.js (v0.6.0, sha256 a35a99d4fe824689)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import fs from 'node:fs'
|
|
27
|
+
import { recordFor } from './dsh-log-trim.ledger.js'
|
|
27
28
|
import { commitTrim, derivedHistory, dropOrphanCompactions, encodeLog, foldSurface, readLog, renumber, seededHeaderFor, textLengthOf } from './dsh-log-trim.vendor.compact-session.mjs'
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -224,6 +225,7 @@ export async function applyLossless(logFile, config, plan, options = {}) {
|
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
const summary = commitTrim({ staging, backup }, logFile)
|
|
228
|
+
recordFor(logFile, 'lossless', original.events, renamed.events, { bytesBefore: original.compressedBytes, bytesAfter: summary.after.bytes, record: config?.recordLedger !== false })
|
|
227
229
|
return {
|
|
228
230
|
...summary,
|
|
229
231
|
mode: 'lossless',
|
|
@@ -254,6 +256,7 @@ export async function applyRebuild(logFile, config) {
|
|
|
254
256
|
}
|
|
255
257
|
const beforeBytes = fs.statSync(staging).size
|
|
256
258
|
commitTrim({ staging, backup }, logFile)
|
|
259
|
+
recordFor(logFile, 'rebuild', original.events, rebuilt, { bytesBefore: original.compressedBytes, bytesAfter: beforeBytes, record: config?.recordLedger !== false })
|
|
257
260
|
return {
|
|
258
261
|
mode: 'rebuild',
|
|
259
262
|
before: { events: original.events.length, bytes: original.compressedBytes, windows: 0 },
|
|
@@ -271,7 +274,7 @@ export async function applyRebuild(logFile, config) {
|
|
|
271
274
|
* @param {number} keepTurns - 保留最近几轮。
|
|
272
275
|
* @returns {Promise<object|null>} 摘要;没有可清的就返回 null。
|
|
273
276
|
*/
|
|
274
|
-
export async function applyStreamOnly(logFile, keepTurns) {
|
|
277
|
+
export async function applyStreamOnly(logFile, keepTurns, options = {}) {
|
|
275
278
|
const { stripOldStreams } = await import('./dsh-log-trim.vendor.compact-session.mjs')
|
|
276
279
|
const original = readLog(logFile)
|
|
277
280
|
const stripped = stripOldStreams(original.events, keepTurns)
|
|
@@ -288,6 +291,11 @@ export async function applyStreamOnly(logFile, keepTurns) {
|
|
|
288
291
|
}
|
|
289
292
|
const beforeBytes = fs.statSync(staging).size
|
|
290
293
|
commitTrim({ staging, backup }, logFile)
|
|
294
|
+
recordFor(logFile, 'stream', original.events, stripped.events, {
|
|
295
|
+
bytesBefore: original.compressedBytes,
|
|
296
|
+
bytesAfter: beforeBytes,
|
|
297
|
+
record: options.record !== false,
|
|
298
|
+
})
|
|
291
299
|
return { events: stripped.stripped, before: original.compressedBytes, after: beforeBytes, backup: backup.slice(backup.lastIndexOf('.precompact-') + 1) }
|
|
292
300
|
}
|
|
293
301
|
|
|
@@ -327,6 +335,7 @@ export async function applyFlatten(logFile, config) {
|
|
|
327
335
|
}
|
|
328
336
|
const beforeBytes = fs.statSync(staging).size
|
|
329
337
|
commitTrim({ staging, backup }, logFile)
|
|
338
|
+
recordFor(logFile, 'flatten', original.events, folded, { bytesBefore: original.compressedBytes, bytesAfter: beforeBytes, record: config?.recordLedger !== false })
|
|
330
339
|
return {
|
|
331
340
|
mode: 'flatten',
|
|
332
341
|
before: { events: original.events.length, bytes: original.compressedBytes, windows: 0 },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-log-trim host half (dsh-log-trim.roots.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-log-trim/lib/roots.js (v0.
|
|
3
|
+
// Source: ../dsh-log-trim/lib/roots.js (v0.6.0, sha256 028d9c12a395aaab)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-log-trim host half (dsh-log-trim.vendor.compact-session.mjs), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-log-trim/lib/vendor.compact-session.mjs (v0.
|
|
3
|
+
// Source: ../dsh-log-trim/lib/vendor.compact-session.mjs (v0.6.0, sha256 2c1c819e732c9649)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-log-trim host half (dsh-log-trim.verify-session-log.mjs), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-log-trim/lib/verify-session-log.mjs (v0.
|
|
3
|
+
// Source: ../dsh-log-trim/lib/verify-session-log.mjs (v0.6.0, sha256 6a0527e8483fb252)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|