dsh-daoing-memory 0.1.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/LICENSE +21 -0
- package/README.md +91 -0
- package/README.zh-CN.md +91 -0
- package/cordis.patch.yml +22 -0
- package/lib/client.js +10339 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +3138 -0
- package/lib/invariant.js +33 -0
- package/lib/tools.js +1472 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +4058 -0
- package/lib/typert.remote-client.d.ts +149 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +3167 -0
- package/lib/types/client/ExperiencePage.d.ts +15 -0
- package/lib/types/client/ExperiencePage.d.ts.map +1 -0
- package/lib/types/client/ExperiencePage.js +117 -0
- package/lib/types/client/ExperiencePage.js.map +1 -0
- package/lib/types/client/FactDiaryPage.d.ts +17 -0
- package/lib/types/client/FactDiaryPage.d.ts.map +1 -0
- package/lib/types/client/FactDiaryPage.js +155 -0
- package/lib/types/client/FactDiaryPage.js.map +1 -0
- package/lib/types/client/HumanOpsPage.d.ts +16 -0
- package/lib/types/client/HumanOpsPage.d.ts.map +1 -0
- package/lib/types/client/HumanOpsPage.js +208 -0
- package/lib/types/client/HumanOpsPage.js.map +1 -0
- package/lib/types/client/LedgerPage.d.ts +13 -0
- package/lib/types/client/LedgerPage.d.ts.map +1 -0
- package/lib/types/client/LedgerPage.js +113 -0
- package/lib/types/client/LedgerPage.js.map +1 -0
- package/lib/types/client/MemoryNavSection.d.ts +20 -0
- package/lib/types/client/MemoryNavSection.d.ts.map +1 -0
- package/lib/types/client/MemoryNavSection.js +23 -0
- package/lib/types/client/MemoryNavSection.js.map +1 -0
- package/lib/types/client/Workbench.d.ts +23 -0
- package/lib/types/client/Workbench.d.ts.map +1 -0
- package/lib/types/client/Workbench.js +140 -0
- package/lib/types/client/Workbench.js.map +1 -0
- package/lib/types/client/actions.d.ts +165 -0
- package/lib/types/client/actions.d.ts.map +1 -0
- package/lib/types/client/actions.js +51 -0
- package/lib/types/client/actions.js.map +1 -0
- package/lib/types/client/index.d.ts +24 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/index.js +103 -0
- package/lib/types/client/index.js.map +1 -0
- package/lib/types/client/navStore.d.ts +45 -0
- package/lib/types/client/navStore.d.ts.map +1 -0
- package/lib/types/client/navStore.js +36 -0
- package/lib/types/client/navStore.js.map +1 -0
- package/lib/types/core.d.ts +195 -0
- package/lib/types/core.d.ts.map +1 -0
- package/lib/types/core.js +1304 -0
- package/lib/types/core.js.map +1 -0
- package/lib/types/index.d.ts +71 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +97 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/invariant.d.ts +21 -0
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/invariant.js +33 -0
- package/lib/types/invariant.js.map +1 -0
- package/lib/types/service.d.ts +130 -0
- package/lib/types/service.d.ts.map +1 -0
- package/lib/types/service.js +417 -0
- package/lib/types/service.js.map +1 -0
- package/lib/types/store.d.ts +244 -0
- package/lib/types/store.d.ts.map +1 -0
- package/lib/types/store.js +864 -0
- package/lib/types/store.js.map +1 -0
- package/lib/types/tools.d.ts +25 -0
- package/lib/types/tools.d.ts.map +1 -0
- package/lib/types/tools.js +1144 -0
- package/lib/types/tools.js.map +1 -0
- package/lib/types/types.d.ts +836 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +8 -0
- package/lib/types/types.js.map +1 -0
- package/package.json +106 -0
- package/skill/memory-extraction.md +62 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,3138 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
|
+
//#region lib/types/store.js
|
|
7
|
+
/**
|
|
8
|
+
* Memory store: durable SQLite persistence for the experience lifecycle,
|
|
9
|
+
* use reports, diary, facts, extractions, recall telemetry, and the
|
|
10
|
+
* append-only hash-chained ledger. Uses node:sqlite (DatabaseSync) so the
|
|
11
|
+
* library runs with zero external services.
|
|
12
|
+
* @module dsh-daoing-memory/store
|
|
13
|
+
*/
|
|
14
|
+
/** Monotone store schema version. v1→v2 is an additive ALTER migration (see open()). */
|
|
15
|
+
const MEMORY_SCHEMA_VERSION = 5;
|
|
16
|
+
/** Half-life (ms) of the recency weighting applied to verification samples. */
|
|
17
|
+
const TRUST_HALF_LIFE_MS = 4320 * 60 * 60 * 1e3;
|
|
18
|
+
/** Hash one ledger block's content, chained to the previous hash. */
|
|
19
|
+
function blockHash(ts, op, objectType, objectId, actor, payload, prevHash) {
|
|
20
|
+
const body = JSON.stringify({
|
|
21
|
+
ts,
|
|
22
|
+
op,
|
|
23
|
+
objectType,
|
|
24
|
+
objectId,
|
|
25
|
+
actor,
|
|
26
|
+
payload,
|
|
27
|
+
prevHash
|
|
28
|
+
});
|
|
29
|
+
return createHash("sha256").update(body).digest("hex").slice(0, 24);
|
|
30
|
+
}
|
|
31
|
+
/** Tokenize into latin words + CJK bigrams so Chinese situations match. */
|
|
32
|
+
function tokenize(text) {
|
|
33
|
+
const lower = text.toLowerCase();
|
|
34
|
+
const latin = lower.match(/[a-z0-9_]+/g) ?? [];
|
|
35
|
+
const cjk = lower.match(/[\u4e00-\u9fff]+/g) ?? [];
|
|
36
|
+
const bigrams = [];
|
|
37
|
+
for (const seg of cjk) {
|
|
38
|
+
if (seg.length === 1) bigrams.push(seg);
|
|
39
|
+
for (let i = 0; i < seg.length - 1; i++) bigrams.push(seg.slice(i, i + 2));
|
|
40
|
+
}
|
|
41
|
+
return [...latin, ...bigrams];
|
|
42
|
+
}
|
|
43
|
+
/** Rough token estimate for injection budgeting (CJK-heavy text). */
|
|
44
|
+
function estimateTokens(text) {
|
|
45
|
+
const cjk = (text.match(/[\u4e00-\u9fff]/g) ?? []).length;
|
|
46
|
+
const rest = text.length - cjk;
|
|
47
|
+
return Math.ceil(cjk * .6 + rest / 4);
|
|
48
|
+
}
|
|
49
|
+
/** The durable store behind the memory core. */
|
|
50
|
+
var MemoryStore = class {
|
|
51
|
+
db;
|
|
52
|
+
/** @param db - an open node:sqlite DatabaseSync handle. */
|
|
53
|
+
constructor(db) {
|
|
54
|
+
this.db = db;
|
|
55
|
+
db.exec(`
|
|
56
|
+
PRAGMA journal_mode = WAL;
|
|
57
|
+
CREATE TABLE IF NOT EXISTS memory_meta (
|
|
58
|
+
key TEXT PRIMARY KEY,
|
|
59
|
+
value TEXT NOT NULL
|
|
60
|
+
);
|
|
61
|
+
CREATE TABLE IF NOT EXISTS experiences (
|
|
62
|
+
family_id TEXT NOT NULL,
|
|
63
|
+
revision INTEGER NOT NULL,
|
|
64
|
+
kind TEXT NOT NULL,
|
|
65
|
+
source TEXT NOT NULL,
|
|
66
|
+
family TEXT NOT NULL,
|
|
67
|
+
gist TEXT NOT NULL,
|
|
68
|
+
situation TEXT NOT NULL,
|
|
69
|
+
path TEXT NOT NULL,
|
|
70
|
+
reasoning TEXT NOT NULL,
|
|
71
|
+
limits TEXT NOT NULL,
|
|
72
|
+
status TEXT NOT NULL,
|
|
73
|
+
alpha REAL NOT NULL,
|
|
74
|
+
beta REAL NOT NULL,
|
|
75
|
+
last_verified_at INTEGER,
|
|
76
|
+
pinned INTEGER NOT NULL DEFAULT 0,
|
|
77
|
+
tokens_saved REAL NOT NULL DEFAULT 0,
|
|
78
|
+
tokens_spent REAL NOT NULL DEFAULT 0,
|
|
79
|
+
parent_revision INTEGER,
|
|
80
|
+
failure_reason TEXT,
|
|
81
|
+
evidence TEXT,
|
|
82
|
+
challenge_reason TEXT,
|
|
83
|
+
deleted INTEGER NOT NULL DEFAULT 0,
|
|
84
|
+
context TEXT NOT NULL DEFAULT '',
|
|
85
|
+
verified_count INTEGER NOT NULL DEFAULT 0,
|
|
86
|
+
reject_count INTEGER NOT NULL DEFAULT 0,
|
|
87
|
+
global_flag INTEGER NOT NULL DEFAULT 0,
|
|
88
|
+
created_at INTEGER NOT NULL,
|
|
89
|
+
updated_at INTEGER NOT NULL,
|
|
90
|
+
PRIMARY KEY (family_id, revision)
|
|
91
|
+
);
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_exp_status ON experiences (status, deleted);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_exp_family ON experiences (family);
|
|
94
|
+
CREATE TABLE IF NOT EXISTS use_reports (
|
|
95
|
+
id TEXT PRIMARY KEY,
|
|
96
|
+
experience_id TEXT NOT NULL,
|
|
97
|
+
revision INTEGER NOT NULL,
|
|
98
|
+
outcome TEXT NOT NULL,
|
|
99
|
+
attribution TEXT NOT NULL,
|
|
100
|
+
counted TEXT NOT NULL,
|
|
101
|
+
evidence TEXT,
|
|
102
|
+
dedupe_key TEXT,
|
|
103
|
+
ts INTEGER NOT NULL
|
|
104
|
+
);
|
|
105
|
+
CREATE INDEX IF NOT EXISTS idx_reports_exp ON use_reports (experience_id, revision, ts);
|
|
106
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_reports_dedupe
|
|
107
|
+
ON use_reports (experience_id, revision, dedupe_key)
|
|
108
|
+
WHERE dedupe_key IS NOT NULL;
|
|
109
|
+
CREATE TABLE IF NOT EXISTS ledger (
|
|
110
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
111
|
+
ts INTEGER NOT NULL,
|
|
112
|
+
op TEXT NOT NULL,
|
|
113
|
+
object_type TEXT NOT NULL,
|
|
114
|
+
object_id TEXT NOT NULL,
|
|
115
|
+
actor TEXT NOT NULL,
|
|
116
|
+
reason TEXT,
|
|
117
|
+
payload TEXT NOT NULL,
|
|
118
|
+
prev_hash TEXT NOT NULL,
|
|
119
|
+
hash TEXT NOT NULL
|
|
120
|
+
);
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_ledger_object ON ledger (object_type, object_id, seq DESC);
|
|
122
|
+
CREATE TABLE IF NOT EXISTS diary (
|
|
123
|
+
id TEXT PRIMARY KEY,
|
|
124
|
+
ts INTEGER NOT NULL,
|
|
125
|
+
kind TEXT NOT NULL,
|
|
126
|
+
content TEXT NOT NULL,
|
|
127
|
+
session_ref TEXT,
|
|
128
|
+
tags TEXT NOT NULL,
|
|
129
|
+
extracted INTEGER NOT NULL DEFAULT 0
|
|
130
|
+
);
|
|
131
|
+
CREATE TABLE IF NOT EXISTS facts (
|
|
132
|
+
id TEXT PRIMARY KEY,
|
|
133
|
+
category TEXT NOT NULL,
|
|
134
|
+
fact_key TEXT NOT NULL,
|
|
135
|
+
value TEXT NOT NULL,
|
|
136
|
+
origin TEXT NOT NULL,
|
|
137
|
+
source_diary_ids TEXT NOT NULL,
|
|
138
|
+
corroboration INTEGER NOT NULL DEFAULT 1,
|
|
139
|
+
valid_from INTEGER NOT NULL,
|
|
140
|
+
valid_to INTEGER,
|
|
141
|
+
recorded_at INTEGER NOT NULL,
|
|
142
|
+
superseded_by TEXT,
|
|
143
|
+
locked INTEGER NOT NULL DEFAULT 0,
|
|
144
|
+
deleted INTEGER NOT NULL DEFAULT 0,
|
|
145
|
+
conflict_pending INTEGER NOT NULL DEFAULT 0
|
|
146
|
+
);
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_facts_slot ON facts (category, fact_key, valid_to);
|
|
148
|
+
CREATE TABLE IF NOT EXISTS extractions (
|
|
149
|
+
id TEXT PRIMARY KEY,
|
|
150
|
+
ts INTEGER NOT NULL,
|
|
151
|
+
trigger TEXT NOT NULL,
|
|
152
|
+
summary TEXT NOT NULL,
|
|
153
|
+
produced_fact_ids TEXT NOT NULL,
|
|
154
|
+
diary_count INTEGER NOT NULL
|
|
155
|
+
);
|
|
156
|
+
CREATE TABLE IF NOT EXISTS recall_events (
|
|
157
|
+
id TEXT PRIMARY KEY,
|
|
158
|
+
ts INTEGER NOT NULL,
|
|
159
|
+
situation TEXT NOT NULL,
|
|
160
|
+
injected_ids TEXT NOT NULL,
|
|
161
|
+
none INTEGER NOT NULL DEFAULT 0,
|
|
162
|
+
context TEXT NOT NULL DEFAULT ''
|
|
163
|
+
);
|
|
164
|
+
CREATE TABLE IF NOT EXISTS concerns (
|
|
165
|
+
id TEXT PRIMARY KEY,
|
|
166
|
+
parent_id TEXT,
|
|
167
|
+
title TEXT NOT NULL,
|
|
168
|
+
background TEXT NOT NULL DEFAULT '',
|
|
169
|
+
kind TEXT,
|
|
170
|
+
status TEXT,
|
|
171
|
+
ts INTEGER NOT NULL,
|
|
172
|
+
source_diary_ids TEXT NOT NULL,
|
|
173
|
+
context TEXT NOT NULL DEFAULT '',
|
|
174
|
+
deleted INTEGER NOT NULL DEFAULT 0
|
|
175
|
+
);
|
|
176
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
177
|
+
id TEXT PRIMARY KEY,
|
|
178
|
+
ts INTEGER NOT NULL,
|
|
179
|
+
merged_ids TEXT NOT NULL,
|
|
180
|
+
produced_id TEXT NOT NULL,
|
|
181
|
+
note TEXT NOT NULL DEFAULT ''
|
|
182
|
+
);
|
|
183
|
+
`);
|
|
184
|
+
const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get("schema_version");
|
|
185
|
+
if (row === void 0) db.prepare("INSERT INTO memory_meta (key, value) VALUES (?, ?)").run("schema_version", String(5));
|
|
186
|
+
else {
|
|
187
|
+
let v = Number(row.value);
|
|
188
|
+
if (v === 1) {
|
|
189
|
+
db.exec(`
|
|
190
|
+
ALTER TABLE experiences ADD COLUMN context TEXT NOT NULL DEFAULT '';
|
|
191
|
+
ALTER TABLE experiences ADD COLUMN verified_count INTEGER NOT NULL DEFAULT 0;
|
|
192
|
+
ALTER TABLE experiences ADD COLUMN reject_count INTEGER NOT NULL DEFAULT 0;
|
|
193
|
+
ALTER TABLE experiences ADD COLUMN global_flag INTEGER NOT NULL DEFAULT 0;
|
|
194
|
+
ALTER TABLE recall_events ADD COLUMN context TEXT NOT NULL DEFAULT '';
|
|
195
|
+
`);
|
|
196
|
+
v = 2;
|
|
197
|
+
}
|
|
198
|
+
if (v === 2 && true) v = 3;
|
|
199
|
+
if (v === 3 && true) v = 4;
|
|
200
|
+
if (v === 4 && true) {
|
|
201
|
+
db.exec(`ALTER TABLE concerns ADD COLUMN background TEXT NOT NULL DEFAULT '';`);
|
|
202
|
+
v = 5;
|
|
203
|
+
}
|
|
204
|
+
if (v !== 5) throw new Error(`memory store schema version ${row.value} does not match 5; refusing to open`);
|
|
205
|
+
db.prepare("UPDATE memory_meta SET value = ? WHERE key = ?").run(String(5), "schema_version");
|
|
206
|
+
}
|
|
207
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_exp_context ON experiences (context);");
|
|
208
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_concern_parent ON concerns (parent_id);");
|
|
209
|
+
}
|
|
210
|
+
/** Insert or update one experience revision row. */
|
|
211
|
+
upsertExperience(s) {
|
|
212
|
+
this.db.prepare(`
|
|
213
|
+
INSERT INTO experiences (
|
|
214
|
+
family_id, revision, kind, source, family, gist, situation, path, reasoning, limits,
|
|
215
|
+
status, alpha, beta, last_verified_at, pinned, tokens_saved, tokens_spent,
|
|
216
|
+
parent_revision, failure_reason, evidence, challenge_reason, deleted,
|
|
217
|
+
context, verified_count, reject_count, global_flag, created_at, updated_at
|
|
218
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0,?,?,?,?,?,?)
|
|
219
|
+
ON CONFLICT (family_id, revision) DO UPDATE SET
|
|
220
|
+
kind = excluded.kind,
|
|
221
|
+
source = excluded.source,
|
|
222
|
+
family = excluded.family,
|
|
223
|
+
gist = excluded.gist,
|
|
224
|
+
situation = excluded.situation,
|
|
225
|
+
path = excluded.path,
|
|
226
|
+
reasoning = excluded.reasoning,
|
|
227
|
+
limits = excluded.limits,
|
|
228
|
+
status = excluded.status,
|
|
229
|
+
alpha = excluded.alpha,
|
|
230
|
+
beta = excluded.beta,
|
|
231
|
+
last_verified_at = excluded.last_verified_at,
|
|
232
|
+
pinned = excluded.pinned,
|
|
233
|
+
tokens_saved = excluded.tokens_saved,
|
|
234
|
+
tokens_spent = excluded.tokens_spent,
|
|
235
|
+
parent_revision = excluded.parent_revision,
|
|
236
|
+
failure_reason = excluded.failure_reason,
|
|
237
|
+
evidence = excluded.evidence,
|
|
238
|
+
challenge_reason = excluded.challenge_reason,
|
|
239
|
+
context = excluded.context,
|
|
240
|
+
verified_count = excluded.verified_count,
|
|
241
|
+
reject_count = excluded.reject_count,
|
|
242
|
+
global_flag = excluded.global_flag,
|
|
243
|
+
updated_at = excluded.updated_at
|
|
244
|
+
`).run(s.id, s.revision, s.kind, s.source, s.family, s.gist, JSON.stringify(s.situation), JSON.stringify(s.path), s.reasoning, JSON.stringify(s.limits), s.status, s.alpha, s.beta, s.lastVerifiedAt ?? null, s.pinned ? 1 : 0, s.tokensSaved, s.tokensSpent, s.parentRevision ?? null, s.failureReason ?? null, s.evidence === void 0 ? null : JSON.stringify(s.evidence), s.challengeReason ?? null, s.context ?? "", s.verifiedCount ?? 0, s.rejectCount ?? 0, s.globalFlag === true ? 1 : 0, s.createdAt, s.updatedAt);
|
|
245
|
+
}
|
|
246
|
+
/** Read one experience revision. */
|
|
247
|
+
getExperience(familyId, revision) {
|
|
248
|
+
const row = this.db.prepare("SELECT * FROM experiences WHERE family_id = ? AND revision = ? AND deleted = 0").get(familyId, revision);
|
|
249
|
+
return row === void 0 ? void 0 : this.rowToExperience(row);
|
|
250
|
+
}
|
|
251
|
+
/** The active (non-superseded, non-deleted) revision of a family, if any. */
|
|
252
|
+
getActiveRevision(familyId) {
|
|
253
|
+
const row = this.db.prepare(`
|
|
254
|
+
SELECT * FROM experiences
|
|
255
|
+
WHERE family_id = ? AND deleted = 0 AND status IN ('candidate', 'live', 'challenged')
|
|
256
|
+
ORDER BY revision DESC LIMIT 1
|
|
257
|
+
`).get(familyId);
|
|
258
|
+
return row === void 0 ? void 0 : this.rowToExperience(row);
|
|
259
|
+
}
|
|
260
|
+
/** All revisions of one family, oldest first. */
|
|
261
|
+
getFamily(familyId) {
|
|
262
|
+
return this.db.prepare("SELECT * FROM experiences WHERE family_id = ? AND deleted = 0 ORDER BY revision ASC").all(familyId).map((row) => this.rowToExperience(row));
|
|
263
|
+
}
|
|
264
|
+
/** List experience revisions by filter; live-ish first, best trust first. */
|
|
265
|
+
listExperiences(filter) {
|
|
266
|
+
const clauses = ["deleted = 0"];
|
|
267
|
+
const params = [];
|
|
268
|
+
if (filter.status !== void 0) {
|
|
269
|
+
clauses.push("status = ?");
|
|
270
|
+
params.push(filter.status);
|
|
271
|
+
}
|
|
272
|
+
if (filter.kind !== void 0) {
|
|
273
|
+
clauses.push("kind = ?");
|
|
274
|
+
params.push(filter.kind);
|
|
275
|
+
}
|
|
276
|
+
if (filter.family !== void 0) {
|
|
277
|
+
clauses.push("family = ?");
|
|
278
|
+
params.push(filter.family);
|
|
279
|
+
}
|
|
280
|
+
if (filter.context !== void 0) {
|
|
281
|
+
clauses.push("context = ?");
|
|
282
|
+
params.push(filter.context);
|
|
283
|
+
}
|
|
284
|
+
return this.db.prepare(`
|
|
285
|
+
SELECT * FROM experiences WHERE ${clauses.join(" AND ")}
|
|
286
|
+
ORDER BY CASE status WHEN 'live' THEN 0 WHEN 'candidate' THEN 1 WHEN 'challenged' THEN 2
|
|
287
|
+
WHEN 'cold' THEN 3 WHEN 'archived' THEN 4 ELSE 5 END,
|
|
288
|
+
(alpha + 1.0) / (alpha + beta + 2.0) DESC,
|
|
289
|
+
updated_at DESC
|
|
290
|
+
`).all(...params).map((row) => this.rowToExperience(row));
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Recall candidates: token overlap against situation+gist+limits of the given
|
|
294
|
+
* statuses, best overlap first. An optional context scopes the pool: only
|
|
295
|
+
* same-context or globally-shared (global_flag) revisions qualify (006 §2).
|
|
296
|
+
*/
|
|
297
|
+
recallCandidates(queryTokens, topK, opts = {}) {
|
|
298
|
+
const statuses = opts.statuses ?? ["live"];
|
|
299
|
+
let sql = `
|
|
300
|
+
SELECT * FROM experiences
|
|
301
|
+
WHERE deleted = 0 AND status IN (${statuses.map(() => "?").join(", ")})
|
|
302
|
+
`;
|
|
303
|
+
const params = [...statuses];
|
|
304
|
+
if (opts.context !== void 0 && opts.context !== "") {
|
|
305
|
+
sql += " AND (context = ? OR global_flag = 1)";
|
|
306
|
+
params.push(opts.context);
|
|
307
|
+
}
|
|
308
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
309
|
+
const scored = [];
|
|
310
|
+
for (const row of rows) {
|
|
311
|
+
const snapshot = this.rowToExperience(row);
|
|
312
|
+
const hay = new Set(tokenize([
|
|
313
|
+
snapshot.gist,
|
|
314
|
+
...snapshot.situation,
|
|
315
|
+
...snapshot.limits
|
|
316
|
+
].join(" ")));
|
|
317
|
+
if (hay.size === 0) continue;
|
|
318
|
+
let hits = 0;
|
|
319
|
+
for (const token of queryTokens) if (hay.has(token)) hits += 1;
|
|
320
|
+
const score = hits / Math.sqrt(queryTokens.size * hay.size);
|
|
321
|
+
if (score > 0) scored.push({
|
|
322
|
+
snapshot,
|
|
323
|
+
score
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
scored.sort((a, b) => b.score - a.score);
|
|
327
|
+
return scored.slice(0, topK);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Near-duplicate detection for the information-gain gate (007 flow-log fix).
|
|
331
|
+
* Unlike recallCandidates it scores on gist+situation only (the lesson's
|
|
332
|
+
* identity), so identical lessons with divergent limits/paths still match.
|
|
333
|
+
* Returns the single best match across the requested statuses, or undefined.
|
|
334
|
+
*/
|
|
335
|
+
findNearDuplicate(queryTokens, statuses) {
|
|
336
|
+
if (queryTokens.size === 0) return void 0;
|
|
337
|
+
const sql = `
|
|
338
|
+
SELECT * FROM experiences
|
|
339
|
+
WHERE deleted = 0 AND status IN (${statuses.map(() => "?").join(", ")})
|
|
340
|
+
`;
|
|
341
|
+
const rows = this.db.prepare(sql).all(...statuses);
|
|
342
|
+
let best;
|
|
343
|
+
for (const row of rows) {
|
|
344
|
+
const snapshot = this.rowToExperience(row);
|
|
345
|
+
const hay = new Set(tokenize([snapshot.gist, ...snapshot.situation].join(" ")));
|
|
346
|
+
if (hay.size === 0) continue;
|
|
347
|
+
let hits = 0;
|
|
348
|
+
for (const token of queryTokens) if (hay.has(token)) hits += 1;
|
|
349
|
+
const score = hits / Math.sqrt(queryTokens.size * hay.size);
|
|
350
|
+
if (best === void 0 || score > best.score) best = {
|
|
351
|
+
snapshot,
|
|
352
|
+
score
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return best;
|
|
356
|
+
}
|
|
357
|
+
/** Count live revisions in one family (capacity budget). */
|
|
358
|
+
countFamilyActive(familyTag) {
|
|
359
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM experiences WHERE family = ? AND status = 'live' AND deleted = 0").get(familyTag).n;
|
|
360
|
+
}
|
|
361
|
+
/** Mark one revision deleted (human delete tombstone). */
|
|
362
|
+
deleteFamily(familyId) {
|
|
363
|
+
this.db.prepare("UPDATE experiences SET deleted = 1, updated_at = ? WHERE family_id = ?").run(Date.now(), familyId);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Insert one counted report; a repeated (family, revision, dedupeKey)
|
|
367
|
+
* returns false instead of double-counting (idempotent use).
|
|
368
|
+
*/
|
|
369
|
+
insertReport(report) {
|
|
370
|
+
try {
|
|
371
|
+
this.db.prepare(`
|
|
372
|
+
INSERT INTO use_reports (id, experience_id, revision, outcome, attribution, counted, evidence, dedupe_key, ts)
|
|
373
|
+
VALUES (?,?,?,?,?,?,?,?,?)
|
|
374
|
+
`).run(report.id, report.experienceId, report.revision, report.outcome, report.attribution, report.counted, report.evidence === void 0 ? null : JSON.stringify(report.evidence), report.dedupeKey ?? null, report.ts);
|
|
375
|
+
return true;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error instanceof Error && error.message.includes("UNIQUE")) return false;
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** Recent counted reports of one revision, newest first. */
|
|
382
|
+
reportsFor(familyId, revision, limit) {
|
|
383
|
+
return this.db.prepare(`
|
|
384
|
+
SELECT * FROM use_reports WHERE experience_id = ? AND revision = ? AND counted != 'none'
|
|
385
|
+
ORDER BY ts DESC LIMIT ?
|
|
386
|
+
`).all(familyId, revision, limit).map((row) => this.rowToReport(row));
|
|
387
|
+
}
|
|
388
|
+
/** Every report row (export). */
|
|
389
|
+
allReports() {
|
|
390
|
+
return this.db.prepare("SELECT * FROM use_reports ORDER BY ts ASC").all().map((row) => this.rowToReport(row));
|
|
391
|
+
}
|
|
392
|
+
/** Recency-weighted alpha/beta over counted reports. */
|
|
393
|
+
weightedTrust(familyId, revision, now) {
|
|
394
|
+
const rows = this.db.prepare(`
|
|
395
|
+
SELECT ts, counted FROM use_reports
|
|
396
|
+
WHERE experience_id = ? AND revision = ? AND counted IN ('alpha', 'beta')
|
|
397
|
+
`).all(familyId, revision);
|
|
398
|
+
let alpha = 0;
|
|
399
|
+
let beta = 0;
|
|
400
|
+
for (const row of rows) {
|
|
401
|
+
const weight = Math.pow(.5, (now - row.ts) / TRUST_HALF_LIFE_MS);
|
|
402
|
+
if (row.counted === "alpha") alpha += weight;
|
|
403
|
+
else beta += weight;
|
|
404
|
+
}
|
|
405
|
+
return (alpha + 1) / (alpha + beta + 2);
|
|
406
|
+
}
|
|
407
|
+
/** Append one block, chaining the hash; returns the stored block. */
|
|
408
|
+
appendLedger(block) {
|
|
409
|
+
const hash = blockHash(block.ts, block.op, block.objectType, block.objectId, block.actor, block.payload, block.prevHash);
|
|
410
|
+
const info = this.db.prepare(`
|
|
411
|
+
INSERT INTO ledger (ts, op, object_type, object_id, actor, reason, payload, prev_hash, hash)
|
|
412
|
+
VALUES (?,?,?,?,?,?,?,?,?)
|
|
413
|
+
`).run(block.ts, block.op, block.objectType, block.objectId, block.actor, block.reason ?? null, block.payload, block.prevHash, hash);
|
|
414
|
+
return {
|
|
415
|
+
...block,
|
|
416
|
+
seq: Number(info.lastInsertRowid),
|
|
417
|
+
hash
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/** The newest block's hash ('' when the ledger is empty). */
|
|
421
|
+
ledgerHead() {
|
|
422
|
+
return this.db.prepare("SELECT hash FROM ledger ORDER BY seq DESC LIMIT 1").get()?.hash ?? "";
|
|
423
|
+
}
|
|
424
|
+
/** Ledger blocks, newest first, optionally filtered. */
|
|
425
|
+
ledgerQuery(filter) {
|
|
426
|
+
const { clauses, params } = this.ledgerFilterClauses(filter);
|
|
427
|
+
params.push(filter.limit);
|
|
428
|
+
let sql = `SELECT * FROM ledger WHERE ${clauses.join(" AND ")} ORDER BY seq DESC LIMIT ?`;
|
|
429
|
+
if (filter.offset !== void 0 && filter.offset > 0) {
|
|
430
|
+
sql += " OFFSET ?";
|
|
431
|
+
params.push(filter.offset);
|
|
432
|
+
}
|
|
433
|
+
return this.db.prepare(sql).all(...params).map((row) => this.rowToLedger(row));
|
|
434
|
+
}
|
|
435
|
+
/** Shared WHERE-builder for the ledger query and its filtered count. */
|
|
436
|
+
ledgerFilterClauses(filter) {
|
|
437
|
+
const clauses = ["1 = 1"];
|
|
438
|
+
const params = [];
|
|
439
|
+
if (filter.objectType !== void 0) {
|
|
440
|
+
clauses.push("object_type = ?");
|
|
441
|
+
params.push(filter.objectType);
|
|
442
|
+
}
|
|
443
|
+
if (filter.objectId !== void 0) {
|
|
444
|
+
clauses.push("object_id = ?");
|
|
445
|
+
params.push(filter.objectId);
|
|
446
|
+
}
|
|
447
|
+
if (filter.op !== void 0) {
|
|
448
|
+
clauses.push("op = ?");
|
|
449
|
+
params.push(filter.op);
|
|
450
|
+
}
|
|
451
|
+
if (filter.seqFrom !== void 0) {
|
|
452
|
+
clauses.push("seq >= ?");
|
|
453
|
+
params.push(filter.seqFrom);
|
|
454
|
+
}
|
|
455
|
+
if (filter.seqTo !== void 0) {
|
|
456
|
+
clauses.push("seq <= ?");
|
|
457
|
+
params.push(filter.seqTo);
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
clauses,
|
|
461
|
+
params
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
/** Count ledger blocks matching a filter (for pagination, 007 §2). */
|
|
465
|
+
ledgerQueryCount(filter) {
|
|
466
|
+
const { clauses, params } = this.ledgerFilterClauses(filter);
|
|
467
|
+
return this.db.prepare(`SELECT COUNT(*) AS n FROM ledger WHERE ${clauses.join(" AND ")}`).get(...params).n;
|
|
468
|
+
}
|
|
469
|
+
/** The complete ledger, oldest first (integrity checks + export). */
|
|
470
|
+
ledgerAll() {
|
|
471
|
+
return this.db.prepare("SELECT * FROM ledger ORDER BY seq ASC").all().map((row) => this.rowToLedger(row));
|
|
472
|
+
}
|
|
473
|
+
/** Total ledger block count. */
|
|
474
|
+
ledgerCount() {
|
|
475
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM ledger").get().n;
|
|
476
|
+
}
|
|
477
|
+
/** Append one diary entry (append-only layer). */
|
|
478
|
+
insertDiary(entry) {
|
|
479
|
+
this.db.prepare(`
|
|
480
|
+
INSERT INTO diary (id, ts, kind, content, session_ref, tags, extracted)
|
|
481
|
+
VALUES (?,?,?,?,?,?,?)
|
|
482
|
+
`).run(entry.id, entry.ts, entry.kind, entry.content, entry.sessionRef ?? null, JSON.stringify(entry.tags), entry.extracted ? 1 : 0);
|
|
483
|
+
}
|
|
484
|
+
/** Diary entries, newest first. */
|
|
485
|
+
listDiary(limit, offset, onlyUnextracted) {
|
|
486
|
+
const where = onlyUnextracted ? "WHERE extracted = 0" : "";
|
|
487
|
+
return this.db.prepare(`SELECT * FROM diary ${where} ORDER BY ts DESC, rowid DESC LIMIT ? OFFSET ?`).all(limit, offset).map((row) => this.rowToDiary(row));
|
|
488
|
+
}
|
|
489
|
+
/** Unextracted entries, oldest first (the extraction window). */
|
|
490
|
+
unextractedDiary() {
|
|
491
|
+
return this.db.prepare("SELECT * FROM diary WHERE extracted = 0 ORDER BY ts ASC").all().map((row) => this.rowToDiary(row));
|
|
492
|
+
}
|
|
493
|
+
/** Mark diary entries extracted. */
|
|
494
|
+
markDiaryExtracted(ids) {
|
|
495
|
+
const stmt = this.db.prepare("UPDATE diary SET extracted = 1 WHERE id = ?");
|
|
496
|
+
for (const id of ids) stmt.run(id);
|
|
497
|
+
}
|
|
498
|
+
/** One diary entry by id. */
|
|
499
|
+
getDiary(id) {
|
|
500
|
+
const row = this.db.prepare("SELECT * FROM diary WHERE id = ?").get(id);
|
|
501
|
+
return row === void 0 ? void 0 : this.rowToDiary(row);
|
|
502
|
+
}
|
|
503
|
+
/** Several diary entries by id, preserving the requested order (008 Path A: fact→diary provenance). */
|
|
504
|
+
getDiaryByIds(ids) {
|
|
505
|
+
const out = [];
|
|
506
|
+
const stmt = this.db.prepare("SELECT * FROM diary WHERE id = ?");
|
|
507
|
+
for (const id of ids) {
|
|
508
|
+
const row = stmt.get(id);
|
|
509
|
+
if (row !== void 0) out.push(this.rowToDiary(row));
|
|
510
|
+
}
|
|
511
|
+
return out;
|
|
512
|
+
}
|
|
513
|
+
/** Diary counters. */
|
|
514
|
+
diaryCounts() {
|
|
515
|
+
const total = this.db.prepare("SELECT COUNT(*) AS n FROM diary").get();
|
|
516
|
+
const unextracted = this.db.prepare("SELECT COUNT(*) AS n FROM diary WHERE extracted = 0").get();
|
|
517
|
+
return {
|
|
518
|
+
total: total.n,
|
|
519
|
+
unextracted: unextracted.n
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
/** Insert one fact version. */
|
|
523
|
+
insertFact(fact) {
|
|
524
|
+
this.db.prepare(`
|
|
525
|
+
INSERT INTO facts (
|
|
526
|
+
id, category, fact_key, value, origin, source_diary_ids, corroboration,
|
|
527
|
+
valid_from, valid_to, recorded_at, superseded_by, locked, deleted, conflict_pending
|
|
528
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
529
|
+
`).run(fact.id, fact.category, fact.factKey, fact.value, fact.origin, JSON.stringify(fact.sourceDiaryIds), fact.corroboration, fact.validFrom, fact.validTo ?? null, fact.recordedAt, fact.supersededBy ?? null, fact.locked ? 1 : 0, fact.deleted ? 1 : 0, fact.conflictPending ? 1 : 0);
|
|
530
|
+
}
|
|
531
|
+
/** Update one fact version in place (locking, tombstones, supersede links). */
|
|
532
|
+
updateFact(fact) {
|
|
533
|
+
this.db.prepare(`
|
|
534
|
+
UPDATE facts SET value = ?, valid_to = ?, superseded_by = ?, locked = ?, deleted = ?, conflict_pending = ?
|
|
535
|
+
WHERE id = ?
|
|
536
|
+
`).run(fact.value, fact.validTo ?? null, fact.supersededBy ?? null, fact.locked ? 1 : 0, fact.deleted ? 1 : 0, fact.conflictPending ? 1 : 0, fact.id);
|
|
537
|
+
}
|
|
538
|
+
/** The current (open valid-time window, not deleted) version of one slot. */
|
|
539
|
+
currentFact(category, factKey) {
|
|
540
|
+
const row = this.db.prepare("SELECT * FROM facts WHERE category = ? AND fact_key = ? AND valid_to IS NULL AND deleted = 0 ORDER BY recorded_at DESC LIMIT 1").get(category, factKey);
|
|
541
|
+
return row === void 0 ? void 0 : this.rowToFact(row);
|
|
542
|
+
}
|
|
543
|
+
/** One fact version by id. */
|
|
544
|
+
getFact(id) {
|
|
545
|
+
const row = this.db.prepare("SELECT * FROM facts WHERE id = ?").get(id);
|
|
546
|
+
return row === void 0 ? void 0 : this.rowToFact(row);
|
|
547
|
+
}
|
|
548
|
+
/** Shared WHERE for fact filters (008 §3: reused by list + count). */
|
|
549
|
+
factFilterClauses(filter) {
|
|
550
|
+
const clauses = ["deleted = 0"];
|
|
551
|
+
const params = [];
|
|
552
|
+
if (!filter.includeHistory) clauses.push("valid_to IS NULL");
|
|
553
|
+
if (filter.category !== void 0) {
|
|
554
|
+
clauses.push("category = ?");
|
|
555
|
+
params.push(filter.category);
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
where: clauses.join(" AND "),
|
|
559
|
+
params
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/** Fact versions by filter (008 §3: server-side pagination). */
|
|
563
|
+
listFacts(filter, limit, offset) {
|
|
564
|
+
const { where, params } = this.factFilterClauses(filter);
|
|
565
|
+
return this.db.prepare(`SELECT * FROM facts WHERE ${where} ORDER BY category, fact_key, recorded_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset).map((row) => this.rowToFact(row));
|
|
566
|
+
}
|
|
567
|
+
/** Count of facts matching the filter (008 §3: pagination total). */
|
|
568
|
+
factFilteredCount(filter) {
|
|
569
|
+
const { where, params } = this.factFilterClauses(filter);
|
|
570
|
+
return this.db.prepare(`SELECT COUNT(*) AS n FROM facts WHERE ${where}`).get(...params).n;
|
|
571
|
+
}
|
|
572
|
+
/** All fact versions including tombstones (export). */
|
|
573
|
+
allFacts() {
|
|
574
|
+
return this.db.prepare("SELECT * FROM facts ORDER BY recorded_at ASC").all().map((row) => this.rowToFact(row));
|
|
575
|
+
}
|
|
576
|
+
/** Fact counters. */
|
|
577
|
+
factCounts() {
|
|
578
|
+
const q = (where) => this.db.prepare(`SELECT COUNT(*) AS n FROM facts WHERE ${where}`).get().n;
|
|
579
|
+
return {
|
|
580
|
+
total: q("deleted = 0"),
|
|
581
|
+
current: q("valid_to IS NULL AND deleted = 0"),
|
|
582
|
+
locked: q("valid_to IS NULL AND deleted = 0 AND locked = 1"),
|
|
583
|
+
conflictPending: q("valid_to IS NULL AND deleted = 0 AND conflict_pending = 1")
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
rowToConcern(row) {
|
|
587
|
+
return {
|
|
588
|
+
id: row.id,
|
|
589
|
+
...row.parent_id === null ? {} : { parentId: row.parent_id },
|
|
590
|
+
title: row.title,
|
|
591
|
+
...row.background === "" ? {} : { background: row.background },
|
|
592
|
+
...row.kind === null ? {} : { kind: row.kind },
|
|
593
|
+
...row.status === null ? {} : { status: row.status },
|
|
594
|
+
ts: row.ts,
|
|
595
|
+
sourceDiaryIds: JSON.parse(row.source_diary_ids),
|
|
596
|
+
...row.context === "" ? {} : { context: row.context },
|
|
597
|
+
deleted: row.deleted === 1
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/** Insert one concerns row (top-level or a discussion mention). */
|
|
601
|
+
insertConcern(c) {
|
|
602
|
+
this.db.prepare(`
|
|
603
|
+
INSERT INTO concerns (id, parent_id, title, background, kind, status, ts, source_diary_ids, context, deleted)
|
|
604
|
+
VALUES (?,?,?,?,?,?,?,?,?,?)
|
|
605
|
+
`).run(c.id, c.parentId ?? null, c.title, c.background ?? "", c.kind ?? null, c.status ?? null, c.ts, JSON.stringify(c.sourceDiaryIds), c.context ?? "", c.deleted ? 1 : 0);
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Top-level concerns (newest first) each with its discussion loop (oldest
|
|
609
|
+
* first). Optional kind/status filter + limit/offset pagination over the
|
|
610
|
+
* top-level rows; a mention set is attached for every returned top-level.
|
|
611
|
+
*/
|
|
612
|
+
listConcernTrees(filter, limit, offset) {
|
|
613
|
+
const where = ["parent_id IS NULL", "deleted = 0"];
|
|
614
|
+
const params = [];
|
|
615
|
+
if (filter.kind !== void 0 && filter.kind !== "") {
|
|
616
|
+
where.push("kind = ?");
|
|
617
|
+
params.push(filter.kind);
|
|
618
|
+
}
|
|
619
|
+
if (filter.status !== void 0 && filter.status !== "") {
|
|
620
|
+
where.push("status = ?");
|
|
621
|
+
params.push(filter.status);
|
|
622
|
+
}
|
|
623
|
+
let sql = `SELECT * FROM concerns WHERE ${where.join(" AND ")} ORDER BY ts DESC`;
|
|
624
|
+
if (limit !== void 0) {
|
|
625
|
+
sql += " LIMIT ? OFFSET ?";
|
|
626
|
+
params.push(limit, offset ?? 0);
|
|
627
|
+
}
|
|
628
|
+
const tops = this.db.prepare(sql).all(...params);
|
|
629
|
+
const mentions = this.db.prepare("SELECT * FROM concerns WHERE parent_id IS NOT NULL AND deleted = 0 ORDER BY ts ASC").all();
|
|
630
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
631
|
+
for (const m of mentions) {
|
|
632
|
+
const list = byParent.get(m.parent_id) ?? [];
|
|
633
|
+
list.push(this.rowToConcern(m));
|
|
634
|
+
byParent.set(m.parent_id, list);
|
|
635
|
+
}
|
|
636
|
+
return tops.map((t) => ({
|
|
637
|
+
concern: this.rowToConcern(t),
|
|
638
|
+
mentions: byParent.get(t.id) ?? []
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
/** Count top-level (non-deleted) concerns matching the optional kind/status filter. */
|
|
642
|
+
listConcernsCount(filter) {
|
|
643
|
+
const where = ["parent_id IS NULL", "deleted = 0"];
|
|
644
|
+
const params = [];
|
|
645
|
+
if (filter.kind !== void 0 && filter.kind !== "") {
|
|
646
|
+
where.push("kind = ?");
|
|
647
|
+
params.push(filter.kind);
|
|
648
|
+
}
|
|
649
|
+
if (filter.status !== void 0 && filter.status !== "") {
|
|
650
|
+
where.push("status = ?");
|
|
651
|
+
params.push(filter.status);
|
|
652
|
+
}
|
|
653
|
+
return this.db.prepare(`SELECT COUNT(*) AS n FROM concerns WHERE ${where.join(" AND ")}`).get(...params).n;
|
|
654
|
+
}
|
|
655
|
+
/** Update a top-level concern's lifecycle status. */
|
|
656
|
+
setConcernStatus(id, status) {
|
|
657
|
+
this.db.prepare("UPDATE concerns SET status = ? WHERE id = ? AND parent_id IS NULL").run(status, id);
|
|
658
|
+
}
|
|
659
|
+
/** Tombstone a concern and its whole discussion loop (human-only cleanup). */
|
|
660
|
+
deleteConcernSubtree(id) {
|
|
661
|
+
this.db.prepare("UPDATE concerns SET deleted = 1 WHERE id = ? OR parent_id = ?").run(id, id);
|
|
662
|
+
}
|
|
663
|
+
/** Record one extraction run. */
|
|
664
|
+
insertExtraction(record) {
|
|
665
|
+
this.db.prepare(`
|
|
666
|
+
INSERT INTO extractions (id, ts, trigger, summary, produced_fact_ids, diary_count)
|
|
667
|
+
VALUES (?,?,?,?,?,?)
|
|
668
|
+
`).run(record.id, record.ts, record.trigger, record.summary, JSON.stringify(record.producedFactIds), record.diaryCount);
|
|
669
|
+
}
|
|
670
|
+
/** Extraction runs, newest first (008 §3: server-side pagination). */
|
|
671
|
+
listExtractions(limit, offset) {
|
|
672
|
+
return this.db.prepare("SELECT * FROM extractions ORDER BY ts DESC, rowid DESC LIMIT ? OFFSET ?").all(limit, offset).map((row) => ({
|
|
673
|
+
id: row.id,
|
|
674
|
+
ts: row.ts,
|
|
675
|
+
trigger: row.trigger,
|
|
676
|
+
summary: row.summary,
|
|
677
|
+
producedFactIds: JSON.parse(row.produced_fact_ids),
|
|
678
|
+
diaryCount: row.diary_count
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
/** Total extraction runs (008 §3: pagination count). */
|
|
682
|
+
extractionCount() {
|
|
683
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM extractions").get().n;
|
|
684
|
+
}
|
|
685
|
+
/** Last extraction ts (cadence gate). */
|
|
686
|
+
lastExtractionTs() {
|
|
687
|
+
return this.db.prepare("SELECT MAX(ts) AS ts FROM extractions").get().ts ?? 0;
|
|
688
|
+
}
|
|
689
|
+
/** Record one consolidation run (which experiences merged into which). */
|
|
690
|
+
recordConsolidation(record) {
|
|
691
|
+
this.db.prepare("INSERT INTO consolidations (id, ts, merged_ids, produced_id, note) VALUES (?,?,?,?,?)").run(record.id, record.ts, JSON.stringify(record.mergedIds), record.producedId, record.note);
|
|
692
|
+
}
|
|
693
|
+
/** Last consolidation ts (interval cadence gate; 0 = never consolidated). */
|
|
694
|
+
lastConsolidationTs() {
|
|
695
|
+
return this.db.prepare("SELECT MAX(ts) AS ts FROM consolidations").get().ts ?? 0;
|
|
696
|
+
}
|
|
697
|
+
/** Non-deleted experiences created strictly after `ts` (new material since last consolidation). */
|
|
698
|
+
countExperiencesSince(ts) {
|
|
699
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM experiences WHERE deleted = 0 AND created_at > ?").get(ts).n;
|
|
700
|
+
}
|
|
701
|
+
/** Archive a set of experiences by family_id (leave recall; recoverable — never hard-deleted autonomously). */
|
|
702
|
+
archiveExperienceIds(ids, ts) {
|
|
703
|
+
const stmt = this.db.prepare(`UPDATE experiences SET status = ?, updated_at = ? WHERE family_id = ? AND deleted = 0 AND status IN ('candidate','live','challenged')`);
|
|
704
|
+
for (const id of ids) stmt.run("archived", ts, id);
|
|
705
|
+
}
|
|
706
|
+
/** Record one recall event. */
|
|
707
|
+
insertRecallEvent(event) {
|
|
708
|
+
this.db.prepare("INSERT INTO recall_events (id, ts, situation, injected_ids, none, context) VALUES (?,?,?,?,?,?)").run(event.id, event.ts, event.situation, JSON.stringify(event.injectedIds), event.none ? 1 : 0, event.context ?? "");
|
|
709
|
+
}
|
|
710
|
+
/** Recall telemetry counters. */
|
|
711
|
+
recallCounts() {
|
|
712
|
+
const events = this.db.prepare("SELECT COUNT(*) AS n FROM recall_events").get();
|
|
713
|
+
const negative = this.db.prepare("SELECT COUNT(*) AS n FROM recall_events WHERE none = 1").get();
|
|
714
|
+
return {
|
|
715
|
+
events: events.n,
|
|
716
|
+
negative: negative.n
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
/** All recall events (export). */
|
|
720
|
+
allRecallEvents() {
|
|
721
|
+
return this.db.prepare("SELECT * FROM recall_events ORDER BY ts ASC").all().map((row) => ({
|
|
722
|
+
id: row.id,
|
|
723
|
+
ts: row.ts,
|
|
724
|
+
situation: row.situation,
|
|
725
|
+
injectedIds: JSON.parse(row.injected_ids),
|
|
726
|
+
none: row.none === 1,
|
|
727
|
+
context: row.context ?? ""
|
|
728
|
+
}));
|
|
729
|
+
}
|
|
730
|
+
rowToExperience(row) {
|
|
731
|
+
const alpha = row.alpha;
|
|
732
|
+
const beta = row.beta;
|
|
733
|
+
const snapshot = {
|
|
734
|
+
id: row.family_id,
|
|
735
|
+
revision: row.revision,
|
|
736
|
+
kind: row.kind,
|
|
737
|
+
source: row.source,
|
|
738
|
+
family: row.family,
|
|
739
|
+
gist: row.gist,
|
|
740
|
+
situation: JSON.parse(row.situation),
|
|
741
|
+
path: JSON.parse(row.path),
|
|
742
|
+
reasoning: row.reasoning,
|
|
743
|
+
limits: JSON.parse(row.limits),
|
|
744
|
+
status: row.status,
|
|
745
|
+
alpha,
|
|
746
|
+
beta,
|
|
747
|
+
samples: alpha + beta,
|
|
748
|
+
trust: (alpha + 1) / (alpha + beta + 2),
|
|
749
|
+
weightedTrust: this.weightedTrust(row.family_id, row.revision, Date.now()),
|
|
750
|
+
pinned: row.pinned === 1,
|
|
751
|
+
tokensSaved: row.tokens_saved,
|
|
752
|
+
tokensSpent: row.tokens_spent,
|
|
753
|
+
context: row.context,
|
|
754
|
+
verifiedCount: row.verified_count,
|
|
755
|
+
rejectCount: row.reject_count,
|
|
756
|
+
globalFlag: row.global_flag === 1,
|
|
757
|
+
createdAt: row.created_at,
|
|
758
|
+
updatedAt: row.updated_at
|
|
759
|
+
};
|
|
760
|
+
if (row.last_verified_at !== null) snapshot.lastVerifiedAt = row.last_verified_at;
|
|
761
|
+
if (row.parent_revision !== null) snapshot.parentRevision = row.parent_revision;
|
|
762
|
+
if (row.failure_reason !== null) snapshot.failureReason = row.failure_reason;
|
|
763
|
+
if (row.evidence !== null) snapshot.evidence = JSON.parse(row.evidence);
|
|
764
|
+
if (row.challenge_reason !== null) snapshot.challengeReason = row.challenge_reason;
|
|
765
|
+
return snapshot;
|
|
766
|
+
}
|
|
767
|
+
rowToReport(row) {
|
|
768
|
+
return {
|
|
769
|
+
id: row.id,
|
|
770
|
+
experienceId: row.experience_id,
|
|
771
|
+
revision: row.revision,
|
|
772
|
+
outcome: row.outcome,
|
|
773
|
+
attribution: row.attribution,
|
|
774
|
+
counted: row.counted,
|
|
775
|
+
evidence: row.evidence === null ? void 0 : JSON.parse(row.evidence),
|
|
776
|
+
dedupeKey: row.dedupe_key === null ? void 0 : row.dedupe_key,
|
|
777
|
+
ts: row.ts
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
rowToLedger(row) {
|
|
781
|
+
const block = {
|
|
782
|
+
seq: row.seq,
|
|
783
|
+
ts: row.ts,
|
|
784
|
+
op: row.op,
|
|
785
|
+
objectType: row.object_type,
|
|
786
|
+
objectId: row.object_id,
|
|
787
|
+
actor: row.actor,
|
|
788
|
+
payload: row.payload,
|
|
789
|
+
prevHash: row.prev_hash,
|
|
790
|
+
hash: row.hash
|
|
791
|
+
};
|
|
792
|
+
if (row.reason !== null) block.reason = row.reason;
|
|
793
|
+
return block;
|
|
794
|
+
}
|
|
795
|
+
rowToDiary(row) {
|
|
796
|
+
const entry = {
|
|
797
|
+
id: row.id,
|
|
798
|
+
ts: row.ts,
|
|
799
|
+
kind: row.kind,
|
|
800
|
+
content: row.content,
|
|
801
|
+
tags: JSON.parse(row.tags),
|
|
802
|
+
extracted: row.extracted === 1
|
|
803
|
+
};
|
|
804
|
+
if (row.session_ref !== null) entry.sessionRef = row.session_ref;
|
|
805
|
+
return entry;
|
|
806
|
+
}
|
|
807
|
+
rowToFact(row) {
|
|
808
|
+
const fact = {
|
|
809
|
+
id: row.id,
|
|
810
|
+
category: row.category,
|
|
811
|
+
factKey: row.fact_key,
|
|
812
|
+
value: row.value,
|
|
813
|
+
origin: row.origin,
|
|
814
|
+
sourceDiaryIds: JSON.parse(row.source_diary_ids),
|
|
815
|
+
corroboration: row.corroboration,
|
|
816
|
+
validFrom: row.valid_from,
|
|
817
|
+
recordedAt: row.recorded_at,
|
|
818
|
+
locked: row.locked === 1,
|
|
819
|
+
deleted: row.deleted === 1,
|
|
820
|
+
conflictPending: row.conflict_pending === 1
|
|
821
|
+
};
|
|
822
|
+
if (row.valid_to !== null) fact.validTo = row.valid_to;
|
|
823
|
+
if (row.superseded_by !== null) fact.supersededBy = row.superseded_by;
|
|
824
|
+
return fact;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
//#endregion
|
|
828
|
+
//#region lib/types/core.js
|
|
829
|
+
/**
|
|
830
|
+
* Memory core: the 生·用·修·记 mechanisms over the durable store. Pure and
|
|
831
|
+
* ctx-free. Implements 005's P0/P1 semantics:
|
|
832
|
+
*
|
|
833
|
+
* - 生 refine: complexity gate + information-gain gate + mandatory evidence
|
|
834
|
+
* pointers; positive and negative candidates symmetric.
|
|
835
|
+
* - 用 recall: recall → adjudication (limits vs situation conflicts,
|
|
836
|
+
* direct/reference/clue/not-applicable) → injection budget → explicit
|
|
837
|
+
* negative channel.
|
|
838
|
+
* - 用·验 report: Beta posterior (alpha+1)/(alpha+beta+2) with recency
|
|
839
|
+
* weighting; four-way attribution with objective-evidence priority and
|
|
840
|
+
* no-count on insufficient signal; idempotent reports.
|
|
841
|
+
* - 修 revise: challenged quarantine (immediately out of recall), draft
|
|
842
|
+
* proposal, adoption only after one successful use or shadow replay,
|
|
843
|
+
* superseded read-only index with rollback.
|
|
844
|
+
* - 记 diary/facts: append-only diary, cadence-driven extraction with source
|
|
845
|
+
* pointers, bi-temporal facts with conflict arbitration and human gates.
|
|
846
|
+
* - Ledger: append-only hash-chained event sourcing for every mutation,
|
|
847
|
+
* including every human operation.
|
|
848
|
+
*
|
|
849
|
+
* @module dsh-daoing-memory/core
|
|
850
|
+
*/
|
|
851
|
+
/** The mechanism defaults (005 mapping documented in the README). */
|
|
852
|
+
const DEFAULT_CORE_CONFIG = {
|
|
853
|
+
diaryExtractEvery: 8,
|
|
854
|
+
diaryExtractIntervalHours: 12,
|
|
855
|
+
recallTopK: 6,
|
|
856
|
+
injectionBudgetTokens: 1200,
|
|
857
|
+
challengeConsecutiveFails: 2,
|
|
858
|
+
challengeWindow: 6,
|
|
859
|
+
challengeWindowFailRate: .6,
|
|
860
|
+
familyLiveCap: 12,
|
|
861
|
+
complexityTokenGate: 4e3,
|
|
862
|
+
complexityStepGate: 6,
|
|
863
|
+
duplicateOverlapGate: .85,
|
|
864
|
+
recallFloorScore: .1,
|
|
865
|
+
shadowPassRate: .8,
|
|
866
|
+
humanFloorAlpha: 5,
|
|
867
|
+
humanFloorBeta: 2,
|
|
868
|
+
pinnedTrustFloor: .67,
|
|
869
|
+
candidateTrialTopK: 3,
|
|
870
|
+
candidateTrialFloorScore: .18,
|
|
871
|
+
consolidateEveryNew: 6,
|
|
872
|
+
consolidateIntervalHours: 24
|
|
873
|
+
};
|
|
874
|
+
/** Objective-evidence markers overriding a claimed experience-attributed failure. */
|
|
875
|
+
const ENVIRONMENT_EVIDENCE_PATTERN = /network|timeout|timed out|EPERM|EACCES|ECONNREFUSED|ENOTFOUND|429|503|quota|rate.?limit|unauthorized|dns|proxy|网络|超时|断网|权限|配额|限流|服务不可用|服务繁忙/i;
|
|
876
|
+
/**
|
|
877
|
+
* Source-authority priors for the ingest channel (006 §1.3): a vetted skill or
|
|
878
|
+
* document starts a candidate with more trust than an unreviewed note. The prior
|
|
879
|
+
* only affects trust once the candidate is verified — candidates never recall.
|
|
880
|
+
*/
|
|
881
|
+
const INGEST_SOURCE_PRIOR = {
|
|
882
|
+
skill: {
|
|
883
|
+
alpha: 5,
|
|
884
|
+
beta: 2
|
|
885
|
+
},
|
|
886
|
+
document: {
|
|
887
|
+
alpha: 4,
|
|
888
|
+
beta: 2
|
|
889
|
+
},
|
|
890
|
+
book: {
|
|
891
|
+
alpha: 4,
|
|
892
|
+
beta: 2
|
|
893
|
+
},
|
|
894
|
+
conversation: {
|
|
895
|
+
alpha: 2,
|
|
896
|
+
beta: 2
|
|
897
|
+
},
|
|
898
|
+
note: {
|
|
899
|
+
alpha: 1,
|
|
900
|
+
beta: 2
|
|
901
|
+
},
|
|
902
|
+
other: {
|
|
903
|
+
alpha: 1,
|
|
904
|
+
beta: 2
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
/** Cosine-ish overlap of two token sets (for intra-batch dedup, 007). */
|
|
908
|
+
function tokenOverlap(a, b) {
|
|
909
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
910
|
+
let hits = 0;
|
|
911
|
+
for (const t of a) if (b.has(t)) hits += 1;
|
|
912
|
+
return hits / Math.sqrt(a.size * b.size);
|
|
913
|
+
}
|
|
914
|
+
/** The memory core: stateless over the injected store. */
|
|
915
|
+
var MemoryCore = class {
|
|
916
|
+
store;
|
|
917
|
+
config;
|
|
918
|
+
/** @param store - the durable SQLite store. */
|
|
919
|
+
/** @param config - resolved mechanism parameters. */
|
|
920
|
+
constructor(store, config) {
|
|
921
|
+
this.store = store;
|
|
922
|
+
this.config = config;
|
|
923
|
+
}
|
|
924
|
+
ledger(op, objectType, objectId, actor, payload, reason) {
|
|
925
|
+
this.store.appendLedger({
|
|
926
|
+
ts: Date.now(),
|
|
927
|
+
op,
|
|
928
|
+
objectType,
|
|
929
|
+
objectId,
|
|
930
|
+
actor,
|
|
931
|
+
payload: JSON.stringify(payload),
|
|
932
|
+
prevHash: this.store.ledgerHead(),
|
|
933
|
+
...reason === void 0 ? {} : { reason }
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
/** 生: refine one completed trajectory into a candidate (dual gate). */
|
|
937
|
+
refine(request, actor) {
|
|
938
|
+
if (!((request.evidence.traceRef ?? "").trim() !== "" || (request.evidence.sessionRef ?? "").trim() !== "" || (request.evidence.note ?? "").trim() !== "")) return {
|
|
939
|
+
accepted: false,
|
|
940
|
+
reason: "rejected-evidence: every assertion needs an episodic evidence pointer (traceRef/sessionRef/note)"
|
|
941
|
+
};
|
|
942
|
+
if (request.kind === "negative" && (request.failureReason ?? "").trim() === "") return {
|
|
943
|
+
accepted: false,
|
|
944
|
+
reason: "rejected-schema: negative experiences must carry the confirmed failureReason"
|
|
945
|
+
};
|
|
946
|
+
const c = request.complexity;
|
|
947
|
+
if (!(request.humanMarked === true || c.hadFailure === true || (c.tokens ?? 0) >= this.config.complexityTokenGate || (c.steps ?? 0) >= this.config.complexityStepGate)) return {
|
|
948
|
+
accepted: false,
|
|
949
|
+
reason: `rejected-complexity: trajectory below both gates (tokens < ${String(this.config.complexityTokenGate)}, steps < ${String(this.config.complexityStepGate)}, no failure, not human-marked)`
|
|
950
|
+
};
|
|
951
|
+
const dedupTokens = new Set(tokenize([request.gist, ...request.situation].join(" ")));
|
|
952
|
+
const near = this.store.findNearDuplicate(dedupTokens, [
|
|
953
|
+
"candidate",
|
|
954
|
+
"live",
|
|
955
|
+
"challenged",
|
|
956
|
+
"archived",
|
|
957
|
+
"cold"
|
|
958
|
+
]);
|
|
959
|
+
if (near !== void 0 && near.score >= this.config.duplicateOverlapGate) {
|
|
960
|
+
this.ledger("corroborate", "experience", near.snapshot.id, actor, {
|
|
961
|
+
family: request.family,
|
|
962
|
+
gist: request.gist,
|
|
963
|
+
score: near.score
|
|
964
|
+
});
|
|
965
|
+
return {
|
|
966
|
+
accepted: false,
|
|
967
|
+
reason: "rejected-information-gain: near-duplicate of an existing experience; corroborated it instead",
|
|
968
|
+
corroboratedId: near.snapshot.id
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
const now = Date.now();
|
|
972
|
+
const snapshot = {
|
|
973
|
+
id: randomUUID(),
|
|
974
|
+
revision: 1,
|
|
975
|
+
kind: request.kind,
|
|
976
|
+
source: "agent",
|
|
977
|
+
family: request.family,
|
|
978
|
+
gist: request.gist,
|
|
979
|
+
situation: [...request.situation],
|
|
980
|
+
path: [...request.path],
|
|
981
|
+
reasoning: request.reasoning,
|
|
982
|
+
limits: [...request.limits],
|
|
983
|
+
status: "candidate",
|
|
984
|
+
alpha: 0,
|
|
985
|
+
beta: 0,
|
|
986
|
+
samples: 0,
|
|
987
|
+
trust: .5,
|
|
988
|
+
weightedTrust: .5,
|
|
989
|
+
pinned: false,
|
|
990
|
+
tokensSaved: 0,
|
|
991
|
+
tokensSpent: (request.complexity.tokens ?? 0) / 10,
|
|
992
|
+
context: request.context ?? "",
|
|
993
|
+
verifiedCount: 0,
|
|
994
|
+
rejectCount: 0,
|
|
995
|
+
globalFlag: false,
|
|
996
|
+
evidence: request.evidence,
|
|
997
|
+
createdAt: now,
|
|
998
|
+
updatedAt: now
|
|
999
|
+
};
|
|
1000
|
+
if (request.failureReason !== void 0) snapshot.failureReason = request.failureReason;
|
|
1001
|
+
this.store.upsertExperience(snapshot);
|
|
1002
|
+
this.ledger("refine", "experience", snapshot.id, actor, {
|
|
1003
|
+
kind: snapshot.kind,
|
|
1004
|
+
family: snapshot.family,
|
|
1005
|
+
revision: 1
|
|
1006
|
+
});
|
|
1007
|
+
return {
|
|
1008
|
+
accepted: true,
|
|
1009
|
+
experience: snapshot
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* 摄取归一: source-agnostic intake. Every extracted draft becomes an earned
|
|
1014
|
+
* candidate carrying provenance (sourceType + sourceRef), the source-authority
|
|
1015
|
+
* prior, and the declared context scope. Candidates never recall until verified.
|
|
1016
|
+
*/
|
|
1017
|
+
ingest(request, actor) {
|
|
1018
|
+
if ((request.sourceRef ?? "").trim() === "") throw new Error("memory: ingest requires a non-empty sourceRef provenance");
|
|
1019
|
+
const prior = INGEST_SOURCE_PRIOR[request.sourceType] ?? INGEST_SOURCE_PRIOR.other;
|
|
1020
|
+
const context = request.context ?? "";
|
|
1021
|
+
const accepted = [];
|
|
1022
|
+
const rejected = [];
|
|
1023
|
+
const acceptedDedup = [];
|
|
1024
|
+
for (const item of request.experiences) {
|
|
1025
|
+
if (item.kind === "negative" && (item.failureReason ?? "").trim() === "") {
|
|
1026
|
+
rejected.push({
|
|
1027
|
+
gist: item.gist,
|
|
1028
|
+
reason: "rejected-schema: negative experiences must carry the confirmed failureReason"
|
|
1029
|
+
});
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
const dedupTokens = new Set(tokenize([item.gist, ...item.situation].join(" ")));
|
|
1033
|
+
const cross = this.store.findNearDuplicate(dedupTokens, [
|
|
1034
|
+
"candidate",
|
|
1035
|
+
"live",
|
|
1036
|
+
"challenged",
|
|
1037
|
+
"archived",
|
|
1038
|
+
"cold"
|
|
1039
|
+
]);
|
|
1040
|
+
if (cross !== void 0 && cross.score >= this.config.duplicateOverlapGate) {
|
|
1041
|
+
this.ledger("corroborate", "experience", cross.snapshot.id, actor, {
|
|
1042
|
+
family: item.family,
|
|
1043
|
+
gist: item.gist,
|
|
1044
|
+
score: cross.score,
|
|
1045
|
+
via: "ingest"
|
|
1046
|
+
});
|
|
1047
|
+
rejected.push({
|
|
1048
|
+
gist: item.gist,
|
|
1049
|
+
reason: "rejected-information-gain: near-duplicate of an existing experience; corroborated it instead"
|
|
1050
|
+
});
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
if (acceptedDedup.some((prev) => tokenOverlap(dedupTokens, prev) >= this.config.duplicateOverlapGate)) {
|
|
1054
|
+
rejected.push({
|
|
1055
|
+
gist: item.gist,
|
|
1056
|
+
reason: "rejected-information-gain: near-duplicate of another item in this same ingest batch"
|
|
1057
|
+
});
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
const now = Date.now();
|
|
1061
|
+
const snapshot = {
|
|
1062
|
+
id: randomUUID(),
|
|
1063
|
+
revision: 1,
|
|
1064
|
+
kind: item.kind,
|
|
1065
|
+
source: "agent",
|
|
1066
|
+
family: item.family,
|
|
1067
|
+
gist: item.gist,
|
|
1068
|
+
situation: [...item.situation],
|
|
1069
|
+
path: [...item.path],
|
|
1070
|
+
reasoning: item.reasoning,
|
|
1071
|
+
limits: [...item.limits],
|
|
1072
|
+
status: "candidate",
|
|
1073
|
+
alpha: prior.alpha,
|
|
1074
|
+
beta: prior.beta,
|
|
1075
|
+
samples: prior.alpha + prior.beta,
|
|
1076
|
+
trust: (prior.alpha + 1) / (prior.alpha + prior.beta + 2),
|
|
1077
|
+
weightedTrust: (prior.alpha + 1) / (prior.alpha + prior.beta + 2),
|
|
1078
|
+
pinned: false,
|
|
1079
|
+
tokensSaved: 0,
|
|
1080
|
+
tokensSpent: 0,
|
|
1081
|
+
context,
|
|
1082
|
+
verifiedCount: 0,
|
|
1083
|
+
rejectCount: 0,
|
|
1084
|
+
globalFlag: false,
|
|
1085
|
+
evidence: { note: `${request.sourceType}:${request.sourceRef}` },
|
|
1086
|
+
createdAt: now,
|
|
1087
|
+
updatedAt: now
|
|
1088
|
+
};
|
|
1089
|
+
if (item.failureReason !== void 0) snapshot.failureReason = item.failureReason;
|
|
1090
|
+
this.store.upsertExperience(snapshot);
|
|
1091
|
+
this.ledger("ingest", "experience", snapshot.id, actor, {
|
|
1092
|
+
kind: snapshot.kind,
|
|
1093
|
+
family: snapshot.family,
|
|
1094
|
+
sourceType: request.sourceType,
|
|
1095
|
+
sourceRef: request.sourceRef,
|
|
1096
|
+
...context === "" ? {} : { context },
|
|
1097
|
+
...request.note === void 0 ? {} : { note: request.note }
|
|
1098
|
+
});
|
|
1099
|
+
accepted.push(snapshot);
|
|
1100
|
+
acceptedDedup.push(dedupTokens);
|
|
1101
|
+
}
|
|
1102
|
+
return {
|
|
1103
|
+
accepted,
|
|
1104
|
+
rejected,
|
|
1105
|
+
sourcePrior: prior
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
/** 用: recall → scope → adjudicate → budget → inject, plus the candidate probe channel. */
|
|
1109
|
+
recall(request, actor) {
|
|
1110
|
+
const topK = request.topK ?? this.config.recallTopK;
|
|
1111
|
+
const budget = request.budgetTokens ?? this.config.injectionBudgetTokens;
|
|
1112
|
+
const context = request.context ?? "";
|
|
1113
|
+
const queryTokens = new Set(tokenize(request.situation));
|
|
1114
|
+
let candidateTrials = [];
|
|
1115
|
+
if (request.includeTrials !== false && queryTokens.size > 0) candidateTrials = this.store.recallCandidates(queryTokens, this.config.candidateTrialTopK * 2, {
|
|
1116
|
+
statuses: ["candidate"],
|
|
1117
|
+
context
|
|
1118
|
+
}).filter((m) => m.score >= this.config.candidateTrialFloorScore).slice(0, this.config.candidateTrialTopK).map((m) => ({
|
|
1119
|
+
experience: m.snapshot,
|
|
1120
|
+
score: m.score
|
|
1121
|
+
}));
|
|
1122
|
+
const candidates = queryTokens.size === 0 ? [] : this.store.recallCandidates(queryTokens, topK * 3, {
|
|
1123
|
+
statuses: request.deep === true ? ["live", "archived"] : ["live"],
|
|
1124
|
+
context
|
|
1125
|
+
});
|
|
1126
|
+
const adjudicated = [];
|
|
1127
|
+
for (const { snapshot, score } of candidates) {
|
|
1128
|
+
if (score < this.config.recallFloorScore) continue;
|
|
1129
|
+
const conflicts = [];
|
|
1130
|
+
for (const limit of snapshot.limits) {
|
|
1131
|
+
const limitTokens = tokenize(limit);
|
|
1132
|
+
if (limitTokens.length === 0) continue;
|
|
1133
|
+
if (limitTokens.filter((token) => queryTokens.has(token)).length / limitTokens.length >= .5) conflicts.push(limit);
|
|
1134
|
+
}
|
|
1135
|
+
const verdict = conflicts.length > 0 ? "reference" : score >= .45 ? "direct" : score >= .22 ? "reference" : "clue";
|
|
1136
|
+
adjudicated.push({
|
|
1137
|
+
experience: snapshot,
|
|
1138
|
+
score,
|
|
1139
|
+
verdict,
|
|
1140
|
+
conflicts
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
if (adjudicated.length === 0) {
|
|
1144
|
+
const none = {
|
|
1145
|
+
items: [],
|
|
1146
|
+
none: true,
|
|
1147
|
+
reason: "no relevant experience in the library for this situation",
|
|
1148
|
+
omitted: 0,
|
|
1149
|
+
estimatedTokens: 0,
|
|
1150
|
+
candidateTrials,
|
|
1151
|
+
consolidationDue: this.consolidationDue().due
|
|
1152
|
+
};
|
|
1153
|
+
this.store.insertRecallEvent({
|
|
1154
|
+
id: randomUUID(),
|
|
1155
|
+
ts: Date.now(),
|
|
1156
|
+
situation: request.situation,
|
|
1157
|
+
injectedIds: [],
|
|
1158
|
+
none: true,
|
|
1159
|
+
context
|
|
1160
|
+
});
|
|
1161
|
+
return none;
|
|
1162
|
+
}
|
|
1163
|
+
const ranked = [...adjudicated].sort((a, b) => {
|
|
1164
|
+
const value = (item) => {
|
|
1165
|
+
const trust = item.experience.pinned ? Math.max(item.experience.weightedTrust, this.config.pinnedTrustFloor) : item.experience.weightedTrust;
|
|
1166
|
+
return item.score * trust;
|
|
1167
|
+
};
|
|
1168
|
+
return value(b) - value(a);
|
|
1169
|
+
});
|
|
1170
|
+
const items = [];
|
|
1171
|
+
let usedTokens = 0;
|
|
1172
|
+
let omitted = 0;
|
|
1173
|
+
for (const item of ranked) {
|
|
1174
|
+
const cost = estimateTokens(JSON.stringify(item.experience));
|
|
1175
|
+
if (usedTokens + cost > budget && items.length > 0) {
|
|
1176
|
+
omitted += 1;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
items.push(item);
|
|
1180
|
+
usedTokens += cost;
|
|
1181
|
+
}
|
|
1182
|
+
this.store.insertRecallEvent({
|
|
1183
|
+
id: randomUUID(),
|
|
1184
|
+
ts: Date.now(),
|
|
1185
|
+
situation: request.situation,
|
|
1186
|
+
injectedIds: items.map((item) => `${item.experience.id}@${String(item.experience.revision)}`),
|
|
1187
|
+
none: false,
|
|
1188
|
+
context
|
|
1189
|
+
});
|
|
1190
|
+
return {
|
|
1191
|
+
items,
|
|
1192
|
+
none: false,
|
|
1193
|
+
omitted,
|
|
1194
|
+
estimatedTokens: usedTokens,
|
|
1195
|
+
candidateTrials,
|
|
1196
|
+
consolidationDue: this.consolidationDue().due
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
/** 用·验: one use outcome → Beta update, attribution, quarantine, gates. */
|
|
1200
|
+
report(request, actor) {
|
|
1201
|
+
const target = request.revision === void 0 ? this.store.getActiveRevision(request.id) : this.store.getExperience(request.id, request.revision);
|
|
1202
|
+
if (target === void 0) throw new Error(`memory: experience not found: ${request.id}`);
|
|
1203
|
+
const base = {
|
|
1204
|
+
snapshot: target,
|
|
1205
|
+
counted: "none",
|
|
1206
|
+
attributionApplied: "unknown",
|
|
1207
|
+
challenged: false,
|
|
1208
|
+
promoted: false,
|
|
1209
|
+
adopted: false
|
|
1210
|
+
};
|
|
1211
|
+
let attribution;
|
|
1212
|
+
let overrideNote;
|
|
1213
|
+
if (request.outcome === "success") attribution = "experience";
|
|
1214
|
+
else {
|
|
1215
|
+
const claimed = request.attribution ?? "unknown";
|
|
1216
|
+
const evidenceNote = request.evidence?.note ?? "";
|
|
1217
|
+
if (evidenceNote !== "" && ENVIRONMENT_EVIDENCE_PATTERN.test(evidenceNote)) {
|
|
1218
|
+
attribution = "environment";
|
|
1219
|
+
if (claimed !== "environment") overrideNote = "objective evidence indicates an environment failure; claim overridden";
|
|
1220
|
+
} else attribution = claimed;
|
|
1221
|
+
if (attribution === "experience" && evidenceNote.trim() === "") {
|
|
1222
|
+
if (target.status !== "candidate") {
|
|
1223
|
+
attribution = "unknown";
|
|
1224
|
+
overrideNote = "experience-attributed failure without evidence note: insufficient signal, not counted";
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
const counted = request.outcome === "success" ? "alpha" : attribution === "experience" ? "beta" : "none";
|
|
1229
|
+
if (!this.store.insertReport({
|
|
1230
|
+
id: randomUUID(),
|
|
1231
|
+
experienceId: target.id,
|
|
1232
|
+
revision: target.revision,
|
|
1233
|
+
outcome: request.outcome,
|
|
1234
|
+
attribution,
|
|
1235
|
+
counted,
|
|
1236
|
+
evidence: request.evidence,
|
|
1237
|
+
dedupeKey: request.dedupeKey,
|
|
1238
|
+
ts: Date.now()
|
|
1239
|
+
})) {
|
|
1240
|
+
if (overrideNote !== void 0) base.overrideNote = overrideNote;
|
|
1241
|
+
return base;
|
|
1242
|
+
}
|
|
1243
|
+
let next = {
|
|
1244
|
+
...target,
|
|
1245
|
+
updatedAt: Date.now()
|
|
1246
|
+
};
|
|
1247
|
+
if (counted === "alpha") next = {
|
|
1248
|
+
...next,
|
|
1249
|
+
alpha: next.alpha + 1,
|
|
1250
|
+
verifiedCount: next.verifiedCount + 1,
|
|
1251
|
+
lastVerifiedAt: Date.now()
|
|
1252
|
+
};
|
|
1253
|
+
else if (counted === "beta") {
|
|
1254
|
+
const isCandidateTrial = next.status === "candidate";
|
|
1255
|
+
next = {
|
|
1256
|
+
...next,
|
|
1257
|
+
beta: next.beta + 1,
|
|
1258
|
+
rejectCount: isCandidateTrial ? next.rejectCount + 1 : next.rejectCount,
|
|
1259
|
+
lastVerifiedAt: Date.now()
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
next = {
|
|
1263
|
+
...next,
|
|
1264
|
+
tokensSaved: next.tokensSaved + (request.tokensSaved ?? 0),
|
|
1265
|
+
tokensSpent: next.tokensSpent + (request.tokensUsed ?? 0),
|
|
1266
|
+
samples: next.alpha + next.beta,
|
|
1267
|
+
trust: (next.alpha + 1) / (next.alpha + next.beta + 2)
|
|
1268
|
+
};
|
|
1269
|
+
next = {
|
|
1270
|
+
...next,
|
|
1271
|
+
weightedTrust: this.store.weightedTrust(next.id, next.revision, Date.now())
|
|
1272
|
+
};
|
|
1273
|
+
if (request.outcome === "success" && attribution === "experience") {
|
|
1274
|
+
if (next.status === "candidate" && next.parentRevision !== void 0) {
|
|
1275
|
+
const adopted = {
|
|
1276
|
+
...next,
|
|
1277
|
+
status: "live"
|
|
1278
|
+
};
|
|
1279
|
+
delete adopted.challengeReason;
|
|
1280
|
+
next = adopted;
|
|
1281
|
+
this.store.upsertExperience(next);
|
|
1282
|
+
this.supersedeParent(next, actor);
|
|
1283
|
+
this.ledger("adopt", "experience", next.id, actor, {
|
|
1284
|
+
revision: next.revision,
|
|
1285
|
+
via: "use"
|
|
1286
|
+
});
|
|
1287
|
+
this.enforceFamilyCap(next.family, actor);
|
|
1288
|
+
base.adopted = true;
|
|
1289
|
+
base.promoted = true;
|
|
1290
|
+
} else if (next.status === "candidate") {
|
|
1291
|
+
next = {
|
|
1292
|
+
...next,
|
|
1293
|
+
status: "live"
|
|
1294
|
+
};
|
|
1295
|
+
this.ledger("promote", "experience", next.id, actor, {
|
|
1296
|
+
revision: next.revision,
|
|
1297
|
+
via: "use"
|
|
1298
|
+
});
|
|
1299
|
+
base.promoted = true;
|
|
1300
|
+
} else if (next.status === "archived") {
|
|
1301
|
+
next = {
|
|
1302
|
+
...next,
|
|
1303
|
+
status: "live"
|
|
1304
|
+
};
|
|
1305
|
+
this.ledger("restore", "experience", next.id, actor, { revision: next.revision });
|
|
1306
|
+
base.promoted = true;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (next.status === "candidate" && counted === "beta") {
|
|
1310
|
+
const reason = "candidate trial failed; human re-release required";
|
|
1311
|
+
next = {
|
|
1312
|
+
...next,
|
|
1313
|
+
status: "cold",
|
|
1314
|
+
challengeReason: reason
|
|
1315
|
+
};
|
|
1316
|
+
this.ledger("trial-fail", "experience", next.id, actor, { revision: next.revision }, reason);
|
|
1317
|
+
base.cooled = true;
|
|
1318
|
+
}
|
|
1319
|
+
if (next.status === "live" && counted === "beta" && this.shouldChallenge(next)) {
|
|
1320
|
+
const reason = "posterior pressure: repeated experience-attributed failures";
|
|
1321
|
+
next = {
|
|
1322
|
+
...next,
|
|
1323
|
+
status: "challenged",
|
|
1324
|
+
challengeReason: reason
|
|
1325
|
+
};
|
|
1326
|
+
this.ledger("challenge", "experience", next.id, actor, { revision: next.revision }, reason);
|
|
1327
|
+
base.challenged = true;
|
|
1328
|
+
}
|
|
1329
|
+
this.store.upsertExperience(next);
|
|
1330
|
+
this.ledger("use", "experience", next.id, actor, {
|
|
1331
|
+
revision: next.revision,
|
|
1332
|
+
outcome: request.outcome,
|
|
1333
|
+
attribution,
|
|
1334
|
+
counted,
|
|
1335
|
+
...request.dedupeKey === void 0 ? {} : { dedupeKey: request.dedupeKey }
|
|
1336
|
+
});
|
|
1337
|
+
base.snapshot = next;
|
|
1338
|
+
base.counted = counted;
|
|
1339
|
+
base.attributionApplied = attribution;
|
|
1340
|
+
if (overrideNote !== void 0) base.overrideNote = overrideNote;
|
|
1341
|
+
return base;
|
|
1342
|
+
}
|
|
1343
|
+
/** Windowed challenge rule: consecutive fails or window failure rate. */
|
|
1344
|
+
shouldChallenge(exp) {
|
|
1345
|
+
const reports = this.store.reportsFor(exp.id, exp.revision, this.config.challengeWindow);
|
|
1346
|
+
let consecutive = 0;
|
|
1347
|
+
for (const report of reports) if (report.outcome === "fail" && report.attribution === "experience") consecutive += 1;
|
|
1348
|
+
else break;
|
|
1349
|
+
if (consecutive >= this.config.challengeConsecutiveFails) return true;
|
|
1350
|
+
if (reports.length >= 3) {
|
|
1351
|
+
if (reports.filter((r) => r.outcome === "fail" && r.attribution === "experience").length / reports.length >= this.config.challengeWindowFailRate) return true;
|
|
1352
|
+
}
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
/** Close the parent revision as superseded after a draft adoption. */
|
|
1356
|
+
supersedeParent(draft, actor) {
|
|
1357
|
+
if (draft.parentRevision === void 0) return;
|
|
1358
|
+
const parent = this.store.getExperience(draft.id, draft.parentRevision);
|
|
1359
|
+
if (parent === void 0) return;
|
|
1360
|
+
this.store.upsertExperience({
|
|
1361
|
+
...parent,
|
|
1362
|
+
status: "superseded",
|
|
1363
|
+
updatedAt: Date.now()
|
|
1364
|
+
});
|
|
1365
|
+
this.ledger("supersede", "experience", parent.id, actor, {
|
|
1366
|
+
revision: parent.revision,
|
|
1367
|
+
by: draft.revision
|
|
1368
|
+
});
|
|
1369
|
+
this.enforceFamilyCap(draft.family, actor);
|
|
1370
|
+
}
|
|
1371
|
+
/** Capacity budget: archive the lowest economic value when a family overflows. */
|
|
1372
|
+
enforceFamilyCap(familyTag, actor) {
|
|
1373
|
+
const overflow = this.store.countFamilyActive(familyTag) - this.config.familyLiveCap;
|
|
1374
|
+
if (overflow <= 0) return;
|
|
1375
|
+
const live = this.store.listExperiences({
|
|
1376
|
+
status: "live",
|
|
1377
|
+
family: familyTag
|
|
1378
|
+
}).filter((exp) => !exp.pinned).sort((a, b) => a.tokensSaved - a.tokensSpent - (b.tokensSaved - b.tokensSpent));
|
|
1379
|
+
for (const exp of live.slice(0, overflow)) {
|
|
1380
|
+
this.store.upsertExperience({
|
|
1381
|
+
...exp,
|
|
1382
|
+
status: "archived",
|
|
1383
|
+
updatedAt: Date.now()
|
|
1384
|
+
});
|
|
1385
|
+
this.ledger("archive", "experience", exp.id, actor, {
|
|
1386
|
+
revision: exp.revision,
|
|
1387
|
+
reason: "family capacity budget"
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
/** 修: propose a revised draft for a challenged experience. */
|
|
1392
|
+
revise(request, actor) {
|
|
1393
|
+
const current = this.store.getActiveRevision(request.id);
|
|
1394
|
+
if (current === void 0) throw new Error(`memory: experience not found: ${request.id}`);
|
|
1395
|
+
if (current.status !== "challenged") throw new Error(`memory: only challenged experiences accept revisions (status=${current.status})`);
|
|
1396
|
+
const now = Date.now();
|
|
1397
|
+
const draft = {
|
|
1398
|
+
...current,
|
|
1399
|
+
revision: current.revision + 1,
|
|
1400
|
+
status: "candidate",
|
|
1401
|
+
gist: request.gist ?? current.gist,
|
|
1402
|
+
situation: request.situation ?? current.situation,
|
|
1403
|
+
path: request.path ?? current.path,
|
|
1404
|
+
reasoning: request.reasoning ?? current.reasoning,
|
|
1405
|
+
limits: request.limits ?? current.limits,
|
|
1406
|
+
alpha: 0,
|
|
1407
|
+
beta: 0,
|
|
1408
|
+
samples: 0,
|
|
1409
|
+
trust: .5,
|
|
1410
|
+
weightedTrust: .5,
|
|
1411
|
+
verifiedCount: 0,
|
|
1412
|
+
rejectCount: 0,
|
|
1413
|
+
parentRevision: current.revision,
|
|
1414
|
+
createdAt: now,
|
|
1415
|
+
updatedAt: now
|
|
1416
|
+
};
|
|
1417
|
+
delete draft.lastVerifiedAt;
|
|
1418
|
+
delete draft.challengeReason;
|
|
1419
|
+
this.store.upsertExperience(draft);
|
|
1420
|
+
this.ledger("propose", "experience", draft.id, actor, { revision: draft.revision }, request.reason);
|
|
1421
|
+
return draft;
|
|
1422
|
+
}
|
|
1423
|
+
/** V1 controlled re-enactment: replay historical samples against a draft. */
|
|
1424
|
+
verifyShadow(request, actor) {
|
|
1425
|
+
const draft = this.store.getExperience(request.id, request.revision);
|
|
1426
|
+
if (draft === void 0) throw new Error(`memory: draft not found: ${request.id} v${String(request.revision)}`);
|
|
1427
|
+
if (draft.status !== "candidate") throw new Error(`memory: shadow replay verifies candidate drafts (status=${draft.status})`);
|
|
1428
|
+
if (request.samples.length === 0) return {
|
|
1429
|
+
passed: false,
|
|
1430
|
+
agreement: 0,
|
|
1431
|
+
reason: "no samples supplied"
|
|
1432
|
+
};
|
|
1433
|
+
const draftTokens = new Set(tokenize([
|
|
1434
|
+
draft.gist,
|
|
1435
|
+
...draft.situation,
|
|
1436
|
+
...draft.limits
|
|
1437
|
+
].join(" ")));
|
|
1438
|
+
let matched = 0;
|
|
1439
|
+
for (const sample of request.samples) {
|
|
1440
|
+
const sampleTokens = new Set(tokenize(sample.situation));
|
|
1441
|
+
let hits = 0;
|
|
1442
|
+
for (const token of sampleTokens) if (draftTokens.has(token)) hits += 1;
|
|
1443
|
+
if ((sampleTokens.size === 0 ? 0 : hits / sampleTokens.size) >= this.config.recallFloorScore * 2 === (sample.expected === "success")) matched += 1;
|
|
1444
|
+
}
|
|
1445
|
+
const agreement = matched / request.samples.length;
|
|
1446
|
+
if (agreement < this.config.shadowPassRate) {
|
|
1447
|
+
this.ledger("shadow-fail", "experience", draft.id, actor, {
|
|
1448
|
+
revision: draft.revision,
|
|
1449
|
+
agreement
|
|
1450
|
+
});
|
|
1451
|
+
return {
|
|
1452
|
+
passed: false,
|
|
1453
|
+
agreement,
|
|
1454
|
+
reason: `agreement ${agreement.toFixed(2)} below pass rate ${String(this.config.shadowPassRate)}`
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
const adopted = {
|
|
1458
|
+
...draft,
|
|
1459
|
+
status: "live",
|
|
1460
|
+
lastVerifiedAt: Date.now(),
|
|
1461
|
+
updatedAt: Date.now()
|
|
1462
|
+
};
|
|
1463
|
+
this.store.upsertExperience(adopted);
|
|
1464
|
+
this.supersedeParent(adopted, actor);
|
|
1465
|
+
this.ledger("shadow-pass", "experience", adopted.id, actor, {
|
|
1466
|
+
revision: adopted.revision,
|
|
1467
|
+
agreement
|
|
1468
|
+
});
|
|
1469
|
+
return {
|
|
1470
|
+
passed: true,
|
|
1471
|
+
agreement,
|
|
1472
|
+
snapshot: adopted
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
/** Roll the family back to a superseded revision. */
|
|
1476
|
+
rollback(request, actor) {
|
|
1477
|
+
const target = this.store.getExperience(request.id, request.toRevision);
|
|
1478
|
+
if (target === void 0) throw new Error(`memory: revision not found: ${request.id} v${String(request.toRevision)}`);
|
|
1479
|
+
if (target.status !== "superseded") throw new Error(`memory: only superseded revisions can be restored (status=${target.status})`);
|
|
1480
|
+
const current = this.store.getActiveRevision(request.id);
|
|
1481
|
+
if (current !== void 0) {
|
|
1482
|
+
this.store.upsertExperience({
|
|
1483
|
+
...current,
|
|
1484
|
+
status: "superseded",
|
|
1485
|
+
updatedAt: Date.now()
|
|
1486
|
+
});
|
|
1487
|
+
this.ledger("supersede", "experience", current.id, actor, {
|
|
1488
|
+
revision: current.revision,
|
|
1489
|
+
by: request.toRevision
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
const restored = {
|
|
1493
|
+
...target,
|
|
1494
|
+
status: "live",
|
|
1495
|
+
updatedAt: Date.now()
|
|
1496
|
+
};
|
|
1497
|
+
this.store.upsertExperience(restored);
|
|
1498
|
+
this.ledger("rollback", "experience", restored.id, actor, { revision: restored.revision }, request.reason);
|
|
1499
|
+
return restored;
|
|
1500
|
+
}
|
|
1501
|
+
/** One experience revision. */
|
|
1502
|
+
get(id, revision) {
|
|
1503
|
+
return revision === void 0 ? this.store.getActiveRevision(id) : this.store.getExperience(id, revision);
|
|
1504
|
+
}
|
|
1505
|
+
/** Experience revisions by filter. */
|
|
1506
|
+
list(filter) {
|
|
1507
|
+
return this.store.listExperiences(filter);
|
|
1508
|
+
}
|
|
1509
|
+
/** Every revision of one family (the rollback picker's data). */
|
|
1510
|
+
family(id) {
|
|
1511
|
+
return this.store.getFamily(id);
|
|
1512
|
+
}
|
|
1513
|
+
/** 记: append one diary entry; signals when the extraction cadence is due. */
|
|
1514
|
+
appendDiary(request, actor) {
|
|
1515
|
+
const entry = {
|
|
1516
|
+
id: randomUUID(),
|
|
1517
|
+
ts: Date.now(),
|
|
1518
|
+
kind: request.kind,
|
|
1519
|
+
content: request.content,
|
|
1520
|
+
tags: request.tags ?? [],
|
|
1521
|
+
extracted: false
|
|
1522
|
+
};
|
|
1523
|
+
if (request.sessionRef !== void 0) entry.sessionRef = request.sessionRef;
|
|
1524
|
+
this.store.insertDiary(entry);
|
|
1525
|
+
this.ledger("diary", "diary", entry.id, actor, { kind: entry.kind });
|
|
1526
|
+
const pending = this.store.unextractedDiary();
|
|
1527
|
+
const intervalOk = Date.now() - this.store.lastExtractionTs() >= this.config.diaryExtractIntervalHours * 60 * 60 * 1e3;
|
|
1528
|
+
if (!(pending.length >= this.config.diaryExtractEvery && intervalOk)) return {
|
|
1529
|
+
entry,
|
|
1530
|
+
extractionDue: false
|
|
1531
|
+
};
|
|
1532
|
+
return {
|
|
1533
|
+
entry,
|
|
1534
|
+
extractionDue: true,
|
|
1535
|
+
pendingDiary: pending
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
/** 上升通道: apply extracted facts over the pending diary window. */
|
|
1539
|
+
extract(request, actor, trigger = "manual") {
|
|
1540
|
+
const result = {
|
|
1541
|
+
applied: [],
|
|
1542
|
+
conflicts: [],
|
|
1543
|
+
rejected: [],
|
|
1544
|
+
appliedConcerns: 0
|
|
1545
|
+
};
|
|
1546
|
+
const now = Date.now();
|
|
1547
|
+
const consumedDiary = /* @__PURE__ */ new Set();
|
|
1548
|
+
for (const proposal of request.proposals) {
|
|
1549
|
+
const rejection = this.validateProposal(proposal);
|
|
1550
|
+
if (rejection !== void 0) {
|
|
1551
|
+
result.rejected.push({
|
|
1552
|
+
proposal,
|
|
1553
|
+
reason: rejection
|
|
1554
|
+
});
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
for (const id of proposal.sourceDiaryIds) consumedDiary.add(id);
|
|
1558
|
+
const current = this.store.currentFact(proposal.category, proposal.factKey);
|
|
1559
|
+
if (current !== void 0 && current.value === proposal.value) {
|
|
1560
|
+
const merged = [...new Set([...current.sourceDiaryIds, ...proposal.sourceDiaryIds])];
|
|
1561
|
+
const corroborated = {
|
|
1562
|
+
...current,
|
|
1563
|
+
corroboration: current.corroboration + 1,
|
|
1564
|
+
sourceDiaryIds: merged
|
|
1565
|
+
};
|
|
1566
|
+
this.store.updateFact(corroborated);
|
|
1567
|
+
this.ledger("fact-corroborate", "fact", corroborated.id, actor, {
|
|
1568
|
+
category: proposal.category,
|
|
1569
|
+
factKey: proposal.factKey
|
|
1570
|
+
});
|
|
1571
|
+
result.applied.push(corroborated);
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
if (current !== void 0 && current.locked) {
|
|
1575
|
+
const conflict = {
|
|
1576
|
+
id: randomUUID(),
|
|
1577
|
+
category: proposal.category,
|
|
1578
|
+
factKey: proposal.factKey,
|
|
1579
|
+
value: proposal.value,
|
|
1580
|
+
origin: "extraction",
|
|
1581
|
+
sourceDiaryIds: [...proposal.sourceDiaryIds],
|
|
1582
|
+
corroboration: 1,
|
|
1583
|
+
validFrom: now,
|
|
1584
|
+
validTo: now,
|
|
1585
|
+
recordedAt: now,
|
|
1586
|
+
locked: false,
|
|
1587
|
+
deleted: false,
|
|
1588
|
+
conflictPending: true
|
|
1589
|
+
};
|
|
1590
|
+
this.store.insertFact(conflict);
|
|
1591
|
+
this.ledger("fact-conflict", "fact", conflict.id, actor, {
|
|
1592
|
+
category: proposal.category,
|
|
1593
|
+
factKey: proposal.factKey,
|
|
1594
|
+
against: current.id
|
|
1595
|
+
});
|
|
1596
|
+
result.conflicts.push(conflict);
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
const version = {
|
|
1600
|
+
id: randomUUID(),
|
|
1601
|
+
category: proposal.category,
|
|
1602
|
+
factKey: proposal.factKey,
|
|
1603
|
+
value: proposal.value,
|
|
1604
|
+
origin: current === void 0 ? "extraction" : "supersede",
|
|
1605
|
+
sourceDiaryIds: [...proposal.sourceDiaryIds],
|
|
1606
|
+
corroboration: 1,
|
|
1607
|
+
validFrom: now,
|
|
1608
|
+
recordedAt: now,
|
|
1609
|
+
locked: false,
|
|
1610
|
+
deleted: false,
|
|
1611
|
+
conflictPending: false
|
|
1612
|
+
};
|
|
1613
|
+
if (current !== void 0) {
|
|
1614
|
+
this.store.updateFact({
|
|
1615
|
+
...current,
|
|
1616
|
+
validTo: now,
|
|
1617
|
+
supersededBy: version.id
|
|
1618
|
+
});
|
|
1619
|
+
this.ledger("fact-supersede", "fact", current.id, actor, {
|
|
1620
|
+
category: proposal.category,
|
|
1621
|
+
factKey: proposal.factKey,
|
|
1622
|
+
by: version.id
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
this.store.insertFact(version);
|
|
1626
|
+
this.ledger("fact-extract", "fact", version.id, actor, {
|
|
1627
|
+
category: proposal.category,
|
|
1628
|
+
factKey: proposal.factKey
|
|
1629
|
+
});
|
|
1630
|
+
result.applied.push(version);
|
|
1631
|
+
}
|
|
1632
|
+
for (const cp of request.concerns ?? []) {
|
|
1633
|
+
for (const id of cp.sourceDiaryIds) consumedDiary.add(id);
|
|
1634
|
+
if (cp.action === "new") {
|
|
1635
|
+
if ((cp.title ?? "").trim() === "") continue;
|
|
1636
|
+
const top = {
|
|
1637
|
+
id: randomUUID(),
|
|
1638
|
+
title: (cp.title ?? "").trim(),
|
|
1639
|
+
...cp.background === void 0 || cp.background.trim() === "" ? {} : { background: cp.background.trim() },
|
|
1640
|
+
kind: cp.kind ?? "other",
|
|
1641
|
+
status: "ongoing",
|
|
1642
|
+
ts: now,
|
|
1643
|
+
sourceDiaryIds: [...cp.sourceDiaryIds],
|
|
1644
|
+
...cp.context === void 0 ? {} : { context: cp.context },
|
|
1645
|
+
deleted: false
|
|
1646
|
+
};
|
|
1647
|
+
this.store.insertConcern(top);
|
|
1648
|
+
this.ledger("concern-new", "concern", top.id, actor, { kind: top.kind }, top.title);
|
|
1649
|
+
result.appliedConcerns += 1;
|
|
1650
|
+
} else if (cp.action === "mention") {
|
|
1651
|
+
if (cp.concernId === void 0 || (cp.title ?? "").trim() === "") continue;
|
|
1652
|
+
const mention = {
|
|
1653
|
+
id: randomUUID(),
|
|
1654
|
+
parentId: cp.concernId,
|
|
1655
|
+
title: (cp.title ?? "").trim(),
|
|
1656
|
+
ts: now,
|
|
1657
|
+
sourceDiaryIds: [...cp.sourceDiaryIds],
|
|
1658
|
+
deleted: false
|
|
1659
|
+
};
|
|
1660
|
+
this.store.insertConcern(mention);
|
|
1661
|
+
this.ledger("concern-mention", "concern", cp.concernId, actor, {}, mention.title);
|
|
1662
|
+
result.appliedConcerns += 1;
|
|
1663
|
+
} else if (cp.action === "status") {
|
|
1664
|
+
if (cp.concernId === void 0 || cp.status === void 0) continue;
|
|
1665
|
+
this.store.setConcernStatus(cp.concernId, cp.status);
|
|
1666
|
+
this.ledger("concern-status", "concern", cp.concernId, actor, { status: cp.status });
|
|
1667
|
+
result.appliedConcerns += 1;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
if (consumedDiary.size > 0) this.store.markDiaryExtracted([...consumedDiary]);
|
|
1671
|
+
const producedIds = [...result.applied, ...result.conflicts].map((fact) => fact.id);
|
|
1672
|
+
this.store.insertExtraction({
|
|
1673
|
+
id: randomUUID(),
|
|
1674
|
+
ts: now,
|
|
1675
|
+
trigger,
|
|
1676
|
+
summary: request.summary,
|
|
1677
|
+
producedFactIds: producedIds,
|
|
1678
|
+
diaryCount: consumedDiary.size
|
|
1679
|
+
});
|
|
1680
|
+
this.ledger("extract", "library", "diary-window", actor, {
|
|
1681
|
+
proposals: request.proposals.length,
|
|
1682
|
+
applied: result.applied.length,
|
|
1683
|
+
conflicts: result.conflicts.length,
|
|
1684
|
+
rejected: result.rejected.length
|
|
1685
|
+
}, request.summary);
|
|
1686
|
+
return result;
|
|
1687
|
+
}
|
|
1688
|
+
/** Validate one proposal's shape and source pointers. */
|
|
1689
|
+
validateProposal(proposal) {
|
|
1690
|
+
if (proposal.category.trim() === "" || proposal.factKey.trim() === "" || proposal.value.trim() === "") return "category, factKey and value are required";
|
|
1691
|
+
if (proposal.sourceDiaryIds.length === 0) return "sourceDiaryIds must point to at least one diary entry";
|
|
1692
|
+
for (const id of proposal.sourceDiaryIds) {
|
|
1693
|
+
const entry = this.store.getDiary(id);
|
|
1694
|
+
if (entry === void 0) return `unknown diary entry: ${id}`;
|
|
1695
|
+
if (entry.extracted) return `diary entry already extracted: ${id}`;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
/** Diary entries for the workbench timeline. */
|
|
1699
|
+
listDiary(limit, offset, onlyUnextracted) {
|
|
1700
|
+
return this.store.listDiary(limit, offset, onlyUnextracted);
|
|
1701
|
+
}
|
|
1702
|
+
/** Several diary entries by id (008 Path A: fact→diary provenance). */
|
|
1703
|
+
getDiaryByIds(ids) {
|
|
1704
|
+
return this.store.getDiaryByIds(ids);
|
|
1705
|
+
}
|
|
1706
|
+
/** Fact versions for the workbench (008 §3: server-side pagination). */
|
|
1707
|
+
listFacts(category, includeHistory, limit, offset) {
|
|
1708
|
+
return this.store.listFacts(category === void 0 ? { includeHistory } : {
|
|
1709
|
+
category,
|
|
1710
|
+
includeHistory
|
|
1711
|
+
}, limit, offset);
|
|
1712
|
+
}
|
|
1713
|
+
/** Count of facts matching the workbench filter (008 §3: pagination total). */
|
|
1714
|
+
listFactsCount(category, includeHistory) {
|
|
1715
|
+
return this.store.factFilteredCount(category === void 0 ? { includeHistory } : {
|
|
1716
|
+
category,
|
|
1717
|
+
includeHistory
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
/** 关心事项 (007 §2 / 010 §D): top-level concerns + loop, kind/status filter + pagination. */
|
|
1721
|
+
listConcerns(kind, status, limit, offset) {
|
|
1722
|
+
const filter = {};
|
|
1723
|
+
if (kind !== void 0) filter.kind = kind;
|
|
1724
|
+
if (status !== void 0) filter.status = status;
|
|
1725
|
+
return this.store.listConcernTrees(filter, limit, offset);
|
|
1726
|
+
}
|
|
1727
|
+
/** Count of top-level concerns matching the workbench filter (010 §D: pagination total). */
|
|
1728
|
+
listConcernsCount(kind, status) {
|
|
1729
|
+
const filter = {};
|
|
1730
|
+
if (kind !== void 0) filter.kind = kind;
|
|
1731
|
+
if (status !== void 0) filter.status = status;
|
|
1732
|
+
return this.store.listConcernsCount(filter);
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* 010 §F: a compact runtime snapshot for the AI's context — the profile (the
|
|
1736
|
+
* AI's perception of the user) plus still-open concern memos it may remind
|
|
1737
|
+
* the user about. Empty string when there is nothing yet.
|
|
1738
|
+
*/
|
|
1739
|
+
profileSnapshot() {
|
|
1740
|
+
const facts = this.store.listFacts({ includeHistory: false }, 60, 0);
|
|
1741
|
+
const openConcerns = this.store.listConcernTrees({ status: "ongoing" }, 12, 0);
|
|
1742
|
+
if (facts.length === 0 && openConcerns.length === 0) return "";
|
|
1743
|
+
const lines = [];
|
|
1744
|
+
if (facts.length > 0) {
|
|
1745
|
+
lines.push("User profile (your perception of this user — collaborate accordingly):");
|
|
1746
|
+
for (const f of facts) lines.push(`- [${f.category}] ${f.factKey}: ${f.value}`);
|
|
1747
|
+
}
|
|
1748
|
+
if (openConcerns.length > 0) {
|
|
1749
|
+
lines.push("Open concern memos (the user's unclosed loops — remind when relevant, never fabricate closure):");
|
|
1750
|
+
for (const t of openConcerns) lines.push(`- (${t.concern.kind ?? "other"}) ${t.concern.title}`);
|
|
1751
|
+
}
|
|
1752
|
+
return lines.join("\n");
|
|
1753
|
+
}
|
|
1754
|
+
/** Extraction runs, newest first (008 §3: server-side pagination). */
|
|
1755
|
+
extractionLog(limit, offset) {
|
|
1756
|
+
return this.store.listExtractions(limit, offset);
|
|
1757
|
+
}
|
|
1758
|
+
/** Total extraction runs (008 §3: pagination total). */
|
|
1759
|
+
extractionLogCount() {
|
|
1760
|
+
return this.store.extractionCount();
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Whether a consolidation run is due. The period is measured as a DURATION
|
|
1764
|
+
* since the last consolidation (interval), not a fixed clock time: a run is
|
|
1765
|
+
* due once (a) enough NEW experiences have accumulated since the last run
|
|
1766
|
+
* AND (b) enough hours have elapsed since it.
|
|
1767
|
+
*/
|
|
1768
|
+
consolidationDue() {
|
|
1769
|
+
const lastTs = this.store.lastConsolidationTs();
|
|
1770
|
+
const newSince = this.store.countExperiencesSince(lastTs);
|
|
1771
|
+
const hoursSince = lastTs === 0 ? Number.POSITIVE_INFINITY : (Date.now() - lastTs) / 36e5;
|
|
1772
|
+
const enoughNew = newSince >= this.config.consolidateEveryNew;
|
|
1773
|
+
const intervalPassed = hoursSince >= this.config.consolidateIntervalHours;
|
|
1774
|
+
return {
|
|
1775
|
+
due: enoughNew && intervalPassed,
|
|
1776
|
+
lastTs,
|
|
1777
|
+
newSince,
|
|
1778
|
+
hoursSince
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Apply a consolidation run: for each merge, create one consolidated
|
|
1783
|
+
* experience and archive the sources (they leave recall but stay recoverable
|
|
1784
|
+
* — consolidation never hard-deletes). Every step is ledgered.
|
|
1785
|
+
*/
|
|
1786
|
+
consolidate(request, actor) {
|
|
1787
|
+
const now = Date.now();
|
|
1788
|
+
const result = {
|
|
1789
|
+
consolidated: 0,
|
|
1790
|
+
archived: 0,
|
|
1791
|
+
skipped: []
|
|
1792
|
+
};
|
|
1793
|
+
for (const merge of request.merges) {
|
|
1794
|
+
const sources = merge.sourceIds.map((id) => this.store.getActiveRevision(id)).filter((s) => s !== void 0);
|
|
1795
|
+
if (sources.length < 2) {
|
|
1796
|
+
result.skipped.push({
|
|
1797
|
+
sourceIds: merge.sourceIds,
|
|
1798
|
+
reason: "至少需要 2 条仍有效的来源经验"
|
|
1799
|
+
});
|
|
1800
|
+
continue;
|
|
1801
|
+
}
|
|
1802
|
+
if (merge.gist.trim() === "" || merge.reasoning.trim() === "") {
|
|
1803
|
+
result.skipped.push({
|
|
1804
|
+
sourceIds: merge.sourceIds,
|
|
1805
|
+
reason: "合并后的 gist/reasoning 不能为空"
|
|
1806
|
+
});
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
const allLive = sources.every((s) => s.status === "live");
|
|
1810
|
+
const alpha = sources.reduce((sum, s) => sum + s.alpha, 0);
|
|
1811
|
+
const beta = sources.reduce((sum, s) => sum + s.beta, 0);
|
|
1812
|
+
const verifiedCount = sources.reduce((sum, s) => sum + (s.verifiedCount ?? 0), 0);
|
|
1813
|
+
const rejectCount = sources.reduce((sum, s) => sum + (s.rejectCount ?? 0), 0);
|
|
1814
|
+
const lastVerifiedAt = sources.reduce((max, s) => s.lastVerifiedAt !== void 0 && (max === void 0 || s.lastVerifiedAt > max) ? s.lastVerifiedAt : max, void 0);
|
|
1815
|
+
const consolidated = {
|
|
1816
|
+
id: randomUUID(),
|
|
1817
|
+
revision: 1,
|
|
1818
|
+
kind: merge.kind,
|
|
1819
|
+
source: "agent",
|
|
1820
|
+
family: merge.family,
|
|
1821
|
+
gist: merge.gist.trim(),
|
|
1822
|
+
situation: merge.situation,
|
|
1823
|
+
path: merge.path.map((action, index) => ({
|
|
1824
|
+
action,
|
|
1825
|
+
order: index + 1
|
|
1826
|
+
})),
|
|
1827
|
+
reasoning: merge.reasoning.trim(),
|
|
1828
|
+
limits: merge.limits,
|
|
1829
|
+
status: allLive ? "live" : "candidate",
|
|
1830
|
+
alpha,
|
|
1831
|
+
beta,
|
|
1832
|
+
samples: alpha + beta,
|
|
1833
|
+
trust: (alpha + 1) / (alpha + beta + 2),
|
|
1834
|
+
weightedTrust: (alpha + 1) / (alpha + beta + 2),
|
|
1835
|
+
...lastVerifiedAt === void 0 ? {} : { lastVerifiedAt },
|
|
1836
|
+
pinned: false,
|
|
1837
|
+
tokensSaved: 0,
|
|
1838
|
+
tokensSpent: 0,
|
|
1839
|
+
evidence: { note: `consolidated from ${String(sources.length)} experiences: ${sources.map((s) => s.id.slice(0, 8)).join(",")}` },
|
|
1840
|
+
context: "",
|
|
1841
|
+
verifiedCount,
|
|
1842
|
+
rejectCount,
|
|
1843
|
+
globalFlag: false,
|
|
1844
|
+
createdAt: now,
|
|
1845
|
+
updatedAt: now
|
|
1846
|
+
};
|
|
1847
|
+
this.store.upsertExperience(consolidated);
|
|
1848
|
+
this.store.archiveExperienceIds(merge.sourceIds, now);
|
|
1849
|
+
this.store.recordConsolidation({
|
|
1850
|
+
id: randomUUID(),
|
|
1851
|
+
ts: now,
|
|
1852
|
+
mergedIds: merge.sourceIds,
|
|
1853
|
+
producedId: consolidated.id,
|
|
1854
|
+
note: merge.note ?? request.note ?? ""
|
|
1855
|
+
});
|
|
1856
|
+
this.ledger("consolidate", "experience", consolidated.id, actor, {
|
|
1857
|
+
mergedFrom: merge.sourceIds,
|
|
1858
|
+
gist: merge.gist.trim(),
|
|
1859
|
+
status: consolidated.status
|
|
1860
|
+
});
|
|
1861
|
+
result.consolidated += 1;
|
|
1862
|
+
result.archived += sources.length;
|
|
1863
|
+
}
|
|
1864
|
+
return result;
|
|
1865
|
+
}
|
|
1866
|
+
/** Human pin/unpin: pinned cards keep the trust floor and escape budgets. */
|
|
1867
|
+
humanPin(request, actor) {
|
|
1868
|
+
const current = this.store.getActiveRevision(request.id);
|
|
1869
|
+
if (current === void 0) throw new Error(`memory: experience not found: ${request.id}`);
|
|
1870
|
+
const next = {
|
|
1871
|
+
...current,
|
|
1872
|
+
pinned: request.pinned,
|
|
1873
|
+
updatedAt: Date.now()
|
|
1874
|
+
};
|
|
1875
|
+
this.store.upsertExperience(next);
|
|
1876
|
+
this.ledger(request.pinned ? "pin" : "unpin", "experience", next.id, actor, { revision: next.revision }, request.reason);
|
|
1877
|
+
return next;
|
|
1878
|
+
}
|
|
1879
|
+
/** Human delete: tombstone the family; the ledger keeps the fingerprint. */
|
|
1880
|
+
humanDeleteExperience(request, actor) {
|
|
1881
|
+
const familyRows = this.store.getFamily(request.id);
|
|
1882
|
+
if (familyRows.length === 0) throw new Error(`memory: experience not found: ${request.id}`);
|
|
1883
|
+
const head = familyRows[familyRows.length - 1];
|
|
1884
|
+
this.store.deleteFamily(request.id);
|
|
1885
|
+
this.ledger("delete", "experience", request.id, actor, {
|
|
1886
|
+
gist: head.gist,
|
|
1887
|
+
family: head.family,
|
|
1888
|
+
revisions: familyRows.length
|
|
1889
|
+
}, request.reason);
|
|
1890
|
+
}
|
|
1891
|
+
/** Human edit: rewrite fields of the active revision in place. */
|
|
1892
|
+
humanEditExperience(request, actor) {
|
|
1893
|
+
const current = this.store.getActiveRevision(request.id);
|
|
1894
|
+
if (current === void 0) throw new Error(`memory: experience not found: ${request.id}`);
|
|
1895
|
+
const next = {
|
|
1896
|
+
...current,
|
|
1897
|
+
gist: request.gist ?? current.gist,
|
|
1898
|
+
situation: request.situation ?? current.situation,
|
|
1899
|
+
path: request.path ?? current.path,
|
|
1900
|
+
reasoning: request.reasoning ?? current.reasoning,
|
|
1901
|
+
limits: request.limits ?? current.limits,
|
|
1902
|
+
family: request.family ?? current.family,
|
|
1903
|
+
context: request.context ?? current.context,
|
|
1904
|
+
globalFlag: request.globalFlag ?? current.globalFlag,
|
|
1905
|
+
updatedAt: Date.now()
|
|
1906
|
+
};
|
|
1907
|
+
this.store.upsertExperience(next);
|
|
1908
|
+
this.ledger("edit", "experience", next.id, actor, { revision: next.revision }, request.reason);
|
|
1909
|
+
return next;
|
|
1910
|
+
}
|
|
1911
|
+
/** Human injection: fixed format, source=human, trust floor, directly live. */
|
|
1912
|
+
humanAddExperience(request, actor) {
|
|
1913
|
+
const now = Date.now();
|
|
1914
|
+
const snapshot = {
|
|
1915
|
+
id: randomUUID(),
|
|
1916
|
+
revision: 1,
|
|
1917
|
+
kind: request.kind,
|
|
1918
|
+
source: "human",
|
|
1919
|
+
family: request.family,
|
|
1920
|
+
gist: request.gist,
|
|
1921
|
+
situation: [...request.situation],
|
|
1922
|
+
path: [...request.path],
|
|
1923
|
+
reasoning: request.reasoning,
|
|
1924
|
+
limits: [...request.limits],
|
|
1925
|
+
status: "live",
|
|
1926
|
+
alpha: this.config.humanFloorAlpha,
|
|
1927
|
+
beta: this.config.humanFloorBeta,
|
|
1928
|
+
samples: this.config.humanFloorAlpha + this.config.humanFloorBeta,
|
|
1929
|
+
trust: (this.config.humanFloorAlpha + 1) / (this.config.humanFloorAlpha + this.config.humanFloorBeta + 2),
|
|
1930
|
+
weightedTrust: (this.config.humanFloorAlpha + 1) / (this.config.humanFloorAlpha + this.config.humanFloorBeta + 2),
|
|
1931
|
+
lastVerifiedAt: now,
|
|
1932
|
+
pinned: false,
|
|
1933
|
+
tokensSaved: 0,
|
|
1934
|
+
tokensSpent: 0,
|
|
1935
|
+
context: request.context ?? "",
|
|
1936
|
+
verifiedCount: 0,
|
|
1937
|
+
rejectCount: 0,
|
|
1938
|
+
globalFlag: false,
|
|
1939
|
+
createdAt: now,
|
|
1940
|
+
updatedAt: now
|
|
1941
|
+
};
|
|
1942
|
+
if (request.failureReason !== void 0) snapshot.failureReason = request.failureReason;
|
|
1943
|
+
this.store.upsertExperience(snapshot);
|
|
1944
|
+
this.ledger("add", "experience", snapshot.id, actor, {
|
|
1945
|
+
kind: snapshot.kind,
|
|
1946
|
+
family: snapshot.family,
|
|
1947
|
+
trustFloor: snapshot.trust
|
|
1948
|
+
}, request.reason);
|
|
1949
|
+
return snapshot;
|
|
1950
|
+
}
|
|
1951
|
+
/** Human authority (V2): promote a candidate straight to live. */
|
|
1952
|
+
humanPromote(id, reason, actor) {
|
|
1953
|
+
const current = this.store.getActiveRevision(id);
|
|
1954
|
+
if (current === void 0) throw new Error(`memory: experience not found: ${id}`);
|
|
1955
|
+
if (current.status !== "candidate") throw new Error(`memory: only candidates accept human promotion (status=${current.status})`);
|
|
1956
|
+
const next = {
|
|
1957
|
+
...current,
|
|
1958
|
+
status: "live",
|
|
1959
|
+
lastVerifiedAt: Date.now(),
|
|
1960
|
+
updatedAt: Date.now()
|
|
1961
|
+
};
|
|
1962
|
+
this.store.upsertExperience(next);
|
|
1963
|
+
if (next.parentRevision !== void 0) this.supersedeParent(next, actor);
|
|
1964
|
+
this.ledger("human-promote", "experience", next.id, actor, { revision: next.revision }, reason);
|
|
1965
|
+
return next;
|
|
1966
|
+
}
|
|
1967
|
+
/** Human re-release (006 §3.2): move a cold-palace revision back to candidate. */
|
|
1968
|
+
humanReleaseCold(request, actor) {
|
|
1969
|
+
const cold = [...this.store.getFamily(request.id)].reverse().find((rev) => rev.status === "cold");
|
|
1970
|
+
if (cold === void 0) throw new Error(`memory: no cold revision to re-release for ${request.id}`);
|
|
1971
|
+
const next = {
|
|
1972
|
+
...cold,
|
|
1973
|
+
status: "candidate",
|
|
1974
|
+
updatedAt: Date.now()
|
|
1975
|
+
};
|
|
1976
|
+
delete next.challengeReason;
|
|
1977
|
+
this.store.upsertExperience(next);
|
|
1978
|
+
this.ledger("release-cold", "experience", next.id, actor, { revision: next.revision }, request.reason);
|
|
1979
|
+
return next;
|
|
1980
|
+
}
|
|
1981
|
+
/** Human acknowledgement of a pending diary entry: reviewed, no fact extracted. */
|
|
1982
|
+
humanAckDiary(request, actor) {
|
|
1983
|
+
const entry = this.store.getDiary(request.diaryId);
|
|
1984
|
+
if (entry === void 0) throw new Error(`memory: unknown diary entry: ${request.diaryId}`);
|
|
1985
|
+
if (entry.extracted) throw new Error(`memory: diary entry already extracted: ${request.diaryId}`);
|
|
1986
|
+
this.store.markDiaryExtracted([request.diaryId]);
|
|
1987
|
+
this.ledger("diary-ack", "diary", request.diaryId, actor, {}, request.reason);
|
|
1988
|
+
return {
|
|
1989
|
+
...entry,
|
|
1990
|
+
extracted: true
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
/** Human lifecycle change of a top-level concern (007 §2.4, audited). */
|
|
1994
|
+
humanSetConcernStatus(request, actor) {
|
|
1995
|
+
this.store.setConcernStatus(request.id, request.status);
|
|
1996
|
+
this.ledger("concern-status", "concern", request.id, actor, {
|
|
1997
|
+
status: request.status,
|
|
1998
|
+
via: "human"
|
|
1999
|
+
}, request.reason);
|
|
2000
|
+
}
|
|
2001
|
+
/** Human delete of a concern subtree (007 §2.4, tombstone, audited). */
|
|
2002
|
+
humanDeleteConcern(request, actor) {
|
|
2003
|
+
this.store.deleteConcernSubtree(request.id);
|
|
2004
|
+
this.ledger("concern-delete", "concern", request.id, actor, { via: "human" }, request.reason);
|
|
2005
|
+
}
|
|
2006
|
+
/** Human fact add: origin=human, locked by default. */
|
|
2007
|
+
humanAddFact(request, actor) {
|
|
2008
|
+
const now = Date.now();
|
|
2009
|
+
const current = this.store.currentFact(request.category, request.factKey);
|
|
2010
|
+
const version = {
|
|
2011
|
+
id: randomUUID(),
|
|
2012
|
+
category: request.category,
|
|
2013
|
+
factKey: request.factKey,
|
|
2014
|
+
value: request.value,
|
|
2015
|
+
origin: "human",
|
|
2016
|
+
sourceDiaryIds: [],
|
|
2017
|
+
corroboration: 1,
|
|
2018
|
+
validFrom: now,
|
|
2019
|
+
recordedAt: now,
|
|
2020
|
+
locked: request.locked ?? true,
|
|
2021
|
+
deleted: false,
|
|
2022
|
+
conflictPending: false
|
|
2023
|
+
};
|
|
2024
|
+
if (current !== void 0) {
|
|
2025
|
+
this.store.updateFact({
|
|
2026
|
+
...current,
|
|
2027
|
+
validTo: now,
|
|
2028
|
+
supersededBy: version.id
|
|
2029
|
+
});
|
|
2030
|
+
this.ledger("fact-supersede", "fact", current.id, actor, { by: version.id }, request.reason);
|
|
2031
|
+
}
|
|
2032
|
+
this.store.insertFact(version);
|
|
2033
|
+
this.ledger("fact-add", "fact", version.id, actor, {
|
|
2034
|
+
category: request.category,
|
|
2035
|
+
factKey: request.factKey
|
|
2036
|
+
}, request.reason);
|
|
2037
|
+
return version;
|
|
2038
|
+
}
|
|
2039
|
+
/** Human fact edit: supersedes the current version with a locked one. */
|
|
2040
|
+
humanEditFact(request, actor) {
|
|
2041
|
+
const current = this.store.getFact(request.factId);
|
|
2042
|
+
if (current === void 0) throw new Error(`memory: fact not found: ${request.factId}`);
|
|
2043
|
+
if (current.validTo !== void 0 || current.deleted) throw new Error("memory: only the current fact version can be edited");
|
|
2044
|
+
const now = Date.now();
|
|
2045
|
+
const version = {
|
|
2046
|
+
...current,
|
|
2047
|
+
id: randomUUID(),
|
|
2048
|
+
value: request.value,
|
|
2049
|
+
origin: "human",
|
|
2050
|
+
validFrom: now,
|
|
2051
|
+
recordedAt: now,
|
|
2052
|
+
locked: true,
|
|
2053
|
+
conflictPending: false,
|
|
2054
|
+
corroboration: current.corroboration
|
|
2055
|
+
};
|
|
2056
|
+
delete version.validTo;
|
|
2057
|
+
delete version.supersededBy;
|
|
2058
|
+
this.store.updateFact({
|
|
2059
|
+
...current,
|
|
2060
|
+
validTo: now,
|
|
2061
|
+
supersededBy: version.id
|
|
2062
|
+
});
|
|
2063
|
+
this.store.insertFact(version);
|
|
2064
|
+
this.ledger("fact-edit", "fact", version.id, actor, {
|
|
2065
|
+
category: version.category,
|
|
2066
|
+
factKey: version.factKey,
|
|
2067
|
+
previous: current.id
|
|
2068
|
+
}, request.reason);
|
|
2069
|
+
return version;
|
|
2070
|
+
}
|
|
2071
|
+
/** Human fact delete: tombstone the current version. */
|
|
2072
|
+
humanDeleteFact(request, actor) {
|
|
2073
|
+
const current = this.store.getFact(request.factId);
|
|
2074
|
+
if (current === void 0) throw new Error(`memory: fact not found: ${request.factId}`);
|
|
2075
|
+
if (current.validTo !== void 0 || current.deleted) throw new Error("memory: only the current fact version can be deleted");
|
|
2076
|
+
this.store.updateFact({
|
|
2077
|
+
...current,
|
|
2078
|
+
validTo: Date.now(),
|
|
2079
|
+
deleted: true
|
|
2080
|
+
});
|
|
2081
|
+
this.ledger("fact-delete", "fact", current.id, actor, {
|
|
2082
|
+
category: current.category,
|
|
2083
|
+
factKey: current.factKey,
|
|
2084
|
+
value: current.value
|
|
2085
|
+
}, request.reason);
|
|
2086
|
+
}
|
|
2087
|
+
/** Human fact confirmation: lock or unlock the current version. */
|
|
2088
|
+
humanConfirmFact(request, actor) {
|
|
2089
|
+
const current = this.store.getFact(request.factId);
|
|
2090
|
+
if (current === void 0) throw new Error(`memory: fact not found: ${request.factId}`);
|
|
2091
|
+
const next = {
|
|
2092
|
+
...current,
|
|
2093
|
+
locked: request.locked,
|
|
2094
|
+
conflictPending: false
|
|
2095
|
+
};
|
|
2096
|
+
this.store.updateFact(next);
|
|
2097
|
+
this.ledger(request.locked ? "fact-lock" : "fact-unlock", "fact", next.id, actor, {
|
|
2098
|
+
category: next.category,
|
|
2099
|
+
factKey: next.factKey
|
|
2100
|
+
}, request.reason);
|
|
2101
|
+
return next;
|
|
2102
|
+
}
|
|
2103
|
+
/** Human rollback (same gate, human actor). */
|
|
2104
|
+
humanRollback(request, actor) {
|
|
2105
|
+
return this.rollback(request, actor);
|
|
2106
|
+
}
|
|
2107
|
+
/** Aggregated library statistics. */
|
|
2108
|
+
stats() {
|
|
2109
|
+
const all = this.store.listExperiences({});
|
|
2110
|
+
const byStatus = {
|
|
2111
|
+
candidate: 0,
|
|
2112
|
+
live: 0,
|
|
2113
|
+
challenged: 0,
|
|
2114
|
+
superseded: 0,
|
|
2115
|
+
archived: 0,
|
|
2116
|
+
cold: 0
|
|
2117
|
+
};
|
|
2118
|
+
const byKind = {
|
|
2119
|
+
positive: 0,
|
|
2120
|
+
negative: 0
|
|
2121
|
+
};
|
|
2122
|
+
let trustSum = 0;
|
|
2123
|
+
let pinned = 0;
|
|
2124
|
+
for (const exp of all) {
|
|
2125
|
+
byStatus[exp.status] += 1;
|
|
2126
|
+
byKind[exp.kind] += 1;
|
|
2127
|
+
trustSum += exp.trust;
|
|
2128
|
+
if (exp.pinned) pinned += 1;
|
|
2129
|
+
}
|
|
2130
|
+
const diary = this.store.diaryCounts();
|
|
2131
|
+
const recall = this.store.recallCounts();
|
|
2132
|
+
const reports = this.store.allReports().filter((r) => r.counted !== "none").length;
|
|
2133
|
+
return {
|
|
2134
|
+
experiences: {
|
|
2135
|
+
total: all.length,
|
|
2136
|
+
byStatus,
|
|
2137
|
+
byKind,
|
|
2138
|
+
pinned,
|
|
2139
|
+
avgTrust: all.length === 0 ? 0 : trustSum / all.length
|
|
2140
|
+
},
|
|
2141
|
+
facts: this.store.factCounts(),
|
|
2142
|
+
diary: {
|
|
2143
|
+
total: diary.total,
|
|
2144
|
+
unextracted: diary.unextracted,
|
|
2145
|
+
extractions: this.store.extractionCount()
|
|
2146
|
+
},
|
|
2147
|
+
ledgerBlocks: this.store.ledgerCount(),
|
|
2148
|
+
recall: {
|
|
2149
|
+
events: recall.events,
|
|
2150
|
+
negative: recall.negative,
|
|
2151
|
+
reportsAfterRecall: reports
|
|
2152
|
+
},
|
|
2153
|
+
consolidation: {
|
|
2154
|
+
lastTs: this.store.lastConsolidationTs(),
|
|
2155
|
+
due: this.consolidationDue().due,
|
|
2156
|
+
newSince: this.store.countExperiencesSince(this.store.lastConsolidationTs())
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
/** Verify the ledger hash chain end to end. */
|
|
2161
|
+
verifyLedger() {
|
|
2162
|
+
const blocks = this.store.ledgerAll();
|
|
2163
|
+
let prev = "";
|
|
2164
|
+
for (const block of blocks) {
|
|
2165
|
+
const expected = blockHash(block.ts, block.op, block.objectType, block.objectId, block.actor, block.payload, prev);
|
|
2166
|
+
if (block.prevHash !== prev || block.hash !== expected) return {
|
|
2167
|
+
ok: false,
|
|
2168
|
+
checked: blocks.length,
|
|
2169
|
+
brokenAt: block.seq
|
|
2170
|
+
};
|
|
2171
|
+
prev = block.hash;
|
|
2172
|
+
}
|
|
2173
|
+
return {
|
|
2174
|
+
ok: true,
|
|
2175
|
+
checked: blocks.length
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
/** Ledger query for the workbench and the model tool. */
|
|
2179
|
+
ledgerQuery(request) {
|
|
2180
|
+
return this.store.ledgerQuery({
|
|
2181
|
+
...request.objectType === void 0 ? {} : { objectType: request.objectType },
|
|
2182
|
+
...request.objectId === void 0 ? {} : { objectId: request.objectId },
|
|
2183
|
+
...request.op === void 0 ? {} : { op: request.op },
|
|
2184
|
+
...request.offset === void 0 ? {} : { offset: request.offset },
|
|
2185
|
+
...request.seqFrom === void 0 ? {} : { seqFrom: request.seqFrom },
|
|
2186
|
+
...request.seqTo === void 0 ? {} : { seqTo: request.seqTo },
|
|
2187
|
+
limit: request.limit ?? 50
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
/** Count ledger blocks matching a filter (007 §2 pagination). */
|
|
2191
|
+
ledgerQueryCount(request) {
|
|
2192
|
+
return this.store.ledgerQueryCount({
|
|
2193
|
+
...request.objectType === void 0 ? {} : { objectType: request.objectType },
|
|
2194
|
+
...request.objectId === void 0 ? {} : { objectId: request.objectId },
|
|
2195
|
+
...request.op === void 0 ? {} : { op: request.op },
|
|
2196
|
+
...request.seqFrom === void 0 ? {} : { seqFrom: request.seqFrom },
|
|
2197
|
+
...request.seqTo === void 0 ? {} : { seqTo: request.seqTo }
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
/** Full library export (experiments + migration). */
|
|
2201
|
+
exportLibrary() {
|
|
2202
|
+
const diaryEntries = this.store.listDiary(1e6, 0, false);
|
|
2203
|
+
return {
|
|
2204
|
+
exportedAt: Date.now(),
|
|
2205
|
+
schemaVersion: 1,
|
|
2206
|
+
experiences: this.store.listExperiences({}),
|
|
2207
|
+
reports: this.store.allReports().map((report) => ({
|
|
2208
|
+
id: report.id,
|
|
2209
|
+
experienceId: report.experienceId,
|
|
2210
|
+
revision: report.revision,
|
|
2211
|
+
outcome: report.outcome,
|
|
2212
|
+
attribution: report.attribution,
|
|
2213
|
+
counted: report.counted,
|
|
2214
|
+
...report.evidence === void 0 ? {} : { evidence: report.evidence },
|
|
2215
|
+
...report.dedupeKey === void 0 ? {} : { dedupeKey: report.dedupeKey },
|
|
2216
|
+
ts: report.ts
|
|
2217
|
+
})),
|
|
2218
|
+
diary: diaryEntries,
|
|
2219
|
+
facts: this.store.allFacts(),
|
|
2220
|
+
extractions: this.store.listExtractions(1e6, 0),
|
|
2221
|
+
recalls: this.store.allRecallEvents().map((event) => ({
|
|
2222
|
+
id: event.id,
|
|
2223
|
+
ts: event.ts,
|
|
2224
|
+
situation: event.situation,
|
|
2225
|
+
injectedIds: event.injectedIds,
|
|
2226
|
+
none: event.none
|
|
2227
|
+
})),
|
|
2228
|
+
ledger: this.store.ledgerAll()
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
//#endregion
|
|
2233
|
+
//#region lib/types/service.js
|
|
2234
|
+
/**
|
|
2235
|
+
* Memory library Typert Remote service: the wire face over MemoryCore.
|
|
2236
|
+
* Every method takes the calling Agent first (Typert wire identity); the
|
|
2237
|
+
* library itself is process-global — one memory shared by every session.
|
|
2238
|
+
* Human operations carry an audited reason and land in the ledger.
|
|
2239
|
+
* @module dsh-daoing-memory/service
|
|
2240
|
+
*/
|
|
2241
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
2242
|
+
var useValue = arguments.length > 2;
|
|
2243
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
2244
|
+
return useValue ? value : void 0;
|
|
2245
|
+
};
|
|
2246
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2247
|
+
function accept(f) {
|
|
2248
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
2249
|
+
return f;
|
|
2250
|
+
}
|
|
2251
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
2252
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
2253
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
2254
|
+
var _, done = false;
|
|
2255
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
2256
|
+
var context = {};
|
|
2257
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
2258
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
2259
|
+
context.addInitializer = function(f) {
|
|
2260
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
2261
|
+
extraInitializers.push(accept(f || null));
|
|
2262
|
+
};
|
|
2263
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
2264
|
+
get: descriptor.get,
|
|
2265
|
+
set: descriptor.set
|
|
2266
|
+
} : descriptor[key], context);
|
|
2267
|
+
if (kind === "accessor") {
|
|
2268
|
+
if (result === void 0) continue;
|
|
2269
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
2270
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
2271
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
2272
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
2273
|
+
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
2274
|
+
else descriptor[key] = _;
|
|
2275
|
+
}
|
|
2276
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
2277
|
+
done = true;
|
|
2278
|
+
};
|
|
2279
|
+
/** Derive the ledger actor label from the wire identity. */
|
|
2280
|
+
function actorOf(agent) {
|
|
2281
|
+
const sessionId = agent.session?.id;
|
|
2282
|
+
return sessionId === void 0 ? "agent" : `agent:${sessionId}`;
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Remote face of the memory library. All methods delegate to the core; the
|
|
2286
|
+
* core carries the 生·用·修·记 mechanism semantics.
|
|
2287
|
+
*/
|
|
2288
|
+
let MemoryService = (() => {
|
|
2289
|
+
let _classSuper = TypertRemoteService;
|
|
2290
|
+
let _instanceExtraInitializers = [];
|
|
2291
|
+
let _refine_decorators;
|
|
2292
|
+
let _recall_decorators;
|
|
2293
|
+
let _report_decorators;
|
|
2294
|
+
let _ingest_decorators;
|
|
2295
|
+
let _revise_decorators;
|
|
2296
|
+
let _verifyShadow_decorators;
|
|
2297
|
+
let _rollback_decorators;
|
|
2298
|
+
let _get_decorators;
|
|
2299
|
+
let _list_decorators;
|
|
2300
|
+
let _family_decorators;
|
|
2301
|
+
let _appendDiary_decorators;
|
|
2302
|
+
let _extract_decorators;
|
|
2303
|
+
let _listDiary_decorators;
|
|
2304
|
+
let _getDiaryByIds_decorators;
|
|
2305
|
+
let _listFacts_decorators;
|
|
2306
|
+
let _listFactsCount_decorators;
|
|
2307
|
+
let _listConcerns_decorators;
|
|
2308
|
+
let _listConcernsCount_decorators;
|
|
2309
|
+
let _extractionLog_decorators;
|
|
2310
|
+
let _extractionLogCount_decorators;
|
|
2311
|
+
let _consolidate_decorators;
|
|
2312
|
+
let _consolidationDue_decorators;
|
|
2313
|
+
let _ledgerQuery_decorators;
|
|
2314
|
+
let _ledgerQueryCount_decorators;
|
|
2315
|
+
let _verifyLedger_decorators;
|
|
2316
|
+
let _stats_decorators;
|
|
2317
|
+
let _workbenchInfo_decorators;
|
|
2318
|
+
let _exportLibrary_decorators;
|
|
2319
|
+
let _humanPin_decorators;
|
|
2320
|
+
let _humanDeleteExperience_decorators;
|
|
2321
|
+
let _humanEditExperience_decorators;
|
|
2322
|
+
let _humanAddExperience_decorators;
|
|
2323
|
+
let _humanPromote_decorators;
|
|
2324
|
+
let _humanReleaseCold_decorators;
|
|
2325
|
+
let _humanRollback_decorators;
|
|
2326
|
+
let _humanAddFact_decorators;
|
|
2327
|
+
let _humanEditFact_decorators;
|
|
2328
|
+
let _humanDeleteFact_decorators;
|
|
2329
|
+
let _humanConfirmFact_decorators;
|
|
2330
|
+
let _humanAckDiary_decorators;
|
|
2331
|
+
let _humanSetConcernStatus_decorators;
|
|
2332
|
+
let _humanDeleteConcern_decorators;
|
|
2333
|
+
return class MemoryService extends _classSuper {
|
|
2334
|
+
static {
|
|
2335
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
2336
|
+
_refine_decorators = [Remote("refine")];
|
|
2337
|
+
_recall_decorators = [Remote("recall")];
|
|
2338
|
+
_report_decorators = [Remote("report")];
|
|
2339
|
+
_ingest_decorators = [Remote("ingest")];
|
|
2340
|
+
_revise_decorators = [Remote("revise")];
|
|
2341
|
+
_verifyShadow_decorators = [Remote("verifyShadow")];
|
|
2342
|
+
_rollback_decorators = [Remote("rollback")];
|
|
2343
|
+
_get_decorators = [Remote("get")];
|
|
2344
|
+
_list_decorators = [Remote("list")];
|
|
2345
|
+
_family_decorators = [Remote("family")];
|
|
2346
|
+
_appendDiary_decorators = [Remote("appendDiary")];
|
|
2347
|
+
_extract_decorators = [Remote("extract")];
|
|
2348
|
+
_listDiary_decorators = [Remote("listDiary")];
|
|
2349
|
+
_getDiaryByIds_decorators = [Remote("getDiaryByIds")];
|
|
2350
|
+
_listFacts_decorators = [Remote("listFacts")];
|
|
2351
|
+
_listFactsCount_decorators = [Remote("listFactsCount")];
|
|
2352
|
+
_listConcerns_decorators = [Remote("listConcerns")];
|
|
2353
|
+
_listConcernsCount_decorators = [Remote("listConcernsCount")];
|
|
2354
|
+
_extractionLog_decorators = [Remote("extractionLog")];
|
|
2355
|
+
_extractionLogCount_decorators = [Remote("extractionLogCount")];
|
|
2356
|
+
_consolidate_decorators = [Remote("consolidate")];
|
|
2357
|
+
_consolidationDue_decorators = [Remote("consolidationDue")];
|
|
2358
|
+
_ledgerQuery_decorators = [Remote("ledgerQuery")];
|
|
2359
|
+
_ledgerQueryCount_decorators = [Remote("ledgerQueryCount")];
|
|
2360
|
+
_verifyLedger_decorators = [Remote("verifyLedger")];
|
|
2361
|
+
_stats_decorators = [Remote("stats")];
|
|
2362
|
+
_workbenchInfo_decorators = [Remote("workbenchInfo")];
|
|
2363
|
+
_exportLibrary_decorators = [Remote("exportLibrary")];
|
|
2364
|
+
_humanPin_decorators = [Remote("humanPin")];
|
|
2365
|
+
_humanDeleteExperience_decorators = [Remote("humanDeleteExperience")];
|
|
2366
|
+
_humanEditExperience_decorators = [Remote("humanEditExperience")];
|
|
2367
|
+
_humanAddExperience_decorators = [Remote("humanAddExperience")];
|
|
2368
|
+
_humanPromote_decorators = [Remote("humanPromote")];
|
|
2369
|
+
_humanReleaseCold_decorators = [Remote("humanReleaseCold")];
|
|
2370
|
+
_humanRollback_decorators = [Remote("humanRollback")];
|
|
2371
|
+
_humanAddFact_decorators = [Remote("humanAddFact")];
|
|
2372
|
+
_humanEditFact_decorators = [Remote("humanEditFact")];
|
|
2373
|
+
_humanDeleteFact_decorators = [Remote("humanDeleteFact")];
|
|
2374
|
+
_humanConfirmFact_decorators = [Remote("humanConfirmFact")];
|
|
2375
|
+
_humanAckDiary_decorators = [Remote("humanAckDiary")];
|
|
2376
|
+
_humanSetConcernStatus_decorators = [Remote("humanSetConcernStatus")];
|
|
2377
|
+
_humanDeleteConcern_decorators = [Remote("humanDeleteConcern")];
|
|
2378
|
+
__esDecorate(this, null, _refine_decorators, {
|
|
2379
|
+
kind: "method",
|
|
2380
|
+
name: "refine",
|
|
2381
|
+
static: false,
|
|
2382
|
+
private: false,
|
|
2383
|
+
access: {
|
|
2384
|
+
has: (obj) => "refine" in obj,
|
|
2385
|
+
get: (obj) => obj.refine
|
|
2386
|
+
},
|
|
2387
|
+
metadata: _metadata
|
|
2388
|
+
}, null, _instanceExtraInitializers);
|
|
2389
|
+
__esDecorate(this, null, _recall_decorators, {
|
|
2390
|
+
kind: "method",
|
|
2391
|
+
name: "recall",
|
|
2392
|
+
static: false,
|
|
2393
|
+
private: false,
|
|
2394
|
+
access: {
|
|
2395
|
+
has: (obj) => "recall" in obj,
|
|
2396
|
+
get: (obj) => obj.recall
|
|
2397
|
+
},
|
|
2398
|
+
metadata: _metadata
|
|
2399
|
+
}, null, _instanceExtraInitializers);
|
|
2400
|
+
__esDecorate(this, null, _report_decorators, {
|
|
2401
|
+
kind: "method",
|
|
2402
|
+
name: "report",
|
|
2403
|
+
static: false,
|
|
2404
|
+
private: false,
|
|
2405
|
+
access: {
|
|
2406
|
+
has: (obj) => "report" in obj,
|
|
2407
|
+
get: (obj) => obj.report
|
|
2408
|
+
},
|
|
2409
|
+
metadata: _metadata
|
|
2410
|
+
}, null, _instanceExtraInitializers);
|
|
2411
|
+
__esDecorate(this, null, _ingest_decorators, {
|
|
2412
|
+
kind: "method",
|
|
2413
|
+
name: "ingest",
|
|
2414
|
+
static: false,
|
|
2415
|
+
private: false,
|
|
2416
|
+
access: {
|
|
2417
|
+
has: (obj) => "ingest" in obj,
|
|
2418
|
+
get: (obj) => obj.ingest
|
|
2419
|
+
},
|
|
2420
|
+
metadata: _metadata
|
|
2421
|
+
}, null, _instanceExtraInitializers);
|
|
2422
|
+
__esDecorate(this, null, _revise_decorators, {
|
|
2423
|
+
kind: "method",
|
|
2424
|
+
name: "revise",
|
|
2425
|
+
static: false,
|
|
2426
|
+
private: false,
|
|
2427
|
+
access: {
|
|
2428
|
+
has: (obj) => "revise" in obj,
|
|
2429
|
+
get: (obj) => obj.revise
|
|
2430
|
+
},
|
|
2431
|
+
metadata: _metadata
|
|
2432
|
+
}, null, _instanceExtraInitializers);
|
|
2433
|
+
__esDecorate(this, null, _verifyShadow_decorators, {
|
|
2434
|
+
kind: "method",
|
|
2435
|
+
name: "verifyShadow",
|
|
2436
|
+
static: false,
|
|
2437
|
+
private: false,
|
|
2438
|
+
access: {
|
|
2439
|
+
has: (obj) => "verifyShadow" in obj,
|
|
2440
|
+
get: (obj) => obj.verifyShadow
|
|
2441
|
+
},
|
|
2442
|
+
metadata: _metadata
|
|
2443
|
+
}, null, _instanceExtraInitializers);
|
|
2444
|
+
__esDecorate(this, null, _rollback_decorators, {
|
|
2445
|
+
kind: "method",
|
|
2446
|
+
name: "rollback",
|
|
2447
|
+
static: false,
|
|
2448
|
+
private: false,
|
|
2449
|
+
access: {
|
|
2450
|
+
has: (obj) => "rollback" in obj,
|
|
2451
|
+
get: (obj) => obj.rollback
|
|
2452
|
+
},
|
|
2453
|
+
metadata: _metadata
|
|
2454
|
+
}, null, _instanceExtraInitializers);
|
|
2455
|
+
__esDecorate(this, null, _get_decorators, {
|
|
2456
|
+
kind: "method",
|
|
2457
|
+
name: "get",
|
|
2458
|
+
static: false,
|
|
2459
|
+
private: false,
|
|
2460
|
+
access: {
|
|
2461
|
+
has: (obj) => "get" in obj,
|
|
2462
|
+
get: (obj) => obj.get
|
|
2463
|
+
},
|
|
2464
|
+
metadata: _metadata
|
|
2465
|
+
}, null, _instanceExtraInitializers);
|
|
2466
|
+
__esDecorate(this, null, _list_decorators, {
|
|
2467
|
+
kind: "method",
|
|
2468
|
+
name: "list",
|
|
2469
|
+
static: false,
|
|
2470
|
+
private: false,
|
|
2471
|
+
access: {
|
|
2472
|
+
has: (obj) => "list" in obj,
|
|
2473
|
+
get: (obj) => obj.list
|
|
2474
|
+
},
|
|
2475
|
+
metadata: _metadata
|
|
2476
|
+
}, null, _instanceExtraInitializers);
|
|
2477
|
+
__esDecorate(this, null, _family_decorators, {
|
|
2478
|
+
kind: "method",
|
|
2479
|
+
name: "family",
|
|
2480
|
+
static: false,
|
|
2481
|
+
private: false,
|
|
2482
|
+
access: {
|
|
2483
|
+
has: (obj) => "family" in obj,
|
|
2484
|
+
get: (obj) => obj.family
|
|
2485
|
+
},
|
|
2486
|
+
metadata: _metadata
|
|
2487
|
+
}, null, _instanceExtraInitializers);
|
|
2488
|
+
__esDecorate(this, null, _appendDiary_decorators, {
|
|
2489
|
+
kind: "method",
|
|
2490
|
+
name: "appendDiary",
|
|
2491
|
+
static: false,
|
|
2492
|
+
private: false,
|
|
2493
|
+
access: {
|
|
2494
|
+
has: (obj) => "appendDiary" in obj,
|
|
2495
|
+
get: (obj) => obj.appendDiary
|
|
2496
|
+
},
|
|
2497
|
+
metadata: _metadata
|
|
2498
|
+
}, null, _instanceExtraInitializers);
|
|
2499
|
+
__esDecorate(this, null, _extract_decorators, {
|
|
2500
|
+
kind: "method",
|
|
2501
|
+
name: "extract",
|
|
2502
|
+
static: false,
|
|
2503
|
+
private: false,
|
|
2504
|
+
access: {
|
|
2505
|
+
has: (obj) => "extract" in obj,
|
|
2506
|
+
get: (obj) => obj.extract
|
|
2507
|
+
},
|
|
2508
|
+
metadata: _metadata
|
|
2509
|
+
}, null, _instanceExtraInitializers);
|
|
2510
|
+
__esDecorate(this, null, _listDiary_decorators, {
|
|
2511
|
+
kind: "method",
|
|
2512
|
+
name: "listDiary",
|
|
2513
|
+
static: false,
|
|
2514
|
+
private: false,
|
|
2515
|
+
access: {
|
|
2516
|
+
has: (obj) => "listDiary" in obj,
|
|
2517
|
+
get: (obj) => obj.listDiary
|
|
2518
|
+
},
|
|
2519
|
+
metadata: _metadata
|
|
2520
|
+
}, null, _instanceExtraInitializers);
|
|
2521
|
+
__esDecorate(this, null, _getDiaryByIds_decorators, {
|
|
2522
|
+
kind: "method",
|
|
2523
|
+
name: "getDiaryByIds",
|
|
2524
|
+
static: false,
|
|
2525
|
+
private: false,
|
|
2526
|
+
access: {
|
|
2527
|
+
has: (obj) => "getDiaryByIds" in obj,
|
|
2528
|
+
get: (obj) => obj.getDiaryByIds
|
|
2529
|
+
},
|
|
2530
|
+
metadata: _metadata
|
|
2531
|
+
}, null, _instanceExtraInitializers);
|
|
2532
|
+
__esDecorate(this, null, _listFacts_decorators, {
|
|
2533
|
+
kind: "method",
|
|
2534
|
+
name: "listFacts",
|
|
2535
|
+
static: false,
|
|
2536
|
+
private: false,
|
|
2537
|
+
access: {
|
|
2538
|
+
has: (obj) => "listFacts" in obj,
|
|
2539
|
+
get: (obj) => obj.listFacts
|
|
2540
|
+
},
|
|
2541
|
+
metadata: _metadata
|
|
2542
|
+
}, null, _instanceExtraInitializers);
|
|
2543
|
+
__esDecorate(this, null, _listFactsCount_decorators, {
|
|
2544
|
+
kind: "method",
|
|
2545
|
+
name: "listFactsCount",
|
|
2546
|
+
static: false,
|
|
2547
|
+
private: false,
|
|
2548
|
+
access: {
|
|
2549
|
+
has: (obj) => "listFactsCount" in obj,
|
|
2550
|
+
get: (obj) => obj.listFactsCount
|
|
2551
|
+
},
|
|
2552
|
+
metadata: _metadata
|
|
2553
|
+
}, null, _instanceExtraInitializers);
|
|
2554
|
+
__esDecorate(this, null, _listConcerns_decorators, {
|
|
2555
|
+
kind: "method",
|
|
2556
|
+
name: "listConcerns",
|
|
2557
|
+
static: false,
|
|
2558
|
+
private: false,
|
|
2559
|
+
access: {
|
|
2560
|
+
has: (obj) => "listConcerns" in obj,
|
|
2561
|
+
get: (obj) => obj.listConcerns
|
|
2562
|
+
},
|
|
2563
|
+
metadata: _metadata
|
|
2564
|
+
}, null, _instanceExtraInitializers);
|
|
2565
|
+
__esDecorate(this, null, _listConcernsCount_decorators, {
|
|
2566
|
+
kind: "method",
|
|
2567
|
+
name: "listConcernsCount",
|
|
2568
|
+
static: false,
|
|
2569
|
+
private: false,
|
|
2570
|
+
access: {
|
|
2571
|
+
has: (obj) => "listConcernsCount" in obj,
|
|
2572
|
+
get: (obj) => obj.listConcernsCount
|
|
2573
|
+
},
|
|
2574
|
+
metadata: _metadata
|
|
2575
|
+
}, null, _instanceExtraInitializers);
|
|
2576
|
+
__esDecorate(this, null, _extractionLog_decorators, {
|
|
2577
|
+
kind: "method",
|
|
2578
|
+
name: "extractionLog",
|
|
2579
|
+
static: false,
|
|
2580
|
+
private: false,
|
|
2581
|
+
access: {
|
|
2582
|
+
has: (obj) => "extractionLog" in obj,
|
|
2583
|
+
get: (obj) => obj.extractionLog
|
|
2584
|
+
},
|
|
2585
|
+
metadata: _metadata
|
|
2586
|
+
}, null, _instanceExtraInitializers);
|
|
2587
|
+
__esDecorate(this, null, _extractionLogCount_decorators, {
|
|
2588
|
+
kind: "method",
|
|
2589
|
+
name: "extractionLogCount",
|
|
2590
|
+
static: false,
|
|
2591
|
+
private: false,
|
|
2592
|
+
access: {
|
|
2593
|
+
has: (obj) => "extractionLogCount" in obj,
|
|
2594
|
+
get: (obj) => obj.extractionLogCount
|
|
2595
|
+
},
|
|
2596
|
+
metadata: _metadata
|
|
2597
|
+
}, null, _instanceExtraInitializers);
|
|
2598
|
+
__esDecorate(this, null, _consolidate_decorators, {
|
|
2599
|
+
kind: "method",
|
|
2600
|
+
name: "consolidate",
|
|
2601
|
+
static: false,
|
|
2602
|
+
private: false,
|
|
2603
|
+
access: {
|
|
2604
|
+
has: (obj) => "consolidate" in obj,
|
|
2605
|
+
get: (obj) => obj.consolidate
|
|
2606
|
+
},
|
|
2607
|
+
metadata: _metadata
|
|
2608
|
+
}, null, _instanceExtraInitializers);
|
|
2609
|
+
__esDecorate(this, null, _consolidationDue_decorators, {
|
|
2610
|
+
kind: "method",
|
|
2611
|
+
name: "consolidationDue",
|
|
2612
|
+
static: false,
|
|
2613
|
+
private: false,
|
|
2614
|
+
access: {
|
|
2615
|
+
has: (obj) => "consolidationDue" in obj,
|
|
2616
|
+
get: (obj) => obj.consolidationDue
|
|
2617
|
+
},
|
|
2618
|
+
metadata: _metadata
|
|
2619
|
+
}, null, _instanceExtraInitializers);
|
|
2620
|
+
__esDecorate(this, null, _ledgerQuery_decorators, {
|
|
2621
|
+
kind: "method",
|
|
2622
|
+
name: "ledgerQuery",
|
|
2623
|
+
static: false,
|
|
2624
|
+
private: false,
|
|
2625
|
+
access: {
|
|
2626
|
+
has: (obj) => "ledgerQuery" in obj,
|
|
2627
|
+
get: (obj) => obj.ledgerQuery
|
|
2628
|
+
},
|
|
2629
|
+
metadata: _metadata
|
|
2630
|
+
}, null, _instanceExtraInitializers);
|
|
2631
|
+
__esDecorate(this, null, _ledgerQueryCount_decorators, {
|
|
2632
|
+
kind: "method",
|
|
2633
|
+
name: "ledgerQueryCount",
|
|
2634
|
+
static: false,
|
|
2635
|
+
private: false,
|
|
2636
|
+
access: {
|
|
2637
|
+
has: (obj) => "ledgerQueryCount" in obj,
|
|
2638
|
+
get: (obj) => obj.ledgerQueryCount
|
|
2639
|
+
},
|
|
2640
|
+
metadata: _metadata
|
|
2641
|
+
}, null, _instanceExtraInitializers);
|
|
2642
|
+
__esDecorate(this, null, _verifyLedger_decorators, {
|
|
2643
|
+
kind: "method",
|
|
2644
|
+
name: "verifyLedger",
|
|
2645
|
+
static: false,
|
|
2646
|
+
private: false,
|
|
2647
|
+
access: {
|
|
2648
|
+
has: (obj) => "verifyLedger" in obj,
|
|
2649
|
+
get: (obj) => obj.verifyLedger
|
|
2650
|
+
},
|
|
2651
|
+
metadata: _metadata
|
|
2652
|
+
}, null, _instanceExtraInitializers);
|
|
2653
|
+
__esDecorate(this, null, _stats_decorators, {
|
|
2654
|
+
kind: "method",
|
|
2655
|
+
name: "stats",
|
|
2656
|
+
static: false,
|
|
2657
|
+
private: false,
|
|
2658
|
+
access: {
|
|
2659
|
+
has: (obj) => "stats" in obj,
|
|
2660
|
+
get: (obj) => obj.stats
|
|
2661
|
+
},
|
|
2662
|
+
metadata: _metadata
|
|
2663
|
+
}, null, _instanceExtraInitializers);
|
|
2664
|
+
__esDecorate(this, null, _workbenchInfo_decorators, {
|
|
2665
|
+
kind: "method",
|
|
2666
|
+
name: "workbenchInfo",
|
|
2667
|
+
static: false,
|
|
2668
|
+
private: false,
|
|
2669
|
+
access: {
|
|
2670
|
+
has: (obj) => "workbenchInfo" in obj,
|
|
2671
|
+
get: (obj) => obj.workbenchInfo
|
|
2672
|
+
},
|
|
2673
|
+
metadata: _metadata
|
|
2674
|
+
}, null, _instanceExtraInitializers);
|
|
2675
|
+
__esDecorate(this, null, _exportLibrary_decorators, {
|
|
2676
|
+
kind: "method",
|
|
2677
|
+
name: "exportLibrary",
|
|
2678
|
+
static: false,
|
|
2679
|
+
private: false,
|
|
2680
|
+
access: {
|
|
2681
|
+
has: (obj) => "exportLibrary" in obj,
|
|
2682
|
+
get: (obj) => obj.exportLibrary
|
|
2683
|
+
},
|
|
2684
|
+
metadata: _metadata
|
|
2685
|
+
}, null, _instanceExtraInitializers);
|
|
2686
|
+
__esDecorate(this, null, _humanPin_decorators, {
|
|
2687
|
+
kind: "method",
|
|
2688
|
+
name: "humanPin",
|
|
2689
|
+
static: false,
|
|
2690
|
+
private: false,
|
|
2691
|
+
access: {
|
|
2692
|
+
has: (obj) => "humanPin" in obj,
|
|
2693
|
+
get: (obj) => obj.humanPin
|
|
2694
|
+
},
|
|
2695
|
+
metadata: _metadata
|
|
2696
|
+
}, null, _instanceExtraInitializers);
|
|
2697
|
+
__esDecorate(this, null, _humanDeleteExperience_decorators, {
|
|
2698
|
+
kind: "method",
|
|
2699
|
+
name: "humanDeleteExperience",
|
|
2700
|
+
static: false,
|
|
2701
|
+
private: false,
|
|
2702
|
+
access: {
|
|
2703
|
+
has: (obj) => "humanDeleteExperience" in obj,
|
|
2704
|
+
get: (obj) => obj.humanDeleteExperience
|
|
2705
|
+
},
|
|
2706
|
+
metadata: _metadata
|
|
2707
|
+
}, null, _instanceExtraInitializers);
|
|
2708
|
+
__esDecorate(this, null, _humanEditExperience_decorators, {
|
|
2709
|
+
kind: "method",
|
|
2710
|
+
name: "humanEditExperience",
|
|
2711
|
+
static: false,
|
|
2712
|
+
private: false,
|
|
2713
|
+
access: {
|
|
2714
|
+
has: (obj) => "humanEditExperience" in obj,
|
|
2715
|
+
get: (obj) => obj.humanEditExperience
|
|
2716
|
+
},
|
|
2717
|
+
metadata: _metadata
|
|
2718
|
+
}, null, _instanceExtraInitializers);
|
|
2719
|
+
__esDecorate(this, null, _humanAddExperience_decorators, {
|
|
2720
|
+
kind: "method",
|
|
2721
|
+
name: "humanAddExperience",
|
|
2722
|
+
static: false,
|
|
2723
|
+
private: false,
|
|
2724
|
+
access: {
|
|
2725
|
+
has: (obj) => "humanAddExperience" in obj,
|
|
2726
|
+
get: (obj) => obj.humanAddExperience
|
|
2727
|
+
},
|
|
2728
|
+
metadata: _metadata
|
|
2729
|
+
}, null, _instanceExtraInitializers);
|
|
2730
|
+
__esDecorate(this, null, _humanPromote_decorators, {
|
|
2731
|
+
kind: "method",
|
|
2732
|
+
name: "humanPromote",
|
|
2733
|
+
static: false,
|
|
2734
|
+
private: false,
|
|
2735
|
+
access: {
|
|
2736
|
+
has: (obj) => "humanPromote" in obj,
|
|
2737
|
+
get: (obj) => obj.humanPromote
|
|
2738
|
+
},
|
|
2739
|
+
metadata: _metadata
|
|
2740
|
+
}, null, _instanceExtraInitializers);
|
|
2741
|
+
__esDecorate(this, null, _humanReleaseCold_decorators, {
|
|
2742
|
+
kind: "method",
|
|
2743
|
+
name: "humanReleaseCold",
|
|
2744
|
+
static: false,
|
|
2745
|
+
private: false,
|
|
2746
|
+
access: {
|
|
2747
|
+
has: (obj) => "humanReleaseCold" in obj,
|
|
2748
|
+
get: (obj) => obj.humanReleaseCold
|
|
2749
|
+
},
|
|
2750
|
+
metadata: _metadata
|
|
2751
|
+
}, null, _instanceExtraInitializers);
|
|
2752
|
+
__esDecorate(this, null, _humanRollback_decorators, {
|
|
2753
|
+
kind: "method",
|
|
2754
|
+
name: "humanRollback",
|
|
2755
|
+
static: false,
|
|
2756
|
+
private: false,
|
|
2757
|
+
access: {
|
|
2758
|
+
has: (obj) => "humanRollback" in obj,
|
|
2759
|
+
get: (obj) => obj.humanRollback
|
|
2760
|
+
},
|
|
2761
|
+
metadata: _metadata
|
|
2762
|
+
}, null, _instanceExtraInitializers);
|
|
2763
|
+
__esDecorate(this, null, _humanAddFact_decorators, {
|
|
2764
|
+
kind: "method",
|
|
2765
|
+
name: "humanAddFact",
|
|
2766
|
+
static: false,
|
|
2767
|
+
private: false,
|
|
2768
|
+
access: {
|
|
2769
|
+
has: (obj) => "humanAddFact" in obj,
|
|
2770
|
+
get: (obj) => obj.humanAddFact
|
|
2771
|
+
},
|
|
2772
|
+
metadata: _metadata
|
|
2773
|
+
}, null, _instanceExtraInitializers);
|
|
2774
|
+
__esDecorate(this, null, _humanEditFact_decorators, {
|
|
2775
|
+
kind: "method",
|
|
2776
|
+
name: "humanEditFact",
|
|
2777
|
+
static: false,
|
|
2778
|
+
private: false,
|
|
2779
|
+
access: {
|
|
2780
|
+
has: (obj) => "humanEditFact" in obj,
|
|
2781
|
+
get: (obj) => obj.humanEditFact
|
|
2782
|
+
},
|
|
2783
|
+
metadata: _metadata
|
|
2784
|
+
}, null, _instanceExtraInitializers);
|
|
2785
|
+
__esDecorate(this, null, _humanDeleteFact_decorators, {
|
|
2786
|
+
kind: "method",
|
|
2787
|
+
name: "humanDeleteFact",
|
|
2788
|
+
static: false,
|
|
2789
|
+
private: false,
|
|
2790
|
+
access: {
|
|
2791
|
+
has: (obj) => "humanDeleteFact" in obj,
|
|
2792
|
+
get: (obj) => obj.humanDeleteFact
|
|
2793
|
+
},
|
|
2794
|
+
metadata: _metadata
|
|
2795
|
+
}, null, _instanceExtraInitializers);
|
|
2796
|
+
__esDecorate(this, null, _humanConfirmFact_decorators, {
|
|
2797
|
+
kind: "method",
|
|
2798
|
+
name: "humanConfirmFact",
|
|
2799
|
+
static: false,
|
|
2800
|
+
private: false,
|
|
2801
|
+
access: {
|
|
2802
|
+
has: (obj) => "humanConfirmFact" in obj,
|
|
2803
|
+
get: (obj) => obj.humanConfirmFact
|
|
2804
|
+
},
|
|
2805
|
+
metadata: _metadata
|
|
2806
|
+
}, null, _instanceExtraInitializers);
|
|
2807
|
+
__esDecorate(this, null, _humanAckDiary_decorators, {
|
|
2808
|
+
kind: "method",
|
|
2809
|
+
name: "humanAckDiary",
|
|
2810
|
+
static: false,
|
|
2811
|
+
private: false,
|
|
2812
|
+
access: {
|
|
2813
|
+
has: (obj) => "humanAckDiary" in obj,
|
|
2814
|
+
get: (obj) => obj.humanAckDiary
|
|
2815
|
+
},
|
|
2816
|
+
metadata: _metadata
|
|
2817
|
+
}, null, _instanceExtraInitializers);
|
|
2818
|
+
__esDecorate(this, null, _humanSetConcernStatus_decorators, {
|
|
2819
|
+
kind: "method",
|
|
2820
|
+
name: "humanSetConcernStatus",
|
|
2821
|
+
static: false,
|
|
2822
|
+
private: false,
|
|
2823
|
+
access: {
|
|
2824
|
+
has: (obj) => "humanSetConcernStatus" in obj,
|
|
2825
|
+
get: (obj) => obj.humanSetConcernStatus
|
|
2826
|
+
},
|
|
2827
|
+
metadata: _metadata
|
|
2828
|
+
}, null, _instanceExtraInitializers);
|
|
2829
|
+
__esDecorate(this, null, _humanDeleteConcern_decorators, {
|
|
2830
|
+
kind: "method",
|
|
2831
|
+
name: "humanDeleteConcern",
|
|
2832
|
+
static: false,
|
|
2833
|
+
private: false,
|
|
2834
|
+
access: {
|
|
2835
|
+
has: (obj) => "humanDeleteConcern" in obj,
|
|
2836
|
+
get: (obj) => obj.humanDeleteConcern
|
|
2837
|
+
},
|
|
2838
|
+
metadata: _metadata
|
|
2839
|
+
}, null, _instanceExtraInitializers);
|
|
2840
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
2841
|
+
enumerable: true,
|
|
2842
|
+
configurable: true,
|
|
2843
|
+
writable: true,
|
|
2844
|
+
value: _metadata
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
core = __runInitializers(this, _instanceExtraInitializers);
|
|
2848
|
+
workbench;
|
|
2849
|
+
/** @param ctx - host context. */
|
|
2850
|
+
/** @param core - the ctx-free memory core. */
|
|
2851
|
+
/** @param workbench - descriptor the browser half matches workspaces against. */
|
|
2852
|
+
constructor(ctx, core, workbench) {
|
|
2853
|
+
super(ctx, "memory");
|
|
2854
|
+
this.core = core;
|
|
2855
|
+
this.workbench = workbench;
|
|
2856
|
+
}
|
|
2857
|
+
/** 生: refine a completed trajectory into an experience candidate. */
|
|
2858
|
+
refine(agent, request) {
|
|
2859
|
+
return this.core.refine(request, actorOf(agent));
|
|
2860
|
+
}
|
|
2861
|
+
/** 用: recall + adjudication + injection budget + negative channel. */
|
|
2862
|
+
recall(agent, request) {
|
|
2863
|
+
return this.core.recall(request, actorOf(agent));
|
|
2864
|
+
}
|
|
2865
|
+
/** 用·验: report one use outcome with attribution (V0 verification). */
|
|
2866
|
+
report(agent, request) {
|
|
2867
|
+
return this.core.report(request, actorOf(agent));
|
|
2868
|
+
}
|
|
2869
|
+
/** 摄取归一: source-agnostic intake; drafts become earned candidates (006 §1). */
|
|
2870
|
+
ingest(agent, request) {
|
|
2871
|
+
return this.core.ingest(request, actorOf(agent));
|
|
2872
|
+
}
|
|
2873
|
+
/** 修: propose a revised draft for a challenged experience. */
|
|
2874
|
+
revise(agent, request) {
|
|
2875
|
+
return this.core.revise(request, actorOf(agent));
|
|
2876
|
+
}
|
|
2877
|
+
/** V1 controlled re-enactment: shadow replay verification for a draft. */
|
|
2878
|
+
verifyShadow(agent, request) {
|
|
2879
|
+
return this.core.verifyShadow(request, actorOf(agent));
|
|
2880
|
+
}
|
|
2881
|
+
/** Restore a superseded revision to live (rollback). */
|
|
2882
|
+
rollback(agent, request) {
|
|
2883
|
+
return this.core.rollback(request, actorOf(agent));
|
|
2884
|
+
}
|
|
2885
|
+
/** Read one experience revision (active when revision omitted). */
|
|
2886
|
+
get(agent, id, revision) {
|
|
2887
|
+
return this.core.get(id, revision);
|
|
2888
|
+
}
|
|
2889
|
+
/** List experience revisions by filter. */
|
|
2890
|
+
list(agent, filter) {
|
|
2891
|
+
return this.core.list(filter);
|
|
2892
|
+
}
|
|
2893
|
+
/** Every revision of one family (superseded index for rollback). */
|
|
2894
|
+
family(agent, id) {
|
|
2895
|
+
return this.core.family(id);
|
|
2896
|
+
}
|
|
2897
|
+
/** 记: append one diary entry; signals the extraction duty when due. */
|
|
2898
|
+
appendDiary(agent, request) {
|
|
2899
|
+
return this.core.appendDiary(request, actorOf(agent));
|
|
2900
|
+
}
|
|
2901
|
+
/** 上升通道: apply extracted facts over the pending diary window. */
|
|
2902
|
+
extract(agent, request) {
|
|
2903
|
+
return this.core.extract(request, actorOf(agent), "manual");
|
|
2904
|
+
}
|
|
2905
|
+
/** Diary timeline for the workbench (007 §2: server-side pagination, newest first). */
|
|
2906
|
+
listDiary(agent, limit, offset, onlyUnextracted) {
|
|
2907
|
+
return this.core.listDiary(limit, offset, onlyUnextracted);
|
|
2908
|
+
}
|
|
2909
|
+
/** Several diary entries by id (008 Path A: fact→diary provenance). */
|
|
2910
|
+
getDiaryByIds(agent, ids) {
|
|
2911
|
+
return this.core.getDiaryByIds(ids);
|
|
2912
|
+
}
|
|
2913
|
+
/** Fact versions for the workbench (008 §3: server-side pagination). */
|
|
2914
|
+
listFacts(agent, category, includeHistory, limit, offset) {
|
|
2915
|
+
return this.core.listFacts(category === "" ? void 0 : category, includeHistory, limit, offset);
|
|
2916
|
+
}
|
|
2917
|
+
/** Count facts matching the workbench filter (008 §3: pagination total). */
|
|
2918
|
+
listFactsCount(agent, category, includeHistory) {
|
|
2919
|
+
return this.core.listFactsCount(category === "" ? void 0 : category, includeHistory);
|
|
2920
|
+
}
|
|
2921
|
+
/** 关心事项 trees (top-level + discussion loop) for the workbench (010 §D: filter + pagination). */
|
|
2922
|
+
listConcerns(agent, kind, status, limit, offset) {
|
|
2923
|
+
return this.core.listConcerns(kind === "" ? void 0 : kind, status === "" ? void 0 : status, limit, offset);
|
|
2924
|
+
}
|
|
2925
|
+
/** Count of top-level concerns matching the workbench filter (010 §D: pagination total). */
|
|
2926
|
+
listConcernsCount(agent, kind, status) {
|
|
2927
|
+
return this.core.listConcernsCount(kind === "" ? void 0 : kind, status === "" ? void 0 : status);
|
|
2928
|
+
}
|
|
2929
|
+
/** 010 §F: compact profile + open-concern snapshot for the AI's context (host-side). */
|
|
2930
|
+
profileSnapshot() {
|
|
2931
|
+
return this.core.profileSnapshot();
|
|
2932
|
+
}
|
|
2933
|
+
/** Extraction runs for the workbench (008 §3: server-side pagination). */
|
|
2934
|
+
extractionLog(agent, limit, offset) {
|
|
2935
|
+
return this.core.extractionLog(limit, offset);
|
|
2936
|
+
}
|
|
2937
|
+
/** Total extraction runs (008 §3: pagination total). */
|
|
2938
|
+
extractionLogCount(agent) {
|
|
2939
|
+
return this.core.extractionLogCount();
|
|
2940
|
+
}
|
|
2941
|
+
/** Apply a consolidation run: merge related experiences (008 §1). */
|
|
2942
|
+
consolidate(agent, request) {
|
|
2943
|
+
return this.core.consolidate(request, "agent");
|
|
2944
|
+
}
|
|
2945
|
+
/** Whether a consolidation run is due (interval cadence; 008 §1). */
|
|
2946
|
+
consolidationDue(agent) {
|
|
2947
|
+
return this.core.consolidationDue();
|
|
2948
|
+
}
|
|
2949
|
+
/** Ledger query (newest first, filtered). */
|
|
2950
|
+
ledgerQuery(agent, request) {
|
|
2951
|
+
return this.core.ledgerQuery(request);
|
|
2952
|
+
}
|
|
2953
|
+
/** Count ledger blocks matching a filter (007 §2 pagination). */
|
|
2954
|
+
ledgerQueryCount(agent, request) {
|
|
2955
|
+
return this.core.ledgerQueryCount(request);
|
|
2956
|
+
}
|
|
2957
|
+
/** Verify the ledger hash chain end to end. */
|
|
2958
|
+
verifyLedger(agent) {
|
|
2959
|
+
return this.core.verifyLedger();
|
|
2960
|
+
}
|
|
2961
|
+
/** Aggregated library statistics. */
|
|
2962
|
+
stats(agent) {
|
|
2963
|
+
return this.core.stats();
|
|
2964
|
+
}
|
|
2965
|
+
/** Workbench descriptor (workspace matching + config view). */
|
|
2966
|
+
workbenchInfo(agent) {
|
|
2967
|
+
return this.workbench();
|
|
2968
|
+
}
|
|
2969
|
+
/** Full library export for experiments and migration. */
|
|
2970
|
+
exportLibrary(agent) {
|
|
2971
|
+
return this.core.exportLibrary();
|
|
2972
|
+
}
|
|
2973
|
+
/**
|
|
2974
|
+
* Host-only ledger integrity check (no wire identity needed): the package
|
|
2975
|
+
* invariant companion asserts the hash chain through this accessor.
|
|
2976
|
+
* @returns the chain verification outcome.
|
|
2977
|
+
*/
|
|
2978
|
+
verifyLedgerIntegrity() {
|
|
2979
|
+
return this.core.verifyLedger();
|
|
2980
|
+
}
|
|
2981
|
+
/** Human pin/unpin. */
|
|
2982
|
+
humanPin(agent, request) {
|
|
2983
|
+
return this.core.humanPin(request, "human");
|
|
2984
|
+
}
|
|
2985
|
+
/** Human delete (tombstone + ledger fingerprint). */
|
|
2986
|
+
humanDeleteExperience(agent, request) {
|
|
2987
|
+
this.core.humanDeleteExperience(request, "human");
|
|
2988
|
+
return { deleted: true };
|
|
2989
|
+
}
|
|
2990
|
+
/** Human edit of the active revision. */
|
|
2991
|
+
humanEditExperience(agent, request) {
|
|
2992
|
+
return this.core.humanEditExperience(request, "human");
|
|
2993
|
+
}
|
|
2994
|
+
/** Human injection in the fixed experience format. */
|
|
2995
|
+
humanAddExperience(agent, request) {
|
|
2996
|
+
return this.core.humanAddExperience(request, "human");
|
|
2997
|
+
}
|
|
2998
|
+
/** Human authority (V2): promote a candidate straight to live. */
|
|
2999
|
+
humanPromote(agent, id, reason) {
|
|
3000
|
+
return this.core.humanPromote(id, reason, "human");
|
|
3001
|
+
}
|
|
3002
|
+
/** Human re-release of a cold-palace revision back to candidate (006 §3.2). */
|
|
3003
|
+
humanReleaseCold(agent, request) {
|
|
3004
|
+
return this.core.humanReleaseCold(request, "human");
|
|
3005
|
+
}
|
|
3006
|
+
/** Human rollback. */
|
|
3007
|
+
humanRollback(agent, request) {
|
|
3008
|
+
return this.core.humanRollback(request, "human");
|
|
3009
|
+
}
|
|
3010
|
+
/** Human fact add. */
|
|
3011
|
+
humanAddFact(agent, request) {
|
|
3012
|
+
return this.core.humanAddFact(request, "human");
|
|
3013
|
+
}
|
|
3014
|
+
/** Human fact edit. */
|
|
3015
|
+
humanEditFact(agent, request) {
|
|
3016
|
+
return this.core.humanEditFact(request, "human");
|
|
3017
|
+
}
|
|
3018
|
+
/** Human fact delete (tombstone). */
|
|
3019
|
+
humanDeleteFact(agent, request) {
|
|
3020
|
+
this.core.humanDeleteFact(request, "human");
|
|
3021
|
+
return { deleted: true };
|
|
3022
|
+
}
|
|
3023
|
+
/** Human fact confirmation (lock/unlock). */
|
|
3024
|
+
humanConfirmFact(agent, request) {
|
|
3025
|
+
return this.core.humanConfirmFact(request, "human");
|
|
3026
|
+
}
|
|
3027
|
+
/** Human acknowledgement of a pending diary entry (reviewed, no fact extracted). */
|
|
3028
|
+
humanAckDiary(agent, request) {
|
|
3029
|
+
return this.core.humanAckDiary(request, "human");
|
|
3030
|
+
}
|
|
3031
|
+
/** Human lifecycle change of a top-level concern (007 §2.4). */
|
|
3032
|
+
humanSetConcernStatus(agent, request) {
|
|
3033
|
+
this.core.humanSetConcernStatus(request, "human");
|
|
3034
|
+
return { ok: true };
|
|
3035
|
+
}
|
|
3036
|
+
/** Human delete of a concern subtree (007 §2.4). */
|
|
3037
|
+
humanDeleteConcern(agent, request) {
|
|
3038
|
+
this.core.humanDeleteConcern(request, "human");
|
|
3039
|
+
return { deleted: true };
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
})();
|
|
3043
|
+
//#endregion
|
|
3044
|
+
//#region lib/types/index.js
|
|
3045
|
+
/**
|
|
3046
|
+
* Memory library host plugin: the self-evolving memory layer (生·用·修·记 +
|
|
3047
|
+
* diary/fact semantic memory). One process-global library shared by every
|
|
3048
|
+
* session, backed by a local SQLite file. Registers `ctx.memory`
|
|
3049
|
+
* (MemoryService). The monitoring UI is an independent left-sidebar nav group
|
|
3050
|
+
* (browser half), not a workspace; this host half performs no workspace
|
|
3051
|
+
* adoption.
|
|
3052
|
+
* @module dsh-daoing-memory
|
|
3053
|
+
*/
|
|
3054
|
+
/** Resolve the plugin config with explicit defaults; unknown keys fail loud. */
|
|
3055
|
+
function resolveConfig(config) {
|
|
3056
|
+
const home = process.env.DSH_HOME ?? join(process.cwd(), ".dsh");
|
|
3057
|
+
const known = [
|
|
3058
|
+
"databasePath",
|
|
3059
|
+
"workspacePath",
|
|
3060
|
+
"workspaceTitle",
|
|
3061
|
+
"diaryExtractEvery",
|
|
3062
|
+
"diaryExtractIntervalHours",
|
|
3063
|
+
"recallTopK",
|
|
3064
|
+
"injectionBudgetTokens",
|
|
3065
|
+
"challengeConsecutiveFails",
|
|
3066
|
+
"challengeWindow",
|
|
3067
|
+
"challengeWindowFailRate",
|
|
3068
|
+
"familyLiveCap",
|
|
3069
|
+
"complexityTokenGate",
|
|
3070
|
+
"complexityStepGate",
|
|
3071
|
+
"duplicateOverlapGate",
|
|
3072
|
+
"recallFloorScore",
|
|
3073
|
+
"shadowPassRate"
|
|
3074
|
+
];
|
|
3075
|
+
const unknown = Object.keys(config).filter((key) => !known.includes(key));
|
|
3076
|
+
if (unknown.length > 0) throw new Error(`memory: unknown config key(s) ${unknown.join(", ")}`);
|
|
3077
|
+
return {
|
|
3078
|
+
databasePath: resolve(config.databasePath ?? join(home, "storages", "memory.db")),
|
|
3079
|
+
workspacePath: resolve(config.workspacePath ?? join(home, "memory-workbench")),
|
|
3080
|
+
workspaceTitle: config.workspaceTitle ?? "记忆监控",
|
|
3081
|
+
diaryExtractEvery: config.diaryExtractEvery ?? DEFAULT_CORE_CONFIG.diaryExtractEvery,
|
|
3082
|
+
diaryExtractIntervalHours: config.diaryExtractIntervalHours ?? DEFAULT_CORE_CONFIG.diaryExtractIntervalHours,
|
|
3083
|
+
recallTopK: config.recallTopK ?? DEFAULT_CORE_CONFIG.recallTopK,
|
|
3084
|
+
injectionBudgetTokens: config.injectionBudgetTokens ?? DEFAULT_CORE_CONFIG.injectionBudgetTokens,
|
|
3085
|
+
challengeConsecutiveFails: config.challengeConsecutiveFails ?? DEFAULT_CORE_CONFIG.challengeConsecutiveFails,
|
|
3086
|
+
challengeWindow: config.challengeWindow ?? DEFAULT_CORE_CONFIG.challengeWindow,
|
|
3087
|
+
challengeWindowFailRate: config.challengeWindowFailRate ?? DEFAULT_CORE_CONFIG.challengeWindowFailRate,
|
|
3088
|
+
familyLiveCap: config.familyLiveCap ?? DEFAULT_CORE_CONFIG.familyLiveCap,
|
|
3089
|
+
complexityTokenGate: config.complexityTokenGate ?? DEFAULT_CORE_CONFIG.complexityTokenGate,
|
|
3090
|
+
complexityStepGate: config.complexityStepGate ?? DEFAULT_CORE_CONFIG.complexityStepGate,
|
|
3091
|
+
duplicateOverlapGate: config.duplicateOverlapGate ?? DEFAULT_CORE_CONFIG.duplicateOverlapGate,
|
|
3092
|
+
recallFloorScore: config.recallFloorScore ?? DEFAULT_CORE_CONFIG.recallFloorScore,
|
|
3093
|
+
shadowPassRate: config.shadowPassRate ?? DEFAULT_CORE_CONFIG.shadowPassRate
|
|
3094
|
+
};
|
|
3095
|
+
}
|
|
3096
|
+
/**
|
|
3097
|
+
* Host plugin body: open the SQLite store, provide `ctx.memory`, and adopt
|
|
3098
|
+
* the monitoring workspace directory once a workspace registry is available.
|
|
3099
|
+
* @param ctx - host context.
|
|
3100
|
+
* @param config - plugin config (see {@link Config}).
|
|
3101
|
+
*/
|
|
3102
|
+
function apply(ctx, config = {}) {
|
|
3103
|
+
const resolved = resolveConfig(config);
|
|
3104
|
+
const coreConfig = {
|
|
3105
|
+
...DEFAULT_CORE_CONFIG,
|
|
3106
|
+
diaryExtractEvery: resolved.diaryExtractEvery,
|
|
3107
|
+
diaryExtractIntervalHours: resolved.diaryExtractIntervalHours,
|
|
3108
|
+
recallTopK: resolved.recallTopK,
|
|
3109
|
+
injectionBudgetTokens: resolved.injectionBudgetTokens,
|
|
3110
|
+
challengeConsecutiveFails: resolved.challengeConsecutiveFails,
|
|
3111
|
+
challengeWindow: resolved.challengeWindow,
|
|
3112
|
+
challengeWindowFailRate: resolved.challengeWindowFailRate,
|
|
3113
|
+
familyLiveCap: resolved.familyLiveCap,
|
|
3114
|
+
complexityTokenGate: resolved.complexityTokenGate,
|
|
3115
|
+
complexityStepGate: resolved.complexityStepGate,
|
|
3116
|
+
duplicateOverlapGate: resolved.duplicateOverlapGate,
|
|
3117
|
+
recallFloorScore: resolved.recallFloorScore,
|
|
3118
|
+
shadowPassRate: resolved.shadowPassRate
|
|
3119
|
+
};
|
|
3120
|
+
mkdirSync(join(resolved.databasePath, ".."), { recursive: true });
|
|
3121
|
+
const db = new DatabaseSync(resolved.databasePath);
|
|
3122
|
+
const core = new MemoryCore(new MemoryStore(db), coreConfig);
|
|
3123
|
+
const workbenchInfo = () => ({
|
|
3124
|
+
workspacePath: resolved.workspacePath,
|
|
3125
|
+
workspaceTitle: resolved.workspaceTitle,
|
|
3126
|
+
databasePath: resolved.databasePath,
|
|
3127
|
+
diaryExtractEvery: resolved.diaryExtractEvery,
|
|
3128
|
+
injectionBudgetTokens: resolved.injectionBudgetTokens
|
|
3129
|
+
});
|
|
3130
|
+
new MemoryService(ctx, core, workbenchInfo);
|
|
3131
|
+
ctx.effect(() => () => {
|
|
3132
|
+
try {
|
|
3133
|
+
db.close();
|
|
3134
|
+
} catch {}
|
|
3135
|
+
}, "memory: SQLite store lifetime");
|
|
3136
|
+
}
|
|
3137
|
+
//#endregion
|
|
3138
|
+
export { DEFAULT_CORE_CONFIG, MEMORY_SCHEMA_VERSION, MemoryCore, MemoryService, MemoryStore, apply, resolveConfig };
|