openclaw-cortex-memory 0.1.0-Alpha.20 → 0.1.0-Alpha.21

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.
Files changed (60) hide show
  1. package/README.md +163 -228
  2. package/SKILL.md +71 -332
  3. package/dist/index.d.ts +36 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +194 -6
  6. package/dist/index.js.map +1 -1
  7. package/dist/openclaw.plugin.json +208 -12
  8. package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -1
  9. package/dist/src/dedup/three_stage_deduplicator.js +1 -2
  10. package/dist/src/dedup/three_stage_deduplicator.js.map +1 -1
  11. package/dist/src/engine/ts_engine.d.ts +31 -0
  12. package/dist/src/engine/ts_engine.d.ts.map +1 -1
  13. package/dist/src/engine/ts_engine.js +262 -14
  14. package/dist/src/engine/ts_engine.js.map +1 -1
  15. package/dist/src/engine/types.d.ts +1 -0
  16. package/dist/src/engine/types.d.ts.map +1 -1
  17. package/dist/src/graph/ontology.d.ts +65 -15
  18. package/dist/src/graph/ontology.d.ts.map +1 -1
  19. package/dist/src/graph/ontology.js +316 -4
  20. package/dist/src/graph/ontology.js.map +1 -1
  21. package/dist/src/quality/llm_output_validator.d.ts +48 -0
  22. package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
  23. package/dist/src/quality/llm_output_validator.js +404 -0
  24. package/dist/src/quality/llm_output_validator.js.map +1 -0
  25. package/dist/src/reflect/reflector.d.ts.map +1 -1
  26. package/dist/src/reflect/reflector.js +284 -8
  27. package/dist/src/reflect/reflector.js.map +1 -1
  28. package/dist/src/rules/rule_store.d.ts.map +1 -1
  29. package/dist/src/rules/rule_store.js +75 -16
  30. package/dist/src/rules/rule_store.js.map +1 -1
  31. package/dist/src/session/session_end.d.ts +20 -43
  32. package/dist/src/session/session_end.d.ts.map +1 -1
  33. package/dist/src/session/session_end.js +21 -233
  34. package/dist/src/session/session_end.js.map +1 -1
  35. package/dist/src/store/archive_store.d.ts +20 -7
  36. package/dist/src/store/archive_store.d.ts.map +1 -1
  37. package/dist/src/store/archive_store.js +96 -61
  38. package/dist/src/store/archive_store.js.map +1 -1
  39. package/dist/src/store/graph_memory_store.d.ts +44 -0
  40. package/dist/src/store/graph_memory_store.d.ts.map +1 -0
  41. package/dist/src/store/graph_memory_store.js +168 -0
  42. package/dist/src/store/graph_memory_store.js.map +1 -0
  43. package/dist/src/store/read_store.d.ts +27 -0
  44. package/dist/src/store/read_store.d.ts.map +1 -1
  45. package/dist/src/store/read_store.js +653 -94
  46. package/dist/src/store/read_store.js.map +1 -1
  47. package/dist/src/store/vector_store.d.ts +1 -0
  48. package/dist/src/store/vector_store.d.ts.map +1 -1
  49. package/dist/src/store/vector_store.js +1 -0
  50. package/dist/src/store/vector_store.js.map +1 -1
  51. package/dist/src/store/write_store.d.ts +7 -0
  52. package/dist/src/store/write_store.d.ts.map +1 -1
  53. package/dist/src/store/write_store.js +15 -3
  54. package/dist/src/store/write_store.js.map +1 -1
  55. package/dist/src/sync/session_sync.d.ts +48 -0
  56. package/dist/src/sync/session_sync.d.ts.map +1 -1
  57. package/dist/src/sync/session_sync.js +277 -78
  58. package/dist/src/sync/session_sync.js.map +1 -1
  59. package/openclaw.plugin.json +208 -12
  60. package/package.json +6 -4
