cogmem 3.7.1 → 3.7.3
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 +12 -0
- package/MEMORY_ATLAS.md +2 -2
- package/MEMORY_MODEL.md +1 -1
- package/README.md +6 -5
- package/RELEASE_CHECKLIST.md +5 -5
- package/dist/atlas/MemoryAtlasService.d.ts +1 -0
- package/dist/atlas/MemoryAtlasService.js +36 -4
- package/dist/atlas/MemoryAtlasTypes.d.ts +7 -0
- package/dist/belief/BeliefStore.d.ts +3 -1
- package/dist/belief/BeliefStore.js +10 -2
- package/dist/bin/episode.js +107 -16
- package/dist/bin/import-support.js +28 -11
- package/dist/config/CogmemConfig.d.ts +1 -0
- package/dist/config/CogmemConfig.js +45 -0
- package/dist/core/MemoryGraph.js +25 -8
- package/dist/dream/DreamScheduler.d.ts +6 -1
- package/dist/dream/DreamScheduler.js +36 -6
- package/dist/engine/CognitiveGraphCompiler.js +1 -1
- package/dist/engine/ConsolidationPipeline.js +4 -4
- package/dist/engine/DeepWritePromotionPolicy.d.ts +2 -0
- package/dist/engine/DeepWritePromotionPolicy.js +25 -4
- package/dist/engine/DreamCuratorWorker.d.ts +3 -0
- package/dist/engine/DreamCuratorWorker.js +18 -3
- package/dist/engine/FactCompiler.js +2 -2
- package/dist/entity/EntityGovernanceService.js +1 -1
- package/dist/episode/EpisodeActiveScopeGuard.d.ts +3 -0
- package/dist/episode/EpisodeActiveScopeGuard.js +46 -0
- package/dist/episode/EpisodeAssembler.d.ts +20 -2
- package/dist/episode/EpisodeAssembler.js +458 -98
- package/dist/episode/EpisodeBoundaryAuditService.d.ts +70 -0
- package/dist/episode/EpisodeBoundaryAuditService.js +276 -0
- package/dist/episode/EpisodeBoundaryPolicy.d.ts +75 -0
- package/dist/episode/EpisodeBoundaryPolicy.js +189 -0
- package/dist/episode/EpisodeBoundaryReplayEngine.d.ts +65 -0
- package/dist/episode/EpisodeBoundaryReplayEngine.js +272 -0
- package/dist/episode/EpisodeImportIdentity.d.ts +1 -0
- package/dist/episode/EpisodeImportIdentity.js +1 -0
- package/dist/episode/EpisodeInvariantValidator.d.ts +16 -0
- package/dist/episode/EpisodeInvariantValidator.js +99 -0
- package/dist/episode/EpisodeSplitPlanner.d.ts +89 -0
- package/dist/episode/EpisodeSplitPlanner.js +225 -0
- package/dist/episode/EpisodeStore.d.ts +67 -2
- package/dist/episode/EpisodeStore.js +443 -59
- package/dist/episode/EpisodeTypes.d.ts +2 -2
- package/dist/episode/TurnRelationClassifier.d.ts +9 -0
- package/dist/episode/TurnRelationClassifier.js +42 -12
- package/dist/episode/index.d.ts +3 -0
- package/dist/episode/index.js +3 -0
- package/dist/factory.d.ts +62 -3
- package/dist/factory.js +308 -58
- package/dist/mcp/CoreMcpTools.js +103 -8
- package/dist/mcp/server.js +1 -1
- package/dist/migrations/0028_episode_boundary_guardrails.d.ts +3 -0
- package/dist/migrations/0028_episode_boundary_guardrails.js +47 -0
- package/dist/migrations/0029_episode_active_scope_guard.d.ts +3 -0
- package/dist/migrations/0029_episode_active_scope_guard.js +16 -0
- package/dist/migrations/0030_memory_event_local_date_source.d.ts +3 -0
- package/dist/migrations/0030_memory_event_local_date_source.js +111 -0
- package/dist/migrations/0031_episode_boundary_integrity_repair.d.ts +7 -0
- package/dist/migrations/0031_episode_boundary_integrity_repair.js +124 -0
- package/dist/migrations/SchemaMigrationRunner.d.ts +2 -0
- package/dist/migrations/SchemaMigrationRunner.js +46 -15
- package/dist/migrations/index.d.ts +6 -1
- package/dist/migrations/index.js +15 -1
- package/dist/public.d.ts +1 -1
- package/dist/public.js +1 -1
- package/dist/recall/BrainRecall.js +1 -1
- package/dist/store/DeepWriteCandidateStore.d.ts +21 -3
- package/dist/store/DeepWriteCandidateStore.js +118 -11
- package/dist/store/EntityStore.d.ts +4 -0
- package/dist/store/EntityStore.js +24 -12
- package/dist/store/EventStore.d.ts +7 -2
- package/dist/store/EventStore.js +144 -23
- package/dist/store/FactStore.js +6 -5
- package/dist/store/MemoryAtlasStore.d.ts +1 -0
- package/dist/store/MemoryAtlasStore.js +54 -0
- package/dist/types/ExtensionPoints.d.ts +3 -1
- package/dist/types/index.d.ts +1 -0
- package/examples/hermes-backend/AGENTS.md +1 -1
- package/examples/hermes-backend/README.md +1 -1
- package/examples/hermes-backend/SKILL.md +2 -2
- package/examples/hermes-backend/references/operations.md +2 -2
- package/examples/openclaw-backend/AGENTS.md +1 -1
- package/examples/openclaw-backend/README.md +1 -1
- package/examples/openclaw-backend/SKILL.md +2 -2
- package/examples/openclaw-backend/references/operations.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.7.3
|
|
4
|
+
|
|
5
|
+
- Added enforceable Episode Boundary guardrails with configurable max event, duration, idle-gap, and trusted local-date limits, including best-effort decision audit rows that never roll back successful raw event assignment.
|
|
6
|
+
- Added read-only `cogmem episode audit-boundaries` / MCP `cogmem_episode_audit_boundaries` for bounded episode health diagnostics without Dream, repair, Atlas, binding, activation, or decision-audit writes.
|
|
7
|
+
- Added deterministic read-only `cogmem episode split-plan` / MCP `cogmem_episode_split_plan` previews that preserve logical turns and return `applyableInCurrentVersion=false` with no apply command.
|
|
8
|
+
|
|
9
|
+
## 3.7.2
|
|
10
|
+
|
|
11
|
+
- Fixed Memory Atlas `graph-explore` edge projection so selected action, topic, and ordinary memory edges are merged before the final stable edge limit.
|
|
12
|
+
- Added direct internal-edge SQL for selected Atlas nodes, including chunked parameters, so bounded graph results are not silently limited to the first 30 node IDs.
|
|
13
|
+
- Added edge truncation metadata and warnings when `graph-explore` omits candidate edges after prioritizing exact matched cards/facets.
|
|
14
|
+
|
|
3
15
|
## 3.7.1
|
|
4
16
|
|
|
5
17
|
- Hardened OpenClaw volatile recall, Atlas context, turn bridge, and session-state serialization so historical text, card titles, summaries, source windows, related cards, short-term conclusions, and source anchors cannot close or create `COGMEM_*` prompt blocks.
|
package/MEMORY_ATLAS.md
CHANGED
|
@@ -10,7 +10,7 @@ Atlas projects existing project-scoped records into these node kinds:
|
|
|
10
10
|
- Facet nodes: `time`, `topic`, `issue`, `entity`, `session`, `thread`, `memoryKind`, and `actionKind`.
|
|
11
11
|
- Action frames are extracted deterministically from raw user evidence and link actor, target, action kind, time, topic, project, and evidence.
|
|
12
12
|
|
|
13
|
-
Existing memory edges remain canonical. Atlas adds derived navigation edges such as `OCCURRED_ON`, `OCCURRED_IN`, `ABOUT_TOPIC`, `PART_OF_ISSUE`, `INVOLVES_ENTITY`, `IN_SESSION`, `IN_THREAD`, and `HAS_EVIDENCE` without changing the underlying belief or binding graph. The 3.7.
|
|
13
|
+
Existing memory edges remain canonical. Atlas adds derived navigation edges such as `OCCURRED_ON`, `OCCURRED_IN`, `ABOUT_TOPIC`, `PART_OF_ISSUE`, `INVOLVES_ENTITY`, `IN_SESSION`, `IN_THREAD`, and `HAS_EVIDENCE` without changing the underlying belief or binding graph. The 3.7.2 offline curator automatically proposes only bounded `SAME_ISSUE`, `FOLLOWS_UP`, and weak `RELATED_TO` episode relations; stronger relations such as `REFINES`, `CORRECTS`, `CONTRADICTS`, and `SUPERSEDES` remain reserved for explicit evidence-bearing governance/binding flows. Entity facet node IDs are project-scoped; same-name tools or people in different projects do not share Atlas nodes.
|
|
14
14
|
|
|
15
15
|
This is not a copied tree. One episode exists once as `episode:<id>`. A day, topic, issue, entity, session, memory kind, or action kind is only a facet entrance:
|
|
16
16
|
|
|
@@ -145,4 +145,4 @@ Upgrade an existing 3.5.2 database with:
|
|
|
145
145
|
cogmem migrate --yes --backup --json
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
Migration 0025 creates and backfills the disposable projection. Migration 0026 adds exact memory-kind metadata, projection health, and candidate-review audit state. Migration 0027 corrects 3.6.0-upgraded databases by marking Atlas projections dirty until the real action/time rebuild runs. Cogmem 3.7.
|
|
148
|
+
Migration 0025 creates and backfills the disposable projection. Migration 0026 adds exact memory-kind metadata, projection health, and candidate-review audit state. Migration 0027 corrects 3.6.0-upgraded databases by marking Atlas projections dirty until the real action/time rebuild runs. Cogmem 3.7.2 keeps the projection-first schema and adds facet nodes, canonical cards, issue hints, and episode relation edges during Atlas rebuild/maintenance. Projection metadata schema `3.7.2` invalidates earlier 3.7.x Atlas v2 projections so project-scoped entity facets are rebuilt. `cogmem memory tick` refreshes only dirty projects, records rebuild errors, prunes old access telemetry, and decays navigation activation without starting a daemon.
|
package/MEMORY_MODEL.md
CHANGED
|
@@ -115,7 +115,7 @@ For pre-answer agent context, prefer `KernelAgentMemoryBackend.recallPack()` whe
|
|
|
115
115
|
|
|
116
116
|
Collection routing is enforced in compiled and raw fallback paths. Default recall includes untagged and `collection:anchor` memory. Specialized collections such as `collection:theseus` must be requested explicitly with `collection: "theseus"` or `--collection theseus`.
|
|
117
117
|
|
|
118
|
-
`MemoryKernel.buildMemoryMap()` and `cogmem memory map` expose a static self-map: anatomy, data lanes, hard bounds, counters, and commands. The map includes episode counts, unassigned raw evidence, and the sealed Episode Dream backlog. Core schema version
|
|
118
|
+
`MemoryKernel.buildMemoryMap()` and `cogmem memory map` expose a static self-map: anatomy, data lanes, hard bounds, counters, and commands. The map includes episode counts, unassigned raw evidence, and the sealed Episode Dream backlog. Core schema version 28 includes project-scoped topic nodes, aliases, relations and operations; episode cross-references and repair audit; ingest lifecycle state; per-episode Dream failure details; and best-effort episode boundary decision audit rows. Production initialization runs migrations; `EpisodeStore.initializeSchema` is compatibility bootstrap for direct tests only, with a parity test against episode migrations 22–28. Use `cogmem migrate --dry-run --json` before an upgrade and `cogmem migrate --yes --backup` to apply it without rewriting Raw Ledger evidence.
|
|
119
119
|
|
|
120
120
|
Graph edge confidence, stability, and activation are separate. Recall and maintenance may decay activation, but they must not decay provenance confidence or the stability of `SUPPORTS`, `CORRECTS`, `CONTRADICTS`, and `SUPERSEDES` evidence. `BrainGraphView` is a bounded read-only traversal surface; it cannot mutate canonical memory.
|
|
121
121
|
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ It is not a knowledge-base app, a note-taking app, a vector RAG wrapper, an Obsi
|
|
|
11
11
|
|
|
12
12
|
## Status
|
|
13
13
|
|
|
14
|
-
Current version: `3.7.
|
|
14
|
+
Current version: `3.7.3`
|
|
15
15
|
|
|
16
16
|
Distribution: npm registry. GitHub remains the source mirror and hosts this installer, but package install and upgrade resolve `cogmem` from npm by default.
|
|
17
17
|
|
|
@@ -229,7 +229,7 @@ cogmem migrate --dry-run --json
|
|
|
229
229
|
|
|
230
230
|
For a manual migration, run `cogmem migrate --yes --backup`. The migration runner adopts the existing `_meta.schema_version`, applies only later idempotent migrations, preserves Raw Ledger rows, and creates a timestamped, transaction-consistent standalone database backup before changing an on-disk database. The backup includes committed SQLite WAL pages instead of copying only the main database file.
|
|
231
231
|
|
|
232
|
-
Upgrade a 3.5.2 database, a 3.6.x database, or a pre-release schema-25 test database into the current 3.7.
|
|
232
|
+
Upgrade a 3.5.2 database, a 3.6.x database, or a pre-release schema-25 test database into the current 3.7.3 schema and projection set with one command:
|
|
233
233
|
|
|
234
234
|
```bash
|
|
235
235
|
cogmem migrate --yes --backup --json
|
|
@@ -278,11 +278,12 @@ cogmem dream tick --project my-agent --mode auto --max-episodes 10 --json
|
|
|
278
278
|
|
|
279
279
|
Raw events are always written first. `KernelAgentMemoryBackend` and OpenClaw plugin 0.7.1 assemble live turns automatically. The foreground hook uses deterministic rules and previous assistant/user context; background import and repair paths may use the advisory hybrid classifier. Advisory output is allow-listed and cannot directly mutate durable memory. Unknown turns now fail closed as ambiguous. Continuation requires explicit continuation language or project/topic/entity/semantic overlap; Cogmem does not route domains with an expanding hard-coded keyword dictionary.
|
|
280
280
|
|
|
281
|
-
Hookless MCP agents can call `cogmem_episode_append` or bounded `cogmem_episode_import`. Existing OpenClaw/Hermes import commands use stable content identities and the same episode schema. Low-confidence imported groups soft-seal for review unless an operator explicitly forces sealing. Cogmem skips import batch sealing for empty episode boundaries and skips legacy empty Dream jobs so one bad imported episode cannot block the queue. Episode semantic summaries and closure receipts are control hints, never durable evidence; every Dream candidate must cite a non-empty subset of the episode's raw event IDs and still pass CPU governance.
|
|
281
|
+
Hookless MCP agents can call `cogmem_episode_append` or bounded `cogmem_episode_import`. Existing OpenClaw/Hermes import commands use stable content identities and the same episode schema. Low-confidence imported groups soft-seal for review unless an operator explicitly forces sealing. Episode boundary guardrails can enforce configurable max-event, duration, idle-gap, and trusted local-date limits before advisory review, while decision audit writes remain best-effort. Cogmem skips import batch sealing for empty episode boundaries and skips legacy empty Dream jobs so one bad imported episode cannot block the queue. Episode semantic summaries and closure receipts are control hints, never durable evidence; every Dream candidate must cite a non-empty subset of the episode's raw event IDs and still pass CPU governance.
|
|
282
282
|
|
|
283
283
|
User-shaped topic state is project-scoped. Explicit user operations become active and auditable; model-proposed topics, aliases, and relations remain candidates. Use MCP `cogmem_topic_list` to inspect nodes, `cogmem_topic_operate` to create, rename, alias, move, merge, split, or relate topics, and `cogmem_topic_rollback` to reverse an audited operation. Alias collisions fail closed as `needs_review`; applications can also call `TopicGovernance.rollback()` through the public API.
|
|
284
284
|
|
|
285
285
|
Episode surgery is available through `cogmem episode split|merge|move-event|reclassify|requeue-dream` or MCP `cogmem_episode_repair`. Structural repair recomputes closure receipts, invalidates candidates derived from the old episode boundary, preserves cross-references, requeues eligible sealed episodes, and writes an audit record.
|
|
286
|
+
Use `cogmem episode audit-boundaries` for read-only boundary diagnostics and `cogmem episode split-plan` for deterministic read-only split previews. The split preview does not call repair, does not move events, and returns no executable apply command in 3.7.3.
|
|
286
287
|
|
|
287
288
|
Inspect queue state:
|
|
288
289
|
|
|
@@ -304,7 +305,7 @@ cogmem memory review --project my-agent --id <candidate-id> --action approve --a
|
|
|
304
305
|
|
|
305
306
|
`cogmem memory map` remains the system anatomy map. Memory Atlas is the content map. It does not replace recall or create a second fact store; it projects existing topics, entities, clusters, episodes, beliefs, action frames, time buckets, bindings, and evidence into a bounded navigation surface.
|
|
306
307
|
|
|
307
|
-
In 3.7.
|
|
308
|
+
In 3.7.2, Atlas is a multi-dimensional navigation graph rather than a folder tree. A remembered episode has one canonical node such as `episode:<id>`. Time, topic, issue, entity, session/thread, memory-kind, and action-kind nodes are facet entrances connected to that canonical episode with typed edges. Results dedupe by `canonicalId` and explain `matchedFacets` plus `matchedPaths`.
|
|
308
309
|
|
|
309
310
|
```bash
|
|
310
311
|
cogmem memory graph --project my-agent --json
|
|
@@ -798,7 +799,7 @@ npm pack --dry-run --json
|
|
|
798
799
|
npm publish --dry-run --access public
|
|
799
800
|
```
|
|
800
801
|
|
|
801
|
-
Create a GitHub Release from the matching version tag, for example `v3.7.
|
|
802
|
+
Create a GitHub Release from the matching version tag, for example `v3.7.3`. The `.github/workflows/publish.yml` workflow publishes to npm only when the release is published, not when a tag is pushed. The npm Trusted Publisher entry must match repository `liuqin164/cogmem`, workflow file `publish.yml`, and environment `npm publish`.
|
|
802
803
|
|
|
803
804
|
Publish manually only for emergency fallback:
|
|
804
805
|
|
package/RELEASE_CHECKLIST.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# cogmem 3.7.
|
|
1
|
+
# cogmem 3.7.3 Release Checklist
|
|
2
2
|
|
|
3
3
|
This release is distributed through the npm registry. GitHub remains the source and review mirror.
|
|
4
4
|
|
|
5
5
|
## Required Metadata
|
|
6
6
|
|
|
7
7
|
- `package.json` name is `cogmem`.
|
|
8
|
-
- `package.json` version is `3.7.
|
|
8
|
+
- `package.json` version is `3.7.3`.
|
|
9
9
|
- `package.json` has `publishConfig.access = public`.
|
|
10
10
|
- Public export `.` points to `dist/public.js` and `dist/public.d.ts`.
|
|
11
11
|
- Internal subpath `./internal` exists only as an explicit advanced subpath.
|
|
@@ -39,7 +39,7 @@ This release is distributed through the npm registry. GitHub remains the source
|
|
|
39
39
|
- `cogmem episode`
|
|
40
40
|
- `cogmem dream`
|
|
41
41
|
|
|
42
|
-
MCP `tools/list` includes strategy, episode append/import/status/seal/repair, topic list/operate/rollback, candidate review, conditional Dream tick/status, prospective tools, all seven read-only Memory Atlas queries, and explicit `cogmem_graph_touch`. Atlas queries declare read-only/idempotent semantics and never change activation; only touch records selected-node use. Episode append/import never run Dream. MCP Dream tick requires `maintenanceMode: true` to process work; otherwise it is recommendation-only.
|
|
42
|
+
MCP `tools/list` includes strategy, episode append/import/status/audit-boundaries/split-plan/seal/repair, topic list/operate/rollback, candidate review, conditional Dream tick/status, prospective tools, all seven read-only Memory Atlas queries, and explicit `cogmem_graph_touch`. Atlas queries declare read-only/idempotent semantics and never change activation; only touch records selected-node use. Episode append/import never run Dream. Episode audit-boundaries and split-plan are read-only and never repair, split, Dream, Atlas, bind, or update activation. MCP Dream tick requires `maintenanceMode: true` to process work; otherwise it is recommendation-only.
|
|
43
43
|
|
|
44
44
|
## Required Documentation
|
|
45
45
|
|
|
@@ -61,7 +61,7 @@ MCP `tools/list` includes strategy, episode append/import/status/seal/repair, to
|
|
|
61
61
|
- README and skills explain Raw Ledger-first episode assembly, soft/hard sealing, explicit conditional Dream ticks, raw-event evidence grounding, repair/retry, and hookless Hermes MCP/import usage.
|
|
62
62
|
- README and skills give the full post-import maintenance sequence: status, episode status, Dream status, bounded Dream tick, candidate listing, govern candidate, needs-confirmation listing, explicit review, and recall verification.
|
|
63
63
|
- README and skills explain that 3.6.4+ skips empty imported episode boundaries and legacy empty Dream jobs instead of letting them abort `dream tick`.
|
|
64
|
-
- README and skills explain the 3.7.
|
|
64
|
+
- README and skills explain the 3.7.3 agent operations protocol: `memory plan` for next actions, default grouped `memory candidates --json`, `memory list --since/--until/--order`, historical-discussion recall intent, Atlas canonical cards, `matchedFacets`, `relatedButNotSelected`, `relaxationTrace`, Atlas evidence `sourceLocator`, cursor-based `memory bind`, and read-only episode boundary audit/split planning.
|
|
65
65
|
- README, `MEMORY_ATLAS.md`, and installed skills explain the multi-dimensional Atlas model: a canonical episode/raw event exists once, facet nodes connect through typed edges, query results intersect facets, and display/injection dedupes by `canonicalId`.
|
|
66
66
|
- `connect openclaw|hermes --json` documents structured `nextSteps`; `nextCommands` must contain only agent-safe, non-interactive commands and must not include `cogmem init`, `cogmem-init`, gateway restart, or Hermes reload.
|
|
67
67
|
- README and skills document `cogmem mcp` as the preferred MCP server command for new configs while preserving `cogmem-mcp` as a compatibility bin.
|
|
@@ -94,7 +94,7 @@ npm publish --dry-run --access public
|
|
|
94
94
|
|
|
95
95
|
The pack dry-run must include built public API files, CLI files, examples, docs, and `install.sh`. It must not include local databases or machine-specific files.
|
|
96
96
|
|
|
97
|
-
After verification, create a GitHub Release from the matching version tag, for example `v3.7.
|
|
97
|
+
After verification, create a GitHub Release from the matching version tag, for example `v3.7.3`. The release workflow publishes through npm Trusted Publishing when the release is published. It must not publish on tag push alone.
|
|
98
98
|
|
|
99
99
|
Emergency manual fallback:
|
|
100
100
|
|
|
@@ -57,8 +57,8 @@ export class MemoryAtlasService {
|
|
|
57
57
|
nodes = uniqueNodes([...actions.map((action) => this.store.getNode(action.id, projectId)).filter((node) => Boolean(node)), ...nodes]).slice(0, limit);
|
|
58
58
|
}
|
|
59
59
|
const nodesWithEvidence = this.attachEvidence(nodes, projectId, options);
|
|
60
|
-
const
|
|
61
|
-
const result = slice(projectId, nodesWithEvidence, edges, query);
|
|
60
|
+
const edgeProjection = this.edgeProjection(nodesWithEvidence, projectId, exactMatchedNodeIds(cards, facetResult.plan));
|
|
61
|
+
const result = slice(projectId, nodesWithEvidence, edgeProjection.edges, query);
|
|
62
62
|
result.facets = {
|
|
63
63
|
...facetsForPlan(facetResult.plan),
|
|
64
64
|
legacy: { time: compiled.range, target: target.labels.join(', ') || compiled.target, memoryKinds: compiled.memoryKinds, keywords: compiled.keywords },
|
|
@@ -68,6 +68,10 @@ export class MemoryAtlasService {
|
|
|
68
68
|
result.cards = this.attachCardEvidence(cards, projectId, options);
|
|
69
69
|
if (facetResult.relaxationTrace.length)
|
|
70
70
|
result.relaxationTrace = facetResult.relaxationTrace;
|
|
71
|
+
if (edgeProjection.truncation) {
|
|
72
|
+
result.edgeTruncation = edgeProjection.truncation;
|
|
73
|
+
result.warnings.push(`edges_truncated:${edgeProjection.truncation.omitted}_omitted`);
|
|
74
|
+
}
|
|
71
75
|
const hasFacet = Boolean(compiled.range || compiled.target || compiled.memoryKinds.length || compiled.tokens.length);
|
|
72
76
|
result.coldMemoryResurrected = hasFacet && nodes.some((node) => node.activation <= 0.1);
|
|
73
77
|
return result;
|
|
@@ -248,9 +252,23 @@ export class MemoryAtlasService {
|
|
|
248
252
|
});
|
|
249
253
|
}
|
|
250
254
|
edgesFor(nodes, projectId) {
|
|
255
|
+
return this.edgeProjection(nodes, projectId).edges;
|
|
256
|
+
}
|
|
257
|
+
edgeProjection(nodes, projectId, priorityIds = new Set()) {
|
|
251
258
|
const ids = new Set(nodes.map((node) => node.id));
|
|
252
|
-
|
|
253
|
-
|
|
259
|
+
const limit = 60;
|
|
260
|
+
const candidates = this.safeEdges(this.store.listEdgesWithinNodes(projectId, [...ids], 4000)
|
|
261
|
+
.filter((edge) => ids.has(edge.source) && ids.has(edge.target)), projectId);
|
|
262
|
+
const sorted = uniqueEdges(candidates).sort((left, right) => edgePriority(right, priorityIds) - edgePriority(left, priorityIds)
|
|
263
|
+
|| right.confidence - left.confidence
|
|
264
|
+
|| edgeKey(left).localeCompare(edgeKey(right)));
|
|
265
|
+
const edges = sorted.slice(0, limit);
|
|
266
|
+
return {
|
|
267
|
+
edges,
|
|
268
|
+
truncation: sorted.length > edges.length
|
|
269
|
+
? { limit, returned: edges.length, omitted: sorted.length - edges.length, candidateCount: sorted.length, prioritized: priorityIds.size > 0 }
|
|
270
|
+
: undefined,
|
|
271
|
+
};
|
|
254
272
|
}
|
|
255
273
|
safeEdges(edges, projectId) {
|
|
256
274
|
return edges.map((edge) => ({ ...edge, evidenceEventIds: edge.evidenceEventIds.filter((eventId) => {
|
|
@@ -294,6 +312,20 @@ function uniqueIds(ids) { return Array.from(new Set(ids)); }
|
|
|
294
312
|
function uniqueEdges(edges) {
|
|
295
313
|
return Array.from(new Map(edges.map((edge) => [`${edge.source}\0${edge.relation}\0${edge.target}`, edge])).values());
|
|
296
314
|
}
|
|
315
|
+
function edgeKey(edge) { return `${edge.source}\0${edge.relation}\0${edge.target}`; }
|
|
316
|
+
function edgePriority(edge, priorityIds) {
|
|
317
|
+
return (priorityIds.has(edge.source) ? 1 : 0) + (priorityIds.has(edge.target) ? 1 : 0);
|
|
318
|
+
}
|
|
319
|
+
function exactMatchedNodeIds(cards, plan) {
|
|
320
|
+
return new Set([
|
|
321
|
+
...cards.flatMap((card) => [
|
|
322
|
+
card.canonicalId,
|
|
323
|
+
...card.matchedFacets.map((facet) => facet.nodeId),
|
|
324
|
+
...card.matchedPaths.flatMap((path) => path.via),
|
|
325
|
+
]),
|
|
326
|
+
...plan.facets.map((facet) => facet.nodeId).filter((id) => Boolean(id)),
|
|
327
|
+
]);
|
|
328
|
+
}
|
|
297
329
|
function edgeTraversalCost(edge) {
|
|
298
330
|
const confidence = Math.max(0.01, Math.min(1, edge.confidence));
|
|
299
331
|
const relationPenalty = /^(EVIDENCED_BY|DERIVED_FROM|TARGETS|OCCURRED_IN|SUPPORTS|ABOUT|MENTIONS)$/u.test(edge.relation)
|
|
@@ -101,6 +101,13 @@ export interface MemoryAtlasSlice {
|
|
|
101
101
|
edges: MemoryAtlasEdge[];
|
|
102
102
|
nextActions: MemoryAtlasNextAction[];
|
|
103
103
|
warnings: string[];
|
|
104
|
+
edgeTruncation?: {
|
|
105
|
+
limit: number;
|
|
106
|
+
returned: number;
|
|
107
|
+
omitted: number;
|
|
108
|
+
candidateCount: number;
|
|
109
|
+
prioritized: boolean;
|
|
110
|
+
};
|
|
104
111
|
facets?: {
|
|
105
112
|
planner?: {
|
|
106
113
|
intent: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Database from 'bun:sqlite';
|
|
1
2
|
import type { BeliefCandidate, BeliefConflictCandidate, BeliefEvidenceRecord, BeliefRecord, BeliefRevisionDecision } from '../types/index.js';
|
|
2
3
|
import { EventStore } from '../store/EventStore.js';
|
|
3
4
|
import type { PolicyExecutionRecord } from '../store/PolicyExecutionStore.js';
|
|
@@ -6,8 +7,9 @@ export declare class BeliefStore {
|
|
|
6
7
|
private static readonly SOURCE_TRUST;
|
|
7
8
|
private static readonly SCOPE_PRIORITY;
|
|
8
9
|
private db;
|
|
10
|
+
private ownsDb;
|
|
9
11
|
private closed;
|
|
10
|
-
constructor(dbPath?: string, eventStore?: EventStore | undefined);
|
|
12
|
+
constructor(dbPath?: string | Database, eventStore?: EventStore | undefined);
|
|
11
13
|
private initializeSchema;
|
|
12
14
|
findByCanonicalKey(canonicalKey: string): BeliefRecord[];
|
|
13
15
|
countActive(projectId?: string): number;
|
|
@@ -20,10 +20,17 @@ export class BeliefStore {
|
|
|
20
20
|
global: 1
|
|
21
21
|
};
|
|
22
22
|
db;
|
|
23
|
+
ownsDb = true;
|
|
23
24
|
closed = false;
|
|
24
25
|
constructor(dbPath = ':memory:', eventStore) {
|
|
25
26
|
this.eventStore = eventStore;
|
|
26
|
-
|
|
27
|
+
if (dbPath instanceof Database) {
|
|
28
|
+
this.db = dbPath;
|
|
29
|
+
this.ownsDb = false;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.db = new Database(dbPath);
|
|
33
|
+
}
|
|
27
34
|
this.initializeSchema();
|
|
28
35
|
}
|
|
29
36
|
initializeSchema() {
|
|
@@ -892,7 +899,8 @@ export class BeliefStore {
|
|
|
892
899
|
close() {
|
|
893
900
|
if (this.closed)
|
|
894
901
|
return;
|
|
895
|
-
this.
|
|
902
|
+
if (this.ownsDb)
|
|
903
|
+
this.db.close();
|
|
896
904
|
this.closed = true;
|
|
897
905
|
}
|
|
898
906
|
}
|
package/dist/bin/episode.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { createReadStream, existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { createInterface } from 'node:readline';
|
|
4
5
|
import { createStableImportIdentityFactory } from '../episode/EpisodeImportIdentity.js';
|
|
5
6
|
import { createMemoryKernel, createMemoryKernelFromConfig } from '../factory.js';
|
|
@@ -24,12 +25,15 @@ function parseArgs(argv) {
|
|
|
24
25
|
}
|
|
25
26
|
function usage() {
|
|
26
27
|
return [
|
|
27
|
-
'Usage: cogmem episode <append|import|list|get|seal|status|repair|split|merge|move-event|reclassify|requeue-dream> [args]',
|
|
28
|
+
'Usage: cogmem episode <append|import|list|get|seal|status|audit-boundaries|boundary-decisions|split-plan|repair|split|merge|move-event|reclassify|requeue-dream> [args]',
|
|
28
29
|
' append --project <id> --session <id> --source-agent <id> --role <role> --text <text>',
|
|
29
30
|
' import --project <id> --session <id> --source-agent <id> --format jsonl --file <path> [--seal-batch] [--force-seal] [--chunk-size <n>] [--checkpoint-file <path>] [--resume] [--start-line <n>] [--end-line <n>] [--max-lines <n>] [--skip-errors] [--max-errors <n>]',
|
|
30
31
|
' list|status [--project <id>] [--session <id>] [--json]',
|
|
31
|
-
' get --episode <id> [--json]',
|
|
32
|
-
' seal --episode <id> [--mode soft|hard|manual|batch] [--reason <reason>]',
|
|
32
|
+
' get --project <id> --episode <id> [--json]',
|
|
33
|
+
' seal --project <id> --episode <id> [--mode soft|hard|manual|batch] [--reason <reason>]',
|
|
34
|
+
' audit-boundaries --project <id> [--episode <id>] [--status open|soft_sealed|sealed] [--limit <n>] [--cursor <cursor>]',
|
|
35
|
+
' boundary-decisions --project <id> [--event <eventId>] [--limit <n>]',
|
|
36
|
+
' split-plan --project <id> --episode <id> [--include-event-ids]',
|
|
33
37
|
' repair [--project <id>] [--since <globalSeq>] [--limit <n>]',
|
|
34
38
|
' split --project <id> --episode <id> --events <eventId,eventId>',
|
|
35
39
|
' merge --project <id> --source-episode <id> --target-episode <id>',
|
|
@@ -62,6 +66,8 @@ async function main() {
|
|
|
62
66
|
projectId: requiredArg(args, 'project'), sessionId: requiredArg(args, 'session'),
|
|
63
67
|
sourceAgent: requiredArg(args, 'source-agent'), role: roleArg(args.role), text: requiredArg(args, 'text'),
|
|
64
68
|
externalMessageId: stringArg(args, 'external-id'), timestamp: numberArg(args, 'timestamp'),
|
|
69
|
+
threadId: stringArg(args, 'thread-id'), turnId: stringArg(args, 'turn-id'), turnSeq: numberArg(args, 'turn-seq'),
|
|
70
|
+
localDate: stringArg(args, 'local-date'), eventOrdinal: numberArg(args, 'event-ordinal'),
|
|
65
71
|
});
|
|
66
72
|
}
|
|
67
73
|
else if (args.command === 'import') {
|
|
@@ -74,14 +80,44 @@ async function main() {
|
|
|
74
80
|
result = { episodes: kernel.listEpisodes({ projectId, sessionId, limit: numberArg(args, 'limit') }), dream: kernel.getEpisodeDreamStatus(projectId) };
|
|
75
81
|
}
|
|
76
82
|
else if (args.command === 'get') {
|
|
83
|
+
const requestedProject = requiredArg(args, 'project');
|
|
77
84
|
const episodeId = requiredArg(args, 'episode');
|
|
78
|
-
|
|
85
|
+
const episode = kernel.getEpisode(episodeId);
|
|
86
|
+
if (!episode || episode.projectId !== requestedProject)
|
|
87
|
+
throw new Error(`episode_project_mismatch:${episodeId}`);
|
|
88
|
+
result = { episode, events: kernel.listEpisodeEventLinks(episodeId), closureReceipts: kernel.listEpisodeClosureReceipts({ episodeId, projectId: requestedProject }) };
|
|
79
89
|
}
|
|
80
90
|
else if (args.command === 'seal') {
|
|
81
|
-
|
|
91
|
+
const episodeId = requiredArg(args, 'episode');
|
|
92
|
+
const requestedProject = requiredArg(args, 'project');
|
|
93
|
+
const episode = kernel.getEpisode(episodeId);
|
|
94
|
+
if (!episode || episode.projectId !== requestedProject)
|
|
95
|
+
throw new Error(`episode_project_mismatch:${episodeId}`);
|
|
96
|
+
result = kernel.sealEpisode(episodeId, {
|
|
82
97
|
mode: closureModeArg(stringArg(args, 'mode')), reason: stringArg(args, 'reason') || 'cli_manual_seal',
|
|
83
98
|
});
|
|
84
99
|
}
|
|
100
|
+
else if (args.command === 'audit-boundaries') {
|
|
101
|
+
result = kernel.auditEpisodeBoundaries({
|
|
102
|
+
projectId: requiredArg(args, 'project'), episodeId: stringArg(args, 'episode'),
|
|
103
|
+
status: statusArg(stringArg(args, 'status')), limit: numberArg(args, 'limit'), cursor: stringArg(args, 'cursor'),
|
|
104
|
+
maxEvents: numberArg(args, 'max-events'), maxDurationMs: numberArg(args, 'max-duration-ms'),
|
|
105
|
+
maxIdleGapMs: numberArg(args, 'max-idle-gap-ms'), timezone: stringArg(args, 'timezone'),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else if (args.command === 'boundary-decisions') {
|
|
109
|
+
result = { decisions: kernel.listEpisodeBoundaryDecisions({
|
|
110
|
+
projectId: requiredArg(args, 'project'), primaryEventId: stringArg(args, 'event'), limit: numberArg(args, 'limit'),
|
|
111
|
+
}) };
|
|
112
|
+
}
|
|
113
|
+
else if (args.command === 'split-plan') {
|
|
114
|
+
result = kernel.planEpisodeSplit({
|
|
115
|
+
projectId: requiredArg(args, 'project'), episodeId: requiredArg(args, 'episode'),
|
|
116
|
+
maxEvents: numberArg(args, 'max-events'), maxDurationMs: numberArg(args, 'max-duration-ms'),
|
|
117
|
+
maxIdleGapMs: numberArg(args, 'max-idle-gap-ms'), timezone: stringArg(args, 'timezone'),
|
|
118
|
+
includeEventIds: args['include-event-ids'] === true,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
85
121
|
else if (args.command === 'repair') {
|
|
86
122
|
result = kernel.repairEpisodes({ projectId, sinceGlobalSeq: numberArg(args, 'since'), limit: numberArg(args, 'limit') });
|
|
87
123
|
}
|
|
@@ -126,9 +162,17 @@ async function importJsonl(kernel, args) {
|
|
|
126
162
|
const file = requiredArg(args, 'file');
|
|
127
163
|
const checkpointFile = stringArg(args, 'checkpoint-file') || `${file}.cogmem-checkpoint.json`;
|
|
128
164
|
const chunkSize = Math.max(1, Math.min(Math.trunc(numberArg(args, 'chunk-size') ?? 500), 5000));
|
|
165
|
+
const fileHash = createHash('sha256').update(readFileSync(file)).digest('hex');
|
|
129
166
|
const checkpoint = args.resume === true && existsSync(checkpointFile)
|
|
130
167
|
? JSON.parse(readFileSync(checkpointFile, 'utf8'))
|
|
131
168
|
: {};
|
|
169
|
+
const checkpointPrefixHash = checkpoint.processedLine ? sourcePrefixHash(file, checkpoint.processedLine) : undefined;
|
|
170
|
+
if (args.resume === true && checkpoint.fileHash && ((checkpoint.processedPrefixHash ? checkpoint.processedPrefixHash !== checkpointPrefixHash : checkpoint.fileHash !== fileHash)
|
|
171
|
+
|| checkpoint.projectId !== projectId
|
|
172
|
+
|| checkpoint.sourceAgent !== sourceAgent
|
|
173
|
+
|| checkpoint.sessionId !== sessionId
|
|
174
|
+
|| checkpoint.format !== format))
|
|
175
|
+
throw new Error('episode_import_checkpoint_source_mismatch');
|
|
132
176
|
const resumeAfter = Math.max(0, checkpoint.processedLine || 0);
|
|
133
177
|
const startLine = Math.max(1, Math.trunc(numberArg(args, 'start-line') ?? 1));
|
|
134
178
|
const endLine = Math.max(startLine, Math.trunc(numberArg(args, 'end-line') ?? Number.MAX_SAFE_INTEGER));
|
|
@@ -151,15 +195,38 @@ async function importJsonl(kernel, args) {
|
|
|
151
195
|
let duplicates = 0;
|
|
152
196
|
let processed = 0;
|
|
153
197
|
let lineNumber = 0;
|
|
198
|
+
let lastProcessedLine = resumeAfter;
|
|
154
199
|
let selectedLines = 0;
|
|
200
|
+
let windowStopped = false;
|
|
155
201
|
const errors = [];
|
|
156
202
|
const reader = createInterface({ input: createReadStream(file, { encoding: 'utf8' }), crlfDelay: Infinity });
|
|
157
203
|
for await (const rawLine of reader) {
|
|
158
204
|
lineNumber += 1;
|
|
159
205
|
if (!rawLine.trim())
|
|
160
206
|
continue;
|
|
161
|
-
if (lineNumber > endLine || selectedLines >= maxLines)
|
|
207
|
+
if (lineNumber > endLine || selectedLines >= maxLines) {
|
|
208
|
+
windowStopped = true;
|
|
162
209
|
break;
|
|
210
|
+
}
|
|
211
|
+
if (lineNumber <= resumeAfter || lineNumber < startLine) {
|
|
212
|
+
// Rebuild only the deterministic fallback identity state. A malformed
|
|
213
|
+
// skipped line must remain skipped during resume.
|
|
214
|
+
try {
|
|
215
|
+
const prior = JSON.parse(rawLine);
|
|
216
|
+
const priorText = typeof prior.text === 'string' ? prior.text : typeof prior.content === 'string' ? prior.content : undefined;
|
|
217
|
+
const hasExplicitIdentity = typeof prior.externalMessageId === 'string' || typeof prior.id === 'string';
|
|
218
|
+
if (priorText && !hasExplicitIdentity) {
|
|
219
|
+
const priorSession = typeof prior.sessionId === 'string' ? prior.sessionId : sessionId;
|
|
220
|
+
const priorRole = roleValue(prior.role);
|
|
221
|
+
const priorTimestamp = prior.timestamp === undefined ? undefined : timeValue(prior.timestamp);
|
|
222
|
+
getIdentity(priorSession)({ role: priorRole, text: priorText, timestamp: priorTimestamp });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// The checkpoint already records this line as skipped/failed.
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
163
230
|
try {
|
|
164
231
|
const message = JSON.parse(rawLine);
|
|
165
232
|
const text = typeof message.text === 'string' ? message.text : typeof message.content === 'string' ? message.content : undefined;
|
|
@@ -170,21 +237,24 @@ async function importJsonl(kernel, args) {
|
|
|
170
237
|
const resolvedSessionId = typeof message.sessionId === 'string' ? message.sessionId : sessionId;
|
|
171
238
|
const role = roleValue(message.role);
|
|
172
239
|
const timestamp = timeValue(message.timestamp);
|
|
240
|
+
const threadId = typeof message.threadId === 'string' ? message.threadId : undefined;
|
|
241
|
+
const turnId = typeof message.turnId === 'string' ? message.turnId : undefined;
|
|
242
|
+
const turnSeq = typeof message.turnSeq === 'number' && Number.isFinite(message.turnSeq) ? message.turnSeq : undefined;
|
|
243
|
+
const localDate = typeof message.localDate === 'string' ? message.localDate : undefined;
|
|
244
|
+
const eventOrdinal = typeof message.eventOrdinal === 'number' && Number.isFinite(message.eventOrdinal) ? message.eventOrdinal : undefined;
|
|
173
245
|
const externalMessageId = typeof message.externalMessageId === 'string'
|
|
174
246
|
? message.externalMessageId
|
|
175
247
|
: typeof message.id === 'string'
|
|
176
248
|
? message.id
|
|
177
249
|
: getIdentity(resolvedSessionId)({ role, text, timestamp });
|
|
178
|
-
// Rebuild occurrence counters while streaming past the checkpoint/start line.
|
|
179
|
-
if (lineNumber <= resumeAfter || lineNumber < startLine)
|
|
180
|
-
continue;
|
|
181
250
|
selectedLines += 1;
|
|
182
251
|
const result = await kernel.appendEpisodeMessageAsync({
|
|
183
|
-
projectId, sourceAgent, sessionId: resolvedSessionId, role, text, timestamp,
|
|
252
|
+
projectId, sourceAgent, sessionId: resolvedSessionId, role, text, timestamp, threadId, turnId, turnSeq, localDate, eventOrdinal,
|
|
184
253
|
externalMessageId,
|
|
185
254
|
metadata: { imported: true, importFormat: format },
|
|
186
255
|
});
|
|
187
256
|
processed += 1;
|
|
257
|
+
lastProcessedLine = lineNumber;
|
|
188
258
|
result.created ? imported += 1 : duplicates += 1;
|
|
189
259
|
if (result.episodeId)
|
|
190
260
|
episodeIds.add(result.episodeId);
|
|
@@ -193,27 +263,35 @@ async function importJsonl(kernel, args) {
|
|
|
193
263
|
if (result.ignored)
|
|
194
264
|
ignoredEventIds.push(result.eventId);
|
|
195
265
|
if (processed % chunkSize === 0)
|
|
196
|
-
writeCheckpoint(checkpointFile, {
|
|
266
|
+
writeCheckpoint(checkpointFile, {
|
|
267
|
+
processedLine: lineNumber, lastProcessedLine: lineNumber, processed, projectId, sourceAgent, sessionId, format, fileHash,
|
|
268
|
+
processedPrefixHash: sourcePrefixHash(file, lineNumber), fileCompleted: false, windowCompleted: false,
|
|
269
|
+
});
|
|
197
270
|
}
|
|
198
271
|
catch (error) {
|
|
199
272
|
const message = error instanceof Error ? error.message : String(error);
|
|
200
273
|
errors.push({ line: lineNumber, error: message });
|
|
201
274
|
writeCheckpoint(checkpointFile, {
|
|
202
275
|
failedAtLine: lineNumber, error: message, resumeFrom: lineNumber,
|
|
203
|
-
lastProcessedLine
|
|
204
|
-
processed, projectId, sourceAgent, sessionId,
|
|
276
|
+
lastProcessedLine, processedLine: lastProcessedLine,
|
|
277
|
+
processed, projectId, sourceAgent, sessionId, format, fileHash, processedPrefixHash: sourcePrefixHash(file, lastProcessedLine), fileCompleted: false, windowCompleted: false,
|
|
205
278
|
});
|
|
206
279
|
if (!skipErrors || errors.length > maxErrors)
|
|
207
280
|
throw error;
|
|
281
|
+
lastProcessedLine = lineNumber;
|
|
208
282
|
}
|
|
209
283
|
}
|
|
210
|
-
writeCheckpoint(checkpointFile, {
|
|
284
|
+
writeCheckpoint(checkpointFile, {
|
|
285
|
+
processedLine: lastProcessedLine, processed, projectId, sourceAgent, sessionId, format, fileHash, processedPrefixHash: sourcePrefixHash(file, lastProcessedLine),
|
|
286
|
+
completed: !windowStopped, fileCompleted: !windowStopped, windowCompleted: true,
|
|
287
|
+
hasMore: windowStopped, nextLine: windowStopped ? lineNumber : undefined,
|
|
288
|
+
});
|
|
211
289
|
const closureReceipts = args['seal-batch'] === true
|
|
212
290
|
? [...episodeIds].map((episodeId) => kernel.sealImportedEpisode(episodeId, { reason: 'cli_batch_boundary', force: args['force-seal'] === true }))
|
|
213
291
|
: [];
|
|
214
292
|
return {
|
|
215
293
|
importId: `episode-import:${sourceAgent}:${sessionId}`,
|
|
216
|
-
imported, duplicates, processed, errors, resumeFrom:
|
|
294
|
+
imported, duplicates, processed, errors, resumeFrom: lastProcessedLine + 1, checkpointFile,
|
|
217
295
|
episodeIds: [...episodeIds], unassignedEventIds, ignoredEventIds, closureReceipts, dreamRan: false,
|
|
218
296
|
};
|
|
219
297
|
}
|
|
@@ -222,6 +300,10 @@ function writeCheckpoint(path, value) {
|
|
|
222
300
|
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
223
301
|
renameSync(temporary, path);
|
|
224
302
|
}
|
|
303
|
+
function sourcePrefixHash(file, lines) {
|
|
304
|
+
const text = readFileSync(file, 'utf8');
|
|
305
|
+
return createHash('sha256').update(text.split(/\r?\n/u).slice(0, Math.max(0, lines)).join('\n')).digest('hex');
|
|
306
|
+
}
|
|
225
307
|
function stringArg(args, key) { return typeof args[key] === 'string' && args[key] ? args[key] : undefined; }
|
|
226
308
|
function requiredArg(args, key) { const value = stringArg(args, key); if (!value)
|
|
227
309
|
throw new Error(`--${key} is required`); return value; }
|
|
@@ -248,6 +330,13 @@ function dreamModeArg(value) {
|
|
|
248
330
|
return value;
|
|
249
331
|
throw new Error('mode must be micro, normal, or deep');
|
|
250
332
|
}
|
|
333
|
+
function statusArg(value) {
|
|
334
|
+
if (!value)
|
|
335
|
+
return undefined;
|
|
336
|
+
if (value === 'open' || value === 'soft_sealed' || value === 'sealed')
|
|
337
|
+
return value;
|
|
338
|
+
throw new Error('status must be open, soft_sealed, or sealed');
|
|
339
|
+
}
|
|
251
340
|
function episodeTypeArg(value) {
|
|
252
341
|
if (!value)
|
|
253
342
|
return undefined;
|
|
@@ -256,6 +345,8 @@ function episodeTypeArg(value) {
|
|
|
256
345
|
throw new Error('invalid episode type');
|
|
257
346
|
}
|
|
258
347
|
function timeValue(value) {
|
|
348
|
+
if (value === undefined || value === null || value === '')
|
|
349
|
+
return undefined;
|
|
259
350
|
if (typeof value === 'number' && Number.isFinite(value))
|
|
260
351
|
return value;
|
|
261
352
|
if (typeof value === 'string') {
|
|
@@ -263,6 +354,6 @@ function timeValue(value) {
|
|
|
263
354
|
if (!Number.isNaN(parsed))
|
|
264
355
|
return parsed;
|
|
265
356
|
}
|
|
266
|
-
|
|
357
|
+
throw new Error('timestamp must be a finite number or parseable date');
|
|
267
358
|
}
|
|
268
359
|
main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); });
|
|
@@ -336,22 +336,26 @@ async function recordRawImportedEvidence(kernel, projectId, envelope) {
|
|
|
336
336
|
const threadId = sourceRef?.threadId || stringRecordField(metadata.threadId) || record.provenance.sourceId;
|
|
337
337
|
const sessionId = sourceRef?.sessionId || stringRecordField(metadata.sessionId) || record.provenance.sourceId;
|
|
338
338
|
const eventOrdinal = sourceRef?.eventOrdinal ?? sourceRef?.sourceOffset;
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
record.
|
|
342
|
-
|
|
343
|
-
sourceRef?.
|
|
344
|
-
sourceRef?.
|
|
345
|
-
|
|
346
|
-
|
|
339
|
+
const allowNonUserEpisodeStart = metadata.importedSummarySupport === true || record.provenance.reliabilityClass === 'imported_summary';
|
|
340
|
+
const importAnchor = createHash('sha256').update(JSON.stringify({
|
|
341
|
+
sourceId: record.provenance.sourceId,
|
|
342
|
+
recordId: record.recordId ?? null,
|
|
343
|
+
sourcePath: sourceRef?.sourcePath ?? null,
|
|
344
|
+
lineStart: sourceRef?.lineStart ?? null,
|
|
345
|
+
lineEnd: sourceRef?.lineEnd ?? null,
|
|
346
|
+
eventOrdinal: eventOrdinal ?? null,
|
|
347
|
+
})).digest('hex');
|
|
348
|
+
const contentHash = createHash('sha256').update(record.text).digest('hex');
|
|
347
349
|
const existing = findImportedRawAnchor(kernel, {
|
|
348
350
|
projectId,
|
|
349
351
|
threadId,
|
|
350
352
|
sourceId: record.provenance.sourceId,
|
|
351
353
|
importAnchor,
|
|
352
|
-
contentHash
|
|
354
|
+
contentHash,
|
|
353
355
|
});
|
|
354
356
|
if (existing) {
|
|
357
|
+
if (existing.contentHash !== contentHash)
|
|
358
|
+
throw new Error(`import_anchor_content_conflict:${importAnchor}`);
|
|
355
359
|
let link = kernel.episodeStore.getEventLink(existing.eventId);
|
|
356
360
|
if (!link && !kernel.episodeStore.hasEventDisposition(existing.eventId)) {
|
|
357
361
|
await kernel.assembleEpisodeTurnAsync([existing], {
|
|
@@ -359,6 +363,7 @@ async function recordRawImportedEvidence(kernel, projectId, envelope) {
|
|
|
359
363
|
sessionId,
|
|
360
364
|
sourceAgent: record.provenance.sourceType || record.provenance.sourceId,
|
|
361
365
|
now: record.timestamp,
|
|
366
|
+
allowNonUserEpisodeStart,
|
|
362
367
|
});
|
|
363
368
|
link = kernel.episodeStore.getEventLink(existing.eventId);
|
|
364
369
|
}
|
|
@@ -371,6 +376,8 @@ async function recordRawImportedEvidence(kernel, projectId, envelope) {
|
|
|
371
376
|
sessionId,
|
|
372
377
|
turnId: sourceRef?.turnId || record.turnId || record.recordId,
|
|
373
378
|
turnSeq: sourceRef?.turnSeq,
|
|
379
|
+
localDate: localDateFromTimestamp(record.timestamp),
|
|
380
|
+
localDateSource: 'generated_utc',
|
|
374
381
|
role,
|
|
375
382
|
rawEventType: 'message',
|
|
376
383
|
content: record.text,
|
|
@@ -400,19 +407,29 @@ async function recordRawImportedEvidence(kernel, projectId, envelope) {
|
|
|
400
407
|
sessionId,
|
|
401
408
|
sourceAgent: record.provenance.sourceType || record.provenance.sourceId,
|
|
402
409
|
now: record.timestamp,
|
|
410
|
+
allowNonUserEpisodeStart,
|
|
403
411
|
});
|
|
404
412
|
return { event, created: true, episodeId: assembly.episode?.episodeId };
|
|
405
413
|
}
|
|
406
414
|
function findImportedRawAnchor(kernel, input) {
|
|
407
|
-
|
|
415
|
+
const indexed = kernel.eventStore.findImportedEventAnchor(input.projectId, input.sourceId, input.importAnchor);
|
|
416
|
+
if (indexed)
|
|
417
|
+
return indexed;
|
|
418
|
+
return kernel.getThreadEvents(input.threadId, { projectId: input.projectId, limit: 10_000 }).find((event) => {
|
|
408
419
|
const payload = event.payload;
|
|
409
420
|
return event.sourceId === input.sourceId
|
|
410
|
-
&&
|
|
421
|
+
&& payload.metadata?.importAnchor === input.importAnchor;
|
|
411
422
|
});
|
|
412
423
|
}
|
|
413
424
|
function stringRecordField(value) {
|
|
414
425
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
415
426
|
}
|
|
427
|
+
function localDateFromTimestamp(timestamp) {
|
|
428
|
+
if (typeof timestamp !== 'number' || !Number.isFinite(timestamp))
|
|
429
|
+
return undefined;
|
|
430
|
+
const date = new Date(timestamp);
|
|
431
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString().slice(0, 10);
|
|
432
|
+
}
|
|
416
433
|
function openKernel(args, workspaceRoot) {
|
|
417
434
|
const explicitDb = stringArg(args, 'db');
|
|
418
435
|
if (explicitDb) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MemoryKernelOptions } from '../factory.js';
|
|
2
2
|
import { ModelRegistry } from '../models/ModelRegistry.js';
|
|
3
3
|
import { type ConfigDiagnosticLike } from './VectorDimension.js';
|
|
4
|
+
export type { ConfigDiagnosticLike } from './VectorDimension.js';
|
|
4
5
|
export type CogmemConfigKind = 'toml' | 'missing';
|
|
5
6
|
export type EnvLike = Record<string, string | undefined>;
|
|
6
7
|
export interface CogmemConfigResolution {
|