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/dist/factory.js
CHANGED
|
@@ -48,7 +48,7 @@ import { ReEmbeddingPipeline } from './embedding/ReEmbeddingPipeline.js';
|
|
|
48
48
|
import { TopicAliasRegistry, TopicGovernance, TopicPathRegistry as UserTopicPathRegistry, TopicRelationGraph } from './topic/index.js';
|
|
49
49
|
import { CorrectionResolver } from './episode/CorrectionResolver.js';
|
|
50
50
|
import { CandidateReviewService, MemoryGovernanceExecutor, MemoryGovernanceValidator, PiiRedactor, } from './governance/index.js';
|
|
51
|
-
import {
|
|
51
|
+
import { ALL_MIGRATIONS, KERNEL_MIGRATIONS, SchemaMigrationRunner } from './migrations/index.js';
|
|
52
52
|
import { EntityGovernanceService } from './entity/index.js';
|
|
53
53
|
import { TemporalMemoryService } from './temporal/index.js';
|
|
54
54
|
import { ContextCortex } from './context/index.js';
|
|
@@ -56,6 +56,10 @@ import { ProspectiveMemoryService } from './prospective/index.js';
|
|
|
56
56
|
import { StrategyCortex } from './strategy/index.js';
|
|
57
57
|
import { ContextOutcomeStore, MemoryUseJudge } from './eval/strategy/index.js';
|
|
58
58
|
import { EpisodeAssembler, EpisodeStore } from './episode/index.js';
|
|
59
|
+
import { EpisodeBoundaryAuditService } from './episode/EpisodeBoundaryAuditService.js';
|
|
60
|
+
import { EpisodeBoundaryPolicy, normalizeEpisodeBoundaryConfig } from './episode/EpisodeBoundaryPolicy.js';
|
|
61
|
+
import { logicalTurnsFromPairs } from './episode/EpisodeBoundaryReplayEngine.js';
|
|
62
|
+
import { EpisodeSplitPlanner } from './episode/EpisodeSplitPlanner.js';
|
|
59
63
|
import { DreamScheduler } from './dream/index.js';
|
|
60
64
|
import { loadCogmemConfig, resolveCogmemConfigPath, } from './config/CogmemConfig.js';
|
|
61
65
|
import { ModelRegistry } from './models/ModelRegistry.js';
|
|
@@ -83,8 +87,8 @@ import { SqliteVecStore } from './store/SqliteVecStore.js';
|
|
|
83
87
|
import { VectorStore } from './store/VectorStore.js';
|
|
84
88
|
import { config } from './utils/Config.js';
|
|
85
89
|
import { KernelRunningError, SnapshotExporter, SnapshotImporter, } from './snapshot/index.js';
|
|
86
|
-
const CORE_VERSION = '3.7.
|
|
87
|
-
const LATEST_SCHEMA_VERSION =
|
|
90
|
+
const CORE_VERSION = '3.7.3';
|
|
91
|
+
const LATEST_SCHEMA_VERSION = Math.max(...ALL_MIGRATIONS.map((migration) => Number.parseInt(migration.version, 10)));
|
|
88
92
|
export class MemoryKernel {
|
|
89
93
|
options;
|
|
90
94
|
memoryGraph;
|
|
@@ -117,10 +121,14 @@ export class MemoryKernel {
|
|
|
117
121
|
pipelineMetrics;
|
|
118
122
|
episodeStore;
|
|
119
123
|
episodeAssembler;
|
|
124
|
+
episodeBoundaryPolicy;
|
|
125
|
+
episodeBoundaryAuditService;
|
|
126
|
+
episodeSplitPlanner;
|
|
120
127
|
userTopicPathRegistry;
|
|
121
128
|
topicAliasRegistry;
|
|
122
129
|
topicRelationGraph;
|
|
123
130
|
topicGovernance;
|
|
131
|
+
configDiagnostics;
|
|
124
132
|
dbPath;
|
|
125
133
|
embedder;
|
|
126
134
|
embeddingProvider;
|
|
@@ -160,21 +168,27 @@ export class MemoryKernel {
|
|
|
160
168
|
closed = false;
|
|
161
169
|
constructor(options = {}) {
|
|
162
170
|
this.options = options;
|
|
171
|
+
this.configDiagnostics = [...(options.configDiagnostics || [])];
|
|
172
|
+
const normalizedBoundary = normalizeEpisodeBoundaryConfig(options.episodeBoundary);
|
|
173
|
+
this.configDiagnostics.push(...normalizedBoundary.diagnostics);
|
|
163
174
|
this.dbPath = options.dbPath ?? ':memory:';
|
|
164
175
|
this.encryptionProvider = options.encryptionProvider;
|
|
165
176
|
this.piiRedactor = options.redactionPolicy === false ? undefined : new PiiRedactor(options.redactionPolicy);
|
|
166
177
|
this.memoryGraph = new MemoryGraph(this.dbPath);
|
|
167
|
-
this.eventStore = new EventStore(this.dbPath, this.encryptionProvider);
|
|
168
178
|
this.factStore = new FactStore(this.dbPath, this.encryptionProvider);
|
|
169
179
|
const db = this.factStore.getDatabase();
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
this.eventStore = new EventStore(db, this.encryptionProvider);
|
|
181
|
+
db.exec('PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;');
|
|
182
|
+
if (db.prepare('PRAGMA foreign_keys').get()?.foreign_keys !== 1) {
|
|
183
|
+
throw new Error('memory_kernel_foreign_keys_disabled');
|
|
184
|
+
}
|
|
185
|
+
new SchemaMigrationRunner(db, KERNEL_MIGRATIONS).run();
|
|
172
186
|
this.ensureMetaTable(db);
|
|
173
187
|
this.entityStore = new EntityStore(db);
|
|
174
188
|
this.ensureGovernanceAuditTable(db);
|
|
175
189
|
const vectorDimension = options.vectorDimension ?? config.vector.dimension;
|
|
176
190
|
this.modelRegistry = options.modelRegistry ?? ModelRegistry.defaults();
|
|
177
|
-
this.beliefStore = new BeliefStore(
|
|
191
|
+
this.beliefStore = new BeliefStore(db, this.eventStore);
|
|
178
192
|
this.beliefGovernanceService = new BeliefGovernanceService(db, (eventId) => {
|
|
179
193
|
const event = this.eventStore.getEvent(eventId);
|
|
180
194
|
return event ? { eventId, projectId: event.projectId, role: event.role } : undefined;
|
|
@@ -207,6 +221,9 @@ export class MemoryKernel {
|
|
|
207
221
|
this.neuronEmbeddingStore = new NeuronEmbeddingStore(db);
|
|
208
222
|
this.dreamLedgerStore = new DreamLedgerStore(db);
|
|
209
223
|
this.episodeStore = new EpisodeStore(db, (eventId) => this.eventStore.getEvent(eventId), { initializeSchemaForTests: false });
|
|
224
|
+
this.episodeBoundaryPolicy = new EpisodeBoundaryPolicy(normalizedBoundary.config);
|
|
225
|
+
this.episodeBoundaryAuditService = new EpisodeBoundaryAuditService(this.episodeStore, (eventId) => this.eventStore.getEvent(eventId), this.episodeBoundaryPolicy.config, this.configDiagnostics);
|
|
226
|
+
this.episodeSplitPlanner = new EpisodeSplitPlanner(this.episodeStore, (eventId) => this.eventStore.getEvent(eventId), this.episodeBoundaryPolicy.config, this.configDiagnostics);
|
|
210
227
|
this.userTopicPathRegistry = new UserTopicPathRegistry(db);
|
|
211
228
|
this.topicAliasRegistry = new TopicAliasRegistry(db);
|
|
212
229
|
this.topicRelationGraph = new TopicRelationGraph(db);
|
|
@@ -223,7 +240,7 @@ export class MemoryKernel {
|
|
|
223
240
|
topicPathMatch: Boolean(episode?.topicPath && matchedPaths.includes(episode.topicPath)),
|
|
224
241
|
currentTopicPath: matchedPaths.length === 1 ? matchedPaths[0] : undefined,
|
|
225
242
|
};
|
|
226
|
-
});
|
|
243
|
+
}, this.episodeBoundaryPolicy);
|
|
227
244
|
this.activationStore = new ActivationStore(db);
|
|
228
245
|
this.memoryBindingStore = new MemoryBindingStore(db);
|
|
229
246
|
this.memoryBindingService = new MemoryBindingService(this.memoryBindingStore, this.entityStore);
|
|
@@ -306,7 +323,7 @@ export class MemoryKernel {
|
|
|
306
323
|
statement: `${belief.subject} ${belief.predicate} ${String(belief.objectValue)}`, projectId: belief.projectId,
|
|
307
324
|
})))),
|
|
308
325
|
});
|
|
309
|
-
this.dreamScheduler = new DreamScheduler(this.episodeStore, this.dreamCuratorWorker);
|
|
326
|
+
this.dreamScheduler = new DreamScheduler(this.episodeStore, this.dreamCuratorWorker, this.deepWriteCandidateStore);
|
|
310
327
|
this.topicSummaryBoard = new TopicSummaryBoard(this.memoryGraph, this.summaryStore);
|
|
311
328
|
this.topicDecayPolicy = new TopicDecayPolicy(this.memoryGraph);
|
|
312
329
|
this.localSemanticCompiler = new LocalSemanticCompiler();
|
|
@@ -579,6 +596,7 @@ export class MemoryKernel {
|
|
|
579
596
|
threadId: input.threadId,
|
|
580
597
|
sessionId: input.sessionId,
|
|
581
598
|
localDate: input.localDate,
|
|
599
|
+
localDateSource: input.localDateSource ?? (input.localDate ? 'explicit' : 'generated_utc'),
|
|
582
600
|
turnId: input.turnId,
|
|
583
601
|
turnSeq: input.turnSeq,
|
|
584
602
|
eventOrdinal: input.eventOrdinal,
|
|
@@ -592,7 +610,8 @@ export class MemoryKernel {
|
|
|
592
610
|
charStart: input.charStart,
|
|
593
611
|
charEnd: input.charEnd,
|
|
594
612
|
occurredAt,
|
|
595
|
-
orderingConfidence:
|
|
613
|
+
orderingConfidence: input.orderingConfidence
|
|
614
|
+
?? (input.turnSeq !== undefined || input.eventOrdinal !== undefined || input.sourceOffset !== undefined || input.lineStart !== undefined ? 'high' : 'low'),
|
|
596
615
|
payload: {
|
|
597
616
|
text,
|
|
598
617
|
metadata: input.metadata,
|
|
@@ -816,7 +835,7 @@ export class MemoryKernel {
|
|
|
816
835
|
const existingEvent = reservedEventId ? this.eventStore.getEvent(reservedEventId) : null;
|
|
817
836
|
if (existingEvent) {
|
|
818
837
|
this.assertEpisodeIngestIdentity(existingEvent, input);
|
|
819
|
-
return this.resumeEpisodeMessage(existingEvent, input, false);
|
|
838
|
+
return this.finishIngestMessage(existingEvent, input, false, () => this.resumeEpisodeMessage(existingEvent, input, false));
|
|
820
839
|
}
|
|
821
840
|
}
|
|
822
841
|
let event;
|
|
@@ -827,6 +846,10 @@ export class MemoryKernel {
|
|
|
827
846
|
workspaceId: input.projectId,
|
|
828
847
|
threadId: input.threadId || input.sessionId,
|
|
829
848
|
sessionId: input.sessionId,
|
|
849
|
+
turnId: input.turnId,
|
|
850
|
+
turnSeq: input.turnSeq,
|
|
851
|
+
localDate: input.localDate,
|
|
852
|
+
eventOrdinal: input.eventOrdinal,
|
|
830
853
|
role: input.role,
|
|
831
854
|
content: input.text,
|
|
832
855
|
occurredAt: input.timestamp,
|
|
@@ -845,19 +868,9 @@ export class MemoryKernel {
|
|
|
845
868
|
throw error;
|
|
846
869
|
}
|
|
847
870
|
this.assertEpisodeIngestIdentity(concurrent, input);
|
|
848
|
-
|
|
849
|
-
this.episodeStore.markIngestState({
|
|
850
|
-
projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
|
|
851
|
-
externalMessageId: input.externalMessageId, state: 'committed',
|
|
852
|
-
});
|
|
853
|
-
return this.resumeEpisodeMessage(concurrent, input, false);
|
|
871
|
+
return this.finishIngestMessage(concurrent, input, false, () => this.resumeEpisodeMessage(concurrent, input, false));
|
|
854
872
|
}
|
|
855
|
-
|
|
856
|
-
this.episodeStore.markIngestState({
|
|
857
|
-
projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
|
|
858
|
-
externalMessageId: input.externalMessageId, state: 'committed', now: event.occurredAt,
|
|
859
|
-
});
|
|
860
|
-
return this.resumeEpisodeMessage(event, input, true);
|
|
873
|
+
return this.finishIngestMessage(event, input, true, () => this.resumeEpisodeMessage(event, input, true));
|
|
861
874
|
}
|
|
862
875
|
async appendEpisodeMessageAsync(input) {
|
|
863
876
|
let reservedEventId;
|
|
@@ -873,7 +886,7 @@ export class MemoryKernel {
|
|
|
873
886
|
const existingEvent = reservedEventId ? this.eventStore.getEvent(reservedEventId) : null;
|
|
874
887
|
if (existingEvent) {
|
|
875
888
|
this.assertEpisodeIngestIdentity(existingEvent, input);
|
|
876
|
-
return this.resumeEpisodeMessageAsync(existingEvent, input, false);
|
|
889
|
+
return this.finishIngestMessageAsync(existingEvent, input, false, () => this.resumeEpisodeMessageAsync(existingEvent, input, false));
|
|
877
890
|
}
|
|
878
891
|
}
|
|
879
892
|
let event;
|
|
@@ -881,6 +894,7 @@ export class MemoryKernel {
|
|
|
881
894
|
event = this.recordRawEvent({
|
|
882
895
|
eventId: reservedEventId, projectId: input.projectId, workspaceId: input.projectId,
|
|
883
896
|
threadId: input.threadId || input.sessionId, sessionId: input.sessionId, role: input.role,
|
|
897
|
+
turnId: input.turnId, turnSeq: input.turnSeq, localDate: input.localDate, eventOrdinal: input.eventOrdinal,
|
|
884
898
|
content: input.text, occurredAt: input.timestamp, sourceId: `${input.sourceAgent}:${input.sessionId}`,
|
|
885
899
|
metadata: { ...input.metadata, externalMessageId: input.externalMessageId, sourceAgent: input.sourceAgent },
|
|
886
900
|
});
|
|
@@ -896,25 +910,54 @@ export class MemoryKernel {
|
|
|
896
910
|
throw error;
|
|
897
911
|
}
|
|
898
912
|
this.assertEpisodeIngestIdentity(concurrent, input);
|
|
913
|
+
return this.finishIngestMessageAsync(concurrent, input, false, () => this.resumeEpisodeMessageAsync(concurrent, input, false));
|
|
914
|
+
}
|
|
915
|
+
return this.finishIngestMessageAsync(event, input, true, () => this.resumeEpisodeMessageAsync(event, input, true));
|
|
916
|
+
}
|
|
917
|
+
finishIngestMessage(event, input, created, operation) {
|
|
918
|
+
try {
|
|
919
|
+
const result = operation();
|
|
899
920
|
if (input.externalMessageId)
|
|
900
921
|
this.episodeStore.markIngestState({
|
|
901
922
|
projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
|
|
902
|
-
externalMessageId: input.externalMessageId, state: 'committed',
|
|
923
|
+
externalMessageId: input.externalMessageId, state: 'committed', now: event.occurredAt,
|
|
903
924
|
});
|
|
904
|
-
return
|
|
925
|
+
return result;
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
if (input.externalMessageId)
|
|
929
|
+
this.episodeStore.markIngestState({
|
|
930
|
+
projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
|
|
931
|
+
externalMessageId: input.externalMessageId, state: 'failed', error: error instanceof Error ? error.message : String(error), now: event.occurredAt,
|
|
932
|
+
});
|
|
933
|
+
throw error;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
async finishIngestMessageAsync(event, input, created, operation) {
|
|
937
|
+
try {
|
|
938
|
+
const result = await operation();
|
|
939
|
+
if (input.externalMessageId)
|
|
940
|
+
this.episodeStore.markIngestState({
|
|
941
|
+
projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
|
|
942
|
+
externalMessageId: input.externalMessageId, state: 'committed', now: event.occurredAt,
|
|
943
|
+
});
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
946
|
+
catch (error) {
|
|
947
|
+
if (input.externalMessageId)
|
|
948
|
+
this.episodeStore.markIngestState({
|
|
949
|
+
projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
|
|
950
|
+
externalMessageId: input.externalMessageId, state: 'failed', error: error instanceof Error ? error.message : String(error), now: event.occurredAt,
|
|
951
|
+
});
|
|
952
|
+
throw error;
|
|
905
953
|
}
|
|
906
|
-
if (input.externalMessageId)
|
|
907
|
-
this.episodeStore.markIngestState({
|
|
908
|
-
projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
|
|
909
|
-
externalMessageId: input.externalMessageId, state: 'committed', now: event.occurredAt,
|
|
910
|
-
});
|
|
911
|
-
return this.resumeEpisodeMessageAsync(event, input, true);
|
|
912
954
|
}
|
|
913
955
|
resumeEpisodeMessage(event, input, created) {
|
|
914
956
|
let link = this.episodeStore.getEventLink(event.eventId);
|
|
915
957
|
let ignored = this.episodeStore.hasEventDisposition(event.eventId);
|
|
958
|
+
let assembly;
|
|
916
959
|
if (!link && !ignored) {
|
|
917
|
-
|
|
960
|
+
assembly = this.assembleEpisodeTurn([event], {
|
|
918
961
|
projectId: input.projectId,
|
|
919
962
|
sessionId: event.sessionId || input.sessionId,
|
|
920
963
|
sourceAgent: input.sourceAgent,
|
|
@@ -937,13 +980,25 @@ export class MemoryKernel {
|
|
|
937
980
|
sealed: episode?.status === 'sealed',
|
|
938
981
|
dreamRecommended: Boolean(receipt?.dreamRecommended && !receipt.requiresReview && episode?.dreamStatus !== 'processed'),
|
|
939
982
|
dreamRan: false,
|
|
983
|
+
boundaryTriggered: assembly?.boundaryTriggered,
|
|
984
|
+
boundaryDetected: assembly?.boundaryDetected,
|
|
985
|
+
boundaryApplied: assembly?.boundaryApplied,
|
|
986
|
+
boundaryMode: assembly?.boundaryMode,
|
|
987
|
+
boundaryDecisionId: assembly?.boundaryDecisionId,
|
|
988
|
+
boundaryGuardCodes: assembly?.boundaryGuardCodes,
|
|
989
|
+
boundaryAuditRecorded: assembly?.boundaryAuditRecorded,
|
|
990
|
+
boundaryAuditStatus: assembly?.boundaryAuditStatus,
|
|
991
|
+
previousEpisodeId: assembly?.previousEpisodeId,
|
|
992
|
+
reviewerRawResultStatus: assembly?.reviewerRawResultStatus,
|
|
993
|
+
warnings: assembly?.warnings,
|
|
940
994
|
};
|
|
941
995
|
}
|
|
942
996
|
async resumeEpisodeMessageAsync(event, input, created) {
|
|
943
997
|
let link = this.episodeStore.getEventLink(event.eventId);
|
|
944
998
|
let ignored = this.episodeStore.hasEventDisposition(event.eventId);
|
|
999
|
+
let assembly;
|
|
945
1000
|
if (!link && !ignored) {
|
|
946
|
-
|
|
1001
|
+
assembly = await this.assembleEpisodeTurnAsync([event], {
|
|
947
1002
|
projectId: input.projectId, sessionId: event.sessionId || input.sessionId, sourceAgent: input.sourceAgent,
|
|
948
1003
|
conversationThreadId: input.threadId || event.threadId || input.sessionId, now: event.occurredAt,
|
|
949
1004
|
});
|
|
@@ -959,6 +1014,17 @@ export class MemoryKernel {
|
|
|
959
1014
|
sealed: episode?.status === 'sealed',
|
|
960
1015
|
dreamRecommended: Boolean(receipt?.dreamRecommended && !receipt.requiresReview && episode?.dreamStatus !== 'processed'),
|
|
961
1016
|
dreamRan: false,
|
|
1017
|
+
boundaryTriggered: assembly?.boundaryTriggered,
|
|
1018
|
+
boundaryDetected: assembly?.boundaryDetected,
|
|
1019
|
+
boundaryApplied: assembly?.boundaryApplied,
|
|
1020
|
+
boundaryMode: assembly?.boundaryMode,
|
|
1021
|
+
boundaryDecisionId: assembly?.boundaryDecisionId,
|
|
1022
|
+
boundaryGuardCodes: assembly?.boundaryGuardCodes,
|
|
1023
|
+
boundaryAuditRecorded: assembly?.boundaryAuditRecorded,
|
|
1024
|
+
boundaryAuditStatus: assembly?.boundaryAuditStatus,
|
|
1025
|
+
previousEpisodeId: assembly?.previousEpisodeId,
|
|
1026
|
+
reviewerRawResultStatus: assembly?.reviewerRawResultStatus,
|
|
1027
|
+
warnings: assembly?.warnings,
|
|
962
1028
|
};
|
|
963
1029
|
}
|
|
964
1030
|
assertEpisodeIngestIdentity(event, input) {
|
|
@@ -966,7 +1032,15 @@ export class MemoryKernel {
|
|
|
966
1032
|
const expectedText = this.piiRedactor ? this.piiRedactor.redact(input.text).text : input.text;
|
|
967
1033
|
if (event.projectId !== input.projectId
|
|
968
1034
|
|| event.sessionId !== input.sessionId
|
|
1035
|
+
|| event.threadId !== (input.threadId || input.sessionId)
|
|
969
1036
|
|| event.role !== input.role
|
|
1037
|
+
|| !sameProvided(event.turnId, input.turnId)
|
|
1038
|
+
|| !sameProvided(event.turnSeq, input.turnSeq)
|
|
1039
|
+
|| !sameProvided(event.localDate, input.localDate)
|
|
1040
|
+
|| !sameProvided(event.eventOrdinal, input.eventOrdinal)
|
|
1041
|
+
|| (input.timestamp !== undefined && event.occurredAt !== input.timestamp)
|
|
1042
|
+
|| (input.metadata?.imported !== undefined && payload?.metadata?.imported !== input.metadata.imported)
|
|
1043
|
+
|| (input.metadata?.sourceRef !== undefined && JSON.stringify(payload?.metadata?.sourceRef) !== JSON.stringify(input.metadata.sourceRef))
|
|
970
1044
|
|| payload?.text !== expectedText
|
|
971
1045
|
|| payload?.metadata?.sourceAgent !== input.sourceAgent) {
|
|
972
1046
|
throw new Error(`episode_ingest_identity_conflict:${input.externalMessageId || event.eventId}`);
|
|
@@ -1006,6 +1080,17 @@ export class MemoryKernel {
|
|
|
1006
1080
|
listEpisodeEventLinks(episodeId) {
|
|
1007
1081
|
return this.episodeStore.listEventLinks(episodeId);
|
|
1008
1082
|
}
|
|
1083
|
+
auditEpisodeBoundaries(options) {
|
|
1084
|
+
return this.episodeBoundaryAuditService.audit(options);
|
|
1085
|
+
}
|
|
1086
|
+
listEpisodeBoundaryDecisions(options) {
|
|
1087
|
+
if (!options.projectId)
|
|
1088
|
+
throw new Error('projectId is required');
|
|
1089
|
+
return this.episodeStore.listBoundaryDecisions(options);
|
|
1090
|
+
}
|
|
1091
|
+
planEpisodeSplit(options) {
|
|
1092
|
+
return this.episodeSplitPlanner.plan(options);
|
|
1093
|
+
}
|
|
1009
1094
|
getEpisodeDreamStatus(projectId) {
|
|
1010
1095
|
return this.episodeStore.getDreamStatus(projectId);
|
|
1011
1096
|
}
|
|
@@ -1013,18 +1098,24 @@ export class MemoryKernel {
|
|
|
1013
1098
|
return this.episodeStore.retryFailed(projectId);
|
|
1014
1099
|
}
|
|
1015
1100
|
repairEpisodes(options = {}) {
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
.
|
|
1021
|
-
|
|
1101
|
+
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 500), 5000));
|
|
1102
|
+
let afterGlobalSeq = options.sinceGlobalSeq === undefined ? undefined : options.sinceGlobalSeq - 1;
|
|
1103
|
+
const events = [];
|
|
1104
|
+
while (events.length < limit) {
|
|
1105
|
+
const page = this.eventStore.listRawEventsAfterGlobalSeq({ projectId: options.projectId, afterGlobalSeq, limit: Math.min(500, limit - events.length) });
|
|
1106
|
+
if (!page.length)
|
|
1107
|
+
break;
|
|
1108
|
+
events.push(...page);
|
|
1109
|
+
afterGlobalSeq = page.at(-1)?.globalSeq ?? afterGlobalSeq;
|
|
1110
|
+
if (page.length < Math.min(500, limit - events.length + page.length))
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
const eligibleEvents = events
|
|
1022
1114
|
.filter((event) => !this.episodeStore.getEventLink(event.eventId))
|
|
1023
|
-
.filter((event) => !this.episodeStore.hasEventDisposition(event.eventId))
|
|
1024
|
-
.sort((a, b) => (a.globalSeq || 0) - (b.globalSeq || 0));
|
|
1115
|
+
.filter((event) => !this.episodeStore.hasEventDisposition(event.eventId));
|
|
1025
1116
|
let assigned = 0;
|
|
1026
1117
|
const unassignedEventIds = [];
|
|
1027
|
-
for (const event of
|
|
1118
|
+
for (const event of eligibleEvents) {
|
|
1028
1119
|
const projectId = event.projectId || options.projectId;
|
|
1029
1120
|
const sessionId = event.sessionId || event.threadId;
|
|
1030
1121
|
if (!projectId || !sessionId) {
|
|
@@ -1049,9 +1140,13 @@ export class MemoryKernel {
|
|
|
1049
1140
|
});
|
|
1050
1141
|
}
|
|
1051
1142
|
}
|
|
1052
|
-
return { scanned:
|
|
1143
|
+
return { scanned: eligibleEvents.length, assigned, unassigned: unassignedEventIds.length, unassignedEventIds, nextGlobalSeq: afterGlobalSeq, hasMore: events.length >= limit };
|
|
1053
1144
|
}
|
|
1054
1145
|
repairEpisode(input) {
|
|
1146
|
+
validateEpisodeRepairInput(input, this.episodeStore, (eventId) => this.eventStore.getEvent(eventId) ?? undefined);
|
|
1147
|
+
return this.episodeStore.transaction(() => this.repairEpisodeInTransaction(input));
|
|
1148
|
+
}
|
|
1149
|
+
repairEpisodeInTransaction(input) {
|
|
1055
1150
|
const now = input.now ?? Date.now();
|
|
1056
1151
|
const affected = new Set();
|
|
1057
1152
|
const before = {};
|
|
@@ -1101,6 +1196,8 @@ export class MemoryKernel {
|
|
|
1101
1196
|
}
|
|
1102
1197
|
else if (input.operation === 'split') {
|
|
1103
1198
|
const source = this.episodeStore.getEpisode(input.episodeId);
|
|
1199
|
+
if (source.status !== 'sealed')
|
|
1200
|
+
throw new Error('episode_split_requires_sealed_source');
|
|
1104
1201
|
const sourceLinks = this.episodeStore.listEventLinks(input.episodeId);
|
|
1105
1202
|
const selected = sourceLinks.filter((link) => input.eventIds.includes(link.eventId));
|
|
1106
1203
|
if (!selected.length || selected.length === sourceLinks.length)
|
|
@@ -1157,19 +1254,37 @@ export class MemoryKernel {
|
|
|
1157
1254
|
}
|
|
1158
1255
|
}
|
|
1159
1256
|
const staleCandidateIds = [];
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1257
|
+
let candidateCursor;
|
|
1258
|
+
while (true) {
|
|
1259
|
+
const page = this.deepWriteCandidateStore.listCandidates({ projectId: input.projectId, limit: 500, after: candidateCursor });
|
|
1260
|
+
if (!page.length)
|
|
1261
|
+
break;
|
|
1262
|
+
for (const candidate of page) {
|
|
1263
|
+
const content = candidate.content && typeof candidate.content === 'object' ? candidate.content : {};
|
|
1264
|
+
const evidence = Array.isArray(candidate.evidence) ? candidate.evidence : [];
|
|
1265
|
+
const evidenceIds = evidence.flatMap((item) => item && typeof item === 'object' && typeof item.eventId === 'string'
|
|
1266
|
+
? [String(item.eventId)] : typeof item === 'string' ? [item] : []);
|
|
1267
|
+
const runSourceEpisode = typeof content.sourceEpisodeId === 'string' ? content.sourceEpisodeId : undefined;
|
|
1268
|
+
if (candidate.status !== 'superseded' && ((runSourceEpisode && affected.has(runSourceEpisode))
|
|
1269
|
+
|| evidenceIds.some((eventId) => affected.has(this.episodeStore.getEventLink(eventId)?.episodeId || '')))) {
|
|
1270
|
+
this.deepWriteCandidateStore.updateCandidateStatus(candidate.candidateId, 'superseded', {
|
|
1271
|
+
reason: 'episode_repair_invalidated_source',
|
|
1272
|
+
});
|
|
1273
|
+
this.invalidatePromotedCandidate(candidate, now);
|
|
1274
|
+
staleCandidateIds.push(candidate.candidateId);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
const last = page.at(-1);
|
|
1278
|
+
candidateCursor = { createdAt: last.createdAt, candidateId: last.candidateId };
|
|
1279
|
+
if (page.length < 500)
|
|
1280
|
+
break;
|
|
1168
1281
|
}
|
|
1169
1282
|
for (const episodeId of affected) {
|
|
1170
1283
|
const episode = this.episodeStore.getEpisode(episodeId);
|
|
1171
|
-
if (episode?.eventCount && episode.status === 'sealed'
|
|
1172
|
-
|
|
1284
|
+
if (episode?.eventCount && episode.status === 'sealed'
|
|
1285
|
+
&& input.operation !== 'requeue-dream'
|
|
1286
|
+
&& input.operation !== 'invalidate-dream-run') {
|
|
1287
|
+
this.episodeStore.requeueDreamForRepair(episodeId, 'normal', now);
|
|
1173
1288
|
requeuedDream = true;
|
|
1174
1289
|
}
|
|
1175
1290
|
}
|
|
@@ -1177,7 +1292,7 @@ export class MemoryKernel {
|
|
|
1177
1292
|
const repairId = this.episodeStore.recordRepairAudit({ projectId: input.projectId, operation: input.operation, payload: input, before, after, now });
|
|
1178
1293
|
const affectedEpisodeIds = [...affected];
|
|
1179
1294
|
const nextCommands = affectedEpisodeIds.length
|
|
1180
|
-
? affectedEpisodeIds.map((episodeId) => `cogmem memory graph-reindex --project ${input.projectId} --episode ${episodeId} --json`)
|
|
1295
|
+
? affectedEpisodeIds.map((episodeId) => `cogmem memory graph-reindex --project ${shellQuote(input.projectId)} --episode ${shellQuote(episodeId)} --json`)
|
|
1181
1296
|
: [];
|
|
1182
1297
|
return {
|
|
1183
1298
|
repairId,
|
|
@@ -1190,11 +1305,53 @@ export class MemoryKernel {
|
|
|
1190
1305
|
graphRefreshNeeded: affectedEpisodeIds.length > 0,
|
|
1191
1306
|
nextCommands: [
|
|
1192
1307
|
...nextCommands,
|
|
1193
|
-
`cogmem memory graph-explore --project ${input.projectId} --query
|
|
1308
|
+
`cogmem memory graph-explore --project ${shellQuote(input.projectId)} --query '<query>' --json`,
|
|
1194
1309
|
],
|
|
1195
1310
|
note: 'repairId is an audit id, not a dream candidate id; do not run memory dream --promote just because repairId exists.',
|
|
1196
1311
|
};
|
|
1197
1312
|
}
|
|
1313
|
+
invalidatePromotedCandidate(candidate, now) {
|
|
1314
|
+
if (candidate.status !== 'promoted' || !candidate.promotionTargetId)
|
|
1315
|
+
return;
|
|
1316
|
+
const db = this.factStore.getDatabase();
|
|
1317
|
+
const targetId = candidate.promotionTargetId;
|
|
1318
|
+
if (candidate.promotionTargetType === 'fact') {
|
|
1319
|
+
this.factStore.updateFactStatus(targetId, 'superseded', undefined, { repairInvalidatedAt: now, sourceCandidateId: candidate.candidateId });
|
|
1320
|
+
}
|
|
1321
|
+
else if (candidate.promotionTargetType === 'belief') {
|
|
1322
|
+
db.prepare(`UPDATE beliefs SET status = 'suspect', updated_at = ? WHERE id = ?`).run(now, targetId);
|
|
1323
|
+
}
|
|
1324
|
+
else if (candidate.promotionTargetType === 'summary') {
|
|
1325
|
+
this.summaryStore.markSuperseded(targetId);
|
|
1326
|
+
}
|
|
1327
|
+
else if (candidate.promotionTargetType === 'entity') {
|
|
1328
|
+
const support = db.prepare(`
|
|
1329
|
+
SELECT (
|
|
1330
|
+
(SELECT COUNT(*) FROM facts WHERE entity_id = ? AND status IN ('provisional', 'provisional_enriched', 'verified', 'active'))
|
|
1331
|
+
+ (SELECT COUNT(*) FROM beliefs WHERE subject = ? AND status IN ('active', 'verified'))
|
|
1332
|
+
+ (SELECT COUNT(*) FROM entity_mentions WHERE entity_id = ?)
|
|
1333
|
+
+ (SELECT COUNT(*) FROM entity_attributes WHERE entity_id = ?)
|
|
1334
|
+
+ (SELECT COUNT(*) FROM entity_relations WHERE source_entity_id = ? OR target_entity_id = ?)
|
|
1335
|
+
+ ?
|
|
1336
|
+
) AS count
|
|
1337
|
+
`).get(targetId, targetId, targetId, targetId, targetId, targetId, this.deepWriteCandidateStore.countActivePromotions('entity', targetId, candidate.candidateId));
|
|
1338
|
+
if (Number(support?.count || 0) === 0) {
|
|
1339
|
+
this.entityStore.archiveEntity(targetId, now);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
else if (candidate.promotionTargetType === 'graph_edge') {
|
|
1343
|
+
const relationStore = this.extensions.get('relationStore');
|
|
1344
|
+
if (!relationStore || relationStore.getDatabase() !== this.episodeStore.getDatabase()) {
|
|
1345
|
+
throw new Error(`graph_edge_invalidation_failed:${targetId}`);
|
|
1346
|
+
}
|
|
1347
|
+
const invalidated = relationStore.invalidateEdge(targetId, {
|
|
1348
|
+
repairInvalidatedAt: now,
|
|
1349
|
+
sourceCandidateId: candidate.candidateId,
|
|
1350
|
+
});
|
|
1351
|
+
if (invalidated !== true)
|
|
1352
|
+
throw new Error(`graph_edge_invalidation_failed:${targetId}`);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1198
1355
|
listDreamCandidates(options = {}) {
|
|
1199
1356
|
return this.deepWriteCandidateStore.listCandidates(options);
|
|
1200
1357
|
}
|
|
@@ -1380,6 +1537,7 @@ export class MemoryKernel {
|
|
|
1380
1537
|
return count;
|
|
1381
1538
|
}
|
|
1382
1539
|
promoteDreamCandidates(options = {}) {
|
|
1540
|
+
this.deepWriteCandidateStore.recoverStalePromoting(Date.now() - 5 * 60_000, Date.now());
|
|
1383
1541
|
const decisions = this.deepWritePromotionPolicy.promotePending(options.limit ?? 100, {
|
|
1384
1542
|
projectId: options.projectId,
|
|
1385
1543
|
});
|
|
@@ -1603,7 +1761,7 @@ export class MemoryKernel {
|
|
|
1603
1761
|
if (!projectId)
|
|
1604
1762
|
return true;
|
|
1605
1763
|
return conflict.entityIds.some((entityId) => {
|
|
1606
|
-
const entity = this.entityStore.
|
|
1764
|
+
const entity = this.entityStore.getByEntityId(entityId);
|
|
1607
1765
|
return entity?.metadata?.projectId === projectId
|
|
1608
1766
|
|| this.entityStore.listTimeline({ entityId, projectId, limit: 1 }).length > 0;
|
|
1609
1767
|
});
|
|
@@ -1929,6 +2087,12 @@ export class MemoryKernel {
|
|
|
1929
2087
|
}
|
|
1930
2088
|
registerExtension(name, implementation) {
|
|
1931
2089
|
this.extensions.set(name, implementation);
|
|
2090
|
+
if (name === 'relationStore') {
|
|
2091
|
+
const store = implementation;
|
|
2092
|
+
if (store.getDatabase() !== this.episodeStore.getDatabase())
|
|
2093
|
+
throw new Error('relation_store_database_mismatch');
|
|
2094
|
+
this.deepWritePromotionPolicy.setRelationStore(store);
|
|
2095
|
+
}
|
|
1932
2096
|
}
|
|
1933
2097
|
hasExtension(name) {
|
|
1934
2098
|
return this.extensions.has(name);
|
|
@@ -2023,7 +2187,12 @@ export function createMemoryKernelFromConfig(input = {}) {
|
|
|
2023
2187
|
if (error)
|
|
2024
2188
|
throw new Error(`${error.code}: ${error.message}`);
|
|
2025
2189
|
const { configPath: _configPath, cwd: _cwd, env: _env, ...explicitOptions } = options;
|
|
2026
|
-
return createMemoryKernel({
|
|
2190
|
+
return createMemoryKernel({
|
|
2191
|
+
...loaded.options,
|
|
2192
|
+
...explicitOptions,
|
|
2193
|
+
episodeBoundary: { ...loaded.options.episodeBoundary, ...explicitOptions.episodeBoundary },
|
|
2194
|
+
configDiagnostics: [...loaded.diagnostics, ...(explicitOptions.configDiagnostics || [])],
|
|
2195
|
+
});
|
|
2027
2196
|
}
|
|
2028
2197
|
function changedFieldsForRepair(input) {
|
|
2029
2198
|
if (input.operation === 'reclassify') {
|
|
@@ -2041,6 +2210,84 @@ function changedFieldsForRepair(input) {
|
|
|
2041
2210
|
return ['dreamQueue'];
|
|
2042
2211
|
return [];
|
|
2043
2212
|
}
|
|
2213
|
+
function validateEpisodeRepairInput(input, store, resolveEvent) {
|
|
2214
|
+
if (!input.projectId?.trim())
|
|
2215
|
+
throw new Error('episode_project_required');
|
|
2216
|
+
if (input.operation === 'reclassify' && input.importance !== undefined
|
|
2217
|
+
&& (!Number.isFinite(input.importance) || input.importance < 0 || input.importance > 1)) {
|
|
2218
|
+
throw new Error('episode_importance_out_of_range');
|
|
2219
|
+
}
|
|
2220
|
+
if (input.operation === 'split') {
|
|
2221
|
+
if (!input.eventIds.length)
|
|
2222
|
+
throw new Error('episode_split_requires_event_ids');
|
|
2223
|
+
if (new Set(input.eventIds).size !== input.eventIds.length)
|
|
2224
|
+
throw new Error('episode_split_duplicate_event_ids');
|
|
2225
|
+
const source = store.getEpisode(input.episodeId);
|
|
2226
|
+
if (!source || source.projectId !== input.projectId)
|
|
2227
|
+
throw new Error(`episode_project_mismatch:${input.episodeId}`);
|
|
2228
|
+
if (source.status !== 'sealed')
|
|
2229
|
+
throw new Error('episode_split_requires_sealed_source');
|
|
2230
|
+
const sourceIds = new Set(store.listEventLinks(input.episodeId).map((link) => link.eventId));
|
|
2231
|
+
if (input.eventIds.some((eventId) => !sourceIds.has(eventId)))
|
|
2232
|
+
throw new Error('episode_split_event_not_in_source');
|
|
2233
|
+
if (input.eventIds.length >= sourceIds.size)
|
|
2234
|
+
throw new Error('episode_split_requires_proper_subset');
|
|
2235
|
+
const links = store.listEventLinks(input.episodeId);
|
|
2236
|
+
const selected = new Set(input.eventIds);
|
|
2237
|
+
const indices = links.map((link, index) => selected.has(link.eventId) ? index : -1).filter((index) => index >= 0);
|
|
2238
|
+
const first = indices[0];
|
|
2239
|
+
const last = indices.at(-1);
|
|
2240
|
+
if (first === undefined || last === undefined || last - first + 1 !== indices.length) {
|
|
2241
|
+
throw new Error('episode_split_requires_contiguous_range');
|
|
2242
|
+
}
|
|
2243
|
+
const pairs = links.map((link) => ({ link, event: resolveEvent(link.eventId) }));
|
|
2244
|
+
const turns = logicalTurnsFromPairs(pairs);
|
|
2245
|
+
if (turns.some((turn) => {
|
|
2246
|
+
const included = turn.filter((pair) => selected.has(pair.link.eventId)).length;
|
|
2247
|
+
return included > 0 && included !== turn.length;
|
|
2248
|
+
}))
|
|
2249
|
+
throw new Error('episode_split_requires_complete_logical_turns');
|
|
2250
|
+
for (const eventId of selected) {
|
|
2251
|
+
const event = resolveEvent(eventId);
|
|
2252
|
+
if (event?.parentEventId && sourceIds.has(event.parentEventId) && !selected.has(event.parentEventId)) {
|
|
2253
|
+
throw new Error('episode_split_requires_complete_tool_chain');
|
|
2254
|
+
}
|
|
2255
|
+
const childInSource = links.some((link) => resolveEvent(link.eventId)?.parentEventId === eventId);
|
|
2256
|
+
if (childInSource && !links.filter((link) => resolveEvent(link.eventId)?.parentEventId === eventId).every((link) => selected.has(link.eventId))) {
|
|
2257
|
+
throw new Error('episode_split_requires_complete_tool_chain');
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
if (!turns.some((turn) => turn.every((pair) => selected.has(pair.link.eventId)) && turn.some((pair) => pair.event?.role === 'user'))) {
|
|
2261
|
+
throw new Error('episode_split_requires_primary_user_turn');
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
if (input.operation === 'move-event') {
|
|
2265
|
+
const link = store.getEventLink(input.eventId);
|
|
2266
|
+
if (link?.episodeId === input.targetEpisodeId)
|
|
2267
|
+
throw new Error('episode_move_source_equals_target');
|
|
2268
|
+
const source = link ? store.getEpisode(link.episodeId) : undefined;
|
|
2269
|
+
const target = store.getEpisode(input.targetEpisodeId);
|
|
2270
|
+
if (!source || !target || source.projectId !== input.projectId || target.projectId !== input.projectId)
|
|
2271
|
+
throw new Error('episode_project_mismatch');
|
|
2272
|
+
if (source.sessionId !== target.sessionId || source.sourceAgent !== target.sourceAgent || source.conversationThreadId !== target.conversationThreadId) {
|
|
2273
|
+
throw new Error('episode_move_scope_mismatch');
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
if (input.operation === 'merge') {
|
|
2277
|
+
if (input.sourceEpisodeId === input.targetEpisodeId)
|
|
2278
|
+
throw new Error('episode_merge_source_equals_target');
|
|
2279
|
+
const source = store.getEpisode(input.sourceEpisodeId);
|
|
2280
|
+
const target = store.getEpisode(input.targetEpisodeId);
|
|
2281
|
+
if (!source || !target || source.projectId !== input.projectId || target.projectId !== input.projectId)
|
|
2282
|
+
throw new Error('episode_project_mismatch');
|
|
2283
|
+
if (source.sessionId !== target.sessionId || source.sourceAgent !== target.sourceAgent || source.conversationThreadId !== target.conversationThreadId) {
|
|
2284
|
+
throw new Error('episode_merge_scope_mismatch');
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
function shellQuote(value) {
|
|
2289
|
+
return `'${String(value).replace(/'/gu, `'\\''`)}'`;
|
|
2290
|
+
}
|
|
2044
2291
|
function requiredGovernancePayloadString(payload, field) {
|
|
2045
2292
|
const value = payload[field];
|
|
2046
2293
|
if (typeof value !== 'string' || value.trim() === '')
|
|
@@ -2125,6 +2372,9 @@ function optionalGovernancePayloadNumber(payload, field) {
|
|
|
2125
2372
|
function uniqueStrings(values) {
|
|
2126
2373
|
return Array.from(new Set(values.filter(Boolean)));
|
|
2127
2374
|
}
|
|
2375
|
+
function sameProvided(left, right) {
|
|
2376
|
+
return right === undefined || (left ?? undefined) === right;
|
|
2377
|
+
}
|
|
2128
2378
|
function extractNavigationTerms(query) {
|
|
2129
2379
|
return uniqueStrings(query
|
|
2130
2380
|
.toLowerCase()
|