clawvault 2.6.1 → 2.6.4

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 (125) hide show
  1. package/README.md +352 -20
  2. package/bin/clawvault.js +8 -2
  3. package/bin/command-runtime.js +9 -1
  4. package/bin/register-maintenance-commands.js +19 -0
  5. package/bin/register-query-commands.js +58 -6
  6. package/bin/register-workgraph-commands.js +451 -0
  7. package/dist/{chunk-VXEOHTSL.js → chunk-2JQ3O2YL.js} +1 -1
  8. package/dist/{chunk-VR5NE7PZ.js → chunk-2RAZ4ZFE.js} +1 -1
  9. package/dist/chunk-2ZDO52B4.js +52 -0
  10. package/dist/chunk-4BQTQMJP.js +93 -0
  11. package/dist/{chunk-MAKNAHAW.js → chunk-5PJ4STIC.js} +98 -8
  12. package/dist/{chunk-IEVLHNLU.js → chunk-627Q3QWK.js} +3 -3
  13. package/dist/{chunk-R6SXNSFD.js → chunk-6NYYDNNG.js} +3 -3
  14. package/dist/chunk-ECRZL5XR.js +50 -0
  15. package/dist/chunk-GNJL4YGR.js +79 -0
  16. package/dist/{chunk-OZ7RIXTO.js → chunk-IIOU45CK.js} +1 -1
  17. package/dist/chunk-L4HSSQ6T.js +152 -0
  18. package/dist/{chunk-XAVB4GB4.js → chunk-LIGHWOH6.js} +1 -1
  19. package/dist/{chunk-PBEE567J.js → chunk-LUBZXECN.js} +2 -2
  20. package/dist/{chunk-UEOUADMO.js → chunk-MFL6EEPF.js} +204 -35
  21. package/dist/chunk-MM6QGW3P.js +207 -0
  22. package/dist/{chunk-T76H47ZS.js → chunk-MNPUYCHQ.js} +1 -1
  23. package/dist/{chunk-TLGBDTYT.js → chunk-MPOSMDMU.js} +6 -6
  24. package/dist/{chunk-RVYA52PY.js → chunk-NJYJL5AA.js} +1 -1
  25. package/dist/{chunk-Q2J5YTUF.js → chunk-OQGYFZ4A.js} +669 -33
  26. package/dist/{chunk-ME37YNW3.js → chunk-P7SY3D4E.js} +3 -3
  27. package/dist/chunk-RHISK3SZ.js +189 -0
  28. package/dist/{chunk-3BTHWPMB.js → chunk-S5OJEGFG.js} +2 -2
  29. package/dist/{chunk-MGDEINGP.js → chunk-SS4B7P7V.js} +1 -1
  30. package/dist/chunk-U4O6C46S.js +154 -0
  31. package/dist/{chunk-ITPEXLHA.js → chunk-URXDAUVH.js} +24 -5
  32. package/dist/chunk-WIOLLGAD.js +190 -0
  33. package/dist/chunk-WMGIIABP.js +15 -0
  34. package/dist/{chunk-QVMXF7FY.js → chunk-X3SPPUFG.js} +50 -0
  35. package/dist/{chunk-THRJVD4L.js → chunk-Y6VJKXGL.js} +1 -1
  36. package/dist/{chunk-KL4NAOMO.js → chunk-YDWHS4LJ.js} +49 -9
  37. package/dist/{chunk-4VRIMU4O.js → chunk-YNIPYN4F.js} +4 -4
  38. package/dist/{chunk-HIHOUSXS.js → chunk-YXQCA6B7.js} +105 -1
  39. package/dist/cli/index.js +18 -16
  40. package/dist/commands/archive.js +3 -2
  41. package/dist/commands/backlog.js +1 -0
  42. package/dist/commands/blocked.js +1 -0
  43. package/dist/commands/canvas.js +1 -0
  44. package/dist/commands/checkpoint.js +1 -0
  45. package/dist/commands/compat.js +2 -1
  46. package/dist/commands/context.js +5 -3
  47. package/dist/commands/doctor.d.ts +10 -1
  48. package/dist/commands/doctor.js +11 -8
  49. package/dist/commands/embed.js +5 -3
  50. package/dist/commands/entities.js +2 -1
  51. package/dist/commands/graph.js +3 -2
  52. package/dist/commands/inject.d.ts +1 -1
  53. package/dist/commands/inject.js +4 -3
  54. package/dist/commands/kanban.js +1 -0
  55. package/dist/commands/link.js +2 -1
  56. package/dist/commands/migrate-observations.js +3 -2
  57. package/dist/commands/observe.js +8 -6
  58. package/dist/commands/project.js +1 -0
  59. package/dist/commands/rebuild-embeddings.d.ts +21 -0
  60. package/dist/commands/rebuild-embeddings.js +91 -0
  61. package/dist/commands/rebuild.js +6 -4
  62. package/dist/commands/recover.js +1 -0
  63. package/dist/commands/reflect.js +5 -4
  64. package/dist/commands/repair-session.js +1 -0
  65. package/dist/commands/replay.js +7 -6
  66. package/dist/commands/session-recap.js +1 -0
  67. package/dist/commands/setup.js +3 -2
  68. package/dist/commands/shell-init.js +2 -0
  69. package/dist/commands/sleep.d.ts +1 -1
  70. package/dist/commands/sleep.js +8 -6
  71. package/dist/commands/status.d.ts +2 -0
  72. package/dist/commands/status.js +35 -24
  73. package/dist/commands/sync-bd.js +3 -2
  74. package/dist/commands/tailscale.js +3 -2
  75. package/dist/commands/task.js +1 -0
  76. package/dist/commands/template.js +1 -0
  77. package/dist/commands/wake.d.ts +1 -1
  78. package/dist/commands/wake.js +4 -2
  79. package/dist/index.d.ts +333 -10
  80. package/dist/index.js +320 -33
  81. package/dist/{inject-x65KXWPk.d.ts → inject-DYUrDqQO.d.ts} +2 -2
  82. package/dist/ledger-B7g7jhqG.d.ts +44 -0
  83. package/dist/lib/auto-linker.js +1 -0
  84. package/dist/lib/canvas-layout.js +1 -0
  85. package/dist/lib/config.d.ts +27 -3
  86. package/dist/lib/config.js +4 -1
  87. package/dist/lib/entity-index.js +1 -0
  88. package/dist/lib/project-utils.js +1 -0
  89. package/dist/lib/session-repair.js +1 -0
  90. package/dist/lib/session-utils.js +1 -0
  91. package/dist/lib/tailscale.js +1 -0
  92. package/dist/lib/task-utils.js +1 -0
  93. package/dist/lib/template-engine.js +1 -0
  94. package/dist/lib/webdav.js +1 -0
  95. package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
  96. package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
  97. package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
  98. package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
  99. package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
  100. package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
  101. package/dist/registry-BR4326o0.d.ts +30 -0
  102. package/dist/store-CA-6sKCJ.d.ts +34 -0
  103. package/dist/thread-B9LhXNU0.d.ts +41 -0
  104. package/dist/transformers.node-A2ZRORSQ.js +46775 -0
  105. package/dist/{types-C74wgGL1.d.ts → types-BbWJoC1c.d.ts} +1 -1
  106. package/dist/workgraph/index.d.ts +5 -0
  107. package/dist/workgraph/index.js +23 -0
  108. package/dist/workgraph/ledger.d.ts +2 -0
  109. package/dist/workgraph/ledger.js +25 -0
  110. package/dist/workgraph/registry.d.ts +2 -0
  111. package/dist/workgraph/registry.js +19 -0
  112. package/dist/workgraph/store.d.ts +2 -0
  113. package/dist/workgraph/store.js +25 -0
  114. package/dist/workgraph/thread.d.ts +2 -0
  115. package/dist/workgraph/thread.js +25 -0
  116. package/dist/workgraph/types.d.ts +54 -0
  117. package/dist/workgraph/types.js +7 -0
  118. package/hooks/clawvault/HOOK.md +34 -4
  119. package/hooks/clawvault/handler.js +751 -8
  120. package/hooks/clawvault/handler.test.js +247 -0
  121. package/hooks/clawvault/openclaw.plugin.json +72 -0
  122. package/openclaw.plugin.json +84 -0
  123. package/package.json +8 -4
  124. package/dist/chunk-4QYGFWRM.js +0 -88
  125. package/dist/chunk-MXSSG3QU.js +0 -42
