@kira905/dsh-memory-toolkit 0.1.3 → 0.2.0
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 +92 -0
- package/README.en.md +225 -2
- package/README.md +180 -2
- package/lib/deploy-guard.mjs +463 -275
- package/lib/guard-http.mjs +59 -62
- package/lib/pitfall-form-map.mjs +94 -84
- package/lib/pitfall-score.mjs +262 -185
- package/lib/session-file-name.mjs +116 -100
- package/lib/zstd-frames.mjs +88 -67
- package/memory-db-tools/README.md +29 -1
- package/memory-db-tools/audit-analyze.mjs +1 -1
- package/memory-db-tools/audit-export.mjs +60 -16
- package/memory-db-tools/audit-struct-probe.mjs +29 -8
- package/memory-db-tools/lib/known-devices.mjs +137 -0
- package/memory-engine/README.md +58 -4
- package/memory-engine/bench/bench.mjs +1 -1
- package/memory-engine/cli/band-sentinel.mjs +164 -0
- package/memory-engine/cli/distill-rollback.mjs +112 -44
- package/memory-engine/cli/distill-sessions.mjs +237 -0
- package/memory-engine/cli/distill.mjs +196 -95
- package/memory-engine/cli/doc-index-rebuild.mjs +142 -108
- package/memory-engine/cli/doc-index.mjs +47 -38
- package/memory-engine/cli/domain-tags.mjs +246 -162
- package/memory-engine/cli/layer-migrate.mjs +176 -0
- package/memory-engine/cli/layer-move.mjs +189 -0
- package/memory-engine/cli/pitfall-hook.mjs +136 -70
- package/memory-engine/cli/recall.mjs +243 -110
- package/memory-engine/cli/selfcheck.mjs +93 -44
- package/memory-engine/cli/serve-start.mjs +221 -140
- package/memory-engine/cli/sweep.mjs +83 -44
- package/memory-engine/cli/sync.mjs +93 -57
- package/memory-engine/config/audit-devices.json +6 -0
- package/memory-engine/config/bands.json +7 -0
- package/memory-engine/config/layers.json +7 -0
- package/memory-engine/dict/LICENSE-/350/257/215/345/205/270.md +53 -0
- package/memory-engine/dict/lexicon-config.json +11 -0
- package/memory-engine/dict//350/257/215/345/205/270.txt +109750 -0
- package/memory-engine/dict//350/257/215/351/242/221.txt +20000 -0
- package/memory-engine/lib/band-sentinel.mjs +417 -0
- package/memory-engine/lib/cli-strict.mjs +128 -0
- package/memory-engine/lib/cli-util.mjs +147 -125
- package/memory-engine/lib/config.mjs +83 -96
- package/memory-engine/lib/dirty-ledger.mjs +127 -94
- package/memory-engine/lib/distill-rollback.mjs +173 -133
- package/memory-engine/lib/distill.mjs +878 -585
- package/memory-engine/lib/doc-index-store.mjs +208 -235
- package/memory-engine/lib/doc-index.mjs +351 -350
- package/memory-engine/lib/doc-scan.mjs +178 -156
- package/memory-engine/lib/doc-summarize.mjs +220 -205
- package/memory-engine/lib/domain-tags.mjs +358 -277
- package/memory-engine/lib/embedder.mjs +175 -194
- package/memory-engine/lib/hashes.mjs +22 -7
- package/memory-engine/lib/layer-migrate.mjs +330 -0
- package/memory-engine/lib/layer-move.mjs +307 -0
- package/memory-engine/lib/layers.mjs +191 -0
- package/memory-engine/lib/lexicon-recall.mjs +473 -0
- package/memory-engine/lib/memory-source.mjs +78 -80
- package/memory-engine/lib/pitfall-source.mjs +115 -114
- package/memory-engine/lib/policy-source.mjs +279 -261
- package/memory-engine/lib/recall-log.mjs +285 -0
- package/memory-engine/lib/recall.mjs +565 -240
- package/memory-engine/lib/selfcheck.mjs +142 -106
- package/memory-engine/lib/session-distill.mjs +603 -0
- package/memory-engine/lib/session-source.mjs +421 -0
- package/memory-engine/lib/sweep.mjs +107 -130
- package/memory-engine/lib/sync.mjs +110 -140
- package/memory-engine/lib/tick-hook.mjs +123 -148
- package/memory-engine/lib/vectors-store.mjs +213 -188
- package/memory-engine/test/12-layering.test.mjs +539 -0
- package/memory-engine/test/13-session-distill.test.mjs +616 -0
- package/memory-engine/tools/bridge-smoke.mjs +94 -55
- package/memory-engine/tools/fetch-model.mjs +167 -87
- package/memory-engine/tools/smoke-recall.mjs +94 -38
- package/memory-engine/tools/verify-recall-chain.mjs +413 -0
- package/package.json +1 -1
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
2
|
+
// 提炼 CLI:把窗口内的记忆条目聚类,逐组交给模型压成一条「提炼产物」再写回库。
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// node cli/distill.mjs --db <memory.db> [选项]
|
|
6
|
-
// node cli/distill.mjs --db <memory.db> --apply
|
|
4
|
+
// 用法(对外契约:参数名 / 默认值 / 退出码都不变):
|
|
5
|
+
// node cli/distill.mjs --db <memory.db> [选项] 缺省是 **dry-run**:只算、只打印,不动库
|
|
6
|
+
// node cli/distill.mjs --db <memory.db> --apply 真跑:调模型 → 写库 → 落 state → 写日报段
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
// --db <path>
|
|
10
|
-
// --vectors-db <path>
|
|
11
|
-
// --state <path>
|
|
12
|
-
// --owner-user <id>
|
|
13
|
-
// --window-hours <n>
|
|
14
|
-
// --limit <n>
|
|
15
|
-
// --max-calls / --max-tokens
|
|
16
|
-
// --model / --base-url
|
|
17
|
-
// --sim-threshold <f>
|
|
18
|
-
// --min-cluster-chars <n>
|
|
19
|
-
// --min-cluster-size <n>
|
|
20
|
-
// --max-clusters <n>
|
|
21
|
-
// --now <iso>
|
|
22
|
-
// --snapshot-dir <path>
|
|
23
|
-
// --no-dirty-probe
|
|
24
|
-
// --daily-report <file>
|
|
25
|
-
// --no-daily
|
|
26
|
-
// --json
|
|
8
|
+
// 开关:
|
|
9
|
+
// --db <path> 必填(无默认:写死生产路径是明令禁止的)
|
|
10
|
+
// --vectors-db <path> 向量库(缺省 <home>/memory-vectors/vectors.db;home = 库文件的上一级目录)
|
|
11
|
+
// --state <path> 提炼台账(缺省 <home>/butler-distill-state.json)
|
|
12
|
+
// --owner-user <id> 本机 owner 的 userid(缺省查身份表;查不出 ⇒ 归属从严,本轮一次模型都不调)
|
|
13
|
+
// --window-hours <n> 增量窗口(24)
|
|
14
|
+
// --limit <n> 输入条数上限(0 = 不限)
|
|
15
|
+
// --max-calls / --max-tokens 每日成本闸(20 次 / 150000 token)
|
|
16
|
+
// --model / --base-url 模型档(缺省取库里的 DEFAULT_LLM)
|
|
17
|
+
// --sim-threshold <f> 聚类余弦阈值(0.60)
|
|
18
|
+
// --min-cluster-chars <n> 整组正文合计不到这个字数就不值得花一次调用(120)
|
|
19
|
+
// --min-cluster-size <n> 组内少于 N 条不提炼(2:单条不成聚类,也为了别把 recall 段挤掉)
|
|
20
|
+
// --max-clusters <n> 单轮最多处理几组(20)
|
|
21
|
+
// --now <iso> 把某个时刻当"现在"(演练 / 模拟用)
|
|
22
|
+
// --snapshot-dir <path> 写库前先 VACUUM INTO 到该目录
|
|
23
|
+
// --no-dirty-probe 不做全表 hash 比对(不读 memories_dirty 补充源)
|
|
24
|
+
// --daily-report <file> 覆盖日报落点(缺省是今天的管家报告)
|
|
25
|
+
// --no-daily 不写日报段
|
|
26
|
+
// --json stdout 只出 JSON,日志走 stderr
|
|
27
27
|
// --quiet / --help
|
|
28
28
|
//
|
|
29
29
|
// 退出码:0 = 正常(含"没活干")/ 1 = 失败 / 2 = 用法错 / 3 = 环境不可用(库不在)
|
|
@@ -32,87 +32,203 @@ import path from 'node:path';
|
|
|
32
32
|
import { isMainEntry, parseArgs, makeLogger, num } from '../lib/cli-util.mjs';
|
|
33
33
|
import {
|
|
34
34
|
runDistill, readState, renderDailyLine, updateDailySection, dailyReportPath,
|
|
35
|
-
COST_LIMITS, DEFAULT_LLM, DEFAULT_WINDOW_HOURS, CLUSTER_DEFAULTS, STATE_SCHEMA,
|
|
35
|
+
COST_LIMITS, DEFAULT_LLM, DEFAULT_WINDOW_HOURS, CLUSTER_DEFAULTS, STATE_SCHEMA as LIB_STATE_SCHEMA,
|
|
36
36
|
} from '../lib/distill.mjs';
|
|
37
37
|
|
|
38
|
-
const USAGE =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
const USAGE = [
|
|
39
|
+
'node cli/distill.mjs',
|
|
40
|
+
'--db <memory.db>',
|
|
41
|
+
'[--apply]',
|
|
42
|
+
'[--window-hours 24]',
|
|
43
|
+
'[--limit N]',
|
|
44
|
+
'[--max-calls 20]',
|
|
45
|
+
'[--max-tokens 150000]',
|
|
46
|
+
'[--max-clusters 20]',
|
|
47
|
+
'[--min-cluster-size 2]',
|
|
48
|
+
'[--min-cluster-chars 120]',
|
|
49
|
+
'[--model deepseek-flash]',
|
|
50
|
+
'[--now <iso>]',
|
|
51
|
+
'[--snapshot-dir <dir>]',
|
|
52
|
+
'[--daily-report <file>]',
|
|
53
|
+
'[--no-dirty-probe]',
|
|
54
|
+
'[--no-daily]',
|
|
55
|
+
'[--json]',
|
|
56
|
+
'[--help]',
|
|
57
|
+
].join(' ');
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
|
|
59
|
+
export const STATE_SCHEMA = LIB_STATE_SCHEMA;
|
|
60
|
+
|
|
61
|
+
/** 数值旋钮表:flag 名 → 归一化字段 → 缺省值。 */
|
|
62
|
+
const KNOBS = [
|
|
63
|
+
{ flag: 'window-hours', field: 'windowHours', fallback: DEFAULT_WINDOW_HOURS },
|
|
64
|
+
{ flag: 'limit', field: 'limit', fallback: 0 },
|
|
65
|
+
{ flag: 'max-calls', field: 'callsPerDay', fallback: COST_LIMITS.callsPerDay },
|
|
66
|
+
{ flag: 'max-tokens', field: 'tokensPerDay', fallback: COST_LIMITS.tokensPerDay },
|
|
67
|
+
{ flag: 'sim-threshold', field: 'simThreshold', fallback: CLUSTER_DEFAULTS.simThreshold },
|
|
68
|
+
{ flag: 'min-cluster-chars', field: 'minClusterChars', fallback: CLUSTER_DEFAULTS.minClusterChars },
|
|
69
|
+
{ flag: 'min-cluster-size', field: 'minClusterSize', fallback: CLUSTER_DEFAULTS.minClusterSize },
|
|
70
|
+
{ flag: 'max-clusters', field: 'maxClustersPerRun', fallback: CLUSTER_DEFAULTS.maxClustersPerRun },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const OWNER_MODULES = ['../../memory-db-tools/lib/memory-scope.mjs', '../dsh-memory-local/src/schema-migrate.mjs'];
|
|
74
|
+
|
|
75
|
+
/** 本机 owner 的 userid:权威实现 → fork 自检镜像 → 内置回落(和域标签 CLI 同一口径)。 */
|
|
76
|
+
export async function resolveOwnerUser(explicit, home) {
|
|
45
77
|
if (explicit) return { user: explicit, source: 'cli' };
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const mod = await import(new URL(rel, import.meta.url).href);
|
|
50
|
-
const identity = mod.readIdentityTable ? mod.readIdentityTable(home) : null;
|
|
51
|
-
const r = mod.resolveDefaultOwner ? mod.resolveDefaultOwner(identity) : null;
|
|
52
|
-
if (r && r.ownerUser) return { user: r.ownerUser, source: `${rel} (${r.source})`, notes: r.notes ?? [] };
|
|
53
|
-
} catch { /* 试下一个候选 */ }
|
|
78
|
+
for (const spec of OWNER_MODULES) {
|
|
79
|
+
const hit = await probeOwnerModule(spec, home);
|
|
80
|
+
if (hit) return hit;
|
|
54
81
|
}
|
|
55
|
-
return {
|
|
82
|
+
return {
|
|
83
|
+
user: null,
|
|
84
|
+
source: '解析失败(两条权威源都不可用)',
|
|
85
|
+
notes: ['⚠️ 本机 owner 解析不出来 ⇒ 归属从严:本轮不调用任何第三方模型'],
|
|
86
|
+
};
|
|
56
87
|
}
|
|
57
88
|
|
|
58
|
-
async function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
async function probeOwnerModule(spec, home) {
|
|
90
|
+
try {
|
|
91
|
+
const mod = await import(new URL(spec, import.meta.url).href);
|
|
92
|
+
const table = mod.readIdentityTable ? mod.readIdentityTable(home) : null;
|
|
93
|
+
const picked = mod.resolveDefaultOwner ? mod.resolveDefaultOwner(table) : null;
|
|
94
|
+
if (!picked?.ownerUser) return null;
|
|
95
|
+
return { user: picked.ownerUser, source: `${spec} (${picked.source})`, notes: picked.notes ?? [] };
|
|
96
|
+
} catch {
|
|
97
|
+
/* 换下一个候选 */
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
63
101
|
|
|
102
|
+
function readKnobs(flags) {
|
|
103
|
+
const out = {};
|
|
104
|
+
for (const knob of KNOBS) out[knob.field] = num(flags[knob.flag], knob.fallback);
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** 参数校验:返回 {code} 表示当场收工,返回 {dbPath} 表示可以往下走。 */
|
|
109
|
+
function vetArgs(flags) {
|
|
64
110
|
const dbPath = flags.db ? path.resolve(String(flags.db)) : null;
|
|
65
111
|
if (!dbPath) {
|
|
66
112
|
process.stderr.write('用法错:必须给 --db <库路径>(本脚本没有默认库路径)\n');
|
|
67
|
-
return 2;
|
|
113
|
+
return { code: 2 };
|
|
68
114
|
}
|
|
69
115
|
if (!fs.existsSync(dbPath)) {
|
|
70
116
|
process.stderr.write(`环境不可用:库不存在 ${dbPath}\n`);
|
|
71
|
-
return 3;
|
|
117
|
+
return { code: 3 };
|
|
118
|
+
}
|
|
119
|
+
const now = flags.now ? Date.parse(String(flags.now)) : Date.now();
|
|
120
|
+
if (!Number.isFinite(now)) {
|
|
121
|
+
process.stderr.write(`用法错:--now 不是合法时间:${flags.now}\n`);
|
|
122
|
+
return { code: 2 };
|
|
123
|
+
}
|
|
124
|
+
return { dbPath, now };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function composePayload({ r, state, limits, dailyLine, daily, ctx, owner }) {
|
|
128
|
+
return {
|
|
129
|
+
ok: r.ok,
|
|
130
|
+
mode: r.mode,
|
|
131
|
+
at: new Date(ctx.now).toISOString(),
|
|
132
|
+
db: ctx.dbPath,
|
|
133
|
+
vectorsDb: ctx.vectorsDb,
|
|
134
|
+
stateFile: ctx.stateFile,
|
|
135
|
+
model: DEFAULT_LLM.model,
|
|
136
|
+
ownerUser: owner.user,
|
|
137
|
+
ownerSource: owner.source,
|
|
138
|
+
limits,
|
|
139
|
+
inputs: r.inputs,
|
|
140
|
+
clusterStats: r.clusterStats ?? null,
|
|
141
|
+
dirty: r.dirty ?? null,
|
|
142
|
+
planned: r.planned,
|
|
143
|
+
calls: r.calls,
|
|
144
|
+
written: r.written,
|
|
145
|
+
writtenIds: r.writtenIds,
|
|
146
|
+
cost: state.cost ?? null,
|
|
147
|
+
stopping: r.stopping,
|
|
148
|
+
warnings: r.warnings,
|
|
149
|
+
dailyLine,
|
|
150
|
+
daily,
|
|
151
|
+
snapshot: r.snapshot ?? null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function printSummary({ r, state, limits, dailyLine, log }) {
|
|
156
|
+
log('');
|
|
157
|
+
log(`输入 ${r.inputs?.accepted ?? 0} 条(窗口 ${r.inputs?.windowRows ?? 0} / 台账 ${r.inputs?.dirtyRows ?? 0})⇒ 接受 ${r.inputs?.accepted ?? 0}`);
|
|
158
|
+
log(`聚类 ${r.clusterStats?.clusters ?? 0} 组(单条组 ${r.clusterStats?.singletonClusters ?? 0} · 无向量 ${r.clusterStats?.noVector ?? 0})`);
|
|
159
|
+
for (const step of r.planned ?? []) {
|
|
160
|
+
const verb = step.action === 'call' ? '→ 调模型' : '· 跳过 ';
|
|
161
|
+
const chars = step.chars ? ` (${step.chars} 字)` : '';
|
|
162
|
+
log(` ${verb} ${step.key} ids=[${step.ids.join(',')}] ${step.action}${chars}`);
|
|
163
|
+
}
|
|
164
|
+
for (const call of r.calls ?? []) {
|
|
165
|
+
const tail = call.error ? ` · ${call.error}` : '';
|
|
166
|
+
log(` ✦ ${call.key} 调用 ${call.ok ? 'OK' : '失败'} · ${call.tokens} token · 结论 ${call.conclusionChars} 字${tail}`);
|
|
167
|
+
}
|
|
168
|
+
log(`提炼条数 ${r.written}${r.writtenIds?.length ? `(新 id ${r.writtenIds.join(',')})` : ''}`);
|
|
169
|
+
if (r.stopping) log(`停机:${r.stopping}`);
|
|
170
|
+
const spent = Number(state.cost?.promptTokens ?? 0) + Number(state.cost?.completionTokens ?? 0);
|
|
171
|
+
log(`当日调用 ${state.cost?.calls ?? 0}/${limits.callsPerDay} · 当日 token ${spent}/${limits.tokensPerDay}`);
|
|
172
|
+
for (const warn of r.warnings ?? []) log(`⚠️ ${warn}`);
|
|
173
|
+
log(`日报行:${dailyLine}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function main() {
|
|
177
|
+
const { flags } = parseArgs();
|
|
178
|
+
const json = Boolean(flags.json);
|
|
179
|
+
const log = makeLogger({ json, quiet: Boolean(flags.quiet) });
|
|
180
|
+
if (flags.help) {
|
|
181
|
+
process.stdout.write(`用法:${USAGE}\n`);
|
|
182
|
+
return 0;
|
|
72
183
|
}
|
|
73
184
|
|
|
185
|
+
const vetted = vetArgs(flags);
|
|
186
|
+
if (vetted.code !== undefined) return vetted.code;
|
|
187
|
+
const { dbPath, now } = vetted;
|
|
188
|
+
|
|
74
189
|
const home = path.resolve(flags.home ? String(flags.home) : path.dirname(path.dirname(dbPath)));
|
|
75
190
|
const vectorsDb = flags['vectors-db'] ? path.resolve(String(flags['vectors-db'])) : path.join(home, 'memory-vectors', 'vectors.db');
|
|
76
191
|
const stateFile = flags.state ? path.resolve(String(flags.state)) : path.join(home, 'butler-distill-state.json');
|
|
77
192
|
const apply = Boolean(flags.apply);
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
const limits = {
|
|
81
|
-
callsPerDay: num(flags['max-calls'], COST_LIMITS.callsPerDay),
|
|
82
|
-
tokensPerDay: num(flags['max-tokens'], COST_LIMITS.tokensPerDay),
|
|
83
|
-
};
|
|
193
|
+
const knobs = readKnobs(flags);
|
|
194
|
+
const limits = { callsPerDay: knobs.callsPerDay, tokensPerDay: knobs.tokensPerDay };
|
|
84
195
|
const owner = await resolveOwnerUser(flags['owner-user'] ? String(flags['owner-user']) : null, home);
|
|
85
196
|
const snapshotDir = flags['snapshot-dir'] ? path.resolve(String(flags['snapshot-dir'])) : null;
|
|
197
|
+
const model = flags.model ? String(flags.model) : DEFAULT_LLM.model;
|
|
86
198
|
|
|
87
199
|
log(`库 : ${dbPath}`);
|
|
88
200
|
log(`向量库 : ${vectorsDb}`);
|
|
89
201
|
log(`state : ${stateFile}`);
|
|
90
202
|
log(`本机 owner: ${owner.user ?? '(未解析)'}(来源 ${owner.source})`);
|
|
91
|
-
log(`模型 : ${
|
|
203
|
+
log(`模型 : ${model}(成本闸 ≤${limits.callsPerDay} 次 / ≤${limits.tokensPerDay} token 每日)`);
|
|
92
204
|
log(`模式 : ${apply ? 'APPLY(写库)' : 'DRY-RUN(只打印不写库)'}`);
|
|
93
|
-
for (const
|
|
205
|
+
for (const note of owner.notes ?? []) log(`⚠️ ${note}`);
|
|
94
206
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
207
|
+
const stamp = new Date(now).toISOString().replace(/[:.]/g, '-');
|
|
208
|
+
const run = await runDistill({
|
|
209
|
+
memoryDb: dbPath,
|
|
210
|
+
vectorsDb,
|
|
211
|
+
stateFile,
|
|
212
|
+
now,
|
|
213
|
+
windowHours: knobs.windowHours,
|
|
98
214
|
apply,
|
|
99
215
|
limits,
|
|
100
|
-
model
|
|
216
|
+
model,
|
|
101
217
|
baseUrl: flags['base-url'] ? String(flags['base-url']) : DEFAULT_LLM.baseUrl,
|
|
102
218
|
localOwner: owner.user,
|
|
103
|
-
limit:
|
|
104
|
-
simThreshold:
|
|
105
|
-
minClusterChars:
|
|
106
|
-
minClusterSize:
|
|
107
|
-
maxClustersPerRun:
|
|
219
|
+
limit: knobs.limit,
|
|
220
|
+
simThreshold: knobs.simThreshold,
|
|
221
|
+
minClusterChars: knobs.minClusterChars,
|
|
222
|
+
minClusterSize: knobs.minClusterSize,
|
|
223
|
+
maxClustersPerRun: knobs.maxClustersPerRun,
|
|
108
224
|
dirtyProbe: !flags['no-dirty-probe'],
|
|
109
|
-
snapshotFile: snapshotDir ? path.join(snapshotDir, `memory-before-distill-${
|
|
225
|
+
snapshotFile: snapshotDir ? path.join(snapshotDir, `memory-before-distill-${stamp}.db`) : null,
|
|
110
226
|
log,
|
|
111
227
|
});
|
|
112
228
|
|
|
113
|
-
//
|
|
114
|
-
const state =
|
|
115
|
-
const dailyLine =
|
|
229
|
+
// 没花一分钱(dry-run / 环境问题)也要给一行当日成本,日报段才拼得出来
|
|
230
|
+
const state = run.state ?? readState(stateFile, dbPath, new Date(now).toISOString());
|
|
231
|
+
const dailyLine = run.dailyLine ?? renderDailyLine({ mode: run.mode, run: run.run ?? {}, cost: state.cost, limits, stopping: run.stopping });
|
|
116
232
|
|
|
117
233
|
let daily = { ok: false, reason: '未写日报(--no-daily)' };
|
|
118
234
|
if (!flags['no-daily'] && apply) {
|
|
@@ -122,35 +238,20 @@ async function main() {
|
|
|
122
238
|
else log(`日报段未写:${daily.reason}(${file})`);
|
|
123
239
|
}
|
|
124
240
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
cost: state.cost ?? null, stopping: r.stopping, warnings: r.warnings, dailyLine, daily,
|
|
131
|
-
snapshot: r.snapshot ?? null,
|
|
132
|
-
};
|
|
133
|
-
if (json) process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
134
|
-
else {
|
|
135
|
-
log('');
|
|
136
|
-
log(`输入 ${r.inputs?.accepted ?? 0} 条(窗口 ${r.inputs?.windowRows ?? 0} / 台账 ${r.inputs?.dirtyRows ?? 0})⇒ 接受 ${r.inputs?.accepted ?? 0}`);
|
|
137
|
-
log(`聚类 ${r.clusterStats?.clusters ?? 0} 组(单条组 ${r.clusterStats?.singletonClusters ?? 0} · 无向量 ${r.clusterStats?.noVector ?? 0})`);
|
|
138
|
-
for (const p of r.planned ?? []) log(` ${p.action === 'call' ? '→ 调模型' : '· 跳过 '} ${p.key} ids=[${p.ids.join(',')}] ${p.action}${p.chars ? ` (${p.chars} 字)` : ''}`);
|
|
139
|
-
for (const c of r.calls ?? []) log(` ✦ ${c.key} 调用 ${c.ok ? 'OK' : '失败'} · ${c.tokens} token · 结论 ${c.conclusionChars} 字${c.error ? ` · ${c.error}` : ''}`);
|
|
140
|
-
log(`提炼条数 ${r.written}${r.writtenIds?.length ? `(新 id ${r.writtenIds.join(',')})` : ''}`);
|
|
141
|
-
if (r.stopping) log(`停机:${r.stopping}`);
|
|
142
|
-
log(`当日调用 ${state.cost?.calls ?? 0}/${limits.callsPerDay} · 当日 token ${(Number(state.cost?.promptTokens ?? 0) + Number(state.cost?.completionTokens ?? 0))}/${limits.tokensPerDay}`);
|
|
143
|
-
for (const w of r.warnings ?? []) log(`⚠️ ${w}`);
|
|
144
|
-
log(`日报行:${dailyLine}`);
|
|
145
|
-
}
|
|
146
|
-
return r.ok ? 0 : (r.exitCode || 1);
|
|
241
|
+
const ctx = { dbPath, vectorsDb, stateFile, now };
|
|
242
|
+
const payload = composePayload({ r: run, state, limits, dailyLine, daily, ctx, owner });
|
|
243
|
+
if (json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
244
|
+
else printSummary({ r: run, state, limits, dailyLine, log });
|
|
245
|
+
return run.ok ? 0 : (run.exitCode || 1);
|
|
147
246
|
}
|
|
148
247
|
|
|
149
248
|
if (isMainEntry(import.meta.url)) {
|
|
150
|
-
main()
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
249
|
+
main()
|
|
250
|
+
.then((code) => {
|
|
251
|
+
process.exitCode = code;
|
|
252
|
+
})
|
|
253
|
+
.catch((e) => {
|
|
254
|
+
console.error(`未捕获异常:${e?.stack ?? e}`);
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
});
|
|
154
257
|
}
|
|
155
|
-
|
|
156
|
-
export { main, resolveOwnerUser, STATE_SCHEMA };
|