agenr 0.13.4 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +5 -5
- package/dist/{chunk-WB6OH7LM.js → chunk-2JI5DOZ2.js} +413 -9
- package/dist/{chunk-EAIHUY6F.js → chunk-4JHTJXYY.js} +8 -8
- package/dist/{chunk-27X6QJCR.js → chunk-4QW3AAO2.js} +2 -2
- package/dist/{chunk-IJBDKQIX.js → chunk-FKKPPFS3.js} +38 -135
- package/dist/{chunk-NGV264GF.js → chunk-FUOKOMSN.js} +2 -2
- package/dist/{chunk-BIND34BY.js → chunk-LLHGEYS7.js} +132 -48
- package/dist/{chunk-QI23WU2O.js → chunk-SJN63MTD.js} +261 -10
- package/dist/{chunk-HGRL3DZC.js → chunk-TSIZVBIJ.js} +1 -1
- package/dist/{chunk-DK6MSILE.js → chunk-VNPHNLJT.js} +13 -13
- package/dist/{chunk-ODBWAJFI.js → chunk-Y36LO6ZS.js} +4 -4
- package/dist/{chunk-Q7YGZUAE.js → chunk-Y5DAJYYE.js} +4 -6
- package/dist/{chunk-GOV2RGHZ.js → chunk-YTYD266U.js} +0 -151
- package/dist/{classify-entries-ZI2ITP37.js → classify-entries-HFQTEO3X.js} +3 -3
- package/dist/cli-main.d.ts +1 -1
- package/dist/cli-main.js +4618 -3948
- package/dist/{config-2UMKE6BC.js → config-GUTSLBP4.js} +3 -1
- package/dist/edge/openclaw/index.d.ts +1 -1
- package/dist/edge/openclaw/index.js +1077 -1046
- package/dist/{eval-DLXPSFW5.js → eval-C2DQXIT4.js} +7 -7
- package/dist/{ingestion-M2BPZE5M.js → ingestion-N24I2BH3.js} +6 -6
- package/dist/modules/surgeon/adapters/prompts/passes/auto.md +4 -4
- package/dist/modules/surgeon/adapters/prompts/passes/contradictions.md +5 -5
- package/dist/modules/surgeon/adapters/prompts/passes/retirement.md +11 -8
- package/dist/modules/surgeon/adapters/prompts/system.md +3 -2
- package/dist/{openclaw-CCNGS56Y.js → openclaw-TEDSTLJ2.js} +5 -5
- package/dist/{operations-YRBFQMMG.js → operations-KETVRPYZ.js} +6 -6
- package/dist/{process-before-reset-recall-feedback-54DMNNO4.js → process-before-reset-recall-feedback-CGCLY6WY.js} +1 -1
- package/dist/{types-gds0qzmj.d.ts → types-jmyBA4zR.d.ts} +26 -10
- package/dist/{workflow-UBLUB6UB.js → workflow-BT24JCAZ.js} +7 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.14.1] - 2026-03-24
|
|
4
|
+
|
|
5
|
+
### Surgeon Health Fixes (001–006)
|
|
6
|
+
|
|
7
|
+
Six targeted fixes based on a traced production surgeon run investigation:
|
|
8
|
+
|
|
9
|
+
- **Temporary entry TTL enforcement.** Temporary entries with low importance and zero recalls auto-expire after a configurable window (default 21 days). Rebuild-aware — entries are protected after a corpus rebuild until they've had time to be recalled. Short-lived entries are excluded from merge clustering.
|
|
10
|
+
- **Co-recall / co-ingest split.** The co-recall edge graph now distinguishes real co-recall (entries recalled together in a session) from co-ingest (entries from the same source file). Legacy edges retyped to `co_ingested`. Quality evolution only reads `co_recalled` edges. Live recall events now carry `session_id`.
|
|
11
|
+
- **Quality scoring recalibration.** Replaced the quality evolution formula with a rebuild-aware, type-aware, positive-signal-only design. Grace periods per entry type (preferences 14d, decisions 60d, etc.) prevent new entries from collapsing to floor scores. Quality only goes up from evidence. Gentle importance-dampened decay for unrecalled entries. Centralized quality tier thresholds. Removed dead seed path.
|
|
12
|
+
- **Two-tier health forgetting metric.** Health stats now report both an actionable cleanup pool (what the surgeon would evaluate) and a mechanical auto-retire count (ultra-conservative threshold). Removed dead `lowScoreTriggerCount` config.
|
|
13
|
+
- **Generic subject key suppression.** Contradiction scanning and clustering now detect and suppress generic `entity/type` subject keys (e.g., `jim/decision`) that collapse unrelated entries into false-positive pairs.
|
|
14
|
+
- **Semantic cluster precision.** Type-aware similarity thresholds for dedup clustering — decisions and lessons require 0.75, events 0.72, facts and preferences 0.60. Prevents thematic neighborhoods from being clustered as duplicates. Type-aware diameter floor in post-union validation.
|
|
15
|
+
|
|
16
|
+
### Other
|
|
17
|
+
|
|
18
|
+
- **`db vector-check` command.** New CLI command for diagnosing vector index health.
|
|
19
|
+
|
|
20
|
+
## [0.14.0] - 2026-03-24
|
|
21
|
+
|
|
22
|
+
### Surgeon — Budget Model Overhaul
|
|
23
|
+
|
|
24
|
+
- **`--budget` now means dollars, not tokens.** The cumulative token budget that double-counted context re-sends across turns is gone. The surgeon is now constrained by two things: cost (dollars) and context window (tokens). Config `surgeon.budget` is dollars per run. Default: $5.
|
|
25
|
+
- **Context-aware limits.** New `surgeon.contextLimit` config field and `--context-limit` CLI flag. Auto-detects from `model.contextWindow * 0.85` if not set. The surgeon stops when context is full, not from artificial token counting.
|
|
26
|
+
- **`costCap` removed.** Replaced by `budget`. Old `costCap` values are accepted as backward-compatible alias with deprecation warning.
|
|
27
|
+
|
|
28
|
+
### Surgeon — Contradiction Improvements
|
|
29
|
+
|
|
30
|
+
- **`coexists` relation for `log_conflict`.** The surgeon can now log reviewed false-positive pairs as coexisting rather than forcing them into `contradicts` or `supersedes`. Coexists conflicts are auto-resolved as `keep-both` so they don't accumulate as pending conflicts and future scans skip them.
|
|
31
|
+
- **Improved contradiction candidate filtering.** Suppresses historical series pairs (release versions, prompt paths, roadmap snapshots) from claim divergence scanning. Prioritizes `current_state` claim divergence pairs.
|
|
32
|
+
|
|
33
|
+
### Surgeon — Retirement Candidate Scoping
|
|
34
|
+
|
|
35
|
+
- **Scoped candidate queries.** `query_candidates` now accepts `scope` parameter: `actionable` (default) filters to entries with high retirement probability — temporary, ephemeral, todos, low-importance events, mislabeled temporal artifacts. `all` shows the full candidate pool for deep sweeps.
|
|
36
|
+
- **Improved candidate ordering.** Actionable scope returns temporary entries first, then ephemeral, then todos, then low-importance events. Durable permanent decisions and preferences are excluded from the default scope.
|
|
37
|
+
- **Two-phase retirement in auto mode.** Surgeon starts with actionable candidates, widens to full pool only if budget remains after exhausting the actionable set.
|
|
38
|
+
|
|
3
39
|
## [0.13.4] - 2026-03-23
|
|
4
40
|
|
|
5
41
|
### Surgeon
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ That's it. The interactive wizard handles everything: auth setup, platform detec
|
|
|
34
34
|
- **Extract** - An LLM reads your transcripts and pulls out structured entries. Smart filtering removes noise (tool calls, file contents, boilerplate - about 80% of a typical session) before the LLM sees it. Hedged or unverified agent claims are capped at importance 5 with an `unverified` tag.
|
|
35
35
|
- **Store** - Entries get embedded and compared against existing knowledge. Near-duplicates reinforce existing entries. New information gets inserted. Online dedup catches copies in real-time.
|
|
36
36
|
- **Recall** - Semantic search plus memory-aware ranking. Entries you recall often score higher. Stale entries decay. Contradicted entries get penalized.
|
|
37
|
-
- **Surgeon** - Corpus health maintenance: deterministic cleanup, quality evolution,
|
|
37
|
+
- **Surgeon** - Corpus health maintenance: deterministic cleanup, quality evolution, and LLM-assisted retirement, dedup, and contradiction resolution.
|
|
38
38
|
|
|
39
39
|
```text
|
|
40
40
|
Transcript -> Filter -> Extract -> Store -> Recall
|
|
@@ -262,13 +262,13 @@ This exposes five MCP tools: `agenr_recall`, `agenr_store`, `agenr_extract`, `ag
|
|
|
262
262
|
| `agenr watcher install` | Install background watch daemon (macOS launchd) |
|
|
263
263
|
| `agenr watcher status` | Show daemon status (running/stopped, pid, watched file, recent logs) |
|
|
264
264
|
| `agenr watcher logs` | Stream or show recent daemon logs |
|
|
265
|
-
| `agenr surgeon run` | Run corpus-health maintenance: cleanup, quality evolution,
|
|
265
|
+
| `agenr surgeon run` | Run corpus-health maintenance: cleanup, quality evolution, and retirement/dedup/contradictions |
|
|
266
266
|
| `agenr benchmark` | Run extraction against benchmark fixtures and score results |
|
|
267
267
|
| `agenr context` | Generate context file for AI tool integration |
|
|
268
|
-
| `agenr health` | Show database health and
|
|
268
|
+
| `agenr health` | Show database health and cleanup status |
|
|
269
269
|
| `agenr mcp` | Start MCP server (stdio) |
|
|
270
270
|
| `agenr todo <subcommand>` | Manage todos in the knowledge base |
|
|
271
|
-
| `agenr db <cmd>` | Database management (stats, version, export, reset, path, check, rebuild-index) |
|
|
271
|
+
| `agenr db <cmd>` | Database management (stats, version, export, reset, path, check, vector-check, rebuild-index) |
|
|
272
272
|
|
|
273
273
|
Full reference: [docs/CLI.md](./docs/CLI.md) | [docs/CONFIGURATION.md](./docs/CONFIGURATION.md)
|
|
274
274
|
|
|
@@ -306,7 +306,7 @@ For the product thesis, see [docs/vision.md](./docs/vision.md).
|
|
|
306
306
|
| Plugin install fails during wizard | Run `openclaw plugins install agenr` manually, then `openclaw gateway restart` |
|
|
307
307
|
| Embeddings fail | Set `OPENAI_API_KEY` env var or `agenr config set-key openai <key>` |
|
|
308
308
|
| Database locked | Wait for the active write-heavy command to finish (`agenr ingest --bulk`, `agenr surgeon run --apply`, etc.) and retry |
|
|
309
|
-
| Recall returns nothing after force-kill | `agenr db rebuild
|
|
309
|
+
| Recall returns nothing after force-kill | `agenr db vector-check --rebuild` (vector shadow-table drift) |
|
|
310
310
|
| Extraction fails mid-file | Retry - dedup skips already-stored entries |
|
|
311
311
|
| Stale handoff entries persist | Run `agenr recall --browse --since 1d` to check, then `agenr retire --id <id>` |
|
|
312
312
|
| Gateway doesn't pick up plugin | Run `openclaw gateway restart` after plugin install |
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toNumber,
|
|
3
|
+
toStringValue
|
|
4
|
+
} from "./chunk-QDGOYFN7.js";
|
|
1
5
|
import {
|
|
2
6
|
authMethodToProvider,
|
|
3
7
|
normalizeProvider,
|
|
@@ -5,7 +9,7 @@ import {
|
|
|
5
9
|
resolveModel,
|
|
6
10
|
resolveModelForTask,
|
|
7
11
|
resolveUserPath
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SJN63MTD.js";
|
|
9
13
|
import {
|
|
10
14
|
toErrorMessage
|
|
11
15
|
} from "./chunk-UUUFPTSM.js";
|
|
@@ -15,6 +19,42 @@ function sleep(ms) {
|
|
|
15
19
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
// src/shared/domain/types.ts
|
|
23
|
+
var KNOWLEDGE_TYPES = [
|
|
24
|
+
"fact",
|
|
25
|
+
"decision",
|
|
26
|
+
"preference",
|
|
27
|
+
"todo",
|
|
28
|
+
"relationship",
|
|
29
|
+
"event",
|
|
30
|
+
"lesson",
|
|
31
|
+
"reflection"
|
|
32
|
+
];
|
|
33
|
+
var ENTRY_TYPES = KNOWLEDGE_TYPES;
|
|
34
|
+
var IMPORTANCE_MIN = 1;
|
|
35
|
+
var IMPORTANCE_MAX = 10;
|
|
36
|
+
var EXPIRY_LEVELS = ["core", "permanent", "temporary"];
|
|
37
|
+
var SCOPE_LEVELS = ["private", "personal", "public"];
|
|
38
|
+
var KNOWLEDGE_PLATFORMS = ["openclaw", "claude-code", "codex", "plaud"];
|
|
39
|
+
var ENTRY_KINDS = ["directive", "state", "identity", "episode", "reference"];
|
|
40
|
+
var TEMPORAL_CLASSES = ["ephemeral", "durable"];
|
|
41
|
+
var REJECTED_CONFLICT_ENTRY_ID = "rejected";
|
|
42
|
+
|
|
43
|
+
// src/shared/domain/subject-key.ts
|
|
44
|
+
var ENTRY_TYPE_ATTRIBUTES = new Set(KNOWLEDGE_TYPES);
|
|
45
|
+
function isGenericSubjectKey(subjectKey, entryType) {
|
|
46
|
+
const normalizedSubjectKey = subjectKey?.trim().toLowerCase();
|
|
47
|
+
const normalizedEntryType = entryType.trim().toLowerCase();
|
|
48
|
+
if (!normalizedSubjectKey || !normalizedEntryType || !ENTRY_TYPE_ATTRIBUTES.has(normalizedEntryType)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const slashIndex = normalizedSubjectKey.lastIndexOf("/");
|
|
52
|
+
if (slashIndex < 0 || slashIndex === normalizedSubjectKey.length - 1) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return normalizedSubjectKey.slice(slashIndex + 1) === normalizedEntryType;
|
|
56
|
+
}
|
|
57
|
+
|
|
18
58
|
// src/modules/store/domain/structured-claim.ts
|
|
19
59
|
var STRUCTURED_CLAIM_IDENTITY_TYPES = /* @__PURE__ */ new Set(["fact", "preference", "decision"]);
|
|
20
60
|
var STRUCTURED_CLAIM_CONFIDENCE_FLOOR = 0.8;
|
|
@@ -109,6 +149,48 @@ var TRANSITION_MULTI_STEP_MARKERS = [
|
|
|
109
149
|
/\bafter that\b/i,
|
|
110
150
|
/\bthen\b.{0,24}\b(?:move|moved|switch|switched|replace|replaced|migrate|migrated|transition|transitioned)\b/i
|
|
111
151
|
];
|
|
152
|
+
var TOPIC_SLUG_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
153
|
+
"the",
|
|
154
|
+
"a",
|
|
155
|
+
"an",
|
|
156
|
+
"for",
|
|
157
|
+
"and",
|
|
158
|
+
"or",
|
|
159
|
+
"to",
|
|
160
|
+
"in",
|
|
161
|
+
"of",
|
|
162
|
+
"on",
|
|
163
|
+
"at",
|
|
164
|
+
"by",
|
|
165
|
+
"with",
|
|
166
|
+
"from",
|
|
167
|
+
"that",
|
|
168
|
+
"this",
|
|
169
|
+
"is",
|
|
170
|
+
"was",
|
|
171
|
+
"are",
|
|
172
|
+
"were",
|
|
173
|
+
"be",
|
|
174
|
+
"been",
|
|
175
|
+
"has",
|
|
176
|
+
"have",
|
|
177
|
+
"had",
|
|
178
|
+
"it",
|
|
179
|
+
"its",
|
|
180
|
+
"create",
|
|
181
|
+
"creating",
|
|
182
|
+
"document",
|
|
183
|
+
"documenting",
|
|
184
|
+
"turn",
|
|
185
|
+
"turning",
|
|
186
|
+
"into",
|
|
187
|
+
"concrete",
|
|
188
|
+
"investigate",
|
|
189
|
+
"investigating"
|
|
190
|
+
]);
|
|
191
|
+
var TOPIC_SLUG_MAX_TOKENS = 6;
|
|
192
|
+
var TOPIC_SLUG_MAX_LENGTH = 60;
|
|
193
|
+
var TOPIC_SLUG_MIN_LENGTH = 3;
|
|
112
194
|
function normalizeLower(value) {
|
|
113
195
|
return value?.trim().toLowerCase() ?? "";
|
|
114
196
|
}
|
|
@@ -325,6 +407,34 @@ function buildSubjectKey(subjectEntity, subjectAttribute, fallback) {
|
|
|
325
407
|
}
|
|
326
408
|
return fallback;
|
|
327
409
|
}
|
|
410
|
+
function extractTopicSlug(text) {
|
|
411
|
+
if (!text || text.trim().length === 0) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const tokens = text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((token) => token.length > 1 && !TOPIC_SLUG_STOP_WORDS.has(token));
|
|
415
|
+
const slug = tokens.slice(0, TOPIC_SLUG_MAX_TOKENS).join("_").slice(0, TOPIC_SLUG_MAX_LENGTH);
|
|
416
|
+
return slug.length >= TOPIC_SLUG_MIN_LENGTH ? slug : null;
|
|
417
|
+
}
|
|
418
|
+
function deriveTopicAttribute(entry, fallback) {
|
|
419
|
+
const normalizedFallback = normalizeLower(fallback);
|
|
420
|
+
const normalizedType = normalizeLower(entry.type);
|
|
421
|
+
const objectSlug = extractTopicSlug(entry.claimObject);
|
|
422
|
+
if (objectSlug && objectSlug !== normalizedFallback && objectSlug !== normalizedType) {
|
|
423
|
+
return objectSlug;
|
|
424
|
+
}
|
|
425
|
+
const subjectSlug = extractTopicSlug(entry.subject);
|
|
426
|
+
if (subjectSlug && subjectSlug !== normalizedFallback && subjectSlug !== normalizedType) {
|
|
427
|
+
return subjectSlug;
|
|
428
|
+
}
|
|
429
|
+
return fallback;
|
|
430
|
+
}
|
|
431
|
+
function resolveSubjectAttribute(entry, subjectEntity, subjectAttribute, fallbackSubjectKey) {
|
|
432
|
+
const subjectKey = buildSubjectKey(subjectEntity, subjectAttribute, fallbackSubjectKey);
|
|
433
|
+
if (!isGenericSubjectKey(subjectKey, entry.type)) {
|
|
434
|
+
return subjectAttribute;
|
|
435
|
+
}
|
|
436
|
+
return deriveTopicAttribute(entry, subjectAttribute);
|
|
437
|
+
}
|
|
328
438
|
function normalizeStructuredClaimFields(entry) {
|
|
329
439
|
const normalizedSubjectEntity = normalizeLower(entry.subjectEntity);
|
|
330
440
|
const normalizedSubjectKey = normalizeLower(entry.subjectKey);
|
|
@@ -341,6 +451,16 @@ function normalizeStructuredClaimFields(entry) {
|
|
|
341
451
|
claimRole: entry.claimRole
|
|
342
452
|
});
|
|
343
453
|
const normalizedAttribute = detectedState.normalizedAttribute ?? subjectAttribute;
|
|
454
|
+
const resolvedAttribute = resolveSubjectAttribute(
|
|
455
|
+
{
|
|
456
|
+
type: entry.type,
|
|
457
|
+
subject: entry.subject,
|
|
458
|
+
claimObject: entry.claimObject
|
|
459
|
+
},
|
|
460
|
+
subjectEntity,
|
|
461
|
+
normalizedAttribute,
|
|
462
|
+
normalizedSubjectKey
|
|
463
|
+
);
|
|
344
464
|
const transitionClaim = detectedState.role ? void 0 : detectTransitionClaim({
|
|
345
465
|
content: entry.content,
|
|
346
466
|
claimObject: entry.claimObject,
|
|
@@ -349,11 +469,11 @@ function normalizeStructuredClaimFields(entry) {
|
|
|
349
469
|
if (subjectEntity) {
|
|
350
470
|
entry.subjectEntity = subjectEntity;
|
|
351
471
|
}
|
|
352
|
-
if (
|
|
353
|
-
entry.subjectAttribute =
|
|
472
|
+
if (resolvedAttribute) {
|
|
473
|
+
entry.subjectAttribute = resolvedAttribute;
|
|
354
474
|
}
|
|
355
|
-
if (subjectEntity ||
|
|
356
|
-
entry.subjectKey = buildSubjectKey(subjectEntity,
|
|
475
|
+
if (subjectEntity || resolvedAttribute) {
|
|
476
|
+
entry.subjectKey = buildSubjectKey(subjectEntity, resolvedAttribute, normalizedSubjectKey);
|
|
357
477
|
}
|
|
358
478
|
if (transitionClaim) {
|
|
359
479
|
entry.claimPredicate = STATE_TRANSITION_PREDICATE;
|
|
@@ -1382,7 +1502,7 @@ var SELF_UPDATE_ACTIVE_EMBEDDINGS_SQL = `
|
|
|
1382
1502
|
`;
|
|
1383
1503
|
var TEMP_REPAIR_TABLE = "_vec_repair";
|
|
1384
1504
|
var REPAIR_CHUNK_SIZE = 100;
|
|
1385
|
-
function
|
|
1505
|
+
function toNumber2(value) {
|
|
1386
1506
|
if (typeof value === "number") {
|
|
1387
1507
|
return value;
|
|
1388
1508
|
}
|
|
@@ -1395,14 +1515,14 @@ function toNumber(value) {
|
|
|
1395
1515
|
return Number.NaN;
|
|
1396
1516
|
}
|
|
1397
1517
|
function toFiniteCount(value) {
|
|
1398
|
-
const numeric =
|
|
1518
|
+
const numeric = toNumber2(value);
|
|
1399
1519
|
if (!Number.isFinite(numeric)) {
|
|
1400
1520
|
return 0;
|
|
1401
1521
|
}
|
|
1402
1522
|
return Math.max(0, Math.trunc(numeric));
|
|
1403
1523
|
}
|
|
1404
1524
|
function normalizeRowid(value) {
|
|
1405
|
-
const numeric =
|
|
1525
|
+
const numeric = toNumber2(value);
|
|
1406
1526
|
if (!Number.isFinite(numeric)) {
|
|
1407
1527
|
return null;
|
|
1408
1528
|
}
|
|
@@ -1416,6 +1536,9 @@ function formatShadowMismatch(stats) {
|
|
|
1416
1536
|
}
|
|
1417
1537
|
return details.join(", ");
|
|
1418
1538
|
}
|
|
1539
|
+
function formatIntegrityDetail(embeddingCount, shadowCount, missingRowids) {
|
|
1540
|
+
return `active=${embeddingCount}, shadow=${shadowCount}, missing=${missingRowids}`;
|
|
1541
|
+
}
|
|
1419
1542
|
async function rollbackQuietly(db) {
|
|
1420
1543
|
try {
|
|
1421
1544
|
await db.execute("ROLLBACK");
|
|
@@ -1508,6 +1631,18 @@ async function getVectorIndexShadowStats(db) {
|
|
|
1508
1631
|
missingRowids
|
|
1509
1632
|
};
|
|
1510
1633
|
}
|
|
1634
|
+
async function checkVectorIntegrity(db) {
|
|
1635
|
+
const stats = await getVectorIndexShadowStats(db);
|
|
1636
|
+
const missingRowids = stats.missingRowids.length;
|
|
1637
|
+
return {
|
|
1638
|
+
drifted: stats.embeddingCount !== stats.shadowCount || missingRowids > 0,
|
|
1639
|
+
detail: formatIntegrityDetail(stats.embeddingCount, stats.shadowCount, missingRowids),
|
|
1640
|
+
activeEntries: stats.embeddingCount,
|
|
1641
|
+
shadowRows: stats.shadowCount,
|
|
1642
|
+
missingRowids,
|
|
1643
|
+
missingRowidValues: stats.missingRowids
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1511
1646
|
async function rebuildVectorIndex(db, options) {
|
|
1512
1647
|
const start = Date.now();
|
|
1513
1648
|
const onLog = options?.onLog ?? (() => void 0);
|
|
@@ -1645,6 +1780,221 @@ async function getBulkIngestMeta(db) {
|
|
|
1645
1780
|
}
|
|
1646
1781
|
}
|
|
1647
1782
|
|
|
1783
|
+
// src/shared/infrastructure/db/co-recall.ts
|
|
1784
|
+
var DEFAULT_EDGE_INCREMENT = 0.1;
|
|
1785
|
+
var MAX_USED_ENTRIES = 20;
|
|
1786
|
+
var CO_RECALL_EDGE_TYPE = "co_recalled";
|
|
1787
|
+
var CO_INGEST_EDGE_TYPE = "co_ingested";
|
|
1788
|
+
var ALL_EDGE_TYPES = [CO_RECALL_EDGE_TYPE, CO_INGEST_EDGE_TYPE];
|
|
1789
|
+
var ALL_EDGE_TYPES_PLACEHOLDERS = ALL_EDGE_TYPES.map(() => "?").join(", ");
|
|
1790
|
+
function normalizePair(a, b) {
|
|
1791
|
+
return a < b ? [a, b] : [b, a];
|
|
1792
|
+
}
|
|
1793
|
+
async function strengthenEdges(db, usedEntryIds, timestamp2, edgeType) {
|
|
1794
|
+
const uniqueIds = Array.from(
|
|
1795
|
+
new Set(
|
|
1796
|
+
usedEntryIds.map((id) => id.trim()).filter((id) => id.length > 0)
|
|
1797
|
+
)
|
|
1798
|
+
).slice(0, MAX_USED_ENTRIES);
|
|
1799
|
+
if (uniqueIds.length < 2) {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
const now = timestamp2 || (/* @__PURE__ */ new Date()).toISOString();
|
|
1803
|
+
const pairs = [];
|
|
1804
|
+
for (let i = 0; i < uniqueIds.length; i += 1) {
|
|
1805
|
+
const a = uniqueIds[i];
|
|
1806
|
+
if (!a) {
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
for (let j = i + 1; j < uniqueIds.length; j += 1) {
|
|
1810
|
+
const b = uniqueIds[j];
|
|
1811
|
+
if (!b || a === b) {
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
pairs.push(normalizePair(a, b));
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
if (pairs.length === 0) {
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
await db.execute("BEGIN");
|
|
1821
|
+
try {
|
|
1822
|
+
for (const [entryA, entryB] of pairs) {
|
|
1823
|
+
await db.execute({
|
|
1824
|
+
sql: `
|
|
1825
|
+
INSERT INTO co_recall_edges (
|
|
1826
|
+
entry_a, entry_b, edge_type, weight, session_count, last_co_recalled, created_at
|
|
1827
|
+
)
|
|
1828
|
+
VALUES (?, ?, ?, ?, 1, ?, ?)
|
|
1829
|
+
ON CONFLICT
|
|
1830
|
+
DO UPDATE SET
|
|
1831
|
+
weight = MIN(co_recall_edges.weight + excluded.weight, 1.0),
|
|
1832
|
+
session_count = co_recall_edges.session_count + 1,
|
|
1833
|
+
last_co_recalled = excluded.last_co_recalled
|
|
1834
|
+
`,
|
|
1835
|
+
args: [
|
|
1836
|
+
entryA,
|
|
1837
|
+
entryB,
|
|
1838
|
+
edgeType,
|
|
1839
|
+
DEFAULT_EDGE_INCREMENT,
|
|
1840
|
+
now,
|
|
1841
|
+
now
|
|
1842
|
+
]
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
await db.execute("COMMIT");
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
try {
|
|
1848
|
+
await db.execute("ROLLBACK");
|
|
1849
|
+
} catch {
|
|
1850
|
+
}
|
|
1851
|
+
throw error;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
async function strengthenCoRecallEdges(db, usedEntryIds, timestamp2) {
|
|
1855
|
+
await strengthenEdges(db, usedEntryIds, timestamp2, CO_RECALL_EDGE_TYPE);
|
|
1856
|
+
}
|
|
1857
|
+
async function strengthenCoIngestEdges(db, usedEntryIds, timestamp2) {
|
|
1858
|
+
await strengthenEdges(db, usedEntryIds, timestamp2, CO_INGEST_EDGE_TYPE);
|
|
1859
|
+
}
|
|
1860
|
+
async function getCoRecallNeighbors(db, entryId, minWeight = 0.1, limit = 10, edgeType = CO_RECALL_EDGE_TYPE) {
|
|
1861
|
+
const normalizedId = entryId.trim();
|
|
1862
|
+
if (!normalizedId) {
|
|
1863
|
+
return [];
|
|
1864
|
+
}
|
|
1865
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 10;
|
|
1866
|
+
const safeMinWeight = Number.isFinite(minWeight) ? Math.max(minWeight, 0) : 0.1;
|
|
1867
|
+
const result = await db.execute({
|
|
1868
|
+
sql: `
|
|
1869
|
+
SELECT
|
|
1870
|
+
CASE WHEN entry_a = ? THEN entry_b ELSE entry_a END AS neighbor_id,
|
|
1871
|
+
weight,
|
|
1872
|
+
session_count,
|
|
1873
|
+
last_co_recalled
|
|
1874
|
+
FROM co_recall_edges
|
|
1875
|
+
WHERE edge_type = ?
|
|
1876
|
+
AND (entry_a = ? OR entry_b = ?)
|
|
1877
|
+
AND weight >= ?
|
|
1878
|
+
ORDER BY weight DESC, session_count DESC, last_co_recalled DESC
|
|
1879
|
+
LIMIT ?
|
|
1880
|
+
`,
|
|
1881
|
+
args: [normalizedId, edgeType, normalizedId, normalizedId, safeMinWeight, safeLimit]
|
|
1882
|
+
});
|
|
1883
|
+
return result.rows.map((row) => ({
|
|
1884
|
+
entryId: toStringValue(row.neighbor_id),
|
|
1885
|
+
weight: toNumber(row.weight),
|
|
1886
|
+
sessionCount: toNumber(row.session_count),
|
|
1887
|
+
lastCoRecalled: toStringValue(row.last_co_recalled)
|
|
1888
|
+
}));
|
|
1889
|
+
}
|
|
1890
|
+
async function getCoRecallEdgeCounts(db, edgeType = CO_RECALL_EDGE_TYPE) {
|
|
1891
|
+
const result = await db.execute({
|
|
1892
|
+
sql: `
|
|
1893
|
+
SELECT entry_id, COUNT(*) AS edge_count
|
|
1894
|
+
FROM (
|
|
1895
|
+
SELECT entry_a AS entry_id
|
|
1896
|
+
FROM co_recall_edges
|
|
1897
|
+
WHERE edge_type = ?
|
|
1898
|
+
UNION ALL
|
|
1899
|
+
SELECT entry_b AS entry_id
|
|
1900
|
+
FROM co_recall_edges
|
|
1901
|
+
WHERE edge_type = ?
|
|
1902
|
+
)
|
|
1903
|
+
GROUP BY entry_id
|
|
1904
|
+
`,
|
|
1905
|
+
args: [edgeType, edgeType]
|
|
1906
|
+
});
|
|
1907
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1908
|
+
for (const row of result.rows) {
|
|
1909
|
+
const entryId = toStringValue(row.entry_id).trim();
|
|
1910
|
+
if (!entryId) {
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
counts.set(entryId, Math.max(0, toNumber(row.edge_count)));
|
|
1914
|
+
}
|
|
1915
|
+
return counts;
|
|
1916
|
+
}
|
|
1917
|
+
async function getTopCoRecallEdges(db, limit = 20, edgeType = CO_RECALL_EDGE_TYPE) {
|
|
1918
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 20;
|
|
1919
|
+
const result = await db.execute({
|
|
1920
|
+
sql: `
|
|
1921
|
+
SELECT entry_a, entry_b, weight, session_count, last_co_recalled
|
|
1922
|
+
FROM co_recall_edges
|
|
1923
|
+
WHERE edge_type = ?
|
|
1924
|
+
ORDER BY weight DESC, session_count DESC, last_co_recalled DESC
|
|
1925
|
+
LIMIT ?
|
|
1926
|
+
`,
|
|
1927
|
+
args: [edgeType, safeLimit]
|
|
1928
|
+
});
|
|
1929
|
+
return result.rows.map((row) => ({
|
|
1930
|
+
entryA: toStringValue(row.entry_a),
|
|
1931
|
+
entryB: toStringValue(row.entry_b),
|
|
1932
|
+
weight: toNumber(row.weight),
|
|
1933
|
+
sessionCount: toNumber(row.session_count),
|
|
1934
|
+
lastCoRecalled: toStringValue(row.last_co_recalled)
|
|
1935
|
+
}));
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// src/shared/infrastructure/db/meta.ts
|
|
1939
|
+
var LAST_CORPUS_REBUILD_AT_META_KEY = "last_corpus_rebuild_at";
|
|
1940
|
+
function normalizeIsoTimestamp(value) {
|
|
1941
|
+
if (value instanceof Date) {
|
|
1942
|
+
return value.toISOString();
|
|
1943
|
+
}
|
|
1944
|
+
const parsed = Date.parse(value);
|
|
1945
|
+
if (!Number.isFinite(parsed)) {
|
|
1946
|
+
throw new Error(`Invalid ISO timestamp: ${value}`);
|
|
1947
|
+
}
|
|
1948
|
+
return new Date(parsed).toISOString();
|
|
1949
|
+
}
|
|
1950
|
+
async function getMetaValue(db, key) {
|
|
1951
|
+
const result = await db.execute({
|
|
1952
|
+
sql: "SELECT value FROM _meta WHERE key = ? LIMIT 1",
|
|
1953
|
+
args: [key]
|
|
1954
|
+
});
|
|
1955
|
+
const raw = result.rows[0];
|
|
1956
|
+
const value = raw?.value ?? (raw ? Object.values(raw)[0] : void 0);
|
|
1957
|
+
if (typeof value !== "string") {
|
|
1958
|
+
return null;
|
|
1959
|
+
}
|
|
1960
|
+
const trimmed = value.trim();
|
|
1961
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1962
|
+
}
|
|
1963
|
+
async function setMetaValue(db, input) {
|
|
1964
|
+
const updatedAt = normalizeIsoTimestamp(input.updatedAt ?? /* @__PURE__ */ new Date());
|
|
1965
|
+
await db.execute({
|
|
1966
|
+
sql: `
|
|
1967
|
+
INSERT INTO _meta (key, value, updated_at)
|
|
1968
|
+
VALUES (?, ?, ?)
|
|
1969
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
1970
|
+
`,
|
|
1971
|
+
args: [input.key, input.value, updatedAt]
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
async function getLastCorpusRebuildAt(db) {
|
|
1975
|
+
return getMetaValue(db, LAST_CORPUS_REBUILD_AT_META_KEY);
|
|
1976
|
+
}
|
|
1977
|
+
async function setLastCorpusRebuildAt(db, value) {
|
|
1978
|
+
const normalized = normalizeIsoTimestamp(value);
|
|
1979
|
+
await setMetaValue(db, {
|
|
1980
|
+
key: LAST_CORPUS_REBUILD_AT_META_KEY,
|
|
1981
|
+
value: normalized,
|
|
1982
|
+
updatedAt: normalized
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
async function initializeLastCorpusRebuildAtForEmptyCorpus(db, at = /* @__PURE__ */ new Date()) {
|
|
1986
|
+
const existing = await getLastCorpusRebuildAt(db);
|
|
1987
|
+
if (existing) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
const result = await db.execute("SELECT COUNT(*) AS count FROM entries");
|
|
1991
|
+
const count = Number(result.rows[0]?.count ?? 0);
|
|
1992
|
+
if (count > 0) {
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
await setLastCorpusRebuildAt(db, at);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1648
1998
|
// src/shared/infrastructure/db/migrations/reflection-importance-cap.ts
|
|
1649
1999
|
var REFLECTION_IMPORTANCE_CAP_META_KEY = "reflection_importance_cap_v1";
|
|
1650
2000
|
async function applyReflectionImportanceCapMigration(db) {
|
|
@@ -2014,6 +2364,7 @@ async function migrateRecallEvents(db) {
|
|
|
2014
2364
|
|
|
2015
2365
|
// src/shared/infrastructure/db/schema/init.ts
|
|
2016
2366
|
var LEGACY_IMPORTANCE_BACKFILL_META_KEY = "legacy_importance_backfill_from_confidence_v1";
|
|
2367
|
+
var CO_INGEST_EDGE_RETYPE_META_KEY = "co_recall_edges_retyped_to_co_ingested_v1";
|
|
2017
2368
|
function readColumnNames(rows) {
|
|
2018
2369
|
return new Set(rows.map((row) => String(row.name ?? "")));
|
|
2019
2370
|
}
|
|
@@ -2134,6 +2485,36 @@ async function backfillLegacyImportanceFromConfidence(client) {
|
|
|
2134
2485
|
} catch {
|
|
2135
2486
|
}
|
|
2136
2487
|
}
|
|
2488
|
+
async function retypeLegacyCoRecallEdges(client) {
|
|
2489
|
+
const edgeInfo = await client.execute("PRAGMA table_info(co_recall_edges)");
|
|
2490
|
+
const edgeColumns = readColumnNames(edgeInfo.rows);
|
|
2491
|
+
if (!edgeColumns.has("edge_type")) {
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
const sentinel = await client.execute({
|
|
2495
|
+
sql: "SELECT 1 AS found FROM _meta WHERE key = ? LIMIT 1",
|
|
2496
|
+
args: [CO_INGEST_EDGE_RETYPE_META_KEY]
|
|
2497
|
+
});
|
|
2498
|
+
if (sentinel.rows.length > 0) {
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
await client.execute({
|
|
2502
|
+
sql: `
|
|
2503
|
+
UPDATE co_recall_edges
|
|
2504
|
+
SET edge_type = ?
|
|
2505
|
+
WHERE edge_type = ?
|
|
2506
|
+
`,
|
|
2507
|
+
args: [CO_INGEST_EDGE_TYPE, CO_RECALL_EDGE_TYPE]
|
|
2508
|
+
});
|
|
2509
|
+
await client.execute({
|
|
2510
|
+
sql: `
|
|
2511
|
+
INSERT INTO _meta (key, value, updated_at)
|
|
2512
|
+
VALUES (?, datetime('now'), datetime('now'))
|
|
2513
|
+
ON CONFLICT(key) DO NOTHING
|
|
2514
|
+
`,
|
|
2515
|
+
args: [CO_INGEST_EDGE_RETYPE_META_KEY]
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2137
2518
|
async function stampSchemaMetadata(client) {
|
|
2138
2519
|
await client.execute({
|
|
2139
2520
|
sql: `
|
|
@@ -2190,11 +2571,13 @@ async function initSchema(client) {
|
|
|
2190
2571
|
await applyReflectionImportanceCapMigration(client);
|
|
2191
2572
|
await applyReflectionRemovalMigration(client);
|
|
2192
2573
|
await backfillLegacyImportanceFromConfidence(client);
|
|
2574
|
+
await retypeLegacyCoRecallEdges(client);
|
|
2193
2575
|
for (const statement of CREATE_INDEX_STATEMENTS) {
|
|
2194
2576
|
await client.execute(statement);
|
|
2195
2577
|
}
|
|
2196
2578
|
await repairVectorIndexIfNeeded(client);
|
|
2197
2579
|
await stampSchemaMetadata(client);
|
|
2580
|
+
await initializeLastCorpusRebuildAtForEmptyCorpus(client);
|
|
2198
2581
|
}
|
|
2199
2582
|
|
|
2200
2583
|
// src/shared/infrastructure/db/schema/reset.ts
|
|
@@ -2940,6 +3323,17 @@ function resolveDefaultAppRuntimeDeps() {
|
|
|
2940
3323
|
}
|
|
2941
3324
|
|
|
2942
3325
|
export {
|
|
3326
|
+
KNOWLEDGE_TYPES,
|
|
3327
|
+
ENTRY_TYPES,
|
|
3328
|
+
IMPORTANCE_MIN,
|
|
3329
|
+
IMPORTANCE_MAX,
|
|
3330
|
+
EXPIRY_LEVELS,
|
|
3331
|
+
SCOPE_LEVELS,
|
|
3332
|
+
KNOWLEDGE_PLATFORMS,
|
|
3333
|
+
ENTRY_KINDS,
|
|
3334
|
+
TEMPORAL_CLASSES,
|
|
3335
|
+
REJECTED_CONFLICT_ENTRY_ID,
|
|
3336
|
+
isGenericSubjectKey,
|
|
2943
3337
|
STRUCTURED_CLAIM_IDENTITY_TYPES,
|
|
2944
3338
|
STATE_ANCHOR_PREDICATE,
|
|
2945
3339
|
STATE_TRANSITION_PREDICATE,
|
|
@@ -2958,13 +3352,23 @@ export {
|
|
|
2958
3352
|
createLogger,
|
|
2959
3353
|
OPENCLAW_PLUGIN_SCHEMA_STATEMENTS,
|
|
2960
3354
|
OPENCLAW_PLUGIN_COLUMN_MIGRATIONS,
|
|
2961
|
-
|
|
3355
|
+
checkVectorIntegrity,
|
|
2962
3356
|
rebuildVectorIndex,
|
|
2963
3357
|
dropFtsTriggersAndIndex,
|
|
2964
3358
|
rebuildFtsAndTriggers,
|
|
2965
3359
|
rebuildVectorIndex2,
|
|
2966
3360
|
setBulkIngestMeta,
|
|
2967
3361
|
clearBulkIngestMeta,
|
|
3362
|
+
MAX_USED_ENTRIES,
|
|
3363
|
+
CO_RECALL_EDGE_TYPE,
|
|
3364
|
+
CO_INGEST_EDGE_TYPE,
|
|
3365
|
+
strengthenCoRecallEdges,
|
|
3366
|
+
strengthenCoIngestEdges,
|
|
3367
|
+
getCoRecallNeighbors,
|
|
3368
|
+
getCoRecallEdgeCounts,
|
|
3369
|
+
getTopCoRecallEdges,
|
|
3370
|
+
getLastCorpusRebuildAt,
|
|
3371
|
+
setLastCorpusRebuildAt,
|
|
2968
3372
|
resetDb,
|
|
2969
3373
|
DEFAULT_DB_PATH,
|
|
2970
3374
|
buildBackupPath,
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
getPendingReviews,
|
|
5
5
|
rehabilitateEntry,
|
|
6
6
|
resolveReview
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LLHGEYS7.js";
|
|
8
8
|
import {
|
|
9
9
|
classifyStructuredConflictPair,
|
|
10
10
|
deleteCoRecallEdgesForEntryIds,
|
|
@@ -12,24 +12,23 @@ import {
|
|
|
12
12
|
getConflictStats,
|
|
13
13
|
resolveConflictLog,
|
|
14
14
|
retireEntries
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import {
|
|
17
|
-
getCoRecallNeighbors,
|
|
18
|
-
getTopCoRecallEdges
|
|
19
|
-
} from "./chunk-GOV2RGHZ.js";
|
|
15
|
+
} from "./chunk-FUOKOMSN.js";
|
|
20
16
|
import {
|
|
21
17
|
DEFAULT_DB_PATH,
|
|
22
18
|
backupDb,
|
|
23
19
|
buildBackupPath,
|
|
20
|
+
checkVectorIntegrity,
|
|
24
21
|
closeDb,
|
|
25
22
|
createLogger,
|
|
23
|
+
getCoRecallNeighbors,
|
|
26
24
|
getDb,
|
|
25
|
+
getTopCoRecallEdges,
|
|
27
26
|
initDb,
|
|
28
27
|
rebuildVectorIndex,
|
|
29
28
|
resetDb,
|
|
30
29
|
resolveDefaultAppRuntimeDeps,
|
|
31
30
|
walCheckpoint
|
|
32
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-2JI5DOZ2.js";
|
|
33
32
|
import {
|
|
34
33
|
toNumber,
|
|
35
34
|
toStringValue
|
|
@@ -37,7 +36,7 @@ import {
|
|
|
37
36
|
import {
|
|
38
37
|
readConfig,
|
|
39
38
|
resolveConfigDir
|
|
40
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-SJN63MTD.js";
|
|
41
40
|
|
|
42
41
|
// src/runtime/operations.ts
|
|
43
42
|
import fs from "fs/promises";
|
|
@@ -525,6 +524,7 @@ function resolveDbCommandDefaults() {
|
|
|
525
524
|
initDb: shared.initDbFn,
|
|
526
525
|
closeDb: shared.closeDbFn,
|
|
527
526
|
backupDb,
|
|
527
|
+
checkVectorIntegrity,
|
|
528
528
|
getConflictShadowSummary,
|
|
529
529
|
resetDb,
|
|
530
530
|
walCheckpoint,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveDbCommandDefaults
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4JHTJXYY.js";
|
|
4
4
|
import {
|
|
5
5
|
resolveConfigDir,
|
|
6
6
|
resolveDefaultKnowledgeDbPath,
|
|
7
7
|
resolveUserPath
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SJN63MTD.js";
|
|
9
9
|
|
|
10
10
|
// src/shared/operations/session-store-metrics/workflow.ts
|
|
11
11
|
import fs from "fs/promises";
|