@@ -14,7 +14,11 @@ import {
14
14
  listConfig,
15
15
  listRouteRules,
16
16
  matchRouteRule
17
- } from "./chunk-ITPEXLHA.js";
17
+ } from "./chunk-URXDAUVH.js";
18
+ import {
19
+ requestLlmCompletion,
20
+ resolveLlmProvider
21
+ } from "./chunk-YXQCA6B7.js";
18
22
  import {
19
23
  ensureLedgerStructure,
20
24
  ensureParentDir,
@@ -33,13 +37,17 @@ import {
33
37
 
34
38
  // src/observer/compressor.ts
35
39
  var OPENAI_BASE_URL = "https://api.openai.com/v1";
40
+ var XAI_BASE_URL = "https://api.x.ai/v1";
36
41
  var OLLAMA_BASE_URL = "http://localhost:11434/v1";
37
42
  var DEFAULT_PROVIDER_MODELS = {
38
43
  anthropic: "claude-3-5-haiku-latest",
39
44
  openai: "gpt-4o-mini",
40
45
  gemini: "gemini-2.0-flash",
46
+ xai: "grok-2-latest",
41
47
  "openai-compatible": "gpt-4o-mini",
42
- ollama: "llama3.2"
48
+ ollama: "llama3.2",
49
+ minimax: "MiniMax-M2.1",
50
+ zai: "glm-4.5-air"
43
51
  };
44
52
  var CRITICAL_RE = /(?:\b(?:decision|decided|chose|chosen|selected|picked|opted|switched to)\s*:?|\bdecid(?:e|ed|ing|ion)\b|\berror\b|\bfail(?:ed|ure|ing)?\b|\bblock(?:ed|er)?\b|\bbreaking(?:\s+change)?s?\b|\bcritical\b|\b\w+\s+chosen\s+(?:for|over|as)\b|\bpublish(?:ed)?\b.*@?\d+\.\d+|\bmerge[d]?\s+(?:PR|pull\s+request)\b|\bshipped\b|\breleased?\b.*v?\d+\.\d+|\bsigned\b.*\b(?:contract|agreement|deal)\b|\bpricing\b.*\$|\bdemo\b.*\b(?:completed?|done|finished)\b|\bmeeting\b.*\b(?:completed?|done|finished)\b|\bstrategy\b.*\b(?:pivot|change|shift)\b)/i;
45
53
  var DEADLINE_WITH_DATE_RE = /(?:(?:\bdeadline\b|\bdue(?:\s+date)?\b|\bcutoff\b).*(?:\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}(?:\/\d{2,4})?|(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{1,2})|(?:\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}(?:\/\d{2,4})?|(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{1,2}).*(?:\bdeadline\b|\bdue(?:\s+date)?\b|\bcutoff\b))/i;
@@ -72,7 +80,7 @@ var Compressor = class {
72
80
  const backend = this.resolveProvider();
73
81
  if (backend) {
74
82
  try {
75
- const llmOutput = backend.provider === "anthropic" ? await this.callAnthropic(prompt, backend) : backend.provider === "gemini" ? await this.callGemini(prompt, backend) : backend.provider === "openai" ? await this.callOpenAI(prompt, backend) : await this.callOpenAICompatible(prompt, backend);
83
+ const llmOutput = backend.provider === "anthropic" ? await this.callAnthropic(prompt, backend) : backend.provider === "gemini" ? await this.callGemini(prompt, backend) : backend.provider === "openai" ? await this.callOpenAI(prompt, backend) : backend.provider === "xai" ? await this.callXAI(prompt, backend) : await this.callOpenAICompatible(prompt, backend);
76
84
  const normalized = this.normalizeLlmOutput(llmOutput);
77
85
  if (normalized) {
78
86
  return this.mergeObservations(existingObservations, normalized);
@@ -130,6 +138,18 @@ var Compressor = class {
130
138
  baseUrl: this.resolveBaseUrl(provider)
131
139
  };
132
140
  }
141
+ if (provider === "xai") {
142
+ const apiKey2 = this.resolveApiKey(provider);
143
+ if (!apiKey2) {
144
+ return null;
145
+ }
146
+ return {
147
+ provider,
148
+ model,
149
+ apiKey: apiKey2,
150
+ baseUrl: XAI_BASE_URL
151
+ };
152
+ }
133
153
  const apiKey = this.resolveApiKey(provider) ?? void 0;
134
154
  return {
135
155
  provider,
@@ -164,6 +184,15 @@ var Compressor = class {
164
184
  apiKey: geminiApiKey
165
185
  };
166
186
  }
187
+ const xaiApiKey = this.readEnvValue("XAI_API_KEY");
188
+ if (xaiApiKey) {
189
+ return {
190
+ provider: "xai",
191
+ model: allowConfiguredModel ? this.resolveModel("xai") : DEFAULT_PROVIDER_MODELS.xai,
192
+ apiKey: xaiApiKey,
193
+ baseUrl: XAI_BASE_URL
194
+ };
195
+ }
167
196
  return null;
168
197
  }
169
198
  resolveModel(provider) {
@@ -184,6 +213,9 @@ var Compressor = class {
184
213
  if (provider === "gemini") {
185
214
  return this.readEnvValue("GEMINI_API_KEY");
186
215
  }
216
+ if (provider === "xai") {
217
+ return this.readEnvValue("XAI_API_KEY");
218
+ }
187
219
  return this.readEnvValue("OPENAI_API_KEY");
188
220
  }
189
221
  resolveBaseUrl(provider) {
@@ -316,6 +348,9 @@ var Compressor = class {
316
348
  async callOpenAI(prompt, backend) {
317
349
  return this.callOpenAICompatible(prompt, backend);
318
350
  }
351
+ async callXAI(prompt, backend) {
352
+ return this.callOpenAICompatible(prompt, backend);
353
+ }
319
354
  async callOpenAICompatible(prompt, backend) {
320
355
  const baseUrl = backend.baseUrl ?? this.resolveBaseUrl(backend.provider);
321
356
  const response = await this.fetchImpl(this.buildOpenAICompatibleUrl(baseUrl), {
@@ -691,13 +726,496 @@ var Reflector = class {
691
726
  }
692
727
  };
693
728
 
694
- // src/observer/observer.ts
695
- import * as fs2 from "fs";
696
- import * as path2 from "path";
729
+ // src/lib/fact-extractor.ts
730
+ function normalizeEntity(name) {
731
+ return name.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, " ").trim();
732
+ }
733
+ function factId(entity, relation, value) {
734
+ const key = `${normalizeEntity(entity)}::${relation.toLowerCase()}::${value.toLowerCase().trim()}`;
735
+ let hash = 0;
736
+ for (let i = 0; i < key.length; i++) {
737
+ const char = key.charCodeAt(i);
738
+ hash = (hash << 5) - hash + char;
739
+ hash = hash & hash;
740
+ }
741
+ return Math.abs(hash).toString(36);
742
+ }
743
+ var PREFERENCE_PATTERNS = [
744
+ {
745
+ // "I prefer X" / "I like X" / "I love X" / "I enjoy X"
746
+ pattern: /\b(?:i|user|they)\s+(?:prefer|like|love|enjoy|want|favor)s?\s+(.+?)(?:\.|,|$)/i,
747
+ extract: (m) => ({
748
+ entity: "user",
749
+ relation: "prefers",
750
+ value: m[1].trim(),
751
+ category: "preference"
752
+ })
753
+ },
754
+ {
755
+ // "my favorite X is Y"
756
+ pattern: /\bmy\s+(?:favorite|favourite|preferred)\s+(\w+)\s+(?:is|are)\s+(.+?)(?:\.|,|$)/i,
757
+ extract: (m) => ({
758
+ entity: "user",
759
+ relation: `favorite_${m[1].toLowerCase()}`,
760
+ value: m[2].trim(),
761
+ category: "preference"
762
+ })
763
+ },
764
+ {
765
+ // "I don't like X" / "I hate X" / "I dislike X"
766
+ pattern: /\b(?:i|user)\s+(?:don'?t\s+like|hate|dislike|avoid)s?\s+(.+?)(?:\.|,|$)/i,
767
+ extract: (m) => ({
768
+ entity: "user",
769
+ relation: "dislikes",
770
+ value: m[1].trim(),
771
+ category: "preference"
772
+ })
773
+ },
774
+ {
775
+ // "I'm allergic to X" / "I have an allergy to X"
776
+ pattern: /\b(?:i'?m|i\s+am|i\s+have)\s+(?:an?\s+)?allerg(?:ic|y)\s+(?:to\s+)?(.+?)(?:\.|,|$)/i,
777
+ extract: (m) => ({
778
+ entity: "user",
779
+ relation: "allergic_to",
780
+ value: m[1].trim(),
781
+ category: "preference"
782
+ })
783
+ }
784
+ ];
785
+ var FACT_PATTERNS = [
786
+ {
787
+ // "X works at Y" / "X is employed at Y"
788
+ pattern: /\b(\w+(?:\s+\w+)?)\s+(?:works?\s+(?:at|for)|is\s+employed\s+(?:at|by))\s+(.+?)(?:\.|,|$)/i,
789
+ extract: (m) => ({
790
+ entity: m[1].trim(),
791
+ relation: "works_at",
792
+ value: m[2].trim(),
793
+ category: "fact"
794
+ })
795
+ },
796
+ {
797
+ // "X lives in Y" / "X moved to Y"
798
+ pattern: /\b(\w+(?:\s+\w+)?)\s+(?:live[sd]?\s+in|moved?\s+to|relocated?\s+to)\s+(.+?)(?:\.|,|$)/i,
799
+ extract: (m) => ({
800
+ entity: m[1].trim(),
801
+ relation: "lives_in",
802
+ value: m[2].trim(),
803
+ category: "fact"
804
+ })
805
+ },
806
+ {
807
+ // "X is Y years old" / "X's age is Y"
808
+ pattern: /\b(\w+(?:\s+\w+)?)\s+(?:is|turned)\s+(\d+)\s+years?\s+old/i,
809
+ extract: (m) => ({
810
+ entity: m[1].trim(),
811
+ relation: "age",
812
+ value: m[2],
813
+ category: "fact"
814
+ })
815
+ },
816
+ {
817
+ // "X bought Y" / "X purchased Y"
818
+ pattern: /\b(\w+(?:\s+\w+)?)\s+(?:bought|purchased|got|acquired)\s+(?:a\s+|an\s+|the\s+)?(.+?)(?:\s+for\s+\$?([\d,.]+))?(?:\.|,|$)/i,
819
+ extract: (m) => ({
820
+ entity: m[1].trim(),
821
+ relation: "bought",
822
+ value: m[3] ? `${m[2].trim()} ($${m[3]})` : m[2].trim(),
823
+ category: "event"
824
+ })
825
+ },
826
+ {
827
+ // "X spent $Y on Z"
828
+ pattern: /\b(\w+(?:\s+\w+)?)\s+spent\s+\$?([\d,.]+)\s+on\s+(.+?)(?:\.|,|$)/i,
829
+ extract: (m) => ({
830
+ entity: m[1].trim(),
831
+ relation: "spent_on",
832
+ value: `$${m[2]} on ${m[3].trim()}`,
833
+ category: "event"
834
+ })
835
+ }
836
+ ];
837
+ var DECISION_PATTERNS = [
838
+ {
839
+ // "decided to X" / "we decided X"
840
+ pattern: /\b(?:i|we|user)\s+decided\s+(?:to\s+)?(.+?)(?:\.|,|$)/i,
841
+ extract: (m) => ({
842
+ entity: "user",
843
+ relation: "decided",
844
+ value: m[1].trim(),
845
+ category: "decision"
846
+ })
847
+ },
848
+ {
849
+ // "chose X over Y"
850
+ pattern: /\b(?:i|we|user)\s+chose\s+(.+?)\s+over\s+(.+?)(?:\.|,|$)/i,
851
+ extract: (m) => ({
852
+ entity: "user",
853
+ relation: "chose",
854
+ value: `${m[1].trim()} (over ${m[2].trim()})`,
855
+ category: "decision"
856
+ })
857
+ }
858
+ ];
859
+ var ALL_PATTERNS = [...PREFERENCE_PATTERNS, ...FACT_PATTERNS, ...DECISION_PATTERNS];
860
+ function extractFactsRuleBased(text, source, timestamp) {
861
+ const facts = [];
862
+ const now = timestamp || (/* @__PURE__ */ new Date()).toISOString();
863
+ const sentences = text.split(/[.!?\n]+/).filter((s) => s.trim().length > 5);
864
+ for (const sentence of sentences) {
865
+ const trimmed = sentence.trim();
866
+ for (const rule of ALL_PATTERNS) {
867
+ const match = trimmed.match(rule.pattern);
868
+ if (match) {
869
+ const extracted = rule.extract(match);
870
+ if (extracted && extracted.value.length > 1 && extracted.value.length < 200) {
871
+ facts.push({
872
+ id: factId(extracted.entity, extracted.relation, extracted.value),
873
+ entity: extracted.entity,
874
+ entityNorm: normalizeEntity(extracted.entity),
875
+ relation: extracted.relation,
876
+ value: extracted.value,
877
+ validFrom: now,
878
+ validUntil: null,
879
+ confidence: 0.7,
880
+ // Rule-based gets moderate confidence
881
+ category: extracted.category,
882
+ source,
883
+ rawText: trimmed
884
+ });
885
+ }
886
+ }
887
+ }
888
+ }
889
+ return facts;
890
+ }
891
+ var EXTRACTION_PROMPT = `Extract structured facts from the following text. Return a JSON array of objects with these fields:
892
+ - entity: the subject (person, place, thing, or "user" for the speaker)
893
+ - relation: the relationship type (e.g., "prefers", "works_at", "lives_in", "bought", "spent_on", "age", "decided", "allergic_to")
894
+ - value: the object of the relation
895
+ - category: one of "preference", "fact", "decision", "entity", "event"
896
+ - confidence: 0.0 to 1.0
697
897
 
698
- // src/observer/router.ts
898
+ Rules:
899
+ - Extract ALL facts, preferences, decisions, and events
900
+ - For preferences, use "user" as entity
901
+ - For monetary amounts, include the dollar sign
902
+ - Be precise \u2014 only extract what's explicitly stated
903
+ - Return empty array [] if no facts found
904
+
905
+ Text:
906
+ `;
907
+ async function extractFactsLlm(text, source, timestamp, llmFn) {
908
+ if (!llmFn) {
909
+ return extractFactsRuleBased(text, source, timestamp);
910
+ }
911
+ const now = timestamp || (/* @__PURE__ */ new Date()).toISOString();
912
+ try {
913
+ const response = await llmFn(EXTRACTION_PROMPT + text);
914
+ const jsonMatch = response.match(/\[[\s\S]*?\]/);
915
+ if (!jsonMatch) {
916
+ return extractFactsRuleBased(text, source, timestamp);
917
+ }
918
+ const parsed = JSON.parse(jsonMatch[0]);
919
+ return parsed.map((f) => ({
920
+ id: factId(f.entity, f.relation, f.value),
921
+ entity: f.entity,
922
+ entityNorm: normalizeEntity(f.entity),
923
+ relation: f.relation,
924
+ value: f.value,
925
+ validFrom: now,
926
+ validUntil: null,
927
+ confidence: Math.min(1, Math.max(0, f.confidence || 0.8)),
928
+ category: f.category || "fact",
929
+ source,
930
+ rawText: text.substring(0, 500)
931
+ }));
932
+ } catch {
933
+ return extractFactsRuleBased(text, source, timestamp);
934
+ }
935
+ }
936
+
937
+ // src/lib/fact-store.ts
699
938
  import * as fs from "fs";
700
939
  import * as path from "path";
940
+ var FactStore = class {
941
+ facts = /* @__PURE__ */ new Map();
942
+ byEntity = /* @__PURE__ */ new Map();
943
+ byRelation = /* @__PURE__ */ new Map();
944
+ byCategory = /* @__PURE__ */ new Map();
945
+ factsPath;
946
+ dirty = false;
947
+ constructor(vaultPath) {
948
+ this.factsPath = path.join(vaultPath, ".clawvault", "facts.jsonl");
949
+ }
950
+ /** Load facts from disk */
951
+ load() {
952
+ this.facts.clear();
953
+ this.byEntity.clear();
954
+ this.byRelation.clear();
955
+ this.byCategory.clear();
956
+ if (!fs.existsSync(this.factsPath)) return;
957
+ const lines = fs.readFileSync(this.factsPath, "utf-8").split("\n").filter((l) => l.trim());
958
+ for (const line of lines) {
959
+ try {
960
+ const fact = JSON.parse(line);
961
+ this.indexFact(fact);
962
+ } catch {
963
+ }
964
+ }
965
+ }
966
+ /** Add facts with conflict resolution. Returns number of conflicts resolved. */
967
+ addFacts(newFacts) {
968
+ let conflicts = 0;
969
+ for (const fact of newFacts) {
970
+ const existing = this.findConflict(fact);
971
+ if (existing) {
972
+ existing.validUntil = fact.validFrom;
973
+ conflicts++;
974
+ }
975
+ this.indexFact(fact);
976
+ this.dirty = true;
977
+ }
978
+ return conflicts;
979
+ }
980
+ /** Find an existing fact that conflicts with the new one */
981
+ findConflict(newFact) {
982
+ const entityFacts = this.byEntity.get(newFact.entityNorm);
983
+ if (!entityFacts) return null;
984
+ for (const id of entityFacts) {
985
+ const existing = this.facts.get(id);
986
+ if (!existing || existing.validUntil) continue;
987
+ if (existing.relation === newFact.relation) {
988
+ if (this.isSimilarValue(existing.value, newFact.value)) {
989
+ return existing;
990
+ }
991
+ if (this.isExclusiveRelation(newFact.relation)) {
992
+ return existing;
993
+ }
994
+ }
995
+ }
996
+ return null;
997
+ }
998
+ /** Check if two values are similar enough to be considered the same fact */
999
+ isSimilarValue(a, b) {
1000
+ const na = a.toLowerCase().trim();
1001
+ const nb = b.toLowerCase().trim();
1002
+ if (na === nb) return true;
1003
+ if (na.includes(nb) || nb.includes(na)) return true;
1004
+ return false;
1005
+ }
1006
+ /** Relations where only one value can be active at a time */
1007
+ isExclusiveRelation(relation) {
1008
+ const exclusive = /* @__PURE__ */ new Set([
1009
+ "lives_in",
1010
+ "works_at",
1011
+ "age",
1012
+ "favorite_color",
1013
+ "favorite_food",
1014
+ "favorite_restaurant",
1015
+ "favorite_movie",
1016
+ "favorite_book",
1017
+ "favorite_music",
1018
+ "favorite_sport",
1019
+ "job_title",
1020
+ "employer",
1021
+ "marital_status",
1022
+ "city",
1023
+ "country"
1024
+ ]);
1025
+ return exclusive.has(relation);
1026
+ }
1027
+ /** Index a fact in all lookup maps */
1028
+ indexFact(fact) {
1029
+ this.facts.set(fact.id, fact);
1030
+ if (!this.byEntity.has(fact.entityNorm)) {
1031
+ this.byEntity.set(fact.entityNorm, /* @__PURE__ */ new Set());
1032
+ }
1033
+ this.byEntity.get(fact.entityNorm).add(fact.id);
1034
+ if (!this.byRelation.has(fact.relation)) {
1035
+ this.byRelation.set(fact.relation, /* @__PURE__ */ new Set());
1036
+ }
1037
+ this.byRelation.get(fact.relation).add(fact.id);
1038
+ if (!this.byCategory.has(fact.category)) {
1039
+ this.byCategory.set(fact.category, /* @__PURE__ */ new Set());
1040
+ }
1041
+ this.byCategory.get(fact.category).add(fact.id);
1042
+ }
1043
+ /** Save facts to disk (full rewrite for consistency) */
1044
+ save() {
1045
+ if (!this.dirty && fs.existsSync(this.factsPath)) return;
1046
+ const dir = path.dirname(this.factsPath);
1047
+ if (!fs.existsSync(dir)) {
1048
+ fs.mkdirSync(dir, { recursive: true });
1049
+ }
1050
+ const lines = Array.from(this.facts.values()).map((f) => JSON.stringify(f)).join("\n");
1051
+ fs.writeFileSync(this.factsPath, lines + "\n", "utf-8");
1052
+ this.dirty = false;
1053
+ }
1054
+ /** Append new facts to disk (faster than full rewrite) */
1055
+ append(facts) {
1056
+ const dir = path.dirname(this.factsPath);
1057
+ if (!fs.existsSync(dir)) {
1058
+ fs.mkdirSync(dir, { recursive: true });
1059
+ }
1060
+ const lines = facts.map((f) => JSON.stringify(f)).join("\n");
1061
+ fs.appendFileSync(this.factsPath, lines + "\n", "utf-8");
1062
+ }
1063
+ // ─── Query methods ──────────────────────────────────────────────────────
1064
+ /** Get all active facts for an entity */
1065
+ getEntityFacts(entity) {
1066
+ const norm = normalizeEntity(entity);
1067
+ const ids = this.byEntity.get(norm);
1068
+ if (!ids) return [];
1069
+ return Array.from(ids).map((id) => this.facts.get(id)).filter((f) => f && !f.validUntil);
1070
+ }
1071
+ /** Get all active facts for a relation */
1072
+ getRelationFacts(relation) {
1073
+ const ids = this.byRelation.get(relation);
1074
+ if (!ids) return [];
1075
+ return Array.from(ids).map((id) => this.facts.get(id)).filter((f) => f && !f.validUntil);
1076
+ }
1077
+ /** Get all active facts in a category */
1078
+ getCategoryFacts(category) {
1079
+ const ids = this.byCategory.get(category);
1080
+ if (!ids) return [];
1081
+ return Array.from(ids).map((id) => this.facts.get(id)).filter((f) => f && !f.validUntil);
1082
+ }
1083
+ /** Get all active preferences */
1084
+ getPreferences() {
1085
+ return this.getCategoryFacts("preference");
1086
+ }
1087
+ /** Search facts by text query (simple keyword match) */
1088
+ searchFacts(query) {
1089
+ const terms = query.toLowerCase().split(/\s+/);
1090
+ const results = [];
1091
+ for (const fact of this.facts.values()) {
1092
+ if (fact.validUntil) continue;
1093
+ const text = `${fact.entity} ${fact.relation} ${fact.value} ${fact.rawText}`.toLowerCase();
1094
+ const matches = terms.filter((t) => text.includes(t)).length;
1095
+ if (matches >= Math.ceil(terms.length * 0.5)) {
1096
+ results.push(fact);
1097
+ }
1098
+ }
1099
+ return results;
1100
+ }
1101
+ /** Get facts valid at a specific time */
1102
+ getFactsAt(timestamp) {
1103
+ const t = new Date(timestamp).getTime();
1104
+ const results = [];
1105
+ for (const fact of this.facts.values()) {
1106
+ const from = new Date(fact.validFrom).getTime();
1107
+ const until = fact.validUntil ? new Date(fact.validUntil).getTime() : Infinity;
1108
+ if (t >= from && t < until) {
1109
+ results.push(fact);
1110
+ }
1111
+ }
1112
+ return results;
1113
+ }
1114
+ /** Get stats */
1115
+ stats() {
1116
+ const active = Array.from(this.facts.values()).filter((f) => !f.validUntil);
1117
+ return {
1118
+ totalFacts: this.facts.size,
1119
+ activeFacts: active.length,
1120
+ supersededFacts: this.facts.size - active.length,
1121
+ entities: this.byEntity.size,
1122
+ relations: this.byRelation.size
1123
+ };
1124
+ }
1125
+ /** Get all facts (for testing/debugging) */
1126
+ getAllFacts() {
1127
+ return Array.from(this.facts.values());
1128
+ }
1129
+ };
1130
+
1131
+ // src/lib/llm-adapter.ts
1132
+ var GEMINI_FLASH_MODEL = "gemini-2.0-flash";
1133
+ function createGeminiFlashAdapter(options = {}) {
1134
+ const apiKey = process.env.GEMINI_API_KEY;
1135
+ return {
1136
+ async call(prompt) {
1137
+ if (!apiKey) {
1138
+ return "";
1139
+ }
1140
+ return requestLlmCompletion({
1141
+ prompt,
1142
+ provider: "gemini",
1143
+ model: options.model ?? GEMINI_FLASH_MODEL,
1144
+ temperature: options.temperature ?? 0.1,
1145
+ maxTokens: options.maxTokens ?? 2e3,
1146
+ fetchImpl: options.fetchImpl
1147
+ });
1148
+ },
1149
+ isAvailable() {
1150
+ return Boolean(apiKey);
1151
+ },
1152
+ getProvider() {
1153
+ return apiKey ? "gemini" : null;
1154
+ }
1155
+ };
1156
+ }
1157
+ function createDefaultAdapter(options = {}) {
1158
+ const resolvedProvider = options.provider !== void 0 ? options.provider : resolveLlmProvider();
1159
+ return {
1160
+ async call(prompt) {
1161
+ if (!resolvedProvider) {
1162
+ return "";
1163
+ }
1164
+ return requestLlmCompletion({
1165
+ prompt,
1166
+ provider: resolvedProvider,
1167
+ model: options.model,
1168
+ temperature: options.temperature ?? 0.1,
1169
+ maxTokens: options.maxTokens ?? 2e3,
1170
+ fetchImpl: options.fetchImpl
1171
+ });
1172
+ },
1173
+ isAvailable() {
1174
+ return resolvedProvider !== null;
1175
+ },
1176
+ getProvider() {
1177
+ return resolvedProvider;
1178
+ }
1179
+ };
1180
+ }
1181
+ function createFactExtractionAdapter(options = {}) {
1182
+ if (options.provider) {
1183
+ return createDefaultAdapter(options);
1184
+ }
1185
+ const geminiAdapter = createGeminiFlashAdapter(options);
1186
+ if (geminiAdapter.isAvailable()) {
1187
+ return geminiAdapter;
1188
+ }
1189
+ return createDefaultAdapter(options);
1190
+ }
1191
+ function createLlmFunction(adapter) {
1192
+ if (!adapter.isAvailable()) {
1193
+ return void 0;
1194
+ }
1195
+ return (prompt) => adapter.call(prompt);
1196
+ }
1197
+ function resolveFactExtractionMode(configuredMode, adapter) {
1198
+ const mode = configuredMode ?? "llm";
1199
+ if (mode === "off") {
1200
+ return { mode: "off", useLlm: false };
1201
+ }
1202
+ if (mode === "rule") {
1203
+ return { mode: "rule", useLlm: false };
1204
+ }
1205
+ const llmAvailable = adapter?.isAvailable() ?? resolveLlmProvider() !== null;
1206
+ if (mode === "llm" || mode === "hybrid") {
1207
+ return { mode, useLlm: llmAvailable };
1208
+ }
1209
+ return { mode: "rule", useLlm: false };
1210
+ }
1211
+
1212
+ // src/observer/observer.ts
1213
+ import * as fs3 from "fs";
1214
+ import * as path3 from "path";
1215
+
1216
+ // src/observer/router.ts
1217
+ import * as fs2 from "fs";
1218
+ import * as path2 from "path";
701
1219
  var CATEGORY_PATTERNS = [
702
1220
  {
703
1221
  category: "decisions",
@@ -764,17 +1282,41 @@ var FUTURE_TASK_HINT_RE = /\b(need to|should|todo|must|plan to)\b/i;
764
1282
  var Router = class {
765
1283
  vaultPath;
766
1284
  extractTasks;
1285
+ extractFacts;
1286
+ factExtractionMode;
1287
+ llmAdapter;
1288
+ factStore;
767
1289
  now;
768
1290
  customRoutes;
769
1291
  constructor(vaultPath, options = {}) {
770
- this.vaultPath = path.resolve(vaultPath);
1292
+ this.vaultPath = path2.resolve(vaultPath);
771
1293
  this.extractTasks = options.extractTasks ?? true;
1294
+ this.extractFacts = options.extractFacts ?? true;
1295
+ this.factExtractionMode = options.factExtractionMode ?? this.loadFactExtractionMode();
1296
+ this.llmAdapter = options.llmAdapter ?? createFactExtractionAdapter();
1297
+ this.factStore = new FactStore(this.vaultPath);
772
1298
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
773
1299
  this.customRoutes = this.loadCustomRoutes();
1300
+ if (this.extractFacts && this.factExtractionMode !== "off") {
1301
+ this.factStore.load();
1302
+ }
1303
+ }
1304
+ loadFactExtractionMode() {
1305
+ try {
1306
+ const config = listConfig(this.vaultPath);
1307
+ const observer = config.observer;
1308
+ const mode = observer?.factExtractionMode;
1309
+ if (mode === "off" || mode === "rule" || mode === "llm" || mode === "hybrid") {
1310
+ return mode;
1311
+ }
1312
+ } catch {
1313
+ }
1314
+ return "llm";
774
1315
  }
775
1316
  /**
776
1317
  * Takes observation markdown and routes items to appropriate vault categories.
777
1318
  * Routes only items with importance >= 0.4.
1319
+ * Also extracts structured facts from observations when fact extraction is enabled.
778
1320
  * Returns a summary of what was routed where.
779
1321
  */
780
1322
  route(observationMarkdown, context = {}) {
@@ -784,6 +1326,7 @@ var Router = class {
784
1326
  const knownWorkItems = this.extractTasks ? this.loadExistingWorkItems() : [];
785
1327
  const knownProjectDefinitions = this.loadKnownProjectDefinitions();
786
1328
  let dedupHits = 0;
1329
+ let factsExtracted = 0;
787
1330
  for (const item of items) {
788
1331
  if (item.importance < 0.4) continue;
789
1332
  if (this.extractTasks && this.isTaskObservation(item.type)) {
@@ -810,8 +1353,81 @@ var Router = class {
810
1353
  routed.push(routedItem);
811
1354
  this.appendToCategory(category, routedItem, knownProjectDefinitions);
812
1355
  }
813
- const summary = this.buildSummary(routed, dedupHits);
814
- return { routed, summary };
1356
+ if (this.extractFacts && this.factExtractionMode !== "off") {
1357
+ const extractedCount = this.extractAndStoreFacts(observationMarkdown, context);
1358
+ factsExtracted = extractedCount;
1359
+ }
1360
+ const summary = this.buildSummary(routed, dedupHits, factsExtracted);
1361
+ return { routed, summary, factsExtracted };
1362
+ }
1363
+ /**
1364
+ * Extract facts from observation markdown and store them in the fact store.
1365
+ * Uses the configured extraction mode (rule-based, LLM, or hybrid).
1366
+ */
1367
+ extractAndStoreFacts(observationMarkdown, context) {
1368
+ const { mode, useLlm } = resolveFactExtractionMode(this.factExtractionMode, this.llmAdapter);
1369
+ if (mode === "off") {
1370
+ return 0;
1371
+ }
1372
+ const source = context.source ?? "observer";
1373
+ const timestamp = context.timestamp?.toISOString() ?? this.now().toISOString();
1374
+ let facts = [];
1375
+ if (mode === "rule" || !useLlm) {
1376
+ facts = extractFactsRuleBased(observationMarkdown, source, timestamp);
1377
+ } else if (mode === "llm") {
1378
+ const llmFn = createLlmFunction(this.llmAdapter);
1379
+ extractFactsLlm(observationMarkdown, source, timestamp, llmFn).then((llmFacts) => {
1380
+ if (llmFacts.length > 0) {
1381
+ this.factStore.addFacts(llmFacts);
1382
+ this.factStore.save();
1383
+ }
1384
+ }).catch(() => {
1385
+ const ruleFacts = extractFactsRuleBased(observationMarkdown, source, timestamp);
1386
+ if (ruleFacts.length > 0) {
1387
+ this.factStore.addFacts(ruleFacts);
1388
+ this.factStore.save();
1389
+ }
1390
+ });
1391
+ return 0;
1392
+ } else if (mode === "hybrid") {
1393
+ facts = extractFactsRuleBased(observationMarkdown, source, timestamp);
1394
+ const llmFn = createLlmFunction(this.llmAdapter);
1395
+ extractFactsLlm(observationMarkdown, source, timestamp, llmFn).then((llmFacts) => {
1396
+ const merged = this.mergeFacts(facts, llmFacts);
1397
+ if (merged.length > facts.length) {
1398
+ this.factStore.addFacts(merged.slice(facts.length));
1399
+ this.factStore.save();
1400
+ }
1401
+ }).catch(() => {
1402
+ });
1403
+ }
1404
+ if (facts.length > 0) {
1405
+ this.factStore.addFacts(facts);
1406
+ this.factStore.save();
1407
+ }
1408
+ return facts.length;
1409
+ }
1410
+ /**
1411
+ * Merge facts from rule-based and LLM extraction, deduplicating by entity+relation.
1412
+ */
1413
+ mergeFacts(ruleFacts, llmFacts) {
1414
+ const seen = /* @__PURE__ */ new Set();
1415
+ const merged = [];
1416
+ for (const fact of ruleFacts) {
1417
+ const key = `${fact.entityNorm}::${fact.relation}`;
1418
+ if (!seen.has(key)) {
1419
+ seen.add(key);
1420
+ merged.push(fact);
1421
+ }
1422
+ }
1423
+ for (const fact of llmFacts) {
1424
+ const key = `${fact.entityNorm}::${fact.relation}`;
1425
+ if (!seen.has(key)) {
1426
+ seen.add(key);
1427
+ merged.push(fact);
1428
+ }
1429
+ }
1430
+ return merged;
815
1431
  }
816
1432
  isTaskObservation(type) {
817
1433
  return type === "task" || type === "todo" || type === "commitment-unresolved";
@@ -1153,46 +1769,46 @@ var Router = class {
1153
1769
  resolveFilePath(category, item, knownProjectDefinitions) {
1154
1770
  const customEntityPath = this.resolveCustomEntityPath(item.content, category);
1155
1771
  if (customEntityPath) {
1156
- const customEntityDir = path.join(this.vaultPath, category, customEntityPath);
1157
- fs.mkdirSync(customEntityDir, { recursive: true });
1772
+ const customEntityDir = path2.join(this.vaultPath, category, customEntityPath);
1773
+ fs2.mkdirSync(customEntityDir, { recursive: true });
1158
1774
  return {
1159
- filePath: path.join(customEntityDir, `${item.date}.md`),
1775
+ filePath: path2.join(customEntityDir, `${item.date}.md`),
1160
1776
  headerLabel: `${category}/${customEntityPath}`
1161
1777
  };
1162
1778
  }
1163
1779
  if (category === "projects") {
1164
1780
  const matchedProjectSlug = this.matchKnownProjectSlug(item.content, knownProjectDefinitions);
1165
1781
  if (matchedProjectSlug) {
1166
- const projectDir = path.join(this.vaultPath, category, matchedProjectSlug);
1167
- fs.mkdirSync(projectDir, { recursive: true });
1782
+ const projectDir = path2.join(this.vaultPath, category, matchedProjectSlug);
1783
+ fs2.mkdirSync(projectDir, { recursive: true });
1168
1784
  return {
1169
- filePath: path.join(projectDir, `${item.date}.md`),
1785
+ filePath: path2.join(projectDir, `${item.date}.md`),
1170
1786
  headerLabel: `${category}/${matchedProjectSlug}`
1171
1787
  };
1172
1788
  }
1173
1789
  } else {
1174
1790
  const entitySlug = this.extractEntitySlug(item.content, category);
1175
1791
  if (entitySlug) {
1176
- const entityDir = path.join(this.vaultPath, category, entitySlug);
1177
- fs.mkdirSync(entityDir, { recursive: true });
1792
+ const entityDir = path2.join(this.vaultPath, category, entitySlug);
1793
+ fs2.mkdirSync(entityDir, { recursive: true });
1178
1794
  return {
1179
- filePath: path.join(entityDir, `${item.date}.md`),
1795
+ filePath: path2.join(entityDir, `${item.date}.md`),
1180
1796
  headerLabel: `${category}/${entitySlug}`
1181
1797
  };
1182
1798
  }
1183
1799
  }
1184
- const categoryDir = path.join(this.vaultPath, category);
1185
- fs.mkdirSync(categoryDir, { recursive: true });
1800
+ const categoryDir = path2.join(this.vaultPath, category);
1801
+ fs2.mkdirSync(categoryDir, { recursive: true });
1186
1802
  return {
1187
- filePath: path.join(categoryDir, `${item.date}.md`),
1803
+ filePath: path2.join(categoryDir, `${item.date}.md`),
1188
1804
  headerLabel: category
1189
1805
  };
1190
1806
  }
1191
1807
  appendToCategory(category, item, knownProjectDefinitions) {
1192
1808
  const destination = this.resolveFilePath(category, item, knownProjectDefinitions);
1193
1809
  const filePath = destination.filePath;
1194
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
1195
- const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8").trim() : "";
1810
+ fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
1811
+ const existing = fs2.existsSync(filePath) ? fs2.readFileSync(filePath, "utf-8").trim() : "";
1196
1812
  const normalizedNew = this.normalizeForDedup(item.content);
1197
1813
  const existingLines = existing.split(/\r?\n/);
1198
1814
  for (const line of existingLines) {
@@ -1226,7 +1842,7 @@ ${entry}
1226
1842
  ` : `${header}
1227
1843
  ${entry}
1228
1844
  `;
1229
- fs.writeFileSync(filePath, newContent, "utf-8");
1845
+ fs2.writeFileSync(filePath, newContent, "utf-8");
1230
1846
  }
1231
1847
  /**
1232
1848
  * Auto-link proper nouns and known entities with [[wiki-links]].
@@ -1399,8 +2015,8 @@ ${entry}
1399
2015
  for (const bg of setA) if (setB.has(bg)) intersection++;
1400
2016
  return intersection / (setA.size + setB.size - intersection);
1401
2017
  }
1402
- buildSummary(routed, dedupHits) {
1403
- if (routed.length === 0) {
2018
+ buildSummary(routed, dedupHits, factsExtracted = 0) {
2019
+ if (routed.length === 0 && factsExtracted === 0) {
1404
2020
  if (dedupHits > 0) {
1405
2021
  return `No items routed to vault categories (dedup hits: ${dedupHits}).`;
1406
2022
  }
@@ -1411,7 +2027,17 @@ ${entry}
1411
2027
  byCat.set(item.category, (byCat.get(item.category) ?? 0) + 1);
1412
2028
  }
1413
2029
  const parts = [...byCat.entries()].map(([cat, count]) => `${cat}: ${count}`);
1414
- const suffix = dedupHits > 0 ? ` (dedup hits: ${dedupHits})` : "";
2030
+ const suffixParts = [];
2031
+ if (dedupHits > 0) {
2032
+ suffixParts.push(`dedup hits: ${dedupHits}`);
2033
+ }
2034
+ if (factsExtracted > 0) {
2035
+ suffixParts.push(`facts: ${factsExtracted}`);
2036
+ }
2037
+ const suffix = suffixParts.length > 0 ? ` (${suffixParts.join(", ")})` : "";
2038
+ if (routed.length === 0 && factsExtracted > 0) {
2039
+ return `Extracted ${factsExtracted} facts${suffix}`;
2040
+ }
1415
2041
  return `Routed ${routed.length} observations \u2192 ${parts.join(", ")}${suffix}`;
1416
2042
  }
1417
2043
  };
@@ -1479,7 +2105,7 @@ var Observer = class {
1479
2105
  observationsCache = "";
1480
2106
  lastRoutingSummary = "";
1481
2107
  constructor(vaultPath, options = {}) {
1482
- this.vaultPath = path2.resolve(vaultPath);
2108
+ this.vaultPath = path3.resolve(vaultPath);
1483
2109
  this.tokenThreshold = options.tokenThreshold ?? 3e4;
1484
2110
  this.reflectThreshold = options.reflectThreshold ?? 4e4;
1485
2111
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
@@ -1583,14 +2209,14 @@ var Observer = class {
1583
2209
  return this.readObservationFile(getLegacyObservationPath(this.vaultPath, toDateKey(date)));
1584
2210
  }
1585
2211
  readObservationFile(filePath) {
1586
- if (!fs2.existsSync(filePath)) {
2212
+ if (!fs3.existsSync(filePath)) {
1587
2213
  return "";
1588
2214
  }
1589
- return fs2.readFileSync(filePath, "utf-8").trim();
2215
+ return fs3.readFileSync(filePath, "utf-8").trim();
1590
2216
  }
1591
2217
  writeObservationFile(filePath, content) {
1592
2218
  ensureParentDir(filePath);
1593
- fs2.writeFileSync(filePath, `${content.trim()}
2219
+ fs3.writeFileSync(filePath, `${content.trim()}
1594
2220
  `, "utf-8");
1595
2221
  }
1596
2222
  deduplicateObservationMarkdown(markdown) {
@@ -1638,7 +2264,7 @@ var Observer = class {
1638
2264
  transcriptId: options.transcriptId ?? null,
1639
2265
  message
1640
2266
  }));
1641
- fs2.appendFileSync(rawPath, `${records.join("\n")}
2267
+ fs3.appendFileSync(rawPath, `${records.join("\n")}
1642
2268
  `, "utf-8");
1643
2269
  }
1644
2270
  sanitizeSource(source) {
@@ -1661,5 +2287,15 @@ var Observer = class {
1661
2287
  export {
1662
2288
  Compressor,
1663
2289
  Reflector,
2290
+ normalizeEntity,
2291
+ factId,
2292
+ extractFactsRuleBased,
2293
+ extractFactsLlm,
2294
+ FactStore,
2295
+ createGeminiFlashAdapter,
2296
+ createDefaultAdapter,
2297
+ createFactExtractionAdapter,
2298
+ createLlmFunction,
2299
+ resolveFactExtractionMode,
1664
2300
  Observer
1665
2301
  };