dsh-trivium 0.6.0 → 0.7.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/README.md +4 -1
- package/README.zh-CN.md +4 -1
- package/lib/client.js +128 -2
- package/lib/server.js +24 -0
- package/lib/turn-ledger.js +34 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -279,6 +279,8 @@ Off by default. After you turn **Chips (memory whitelist)** on in Settings, the
|
|
|
279
279
|
|
|
280
280
|
## Changelog
|
|
281
281
|
|
|
282
|
+
**0.7.0** — Observability release. Settings → Recall check now lists pending and recently delivered corrections per session, scoped to the workspace, and adds a **Check version chains** button that runs the same read-only check as `dsh-trivium-check`. New read-only endpoints: `GET /api/dsh-trivium/corrections` and `GET /api/dsh-trivium/integrity`. A fault-injection regression now proves a sidecar write failure is reported by health and repaired by `bootstrap-write` on reopen, and the README documents the host-side desktop `EPIPE`. Memory format, defaults and the four-tool surface are unchanged.
|
|
283
|
+
|
|
282
284
|
**0.6.0** — Maintenance release. The read-only revision checker and the stale-deadline cleaner now ship with the package as `dsh-trivium-check` and `dsh-trivium-clear-until` (dry run by default; `--apply` clears only deadlines also present in the body, `--all` clears every stale one). The checker previously existed only in the Git checkout even though the 0.5.1 notes told users to run it. This release also pins two verifications: turn identity comes from the host's own user-message id (checked in a real session log), and a genuine second-window attempt with a stale ETag returns 409 and succeeds after refresh. No defaults or data formats changed.
|
|
283
285
|
|
|
284
286
|
**0.5.1** — Correctness release for 0.5.0. A user turn is now booked by the text actually rendered, so three long chips can no longer ship 3 × ~217 tokens against one 300 allowance; commit is idempotent and an explicit zero budget stays zero. Version resolution returns the current node's own body, type, deadline and state instead of only its id, so a query matching a superseded decision no longer shows the old instruction under the new id. Imports plan URI identity and validate the whole resulting graph before one native transaction: two versions are never merged by similar text, no `supersedes` self-loop is written, and a failed transaction leaves no node, body or edge behind. When a delivered fact changes, the session that saw it gets a short correction notice (never the old body), scoped per workspace and consumed only after delivery. Kernel: `triviumdb@0.9.0-beta.3` (pinned prerelease). Windows are supported from 900 px upward; at 390 px the host's 188 px settings sidebar leaves ~154 px for the panel, which is not readable (it does not scroll horizontally). Upgrade note: `until` values written by 0.4.21 from prose dates are not rewritten automatically — clear them with **Show expired decisions** or `ctx_remember` with `until: ""`.
|
|
@@ -352,6 +354,7 @@ The peer ranges now explicitly admit the supported DSH `0.1.5`, `0.2.0`, and `0.
|
|
|
352
354
|
| Memory still there after uninstall | Stop `dsh web` before `dsh plugin remove`. Leftover `.tdb` is a workspace never opened on 0.4.10+; delete `.dsh/trivium.tdb` in that folder. Committed `trivium.jsonl` comes back with `git checkout`. Disable-in-place does not wipe. |
|
|
353
355
|
| Check for updates fails | That request only hits the npm registry for the latest version. Memory is unaffected; try again later. |
|
|
354
356
|
| Old conversations will not open after rc.8 | That is DSH’s own session-store format change, not a broken `.tdb`. Graph memory still works via `ctx_find`; leftover boxes on unmatched old sessions are plot residue. |
|
|
357
|
+
| Desktop shows “A JavaScript error occurred in the main process” with `EPIPE: broken pipe, write` | Host-side, not the plugin: `electron-updater` writes a log line to a console pipe whose parent process has exited (typical when the app was started by a test launcher with `--inspect`). No `dsh-trivium` frame is in that stack. Click OK and start the app from the Start menu; avoid launching it from a terminal or harness that closes. |
|
|
355
358
|
|
|
356
359
|
**Maintenance commands** (shipped with the package since 0.6.0):
|
|
357
360
|
|
|
@@ -365,7 +368,7 @@ Local source checkout: `npm install`, then `node scripts/link-dsh.mjs`, then res
|
|
|
365
368
|
## Release info
|
|
366
369
|
|
|
367
370
|
- GitHub: https://github.com/QWQcool/dsh-trivium
|
|
368
|
-
- Package version: `0.
|
|
371
|
+
- Package version: `0.7.0` · [npm](https://www.npmjs.com/package/dsh-trivium)
|
|
369
372
|
- Tested host: `@deepseek-ai/dsh@0.2.1-rc.4` (the peer range also admits the 0.1.x lines)
|
|
370
373
|
- License: MIT (depends on [TriviumDB](https://github.com/YoKONCy/TriviumDB), Apache-2.0)
|
|
371
374
|
|
package/README.zh-CN.md
CHANGED
|
@@ -275,6 +275,8 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
275
275
|
|
|
276
276
|
## 更新说明
|
|
277
277
|
|
|
278
|
+
**0.7.0** — 可观测性版本。设置 → 召回检查现在按会话列出待发送与最近已发送的更正(按工作区过滤),并新增「检查版本链」按钮,复用 `dsh-trivium-check` 的只读检查。新增只读接口:`GET /api/dsh-trivium/corrections`、`GET /api/dsh-trivium/integrity`。新增故障注入回归:旁路写入失败会被 health 报告,重开时由 `bootstrap-write` 修复;README 补充桌面端 `EPIPE` 的宿主侧说明。记忆格式、默认值与四工具面均未变。
|
|
279
|
+
|
|
278
280
|
**0.6.0** — 维护版本。只读版本链体检与过期期限清理现在随包分发:`dsh-trivium-check`、`dsh-trivium-clear-until`(默认只列不改;`--apply` 只清「期限同时出现在正文里」的那些,`--all` 清所有过期期限)。此前 0.5.1 的说明让用户运行 `scripts/check-integrity.mjs`,但该脚本并不在 npm 包里。本版同时固化两项验收:轮标识取自宿主自己的用户消息 id(已在真实会话日志中确认),以及真实第二窗口带过期 ETag 会得到 409、刷新后成功。默认值与数据格式均未变。
|
|
279
281
|
|
|
280
282
|
**0.5.1** — 0.5.0 的正确性修复版。一轮的预算改为按**实际渲染出的文本**记账,三条长芯片不可能再在同一轮送出 3 × 约 217 token;`commit` 幂等,显式 0 预算保持为 0。版本解析返回当前节点自己的正文、类型、期限与状态,而不是只换 id——查询命中被替代的决策时,不会再把旧指令挂在新 id 下。导入先按 URI 规划身份、校验映射后的完整图,再用一次原生事务提交:两个版本不会因正文相近被合并,不会写出 `supersedes` 自环,事务失败也不留下节点、正文或边。已投递的事实被修改后,看过它的会话会收到一条短更正(不重发旧正文),按工作区隔离,且只有真正送达才消费。内核升到 `triviumdb@0.9.0-beta.3`(精确锁定预发布版)。界面支持宽度为 **≥900px**;390px 时宿主固定 188px 侧栏只给面板留下约 154px,不可读(但不会横向滚动)。升级说明:0.4.21 从正文日期误写的 `until` 不会自动清除,请在「显示过期决策」里清空,或用 `ctx_remember` 传 `until: ""`。
|
|
@@ -348,6 +350,7 @@ peer 范围显式接受已支持的 DSH `0.1.5`、`0.2.0`、`0.2.1` RC 版本线
|
|
|
348
350
|
| 卸载后记忆还在 | 先停 `dsh web` 再 `dsh plugin remove`。剩下的 `.tdb` 是从未用 0.4.10 打开过的工作区,到该文件夹删 `.dsh/trivium.tdb`。已提交的 `trivium.jsonl` 用 `git checkout` 找回。只在配置里 disabled 不会清文件。 |
|
|
349
351
|
| 检查更新失败 | 只请求 npm registry 拿最新版本号,不影响记忆;过一会儿再点即可。 |
|
|
350
352
|
| rc.8 之后旧对话打不开 | 那是 DSH 自己的会话库格式变了,不是 `.tdb` 坏了。图记忆仍可 `ctx_find`;对不上号的旧方框可当情节残留。 |
|
|
353
|
+
| 桌面端弹出「A JavaScript error occurred in the main process / EPIPE: broken pipe, write」 | 宿主侧问题,不是插件:`electron-updater` 往一个已经断开的控制台管道写日志(常见于用 `--inspect` 的测试启动器拉起、父进程随后退出)。栈里没有 `dsh-trivium`。点确定后从开始菜单正常启动;不要从会关闭的终端/测试器拉起。 |
|
|
351
354
|
|
|
352
355
|
**维护命令**(0.6.0 起随包分发):
|
|
353
356
|
|
|
@@ -361,7 +364,7 @@ peer 范围显式接受已支持的 DSH `0.1.5`、`0.2.0`、`0.2.1` RC 版本线
|
|
|
361
364
|
## 发布信息
|
|
362
365
|
|
|
363
366
|
- GitHub: https://github.com/QWQcool/dsh-trivium
|
|
364
|
-
- 包版本:`0.
|
|
367
|
+
- 包版本:`0.7.0` · [npm](https://www.npmjs.com/package/dsh-trivium)
|
|
365
368
|
- 测试宿主:`@deepseek-ai/dsh@0.2.1-rc.4`(peer 范围同时接受 0.1.x 各线)
|
|
366
369
|
- License: MIT(依赖 [TriviumDB](https://github.com/YoKONCy/TriviumDB) 为 Apache-2.0)
|
|
367
370
|
|
package/lib/client.js
CHANGED
|
@@ -84,6 +84,17 @@ window.__ModuleLoader__.load({
|
|
|
84
84
|
recallSemanticOff: "语义通道未运行:只读检查不请求 embedding。",
|
|
85
85
|
recallBudget: "本轮预算",
|
|
86
86
|
recallBudgetHint: "估算 token,不是模型计费。芯片、更正与召回共用这 300。",
|
|
87
|
+
correctionsTitle: "更正",
|
|
88
|
+
correctionsHint: "事实被修改后,看过它的会话会收到一条短更正;只有真正送达才从待发送里移除。",
|
|
89
|
+
correctionsPending: "下一条消息生效",
|
|
90
|
+
correctionsSent: "最近已发送",
|
|
91
|
+
correctionsNone: "没有待发送或最近发送的更正。",
|
|
92
|
+
correctionsSession: "会话",
|
|
93
|
+
integrityTitle: "版本链体检",
|
|
94
|
+
integrityCheck: "检查版本链",
|
|
95
|
+
integrityOk: "版本链正常。",
|
|
96
|
+
integrityIssues: "发现问题",
|
|
97
|
+
integrityScanned: "已扫描",
|
|
87
98
|
recallWillSend: "将发送给模型的文本",
|
|
88
99
|
recallEmptyText: "本轮没有可注入的内容。",
|
|
89
100
|
recallReasons: "入选与过滤原因",
|
|
@@ -311,6 +322,17 @@ window.__ModuleLoader__.load({
|
|
|
311
322
|
recallSemanticOff: "Semantic channel did not run: the read-only check never requests an embedding.",
|
|
312
323
|
recallBudget: "Turn budget",
|
|
313
324
|
recallBudgetHint: "Estimated tokens, not model billing. Chips, corrections and recall share this 300.",
|
|
325
|
+
correctionsTitle: "Corrections",
|
|
326
|
+
correctionsHint: "When a delivered fact changes, the session that saw it gets a short notice; it leaves this list only after delivery.",
|
|
327
|
+
correctionsPending: "applies with the next message",
|
|
328
|
+
correctionsSent: "Recently sent",
|
|
329
|
+
correctionsNone: "No pending or recently sent corrections.",
|
|
330
|
+
correctionsSession: "Session",
|
|
331
|
+
integrityTitle: "Version-chain check",
|
|
332
|
+
integrityCheck: "Check version chains",
|
|
333
|
+
integrityOk: "Version chains are clean.",
|
|
334
|
+
integrityIssues: "Issues found",
|
|
335
|
+
integrityScanned: "Scanned",
|
|
314
336
|
recallWillSend: "Text that would be sent to the model",
|
|
315
337
|
recallEmptyText: "Nothing would be injected this turn.",
|
|
316
338
|
recallReasons: "Selected and filtered reasons",
|
|
@@ -748,6 +770,9 @@ window.__ModuleLoader__.load({
|
|
|
748
770
|
const [previewBusy, setPreviewBusy] = react.useState(false);
|
|
749
771
|
const [traces, setTraces] = react.useState([]);
|
|
750
772
|
const [traceError, setTraceError] = react.useState("");
|
|
773
|
+
const [corrections, setCorrections] = react.useState(null);
|
|
774
|
+
const [integrity, setIntegrity] = react.useState(null);
|
|
775
|
+
const [integrityBusy, setIntegrityBusy] = react.useState(false);
|
|
751
776
|
const [showSuperseded, setShowSuperseded] = react.useState(false);
|
|
752
777
|
const [revisionDialog, setRevisionDialog] = react.useState(null);
|
|
753
778
|
const [revisionBusy, setRevisionBusy] = react.useState(false);
|
|
@@ -950,9 +975,33 @@ window.__ModuleLoader__.load({
|
|
|
950
975
|
}
|
|
951
976
|
};
|
|
952
977
|
|
|
978
|
+
const loadCorrections = react.useCallback(async () => {
|
|
979
|
+
try {
|
|
980
|
+
const q = cwdQuery();
|
|
981
|
+
setCorrections(await api("/api/dsh-trivium/corrections" + (q ? "?" + q : "")));
|
|
982
|
+
} catch (err) {
|
|
983
|
+
setCorrections({ ok: false, error: String(err.message || err), sessions: [] });
|
|
984
|
+
}
|
|
985
|
+
}, [status]);
|
|
986
|
+
|
|
987
|
+
const runIntegrity = async () => {
|
|
988
|
+
setIntegrityBusy(true);
|
|
989
|
+
try {
|
|
990
|
+
const q = cwdQuery();
|
|
991
|
+
setIntegrity(await api("/api/dsh-trivium/integrity" + (q ? "?" + q : "")));
|
|
992
|
+
} catch (err) {
|
|
993
|
+
setIntegrity({ ok: false, error: String(err.message || err) });
|
|
994
|
+
} finally {
|
|
995
|
+
setIntegrityBusy(false);
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
|
|
953
999
|
react.useEffect(() => {
|
|
954
|
-
if (tab === "recall")
|
|
955
|
-
|
|
1000
|
+
if (tab === "recall") {
|
|
1001
|
+
loadTraces();
|
|
1002
|
+
loadCorrections();
|
|
1003
|
+
}
|
|
1004
|
+
}, [tab, loadTraces, loadCorrections]);
|
|
956
1005
|
|
|
957
1006
|
/*
|
|
958
1007
|
* Dialog accessibility: focus moves into the panel, Escape closes it, and
|
|
@@ -1380,6 +1429,83 @@ window.__ModuleLoader__.load({
|
|
|
1380
1429
|
],
|
|
1381
1430
|
})
|
|
1382
1431
|
: null,
|
|
1432
|
+
jsx("div", { style: ui.fieldLabel, children: L.integrityTitle }),
|
|
1433
|
+
jsxs("div", {
|
|
1434
|
+
style: ui.toolbar,
|
|
1435
|
+
children: [
|
|
1436
|
+
jsx(Button, {
|
|
1437
|
+
variant: "outline",
|
|
1438
|
+
size: "sm",
|
|
1439
|
+
disabled: integrityBusy,
|
|
1440
|
+
onClick: runIntegrity,
|
|
1441
|
+
children: integrityBusy ? L.loading : L.integrityCheck,
|
|
1442
|
+
}),
|
|
1443
|
+
integrity
|
|
1444
|
+
? jsx("span", { style: ui.muted, children: integrity.ok ? L.integrityOk : L.integrityIssues })
|
|
1445
|
+
: null,
|
|
1446
|
+
],
|
|
1447
|
+
}),
|
|
1448
|
+
integrity && !integrity.ok && (integrity.issues || []).length
|
|
1449
|
+
? jsx("div", {
|
|
1450
|
+
style: ui.error,
|
|
1451
|
+
children: integrity.issues.map((issue) => "#" + (issue.id ?? "?") + " " + issue.code).join(" · "),
|
|
1452
|
+
})
|
|
1453
|
+
: null,
|
|
1454
|
+
integrity && integrity.ok
|
|
1455
|
+
? jsx("div", { style: ui.muted, children: L.integrityScanned + " " + integrity.nodes + " / " + integrity.edges })
|
|
1456
|
+
: null,
|
|
1457
|
+
jsx("div", { style: ui.fieldLabel, children: L.correctionsTitle }),
|
|
1458
|
+
jsx("div", { style: ui.hint, children: L.correctionsHint }),
|
|
1459
|
+
corrections === null
|
|
1460
|
+
? null
|
|
1461
|
+
: !(corrections.sessions || []).length
|
|
1462
|
+
? jsx("div", { style: ui.muted, children: L.correctionsNone })
|
|
1463
|
+
: jsxs("div", {
|
|
1464
|
+
style: ui.stack,
|
|
1465
|
+
children: corrections.sessions.map((session) =>
|
|
1466
|
+
jsxs(
|
|
1467
|
+
"div",
|
|
1468
|
+
{
|
|
1469
|
+
style: ui.stack,
|
|
1470
|
+
children: [
|
|
1471
|
+
jsx("div", {
|
|
1472
|
+
style: ui.muted,
|
|
1473
|
+
children: L.correctionsSession + " " + String(session.sessionId || "").slice(0, 12),
|
|
1474
|
+
}),
|
|
1475
|
+
...(session.pending || []).map((row) =>
|
|
1476
|
+
jsxs(
|
|
1477
|
+
"div",
|
|
1478
|
+
{
|
|
1479
|
+
style: { ...ui.muted, display: "flex", gap: 6, flexWrap: "wrap" },
|
|
1480
|
+
children: [
|
|
1481
|
+
jsx("span", { style: ui.tag, children: "#" + row.id }),
|
|
1482
|
+
jsx("span", { children: row.note || "" }),
|
|
1483
|
+
jsx("span", { children: L.correctionsPending }),
|
|
1484
|
+
],
|
|
1485
|
+
},
|
|
1486
|
+
"cp-" + session.sessionId + "-" + row.id,
|
|
1487
|
+
),
|
|
1488
|
+
),
|
|
1489
|
+
...(session.sent || []).slice(-3).map((row, i) =>
|
|
1490
|
+
jsxs(
|
|
1491
|
+
"div",
|
|
1492
|
+
{
|
|
1493
|
+
style: { ...ui.muted, display: "flex", gap: 6, flexWrap: "wrap" },
|
|
1494
|
+
children: [
|
|
1495
|
+
jsx("span", { style: ui.tag, children: "✓ #" + row.id }),
|
|
1496
|
+
jsx("span", { children: row.note || "" }),
|
|
1497
|
+
jsx("span", { children: L.correctionsSent }),
|
|
1498
|
+
],
|
|
1499
|
+
},
|
|
1500
|
+
"cs-" + session.sessionId + "-" + i,
|
|
1501
|
+
),
|
|
1502
|
+
),
|
|
1503
|
+
],
|
|
1504
|
+
},
|
|
1505
|
+
"corr-" + session.sessionId,
|
|
1506
|
+
),
|
|
1507
|
+
),
|
|
1508
|
+
}),
|
|
1383
1509
|
jsx("div", { style: ui.fieldLabel, children: L.recallRecent }),
|
|
1384
1510
|
!traces.length
|
|
1385
1511
|
? jsx("div", { style: ui.muted, children: L.recallNoTrace })
|
package/lib/server.js
CHANGED
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
import { embeddingPublicStatus } from "./embed.js";
|
|
22
22
|
import { chipsEnabledOf, configPanelOpenOf, gitSidecarEnabledOf, listedWorkspaces, pluginLocaleOf, recallModeOf, sessionLayerEnabledOf, writeUiSettings } from "./settings.js";
|
|
23
23
|
import { TURN_ADDITIONAL_BUDGET } from "./context-budget.js";
|
|
24
|
+
import { checkRevisionIntegrity } from "./integrity.js";
|
|
25
|
+
import { correctionSessions } from "./turn-ledger.js";
|
|
24
26
|
import { discoverWorkbuddyFiles, discoverExternalFiles, exportMarkdown, importWorkbuddy, importExternal } from "./markdown.js";
|
|
25
27
|
import { checkNpmUpdate, installedVersion } from "./update.js";
|
|
26
28
|
import { writeGateMessage } from "./hygiene.js";
|
|
@@ -202,6 +204,28 @@ async function handle(req, res, { liveOptions, status }) {
|
|
|
202
204
|
return;
|
|
203
205
|
}
|
|
204
206
|
|
|
207
|
+
if (path === "/api/dsh-trivium/corrections" && method === "GET") {
|
|
208
|
+
const paramCwd = url.searchParams.get("cwd");
|
|
209
|
+
const cwd = paramCwd ? (isKnownCwd(paramCwd) ? paramCwd : "") : getActiveCwd();
|
|
210
|
+
sendJson(res, 200, {
|
|
211
|
+
ok: true,
|
|
212
|
+
cwd: cwd || "",
|
|
213
|
+
sessions: correctionSessions(cwd || ""),
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (path === "/api/dsh-trivium/integrity" && method === "GET") {
|
|
219
|
+
const { cwd, db } = await dbForRequest(url);
|
|
220
|
+
if (!db) {
|
|
221
|
+
sendJson(res, 400, { ok: false, message: "no workspace" });
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const report = checkRevisionIntegrity(db);
|
|
225
|
+
sendJson(res, 200, { ok: true, cwd, ...report });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
205
229
|
if (path === "/api/dsh-trivium/settings" && method === "POST") {
|
|
206
230
|
const body = JSON.parse((await readBody(req)) || "{}");
|
|
207
231
|
const saved = writeUiSettings(body);
|
package/lib/turn-ledger.js
CHANGED
|
@@ -35,6 +35,8 @@ function stateOf(sessionId) {
|
|
|
35
35
|
// correction must not cross workspaces.
|
|
36
36
|
cwd: "",
|
|
37
37
|
delivered: new Set(),
|
|
38
|
+
// Last corrections actually handed to the host, for the settings panel.
|
|
39
|
+
sentCorrections: [],
|
|
38
40
|
/*
|
|
39
41
|
* What this session was actually shown, per node: body, state and the
|
|
40
42
|
* fingerprint it was shown under. A later write consults this table to
|
|
@@ -120,6 +122,32 @@ export function pendingCorrections(sessionId) {
|
|
|
120
122
|
return [...stateOf(sessionId).corrections];
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Pending and recently delivered corrections, grouped by session.
|
|
127
|
+
*
|
|
128
|
+
* The panel is not tied to one session, so it asks per workspace and shows the
|
|
129
|
+
* short session id. A session that never received a fact never appears.
|
|
130
|
+
*/
|
|
131
|
+
export function correctionSessions(cwd = "") {
|
|
132
|
+
const want = cwd ? resolve(String(cwd)) : "";
|
|
133
|
+
const out = [];
|
|
134
|
+
for (const state of sessions.values()) {
|
|
135
|
+
if (want && state.cwd && state.cwd !== want) continue;
|
|
136
|
+
if (!state.corrections.length && !state.sentCorrections.length) continue;
|
|
137
|
+
out.push({
|
|
138
|
+
sessionId: state.sessionId,
|
|
139
|
+
cwd: state.cwd,
|
|
140
|
+
pending: state.corrections.map((c) => ({
|
|
141
|
+
id: c.id,
|
|
142
|
+
note: String(c.l0 || ""),
|
|
143
|
+
queuedAt: c.queuedAt || null,
|
|
144
|
+
})),
|
|
145
|
+
sent: [...state.sentCorrections].slice(-10),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
123
151
|
/**
|
|
124
152
|
* A fact changed after a session was shown it. Queue a short correction for
|
|
125
153
|
* every session that actually received this node — "delivered once, never
|
|
@@ -220,6 +248,12 @@ export function commit(reservation) {
|
|
|
220
248
|
// Consumed only now: an over-budget correction stays pending for the
|
|
221
249
|
// next turn instead of being silently dropped.
|
|
222
250
|
deliveredCorrections.add(record.id);
|
|
251
|
+
state.sentCorrections.push({
|
|
252
|
+
id: record.id,
|
|
253
|
+
note: String(record.l0 || ""),
|
|
254
|
+
at: new Date().toISOString(),
|
|
255
|
+
});
|
|
256
|
+
if (state.sentCorrections.length > 20) state.sentCorrections.shift();
|
|
223
257
|
continue;
|
|
224
258
|
}
|
|
225
259
|
if (!record.fingerprint) continue;
|