@@ -1,3 +1,4 @@
1
+ import type { GraphQualityMode } from "../graph/ontology";
1
2
  interface LoggerLike {
2
3
  debug: (message: string, ...args: unknown[]) => void;
3
4
  info: (message: string, ...args: unknown[]) => void;
@@ -13,6 +14,7 @@ interface SessionSyncOptions {
13
14
  baseURL?: string;
14
15
  baseUrl?: string;
15
16
  };
17
+ graphQualityMode?: GraphQualityMode;
16
18
  archiveStore: {
17
19
  storeEvents(events: Array<{
18
20
  event_type: string;
@@ -22,10 +24,14 @@ interface SessionSyncOptions {
22
24
  source: string;
23
25
  target: string;
24
26
  type: string;
27
+ evidence_span?: string;
28
+ confidence?: number;
25
29
  }>;
30
+ entity_types?: Record<string, string>;
26
31
  outcome?: string;
27
32
  session_id: string;
28
33
  source_file: string;
34
+ source_text?: string;
29
35
  confidence?: number;
30
36
  source_event_id?: string;
31
37
  actor?: string;
@@ -39,6 +45,31 @@ interface SessionSyncOptions {
39
45
  }>;
40
46
  }>;
41
47
  };
48
+ graphMemoryStore?: {
49
+ append(input: {
50
+ sourceEventId: string;
51
+ sourceLayer: "archive_event" | "active_only";
52
+ archiveEventId?: string;
53
+ sessionId: string;
54
+ sourceFile?: string;
55
+ eventType?: string;
56
+ entities?: string[];
57
+ entity_types?: Record<string, string>;
58
+ relations?: Array<{
59
+ source: string;
60
+ target: string;
61
+ type: string;
62
+ evidence_span?: string;
63
+ confidence?: number;
64
+ }>;
65
+ gateSource: "sync" | "session_end" | "manual";
66
+ confidence?: number;
67
+ sourceText?: string;
68
+ }): Promise<{
69
+ success: boolean;
70
+ reason?: string;
71
+ }>;
72
+ };
42
73
  writeStore: {
43
74
  writeMemory(args: {
44
75
  text: string;
@@ -51,6 +82,10 @@ interface SessionSyncOptions {
51
82
  }>;
52
83
  };
53
84
  requireLlmForWrite?: boolean;
85
+ writePolicy?: {
86
+ activeTextMaxChars?: number;
87
+ archiveSourceTextMaxChars?: number;
88
+ };
54
89
  }
55
90
  export declare function createSessionSync(options: SessionSyncOptions): {
56
91
  syncMemory(): Promise<{
@@ -73,6 +108,19 @@ export declare function createSessionSync(options: SessionSyncOptions): {
73
108
  archiveEvent: number;
74
109
  skipReasons: Record<string, number>;
75
110
  }>;
111
+ routeTranscript(args: {
112
+ sessionId: string;
113
+ sourceFile: string;
114
+ transcript: string;
115
+ }): Promise<{
116
+ imported: number;
117
+ skipped: number;
118
+ ok: boolean;
119
+ llmDecisions: number;
120
+ activeOnly: number;
121
+ archiveEvent: number;
122
+ skipReasons: Record<string, number>;
123
+ }>;
76
124
  };
77
125
  export {};
78
126
  //# sourceMappingURL=session_sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session_sync.d.ts","sourceRoot":"","sources":["../../../src/sync/session_sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session_sync.d.ts","sourceRoot":"","sources":["../../../src/sync/session_sync.ts"],"names":[],"mappings":"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"}
@@ -37,6 +37,7 @@ exports.createSessionSync = createSessionSync;
37
37
  const crypto = __importStar(require("crypto"));
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
+ const llm_output_validator_1 = require("../quality/llm_output_validator");
40
41
  function asRecord(value) {
41
42
  if (typeof value === "object" && value !== null) {
42
43
  return value;
@@ -211,11 +212,22 @@ function normalizeBaseUrl(value) {
211
212
  return "";
212
213
  return value.endsWith("/") ? value.slice(0, -1) : value;
213
214
  }
214
- const WRITE_GATE_PROMPT_VERSION = "write-gate.v1.1.0";
215
+ function buildEventSnippet(text) {
216
+ const lines = text
217
+ .split(/\r?\n/)
218
+ .map(line => line.trim())
219
+ .filter(Boolean)
220
+ .filter(line => line.length >= 8);
221
+ const actionPattern = /(决定|完成|修复|阻塞|失败|成功|上线|部署|实现|依赖|owner|blocked|resolved|fixed|depends|decide|complete)/i;
222
+ const picked = lines.filter(line => actionPattern.test(line));
223
+ const use = picked.length > 0 ? picked : lines.slice(-20);
224
+ return use.slice(-30).join("\n").slice(-8000);
225
+ }
226
+ const WRITE_GATE_PROMPT_VERSION = "write-gate.v1.3.0";
215
227
  const WRITE_GATE_REGRESSION_SAMPLES = [
216
- "样例A: “今天讨论了三种方案,尚未决策” => active_only",
217
- "样例B: “决定采用B方案并完成上线,错误率下降到0.2%” => archive_event",
218
- "样例C: “好的收到谢谢” => skip",
228
+ "鏍蜂緥A: 鈥滀粖澶╄璁轰簡涓夌鏂规锛屽皻鏈喅绛栤€?=> active_only",
229
+ "鏍蜂緥B: 鈥滃喅瀹氶噰鐢˙鏂规骞跺畬鎴愪笂绾匡紝閿欒鐜囦笅闄嶅埌0.2%鈥?=> archive_event",
230
+ "鏍蜂緥C: 鈥滃ソ鐨勬敹鍒拌阿璋⑩€?=> skip",
219
231
  ];
220
232
  function parseArchiveEventPayload(value) {
221
233
  if (!value || typeof value !== "object") {
@@ -239,70 +251,167 @@ function parseArchiveEventPayload(value) {
239
251
  const source = typeof relation.source === "string" ? relation.source.trim() : "";
240
252
  const target = typeof relation.target === "string" ? relation.target.trim() : "";
241
253
  const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
254
+ const evidenceSpan = typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : "";
255
+ const confidence = typeof relation.confidence === "number"
256
+ ? Math.max(0, Math.min(1, relation.confidence))
257
+ : undefined;
242
258
  if (!source || !target)
243
259
  return null;
244
- return { source, target, type };
260
+ return { source, target, type, evidence_span: evidenceSpan || undefined, confidence };
245
261
  })
246
- .filter((valueItem) => Boolean(valueItem))
262
+ .filter(Boolean)
247
263
  : [];
264
+ const entity_types = typeof obj.entity_types === "object" && obj.entity_types !== null && !Array.isArray(obj.entity_types)
265
+ ? Object.fromEntries(Object.entries(obj.entity_types)
266
+ .filter(([key, value]) => typeof key === "string" && key.trim().length > 0 && typeof value === "string" && value.trim().length > 0)
267
+ .map(([key, value]) => [key.trim(), value.trim()]))
268
+ : undefined;
248
269
  return {
249
270
  event_type: eventType,
250
271
  summary,
251
272
  entities,
273
+ entity_types,
252
274
  relations,
253
275
  outcome: typeof obj.outcome === "string" ? obj.outcome.trim() : "",
254
276
  confidence: typeof obj.confidence === "number" ? Math.max(0, Math.min(1, obj.confidence)) : 0.6,
255
277
  };
256
278
  }
257
- function parseLlmGateDecisions(raw) {
258
- const trimmed = raw.trim();
259
- const fence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
260
- const candidate = fence?.[1]?.trim() || trimmed;
261
- const parsed = JSON.parse(candidate);
262
- const asRecordObj = (typeof parsed === "object" && parsed !== null) ? parsed : null;
263
- const wrapped = Array.isArray(parsed)
264
- ? parsed
265
- : Array.isArray(asRecordObj?.decisions)
266
- ? asRecordObj?.decisions
267
- : [];
268
- if (Array.isArray(wrapped) && wrapped.length > 0) {
269
- const output = [];
270
- for (const item of wrapped) {
279
+ function parseGraphPayload(value) {
280
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
281
+ return null;
282
+ }
283
+ const obj = value;
284
+ const entities = Array.isArray(obj.entities)
285
+ ? obj.entities.map(v => (typeof v === "string" ? v.trim() : "")).filter(Boolean)
286
+ : [];
287
+ const entity_types = typeof obj.entity_types === "object" && obj.entity_types !== null && !Array.isArray(obj.entity_types)
288
+ ? Object.fromEntries(Object.entries(obj.entity_types)
289
+ .filter(([key, val]) => typeof key === "string" && key.trim() && typeof val === "string" && val.trim())
290
+ .map(([key, val]) => [key.trim(), val.trim()]))
291
+ : undefined;
292
+ const relations = Array.isArray(obj.relations)
293
+ ? obj.relations
294
+ .map(item => {
271
295
  if (!item || typeof item !== "object")
272
- continue;
273
- const obj = item;
274
- const target = typeof obj.target_layer === "string" ? obj.target_layer.trim() : "";
275
- if (target !== "active_only" && target !== "archive_event" && target !== "skip") {
276
- continue;
296
+ return null;
297
+ const rel = item;
298
+ const source = typeof rel.source === "string" ? rel.source.trim() : "";
299
+ const target = typeof rel.target === "string" ? rel.target.trim() : "";
300
+ const type = typeof rel.type === "string" && rel.type.trim() ? rel.type.trim() : "related_to";
301
+ if (!source || !target)
302
+ return null;
303
+ const evidenceSpan = typeof rel.evidence_span === "string" ? rel.evidence_span.trim() : "";
304
+ const confidence = typeof rel.confidence === "number"
305
+ ? Math.max(0, Math.min(1, rel.confidence))
306
+ : undefined;
307
+ return {
308
+ source,
309
+ target,
310
+ type,
311
+ ...(evidenceSpan ? { evidence_span: evidenceSpan } : {}),
312
+ ...(typeof confidence === "number" ? { confidence } : {}),
313
+ };
314
+ })
315
+ .filter((item) => item !== null)
316
+ : [];
317
+ if (entities.length === 0 || relations.length === 0) {
318
+ return null;
319
+ }
320
+ return {
321
+ entities,
322
+ entity_types,
323
+ relations,
324
+ confidence: typeof obj.confidence === "number" ? Math.max(0, Math.min(1, obj.confidence)) : undefined,
325
+ };
326
+ }
327
+ function parseLlmGateDecisions(raw, logger) {
328
+ const validation = (0, llm_output_validator_1.validateLlmJsonOutput)(raw);
329
+ if (!validation.valid) {
330
+ if (logger) {
331
+ logger.warn(`quality_gate_decisions_invalid errors=${validation.errors.join("|")}`);
332
+ }
333
+ return [];
334
+ }
335
+ if (validation.warnings.length > 0 && logger) {
336
+ logger.debug(`quality_gate_decisions_warnings warnings=${validation.warnings.join("|")}`);
337
+ }
338
+ const root = validation.data;
339
+ const parsed = Array.isArray(root) ? root : root;
340
+ const output = [];
341
+ const pushDecision = (obj, target) => {
342
+ let event = null;
343
+ if (target === "archive_event") {
344
+ const eventValidation = (0, llm_output_validator_1.validateArchiveEvent)(obj.event || obj);
345
+ if (eventValidation.valid && eventValidation.cleaned) {
346
+ event = {
347
+ event_type: eventValidation.cleaned.event_type || "insight",
348
+ summary: eventValidation.cleaned.summary,
349
+ entities: eventValidation.cleaned.entities,
350
+ entity_types: eventValidation.cleaned.entity_types,
351
+ relations: eventValidation.cleaned.relations,
352
+ outcome: eventValidation.cleaned.outcome || "",
353
+ confidence: eventValidation.cleaned.confidence,
354
+ };
355
+ }
356
+ else {
357
+ if (logger) {
358
+ logger.warn(`quality_event_invalid errors=${eventValidation.errors.join("|")}`);
359
+ }
360
+ return;
277
361
  }
278
- const event = target === "archive_event"
279
- ? parseArchiveEventPayload(obj.event || obj)
280
- : null;
281
- output.push({
282
- target_layer: target,
283
- active_text: typeof obj.active_text === "string" ? obj.active_text.trim() : "",
284
- event: event || undefined,
285
- reason: typeof obj.reason === "string" ? obj.reason.trim() : "",
286
- });
287
362
  }
288
- if (output.length > 0) {
289
- return output;
363
+ output.push({
364
+ candidate_id: typeof obj.candidate_id === "string" ? obj.candidate_id.trim() : "",
365
+ target_layer: target,
366
+ active_text: typeof obj.active_text === "string" ? obj.active_text.trim() : "",
367
+ event: event || undefined,
368
+ graph: parseGraphPayload(obj.graph) || undefined,
369
+ reason: typeof obj.reason === "string" ? obj.reason.trim() : "",
370
+ });
371
+ };
372
+ if (Array.isArray(parsed)) {
373
+ if (logger) {
374
+ logger.warn("quality_gate_decisions_invalid format=array_not_supported_require_routing_plan");
290
375
  }
291
376
  }
292
- const legacyEvents = Array.isArray(asRecordObj?.events) ? asRecordObj?.events : [];
293
- const legacyOutput = [];
294
- for (const item of legacyEvents) {
295
- const parsedEvent = parseArchiveEventPayload(item);
296
- if (!parsedEvent) {
297
- continue;
377
+ else if (parsed && typeof parsed === "object") {
378
+ const rootObj = parsed;
379
+ const routingPlan = (typeof rootObj.routing_plan === "object" && rootObj.routing_plan !== null)
380
+ ? rootObj.routing_plan
381
+ : null;
382
+ if (routingPlan) {
383
+ const buckets = [
384
+ { key: "archive_event", items: routingPlan.archive_event },
385
+ { key: "active_only", items: routingPlan.active_only },
386
+ { key: "skip", items: routingPlan.skip },
387
+ ];
388
+ for (const bucket of buckets) {
389
+ if (!Array.isArray(bucket.items))
390
+ continue;
391
+ for (const item of bucket.items) {
392
+ if (!item || typeof item !== "object")
393
+ continue;
394
+ pushDecision(item, bucket.key);
395
+ }
396
+ }
298
397
  }
299
- legacyOutput.push({
300
- target_layer: "archive_event",
301
- event: parsedEvent,
302
- reason: "legacy_events",
303
- });
398
+ else if (logger) {
399
+ logger.warn("quality_gate_decisions_invalid missing_routing_plan");
400
+ }
401
+ }
402
+ if (output.length === 0 && logger) {
403
+ logger.warn("quality_gate_decisions_empty");
404
+ }
405
+ const deduped = [];
406
+ const seen = new Set();
407
+ for (const item of output) {
408
+ const key = `${item.target_layer}|${item.event?.summary || item.active_text || item.reason || ""}`;
409
+ if (seen.has(key))
410
+ continue;
411
+ seen.add(key);
412
+ deduped.push(item);
304
413
  }
305
- return legacyOutput;
414
+ return deduped;
306
415
  }
307
416
  async function extractGateDecisionsWithLlm(args) {
308
417
  const endpoint = args.llm.baseUrl.endsWith("/chat/completions")
@@ -313,30 +422,35 @@ async function extractGateDecisionsWithLlm(args) {
313
422
  temperature: 0.1,
314
423
  response_format: { type: "json_object" },
315
424
  messages: [
316
- { role: "system", content: "你是出色的记忆提取器。仅输出 JSON" },
425
+ { role: "system", content: "You are a memory write-gate router. Output JSON only." },
317
426
  {
318
427
  role: "user",
319
428
  content: [
320
429
  `prompt_version=${WRITE_GATE_PROMPT_VERSION}`,
321
- "请对以下导入内容做分流判定,target_layer 只能是 active_only、archive_event、skip。",
322
- "分类标准:",
323
- "A) active_only:内容是过程信息/上下文片段/未形成稳定结论;或仅记录进行中状态、临时讨论、零散想法。",
324
- "B) archive_event:内容形成完整可复用事件,需同时满足:有明确对象、有动作/决策、有结果或阶段性结论;summary 需可脱离原上下文理解。",
325
- "C) skip:内容是噪声、重复、空泛寒暄、无业务价值描述,或无法提取清晰事件主体。",
326
- "降噪要求:先剔除与任务无关的寒暄、口头禅、重复表述、无实义感叹词,再做分流判定。",
327
- "降噪要求:对同一事实的重复片段做合并去重,保留一次且优先保留信息更完整的表达。",
328
- "降噪要求:若内容混杂噪声与有效信息,仅保留有效信息进入 active_text event.summary,不要把噪声带入结果。",
329
- "archive_event 额外约束:confidence < 0.35 时优先判为 skip;若关系不明确可省略 relations 但不得伪造。",
330
- "active_only 额外约束:active_text 必须保留关键信息,不得只返回“同上/略”。",
331
- "输出格式必须是 {\"decisions\":[...]}。",
332
- "active_only: 必须给 active_text。",
333
- "archive_event: 必须给 event={event_type,summary,entities[],relations[],outcome,confidence}",
334
- "skip: 必须给 reason。",
335
- "禁止输出任何解释性自然语言。",
430
+ "Execute in 3 stages:",
431
+ "Stage1) Split transcript into candidate_events[]. One candidate should contain one principal event only.",
432
+ "Stage2) Route each candidate_event into target_layer: active_only | archive_event | skip.",
433
+ "Stage3) Output routing plan only. Do NOT claim data has been written.",
434
+ "Classification:",
435
+ "A) active_only: process context, ongoing discussion, temporary status, no stable conclusion.",
436
+ "B) archive_event: reusable event with clear subject + action/decision + outcome/phase conclusion.",
437
+ "C) skip: noise/repetition/chitchat/no clear business value.",
438
+ "Constraints:",
439
+ "- For archive_event: if confidence < 0.35, prefer skip.",
440
+ "- Relations must be grounded in source text. Do not fabricate.",
441
+ "- For active_only: active_text is required.",
442
+ "- Optional graph for active_only: graph={entities[],entity_types,relations[],confidence}.",
443
+ "- For relations: each relation should include source,target,type,evidence_span,confidence.",
444
+ "- If evidence_span or confidence is missing, do not output that relation.",
445
+ "Output JSON schema:",
446
+ "{\"candidate_events\":[{\"candidate_id\":\"c1\",\"span\":\"...\",\"normalized_text\":\"...\"}],\"routing_plan\":{\"archive_event\":[{\"candidate_id\":\"c1\",\"event\":{\"event_type\":\"decision\",\"summary\":\"...\",\"entities\":[\"A\"],\"entity_types\":{\"A\":\"Project\"},\"relations\":[],\"outcome\":\"...\",\"confidence\":0.82}}],\"active_only\":[],\"skip\":[]}}",
447
+ "routing_plan.archive_event[] item: {candidate_id,event}",
448
+ "routing_plan.active_only[] item: {candidate_id,active_text,graph}",
449
+ "routing_plan.skip[] item: {candidate_id,reason}",
336
450
  ...WRITE_GATE_REGRESSION_SAMPLES,
337
- "只输出 JSON",
451
+ "Output JSON only.",
338
452
  "",
339
- args.transcript.slice(-12000),
453
+ buildEventSnippet(args.transcript),
340
454
  ].join("\n"),
341
455
  },
342
456
  ],
@@ -366,7 +480,7 @@ async function extractGateDecisionsWithLlm(args) {
366
480
  lastError = new Error("sync_llm_empty");
367
481
  continue;
368
482
  }
369
- return parseLlmGateDecisions(content);
483
+ return parseLlmGateDecisions(content, args.logger);
370
484
  }
371
485
  catch (error) {
372
486
  clearTimeout(timeoutId);
@@ -390,6 +504,12 @@ function createSessionSync(options) {
390
504
  }
391
505
  async function storeFromTranscript(args) {
392
506
  const skipReasons = {};
507
+ const activeTextMaxChars = typeof options.writePolicy?.activeTextMaxChars === "number"
508
+ ? Math.max(500, Math.min(20000, Math.floor(options.writePolicy.activeTextMaxChars)))
509
+ : 4000;
510
+ const archiveSourceTextMaxChars = typeof options.writePolicy?.archiveSourceTextMaxChars === "number"
511
+ ? Math.max(1000, Math.min(30000, Math.floor(options.writePolicy.archiveSourceTextMaxChars)))
512
+ : 8000;
393
513
  function bumpReason(reason) {
394
514
  const key = reason || "unknown";
395
515
  skipReasons[key] = (skipReasons[key] || 0) + 1;
@@ -407,7 +527,7 @@ function createSessionSync(options) {
407
527
  }
408
528
  options.logger.warn(`Sync gate degraded to active_only for ${args.sessionId}: llm_not_configured`);
409
529
  const fallbackWrite = await options.writeStore.writeMemory({
410
- text: args.transcript.slice(-4000),
530
+ text: args.transcript.slice(-activeTextMaxChars),
411
531
  role: "system",
412
532
  source: `sync_gate_fallback:${args.sourceFile}`,
413
533
  sessionId: args.sessionId,
@@ -433,6 +553,11 @@ function createSessionSync(options) {
433
553
  let skipped = 0;
434
554
  let activeOnly = 0;
435
555
  let archiveEvent = 0;
556
+ let activeAttempted = 0;
557
+ let archiveAttempted = 0;
558
+ let graphAttempted = 0;
559
+ let graphStored = 0;
560
+ let graphSkipped = 0;
436
561
  const archiveInputs = [];
437
562
  for (const decision of decisions) {
438
563
  llmDecisions += 1;
@@ -442,7 +567,8 @@ function createSessionSync(options) {
442
567
  continue;
443
568
  }
444
569
  if (decision.target_layer === "active_only") {
445
- const activeText = (decision.active_text || args.transcript).trim().slice(-4000);
570
+ activeAttempted += 1;
571
+ const activeText = (decision.active_text || args.transcript).trim().slice(-activeTextMaxChars);
446
572
  if (!activeText) {
447
573
  skipped += 1;
448
574
  bumpReason("active_only_empty");
@@ -457,6 +583,34 @@ function createSessionSync(options) {
457
583
  if (writeResult.status === "ok") {
458
584
  imported += 1;
459
585
  activeOnly += 1;
586
+ if (options.graphMemoryStore && decision.graph) {
587
+ graphAttempted += 1;
588
+ const relationFingerprint = (decision.graph.relations || [])
589
+ .map(rel => `${rel.source}|${rel.type}|${rel.target}|${rel.evidence_span || ""}`)
590
+ .sort()
591
+ .join("||");
592
+ const activeSourceEventId = `active:${args.sessionId}:${crypto.createHash("sha1").update(relationFingerprint || activeText).digest("hex").slice(0, 16)}`;
593
+ const graphResult = await options.graphMemoryStore.append({
594
+ sourceEventId: activeSourceEventId,
595
+ sourceLayer: "active_only",
596
+ sessionId: args.sessionId,
597
+ sourceFile: args.sourceFile,
598
+ eventType: "insight",
599
+ entities: decision.graph.entities,
600
+ entity_types: decision.graph.entity_types,
601
+ relations: decision.graph.relations,
602
+ gateSource: "sync",
603
+ confidence: decision.graph.confidence,
604
+ sourceText: activeText,
605
+ });
606
+ if (!graphResult.success) {
607
+ graphSkipped += 1;
608
+ options.logger.info(`graph_skip_reason=${graphResult.reason} source_event_id=${activeSourceEventId}`);
609
+ }
610
+ else {
611
+ graphStored += 1;
612
+ }
613
+ }
460
614
  }
461
615
  else {
462
616
  skipped += 1;
@@ -465,6 +619,7 @@ function createSessionSync(options) {
465
619
  continue;
466
620
  }
467
621
  if (decision.target_layer === "archive_event") {
622
+ archiveAttempted += 1;
468
623
  if (!decision.event) {
469
624
  skipped += 1;
470
625
  bumpReason("archive_event_missing_payload");
@@ -475,26 +630,67 @@ function createSessionSync(options) {
475
630
  summary: decision.event.summary,
476
631
  entities: decision.event.entities,
477
632
  relations: decision.event.relations,
633
+ entity_types: decision.event.entity_types,
478
634
  outcome: decision.event.outcome,
479
635
  confidence: decision.event.confidence,
480
636
  session_id: args.sessionId,
481
637
  source_file: args.sourceFile,
482
- source_event_id: "",
638
+ source_text: args.transcript.slice(-archiveSourceTextMaxChars),
639
+ source_event_id: decision.candidate_id
640
+ ? `candidate:${args.sessionId}:${decision.candidate_id}`
641
+ : `candidate:${args.sessionId}:${crypto.createHash("sha1").update(decision.event.summary).digest("hex").slice(0, 16)}`,
483
642
  actor: "sync_llm_gate",
484
643
  });
485
644
  }
486
645
  }
487
646
  if (archiveInputs.length > 0) {
488
- const archiveResult = await options.archiveStore.storeEvents(archiveInputs);
489
- imported += archiveResult.stored.length;
490
- skipped += archiveResult.skipped.length;
491
- archiveEvent += archiveResult.stored.length;
492
- for (const skip of archiveResult.skipped) {
493
- bumpReason(skip.reason || "archive_store_skipped");
647
+ let archivedSuccess = 0;
648
+ let archivedSkipped = 0;
649
+ for (const inputRecord of archiveInputs) {
650
+ const archiveResult = await options.archiveStore.storeEvents([inputRecord]);
651
+ imported += archiveResult.stored.length;
652
+ skipped += archiveResult.skipped.length;
653
+ archiveEvent += archiveResult.stored.length;
654
+ archivedSuccess += archiveResult.stored.length;
655
+ archivedSkipped += archiveResult.skipped.length;
656
+ for (const skip of archiveResult.skipped) {
657
+ bumpReason(skip.reason || "archive_store_skipped");
658
+ }
659
+ const archiveRecord = archiveResult.stored[0];
660
+ if (!archiveRecord) {
661
+ continue;
662
+ }
663
+ if (!options.graphMemoryStore) {
664
+ continue;
665
+ }
666
+ graphAttempted += 1;
667
+ const graphResult = await options.graphMemoryStore.append({
668
+ // Graph trace points to persisted archive record id for stable lookup.
669
+ sourceEventId: archiveRecord.id,
670
+ sourceLayer: "archive_event",
671
+ archiveEventId: archiveRecord.id,
672
+ sessionId: args.sessionId,
673
+ sourceFile: args.sourceFile,
674
+ eventType: inputRecord.event_type,
675
+ entities: inputRecord.entities,
676
+ entity_types: inputRecord.entity_types,
677
+ relations: inputRecord.relations,
678
+ gateSource: "sync",
679
+ confidence: inputRecord.confidence,
680
+ sourceText: args.transcript,
681
+ });
682
+ if (!graphResult.success) {
683
+ graphSkipped += 1;
684
+ options.logger.info(`graph_skip_reason=${graphResult.reason} source_event_id=${archiveRecord.id}`);
685
+ }
686
+ else {
687
+ graphStored += 1;
688
+ }
494
689
  }
495
- options.logger.info(`sync_archive_result session=${args.sessionId} archived_success=${archiveResult.stored.length} skipped=${archiveResult.skipped.length}`);
690
+ options.logger.info(`sync_archive_result session=${args.sessionId} archived_success=${archivedSuccess} skipped=${archivedSkipped}`);
496
691
  }
497
692
  options.logger.info(`sync_gate_result session=${args.sessionId} llm_decisions=${llmDecisions} active_only=${activeOnly} archive_event=${archiveEvent} skipped=${skipped}`);
693
+ options.logger.info(`sync_gate_metrics session=${args.sessionId} active_attempted=${activeAttempted} archive_attempted=${archiveAttempted} graph_attempted=${graphAttempted} graph_stored=${graphStored} graph_skipped=${graphSkipped} skip_reason_kinds=${Object.keys(skipReasons).length}`);
498
694
  return {
499
695
  imported,
500
696
  skipped,
@@ -661,6 +857,9 @@ function createSessionSync(options) {
661
857
  skipReasons,
662
858
  };
663
859
  }
664
- return { syncMemory, syncDailySummaries };
860
+ async function routeTranscript(args) {
861
+ return storeFromTranscript(args);
862
+ }
863
+ return { syncMemory, syncDailySummaries, routeTranscript };
665
864
  }
666
865
  //# sourceMappingURL=session_sync.js.map