clawvault 3.2.0 → 3.3.0

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 (112) hide show
  1. package/README.md +54 -14
  2. package/bin/clawvault.js +0 -2
  3. package/bin/command-registration.test.js +13 -1
  4. package/bin/help-contract.test.js +14 -0
  5. package/bin/register-core-commands.js +88 -0
  6. package/bin/register-core-commands.test.js +80 -0
  7. package/bin/register-maintenance-commands.js +57 -6
  8. package/bin/register-query-commands.js +10 -28
  9. package/bin/test-helpers/cli-command-fixtures.js +1 -0
  10. package/dist/chunk-2PKBIKDH.js +130 -0
  11. package/dist/{chunk-2JQ3O2YL.js → chunk-5EFSWZO6.js} +3 -3
  12. package/dist/{chunk-77Q5CSPJ.js → chunk-7SWP5FKU.js} +33 -701
  13. package/dist/{chunk-URXDAUVH.js → chunk-AXSJIFOJ.js} +174 -1
  14. package/dist/{chunk-23YDQ3QU.js → chunk-BLQXXX7Q.js} +6 -6
  15. package/dist/chunk-CSHO3PJB.js +684 -0
  16. package/dist/{chunk-SLXOR3CC.js → chunk-DOIUYIXV.js} +2 -2
  17. package/dist/{chunk-NCKFNBHJ.js → chunk-DVOUSOR3.js} +79 -5
  18. package/dist/{chunk-CLJTREDS.js → chunk-ECGJYWNA.js} +193 -41
  19. package/dist/{chunk-BUEW6IIK.js → chunk-EL6UBSX5.js} +5 -5
  20. package/dist/{chunk-6FH3IULF.js → chunk-FZ5I2NF7.js} +1 -1
  21. package/dist/{chunk-ZN54U2OZ.js → chunk-GFCHWMGD.js} +3 -3
  22. package/dist/{chunk-GNJL4YGR.js → chunk-GJO3CFUN.js} +30 -6
  23. package/dist/chunk-H3JZIB5O.js +322 -0
  24. package/dist/chunk-HEHO7SMV.js +51 -0
  25. package/dist/{chunk-STCQGCEQ.js → chunk-HGDDW24U.js} +3 -3
  26. package/dist/chunk-J3YUXVID.js +907 -0
  27. package/dist/{chunk-Y6VJKXGL.js → chunk-KCYWJDDW.js} +1 -1
  28. package/dist/{chunk-W4SPAEE7.js → chunk-OFOCU2V4.js} +5 -4
  29. package/dist/chunk-PTWPPVC7.js +972 -0
  30. package/dist/{chunk-QSHD36LH.js → chunk-QFWERBDP.js} +2 -2
  31. package/dist/{chunk-QSRRMEYM.js → chunk-S7N7HI5E.js} +1 -1
  32. package/dist/{chunk-PBACDKKP.js → chunk-T7E764W3.js} +3 -3
  33. package/dist/chunk-TDWFBDAQ.js +1016 -0
  34. package/dist/{chunk-ESVS6K2B.js → chunk-TWMI3SNN.js} +6 -5
  35. package/dist/{chunk-2RAZ4ZFE.js → chunk-VBILES4B.js} +1 -1
  36. package/dist/{chunk-ESFLMDRB.js → chunk-VXAGOLDP.js} +3 -3
  37. package/dist/chunk-YCUVAOFC.js +158 -0
  38. package/dist/{chunk-SS4B7P7V.js → chunk-YIDV4VV2.js} +1 -1
  39. package/dist/chunk-ZKWPCBYT.js +600 -0
  40. package/dist/cli/index.js +24 -24
  41. package/dist/commands/archive.js +2 -2
  42. package/dist/commands/benchmark.d.ts +12 -0
  43. package/dist/commands/benchmark.js +12 -0
  44. package/dist/commands/context.js +6 -5
  45. package/dist/commands/doctor.d.ts +8 -3
  46. package/dist/commands/doctor.js +6 -20
  47. package/dist/commands/embed.js +5 -4
  48. package/dist/commands/entities.js +1 -1
  49. package/dist/commands/graph.js +2 -2
  50. package/dist/commands/inbox.d.ts +23 -0
  51. package/dist/commands/inbox.js +11 -0
  52. package/dist/commands/inject.d.ts +1 -1
  53. package/dist/commands/inject.js +3 -3
  54. package/dist/commands/link.js +6 -6
  55. package/dist/commands/maintain.d.ts +32 -0
  56. package/dist/commands/maintain.js +12 -0
  57. package/dist/commands/migrate-observations.js +2 -2
  58. package/dist/commands/observe.js +9 -8
  59. package/dist/commands/rebuild-embeddings.js +47 -16
  60. package/dist/commands/rebuild.js +7 -6
  61. package/dist/commands/reflect.js +5 -5
  62. package/dist/commands/replay.js +8 -7
  63. package/dist/commands/setup.js +3 -2
  64. package/dist/commands/sleep.d.ts +1 -1
  65. package/dist/commands/sleep.js +17 -15
  66. package/dist/commands/status.js +26 -24
  67. package/dist/commands/sync-bd.js +2 -2
  68. package/dist/commands/tailscale.js +2 -2
  69. package/dist/commands/wake.d.ts +1 -1
  70. package/dist/commands/wake.js +8 -7
  71. package/dist/index.d.ts +168 -16
  72. package/dist/index.js +271 -108
  73. package/dist/{inject-DYUrDqQO.d.ts → inject-DEb_jpLi.d.ts} +3 -1
  74. package/dist/lib/config.js +1 -1
  75. package/dist/{types-BbWJoC1c.d.ts → types-DslKvCaj.d.ts} +51 -1
  76. package/hooks/clawvault/HOOK.md +22 -5
  77. package/hooks/clawvault/handler.js +213 -78
  78. package/hooks/clawvault/handler.test.js +109 -43
  79. package/hooks/clawvault/integrity.js +112 -0
  80. package/hooks/clawvault/integrity.test.js +32 -0
  81. package/hooks/clawvault/openclaw.plugin.json +133 -15
  82. package/openclaw.plugin.json +126 -20
  83. package/package.json +2 -2
  84. package/bin/register-workgraph-commands.js +0 -1368
  85. package/dist/chunk-33VSQP4J.js +0 -37
  86. package/dist/chunk-4BQTQMJP.js +0 -93
  87. package/dist/chunk-EK6S23ZB.js +0 -469
  88. package/dist/chunk-GAOWA7GR.js +0 -501
  89. package/dist/chunk-GGA32J2R.js +0 -784
  90. package/dist/chunk-MM6QGW3P.js +0 -207
  91. package/dist/chunk-QVEERJSP.js +0 -152
  92. package/dist/chunk-U4O6C46S.js +0 -154
  93. package/dist/chunk-VSL7KY3M.js +0 -189
  94. package/dist/chunk-WMGIIABP.js +0 -15
  95. package/dist/commands/workgraph.d.ts +0 -124
  96. package/dist/commands/workgraph.js +0 -38
  97. package/dist/ledger-B7g7jhqG.d.ts +0 -44
  98. package/dist/registry-BR4326o0.d.ts +0 -30
  99. package/dist/store-CA-6sKCJ.d.ts +0 -34
  100. package/dist/thread-B9LhXNU0.d.ts +0 -41
  101. package/dist/workgraph/index.d.ts +0 -5
  102. package/dist/workgraph/index.js +0 -23
  103. package/dist/workgraph/ledger.d.ts +0 -2
  104. package/dist/workgraph/ledger.js +0 -25
  105. package/dist/workgraph/registry.d.ts +0 -2
  106. package/dist/workgraph/registry.js +0 -19
  107. package/dist/workgraph/store.d.ts +0 -2
  108. package/dist/workgraph/store.js +0 -25
  109. package/dist/workgraph/thread.d.ts +0 -2
  110. package/dist/workgraph/thread.js +0 -25
  111. package/dist/workgraph/types.d.ts +0 -54
  112. package/dist/workgraph/types.js +0 -7
@@ -1,29 +1,30 @@
1
+ import {
2
+ requestLlmCompletion,
3
+ resolveLlmProvider
4
+ } from "./chunk-DVOUSOR3.js";
1
5
  import {
2
6
  listProjects
3
7
  } from "./chunk-4PY655YM.js";
8
+ import {
9
+ listConfig,
10
+ listRouteRules,
11
+ matchRouteRule
12
+ } from "./chunk-AXSJIFOJ.js";
4
13
  import {
5
14
  FactStore,
6
15
  extractFactsLlm,
7
16
  extractFactsRuleBased
8
17
  } from "./chunk-BSJ6RIT7.js";
9
18
  import {
10
- DATE_HEADING_RE,
11
- inferObservationType,
19
+ Compressor
20
+ } from "./chunk-J3YUXVID.js";
21
+ import {
12
22
  normalizeObservationContent,
13
23
  parseObservationLine,
14
24
  parseObservationMarkdown,
15
25
  renderObservationMarkdown,
16
26
  renderScoredObservationLine
17
27
  } from "./chunk-FHFUXL6G.js";
18
- import {
19
- listConfig,
20
- listRouteRules,
21
- matchRouteRule
22
- } from "./chunk-URXDAUVH.js";
23
- import {
24
- requestLlmCompletion,
25
- resolveLlmProvider
26
- } from "./chunk-NCKFNBHJ.js";
27
28
  import {
28
29
  ensureLedgerStructure,
29
30
  ensureParentDir,
@@ -40,680 +41,8 @@ import {
40
41
  updateTask
41
42
  } from "./chunk-QWQ3TIKS.js";
42
43
 
43
- // src/observer/compressor.ts
44
- var OPENAI_BASE_URL = "https://api.openai.com/v1";
45
- var XAI_BASE_URL = "https://api.x.ai/v1";
46
- var OLLAMA_BASE_URL = "http://localhost:11434/v1";
47
- var DEFAULT_PROVIDER_MODELS = {
48
- anthropic: "claude-3-5-haiku-latest",
49
- openai: "gpt-4o-mini",
50
- gemini: "gemini-2.0-flash",
51
- xai: "grok-2-latest",
52
- "openai-compatible": "gpt-4o-mini",
53
- ollama: "llama3.2",
54
- minimax: "MiniMax-M2.1",
55
- zai: "glm-4.5-air"
56
- };
57
- 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;
58
- 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;
59
- var NOTABLE_RE = /\b(prefer(?:ence|s)?|likes?|dislikes?|context|pattern|architecture|approach|trade[- ]?off|milestone|stakeholder|teammate|collaborat(?:e|ed|ion)|discussion|notable|deadline|due|timeline|deploy(?:ed|ment)?|built|configured|launched|proposal|pitch|onboard(?:ed|ing)?|migrat(?:e|ed|ion)|domain|DNS|infra(?:structure)?)\b/i;
60
- var TODO_SIGNAL_RE = /(?:\btodo:\s*|\bwe need to\b|\bdon't forget(?: to)?\b|\bremember to\b|\bmake sure to\b)/i;
61
- var COMMITMENT_TASK_SIGNAL_RE = /\b(?:i'?ll|i will|let me|(?:i'?m\s+)?going to|plan to|should)\b/i;
62
- var UNRESOLVED_COMMITMENT_RE = /\b(?:need to figure out|tbd|to be determined)\b/i;
63
- var DEADLINE_SIGNAL_RE = /\b(?:by\s+(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday|tomorrow)|before\s+the\s+\w+|deadline is)\b/i;
64
- var ROLE_PREFIX_RE = /^([a-z][a-z0-9_-]{1,31})\s*:\s*(.+)$/i;
65
- var BASE64_DATA_URI_RE = /\bdata:[^;\s]+;base64,[A-Za-z0-9+/=]{24,}\b/gi;
66
- var LONG_BASE64_TOKEN_RE = /\b[A-Za-z0-9+/]{80,}={0,2}\b/g;
67
- var NOISE_PREFIX_RE = /^(?:metadata|system metadata|session metadata|tool[_-]?result|toolresult)\s*:/i;
68
- var STRUCTURED_NOISE_MARKER_RE = /\b(?:tool[_-]?result|tool[_-]?use|toolcallid|tooluseid|function[_-]?(?:call|result)|stdout|stderr|exitcode|recordedat|trace(?:_|-)?id|parent(?:_|-)?id|session(?:_|-)?id|metadata|base64|mime(?:type)?)\b/i;
69
- var Compressor = class {
70
- provider;
71
- model;
72
- baseUrl;
73
- apiKey;
74
- now;
75
- fetchImpl;
76
- constructor(options = {}) {
77
- this.provider = options.provider;
78
- this.model = options.model;
79
- this.baseUrl = options.baseUrl;
80
- this.apiKey = options.apiKey;
81
- this.now = options.now ?? (() => /* @__PURE__ */ new Date());
82
- this.fetchImpl = options.fetchImpl ?? fetch;
83
- }
84
- async compress(messages, existingObservations) {
85
- const cleanedMessages = this.sanitizeIncomingMessages(messages);
86
- if (cleanedMessages.length === 0) {
87
- return existingObservations.trim();
88
- }
89
- const prompt = this.buildPrompt(cleanedMessages, existingObservations);
90
- const backend = this.resolveProvider();
91
- if (backend) {
92
- try {
93
- 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);
94
- const normalized = this.normalizeLlmOutput(llmOutput);
95
- if (normalized) {
96
- return this.mergeObservations(existingObservations, normalized);
97
- }
98
- } catch {
99
- }
100
- }
101
- const fallback = this.fallbackCompression(cleanedMessages);
102
- return this.mergeObservations(existingObservations, fallback);
103
- }
104
- sanitizeIncomingMessages(messages) {
105
- const sanitized = [];
106
- for (const message of messages) {
107
- const cleaned = this.sanitizeIncomingMessage(message);
108
- if (cleaned) {
109
- sanitized.push(cleaned);
110
- }
111
- }
112
- return sanitized;
113
- }
114
- sanitizeIncomingMessage(message) {
115
- const normalized = message.replace(/\s+/g, " ").trim();
116
- if (!normalized) {
117
- return "";
118
- }
119
- const sourceTagMatch = /^\[[^\]]+\]\s+/.exec(normalized);
120
- const sourceTag = sourceTagMatch ? sourceTagMatch[0] : "";
121
- const payload = sourceTag ? normalized.slice(sourceTag.length).trimStart() : normalized;
122
- const roleMatch = ROLE_PREFIX_RE.exec(payload);
123
- if (roleMatch && this.isConversationRolePrefix(roleMatch[1])) {
124
- const role = this.normalizeMessageRole(roleMatch[1]);
125
- if (this.shouldDropMessageRole(role)) {
126
- return "";
127
- }
128
- const content = this.stripNoisyData(roleMatch[2]);
129
- if (!content || this.isLikelyStructuredNoise(content)) {
130
- return "";
131
- }
132
- const cleaned2 = `${role}: ${content}`;
133
- return sourceTag ? `${sourceTag}${cleaned2}` : cleaned2;
134
- }
135
- const cleaned = this.stripNoisyData(payload);
136
- if (!cleaned || this.isLikelyStructuredNoise(cleaned)) {
137
- return "";
138
- }
139
- return sourceTag ? `${sourceTag}${cleaned}` : cleaned;
140
- }
141
- normalizeMessageRole(role) {
142
- return role.trim().toLowerCase();
143
- }
144
- isConversationRolePrefix(role) {
145
- const normalized = role.trim().toLowerCase().replace(/[\s_-]+/g, "");
146
- if (!normalized) {
147
- return false;
148
- }
149
- if (normalized === "user" || normalized === "assistant" || normalized === "system") {
150
- return true;
151
- }
152
- if (normalized === "developer" || normalized === "metadata") {
153
- return true;
154
- }
155
- return normalized.startsWith("tool");
156
- }
157
- shouldDropMessageRole(role) {
158
- const normalized = role.replace(/[\s_-]+/g, "");
159
- if (!normalized) {
160
- return false;
161
- }
162
- if (normalized === "system" || normalized === "developer" || normalized === "metadata") {
163
- return true;
164
- }
165
- return normalized.startsWith("tool");
166
- }
167
- stripNoisyData(value) {
168
- return value.replace(BASE64_DATA_URI_RE, " ").replace(LONG_BASE64_TOKEN_RE, " ").replace(/\s+/g, " ").trim();
169
- }
170
- isLikelyStructuredNoise(value) {
171
- const trimmed = value.trim();
172
- if (!trimmed) {
173
- return true;
174
- }
175
- if (NOISE_PREFIX_RE.test(trimmed)) {
176
- return true;
177
- }
178
- const afterToolResult = trimmed.match(/^tool[_-]?result\s*:\s*(\{.*|\[.*)/i);
179
- if (afterToolResult && STRUCTURED_NOISE_MARKER_RE.test(trimmed) && trimmed.length >= 40) {
180
- return true;
181
- }
182
- const looksStructured = trimmed.startsWith("{") || trimmed.startsWith("[");
183
- if (looksStructured && STRUCTURED_NOISE_MARKER_RE.test(trimmed) && trimmed.length >= 40) {
184
- return true;
185
- }
186
- return false;
187
- }
188
- resolveProvider() {
189
- if (process.env.CLAWVAULT_NO_LLM) return null;
190
- if (this.provider) {
191
- const configured = this.resolveConfiguredProvider(this.provider);
192
- if (configured) {
193
- return configured;
194
- }
195
- return this.resolveProviderFromEnv(false);
196
- }
197
- return this.resolveProviderFromEnv(true);
198
- }
199
- resolveConfiguredProvider(provider) {
200
- const model = this.resolveModel(provider);
201
- if (provider === "anthropic") {
202
- const apiKey2 = this.resolveApiKey(provider);
203
- if (!apiKey2) {
204
- return null;
205
- }
206
- return {
207
- provider,
208
- model,
209
- apiKey: apiKey2
210
- };
211
- }
212
- if (provider === "gemini") {
213
- const apiKey2 = this.resolveApiKey(provider);
214
- if (!apiKey2) {
215
- return null;
216
- }
217
- return {
218
- provider,
219
- model,
220
- apiKey: apiKey2
221
- };
222
- }
223
- if (provider === "openai") {
224
- const apiKey2 = this.resolveApiKey(provider);
225
- if (!apiKey2) {
226
- return null;
227
- }
228
- return {
229
- provider,
230
- model,
231
- apiKey: apiKey2,
232
- baseUrl: this.resolveBaseUrl(provider)
233
- };
234
- }
235
- if (provider === "xai") {
236
- const apiKey2 = this.resolveApiKey(provider);
237
- if (!apiKey2) {
238
- return null;
239
- }
240
- return {
241
- provider,
242
- model,
243
- apiKey: apiKey2,
244
- baseUrl: XAI_BASE_URL
245
- };
246
- }
247
- const apiKey = this.resolveApiKey(provider) ?? void 0;
248
- return {
249
- provider,
250
- model,
251
- apiKey,
252
- baseUrl: this.resolveBaseUrl(provider)
253
- };
254
- }
255
- resolveProviderFromEnv(allowConfiguredModel) {
256
- const anthropicApiKey = this.readEnvValue("ANTHROPIC_API_KEY");
257
- if (anthropicApiKey) {
258
- return {
259
- provider: "anthropic",
260
- model: allowConfiguredModel ? this.resolveModel("anthropic") : DEFAULT_PROVIDER_MODELS.anthropic,
261
- apiKey: anthropicApiKey
262
- };
263
- }
264
- const openAiApiKey = this.readEnvValue("OPENAI_API_KEY");
265
- if (openAiApiKey) {
266
- return {
267
- provider: "openai",
268
- model: allowConfiguredModel ? this.resolveModel("openai") : DEFAULT_PROVIDER_MODELS.openai,
269
- apiKey: openAiApiKey,
270
- baseUrl: OPENAI_BASE_URL
271
- };
272
- }
273
- const geminiApiKey = this.readEnvValue("GEMINI_API_KEY");
274
- if (geminiApiKey) {
275
- return {
276
- provider: "gemini",
277
- model: allowConfiguredModel ? this.resolveModel("gemini") : DEFAULT_PROVIDER_MODELS.gemini,
278
- apiKey: geminiApiKey
279
- };
280
- }
281
- const xaiApiKey = this.readEnvValue("XAI_API_KEY");
282
- if (xaiApiKey) {
283
- return {
284
- provider: "xai",
285
- model: allowConfiguredModel ? this.resolveModel("xai") : DEFAULT_PROVIDER_MODELS.xai,
286
- apiKey: xaiApiKey,
287
- baseUrl: XAI_BASE_URL
288
- };
289
- }
290
- return null;
291
- }
292
- resolveModel(provider) {
293
- const configuredModel = this.model?.trim();
294
- if (configuredModel) {
295
- return configuredModel;
296
- }
297
- return DEFAULT_PROVIDER_MODELS[provider];
298
- }
299
- resolveApiKey(provider) {
300
- const configuredApiKey = this.apiKey?.trim();
301
- if (configuredApiKey) {
302
- return configuredApiKey;
303
- }
304
- if (provider === "anthropic") {
305
- return this.readEnvValue("ANTHROPIC_API_KEY");
306
- }
307
- if (provider === "gemini") {
308
- return this.readEnvValue("GEMINI_API_KEY");
309
- }
310
- if (provider === "xai") {
311
- return this.readEnvValue("XAI_API_KEY");
312
- }
313
- return this.readEnvValue("OPENAI_API_KEY");
314
- }
315
- resolveBaseUrl(provider) {
316
- const configuredBaseUrl = this.baseUrl?.trim();
317
- if (configuredBaseUrl) {
318
- return configuredBaseUrl.replace(/\/+$/, "");
319
- }
320
- if (provider === "ollama") {
321
- return OLLAMA_BASE_URL;
322
- }
323
- return OPENAI_BASE_URL;
324
- }
325
- readEnvValue(name) {
326
- const value = process.env[name]?.trim();
327
- return value ? value : null;
328
- }
329
- buildPrompt(messages, existingObservations) {
330
- return [
331
- "You are an observer that compresses raw AI session messages into durable, human-meaningful observations.",
332
- "",
333
- "Rules:",
334
- "- Output markdown only.",
335
- "- Group observations by date heading: ## YYYY-MM-DD",
336
- "- Each observation line MUST follow: - [type|c=<0.00-1.00>|i=<0.00-1.00>] <observation>",
337
- "- Allowed type tags: decision, preference, fact, commitment, task, todo, commitment-unresolved, milestone, lesson, relationship, project",
338
- "- i >= 0.80 for structural/persistent observations (major decisions, blockers, releases, commitments)",
339
- "- i 0.40-0.79 for potentially important observations (notable context, preferences, milestones)",
340
- "- i < 0.40 for contextual/routine observations",
341
- "- Confidence c reflects extraction certainty, not importance.",
342
- "- Preserve source tags when present (e.g., [main], [telegram-dm], [discord], [telegram-group]).",
343
- "",
344
- "TASK EXTRACTION (required):",
345
- `- Emit [todo] for explicit TODO phrasing: "TODO:", "we need to", "don't forget", "remember to", "make sure to".`,
346
- `- Emit [task] for commitments/action intent: "I'll", "I will", "let me", "going to", "plan to", "should".`,
347
- '- Emit [commitment-unresolved] for unresolved commitments/questions: "need to figure out", "TBD", "to be determined".',
348
- '- Deadline language ("by Friday", "before the demo", "deadline is") should increase importance and usually map to [task] unless unresolved.',
349
- "",
350
- "QUALITY FILTERS (important):",
351
- "- DO NOT observe: CLI errors, command failures, tool output parsing issues, retry attempts, debug logs.",
352
- " These are transient noise, not memories. Only observe errors if they represent a BLOCKER or an unresolved problem.",
353
- '- DO NOT observe: "acknowledged the conversation", "said okay", routine confirmations.',
354
- '- MERGE related events into single observations. If 5 images were generated, say "Generated 5 images for X" not 5 separate lines.',
355
- '- MERGE retry sequences: "Tried X, failed, tried Y, succeeded" \u2192 "Resolved X using Y (after initial failure)"',
356
- '- Prefer OUTCOMES over PROCESSES: "Deployed v1.2 to Railway" not "Started deploy... build finished... deploy succeeded"',
357
- "",
358
- "AGENT ATTRIBUTION:",
359
- '- If the transcript shows multiple speakers/agents, prefix observations with who did it: "Pedro asked...", "Clawdious deployed...", "Zeca generated..."',
360
- "- If only one agent is acting, attribution is optional.",
361
- "",
362
- "PROJECT MILESTONES (critical \u2014 these are the most valuable observations):",
363
- "Projects are NOT just code. Milestones include business, strategy, client, and operational events.",
364
- "- Use milestone/decision/commitment types for strategic events with high importance.",
365
- "- Use preference/lesson/relationship/project/fact when appropriate.",
366
- "- Examples:",
367
- ' "- [decision|c=0.95|i=0.90] 14:00 Pricing decision: $33K one-time + $3K/mo for Artemisa"',
368
- ' "- [milestone|c=0.93|i=0.88] 14:00 Published clawvault@2.1.0 to npm"',
369
- ' "- [project|c=0.84|i=0.58] 14:00 Deployed pitch deck to artemisa-pitch-deck.vercel.app"',
370
- "- Do NOT collapse multiple milestones into one line \u2014 each matters for history.",
371
- "",
372
- "COMMITMENT FORMAT (when someone promises/agrees to something):",
373
- '- Prefer: "- [commitment|c=...|i=...] HH:MM [COMMITMENT] <who> committed to <what> by <when>"',
374
- "",
375
- "Keep observations concise and factual. Aim for signal, not completeness.",
376
- "",
377
- "Existing observations (may be empty):",
378
- existingObservations.trim() || "(none)",
379
- "",
380
- "Raw messages:",
381
- ...messages.map((message, index) => `[${index + 1}] ${message}`),
382
- "",
383
- "Return only the updated observation markdown."
384
- ].join("\n");
385
- }
386
- buildOpenAICompatibleUrl(baseUrl) {
387
- const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
388
- return `${normalizedBaseUrl}/chat/completions`;
389
- }
390
- buildOpenAICompatibleHeaders(apiKey) {
391
- const headers = {
392
- "content-type": "application/json"
393
- };
394
- if (apiKey) {
395
- headers.authorization = `Bearer ${apiKey}`;
396
- }
397
- return headers;
398
- }
399
- extractOpenAIContent(content) {
400
- if (typeof content === "string") {
401
- return content.trim();
402
- }
403
- if (!Array.isArray(content)) {
404
- return "";
405
- }
406
- const parts = content.map((part) => {
407
- if (typeof part === "string") {
408
- return part;
409
- }
410
- if (!part || typeof part !== "object") {
411
- return "";
412
- }
413
- const candidate = part;
414
- return typeof candidate.text === "string" ? candidate.text : "";
415
- }).filter((part) => part.trim().length > 0);
416
- return parts.join("\n").trim();
417
- }
418
- async callAnthropic(prompt, backend) {
419
- if (!backend.apiKey) {
420
- return "";
421
- }
422
- const response = await this.fetchImpl("https://api.anthropic.com/v1/messages", {
423
- method: "POST",
424
- headers: {
425
- "content-type": "application/json",
426
- "x-api-key": backend.apiKey,
427
- "anthropic-version": "2023-06-01"
428
- },
429
- body: JSON.stringify({
430
- model: backend.model,
431
- temperature: 0.1,
432
- max_tokens: 1400,
433
- messages: [{ role: "user", content: prompt }]
434
- })
435
- });
436
- if (!response.ok) {
437
- throw new Error(`Anthropic request failed (${response.status})`);
438
- }
439
- const payload = await response.json();
440
- return payload.content?.filter((part) => part.type === "text" && part.text).map((part) => part.text).join("\n").trim() ?? "";
441
- }
442
- async callOpenAI(prompt, backend) {
443
- return this.callOpenAICompatible(prompt, backend);
444
- }
445
- async callXAI(prompt, backend) {
446
- return this.callOpenAICompatible(prompt, backend);
447
- }
448
- async callOpenAICompatible(prompt, backend) {
449
- const baseUrl = backend.baseUrl ?? this.resolveBaseUrl(backend.provider);
450
- const response = await this.fetchImpl(this.buildOpenAICompatibleUrl(baseUrl), {
451
- method: "POST",
452
- headers: this.buildOpenAICompatibleHeaders(backend.apiKey),
453
- body: JSON.stringify({
454
- model: backend.model,
455
- temperature: 0.1,
456
- messages: [
457
- { role: "system", content: "You transform session logs into concise observations." },
458
- { role: "user", content: prompt }
459
- ]
460
- })
461
- });
462
- if (!response.ok) {
463
- throw new Error(`OpenAI-compatible request failed (${response.status})`);
464
- }
465
- const payload = await response.json();
466
- return this.extractOpenAIContent(payload.choices?.[0]?.message?.content);
467
- }
468
- async callGemini(prompt, backend) {
469
- if (!backend.apiKey) {
470
- return "";
471
- }
472
- const model = encodeURIComponent(backend.model);
473
- const response = await this.fetchImpl(
474
- `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${backend.apiKey}`,
475
- {
476
- method: "POST",
477
- headers: { "content-type": "application/json" },
478
- body: JSON.stringify({
479
- contents: [{ parts: [{ text: prompt }] }],
480
- generationConfig: { temperature: 0.1, maxOutputTokens: 1400 }
481
- })
482
- }
483
- );
484
- if (!response.ok) {
485
- throw new Error(`Gemini request failed (${response.status})`);
486
- }
487
- const payload = await response.json();
488
- return payload.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? "";
489
- }
490
- normalizeLlmOutput(output) {
491
- if (!output.trim()) {
492
- return "";
493
- }
494
- const cleaned = output.replace(/^```(?:markdown)?\s*/i, "").replace(/\s*```$/, "").trim();
495
- const lines = cleaned.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
496
- const hasObservationLine = lines.some((line) => line.startsWith("- [") || /^(?:-\s*)?(🔴|🟡|🟢)\s+/.test(line));
497
- if (!hasObservationLine) {
498
- return "";
499
- }
500
- const hasDateHeading = lines.some((line) => DATE_HEADING_RE.test(line));
501
- const result = hasDateHeading ? cleaned : `## ${this.formatDate(this.now())}
502
-
503
- ${cleaned}`;
504
- const sanitized = this.sanitizeWikiLinks(result);
505
- return this.enforceImportanceRules(sanitized);
506
- }
507
- /**
508
- * Fix wiki-link corruption from LLM compression.
509
- * LLMs often fuse preceding word fragments into wiki-links during rewriting:
510
- * "reque[[people/pedro]]" → "[[people/pedro]]"
511
- * "Linke[[agents/zeca]]" → "[[agents/zeca]]"
512
- * "taske[[people/pedro]]a" → "[[people/pedro]]"
513
- * Also fixes trailing word fragments fused after closing brackets.
514
- */
515
- sanitizeWikiLinks(markdown) {
516
- let result = markdown.replace(/\w+\[\[/g, " [[");
517
- result = result.replace(/\]\]\w+/g, "]]");
518
- result = result.replace(/ {2,}/g, " ");
519
- return result;
520
- }
521
- enforceImportanceRules(markdown) {
522
- const parsed = parseObservationMarkdown(markdown);
523
- if (parsed.length === 0) {
524
- return "";
525
- }
526
- const grouped = /* @__PURE__ */ new Map();
527
- for (const record of parsed) {
528
- const adjusted = this.enforceImportanceForRecord(record);
529
- const bucket = grouped.get(record.date) ?? [];
530
- bucket.push(adjusted);
531
- grouped.set(record.date, bucket);
532
- }
533
- return renderObservationMarkdown(grouped);
534
- }
535
- enforceImportanceForRecord(record) {
536
- let importance = record.importance;
537
- let confidence = record.confidence;
538
- let type = record.type;
539
- const inferredTaskType = this.inferTaskType(record.content);
540
- if (this.isCriticalContent(record.content)) {
541
- importance = Math.max(importance, 0.85);
542
- confidence = Math.max(confidence, 0.85);
543
- if (type === "fact") {
544
- type = inferObservationType(record.content);
545
- }
546
- } else if (this.isNotableContent(record.content)) {
547
- importance = Math.max(importance, 0.5);
548
- confidence = Math.max(confidence, 0.75);
549
- }
550
- if (inferredTaskType) {
551
- type = type === "fact" || type === "commitment" ? inferredTaskType : type;
552
- importance = Math.max(importance, inferredTaskType === "commitment-unresolved" ? 0.72 : 0.65);
553
- confidence = Math.max(confidence, 0.8);
554
- }
555
- if (type === "decision" || type === "commitment" || type === "milestone") {
556
- importance = Math.max(importance, 0.6);
557
- }
558
- return {
559
- type,
560
- confidence: this.clamp01(confidence),
561
- importance: this.clamp01(importance),
562
- content: record.content
563
- };
564
- }
565
- fallbackCompression(messages) {
566
- const sections = /* @__PURE__ */ new Map();
567
- const seen = /* @__PURE__ */ new Set();
568
- for (const message of messages) {
569
- const normalized = this.normalizeText(message);
570
- if (!normalized) continue;
571
- const date = this.extractDate(message) ?? this.formatDate(this.now());
572
- const time = this.extractTime(message) ?? this.formatTime(this.now());
573
- const line = `${time} ${normalized}`;
574
- const type = inferObservationType(line);
575
- const importance = this.inferImportance(line, type);
576
- const confidence = this.inferConfidence(line, type, importance);
577
- const dedupeKey = `${date}|${type}|${normalizeObservationContent(line)}`;
578
- if (seen.has(dedupeKey)) continue;
579
- seen.add(dedupeKey);
580
- const bucket = sections.get(date) ?? [];
581
- bucket.push({ type, confidence, importance, content: line });
582
- sections.set(date, bucket);
583
- }
584
- if (sections.size === 0) {
585
- const date = this.formatDate(this.now());
586
- sections.set(date, [{
587
- type: "fact",
588
- confidence: 0.7,
589
- importance: 0.2,
590
- content: `${this.formatTime(this.now())} Processed session updates.`
591
- }]);
592
- }
593
- return this.renderSections(sections);
594
- }
595
- mergeObservations(existing, incoming) {
596
- const existingRecords = parseObservationMarkdown(existing);
597
- const incomingRecords = parseObservationMarkdown(incoming);
598
- if (incomingRecords.length === 0) {
599
- return existing.trim();
600
- }
601
- const merged = /* @__PURE__ */ new Map();
602
- for (const record of existingRecords) {
603
- this.mergeRecord(merged, {
604
- date: record.date,
605
- type: record.type,
606
- confidence: record.confidence,
607
- importance: record.importance,
608
- content: record.content
609
- });
610
- }
611
- for (const record of incomingRecords) {
612
- this.mergeRecord(merged, {
613
- date: record.date,
614
- type: record.type,
615
- confidence: record.confidence,
616
- importance: record.importance,
617
- content: record.content
618
- });
619
- }
620
- return this.renderSections(merged);
621
- }
622
- mergeRecord(sections, input) {
623
- const bucket = sections.get(input.date) ?? [];
624
- const key = normalizeObservationContent(input.content);
625
- const index = bucket.findIndex((line) => normalizeObservationContent(line.content) === key);
626
- if (index === -1) {
627
- bucket.push({
628
- type: input.type,
629
- confidence: this.clamp01(input.confidence),
630
- importance: this.clamp01(input.importance),
631
- content: input.content.trim()
632
- });
633
- sections.set(input.date, bucket);
634
- return;
635
- }
636
- const existing = bucket[index];
637
- bucket[index] = {
638
- type: input.importance >= existing.importance ? input.type : existing.type,
639
- confidence: this.clamp01(Math.max(existing.confidence, input.confidence)),
640
- importance: this.clamp01(Math.max(existing.importance, input.importance)),
641
- content: existing.content.length >= input.content.length ? existing.content : input.content
642
- };
643
- sections.set(input.date, bucket);
644
- }
645
- renderSections(sections) {
646
- return renderObservationMarkdown(sections);
647
- }
648
- inferImportance(text, type) {
649
- const inferredTaskType = this.inferTaskType(text);
650
- if (this.isCriticalContent(text)) return 0.9;
651
- if (inferredTaskType === "commitment-unresolved") return 0.72;
652
- if (inferredTaskType === "task" || inferredTaskType === "todo") return 0.65;
653
- if (this.isNotableContent(text)) return 0.6;
654
- if (type === "decision" || type === "commitment" || type === "milestone") return 0.55;
655
- if (type === "preference" || type === "lesson" || type === "relationship" || type === "project") return 0.45;
656
- return 0.2;
657
- }
658
- inferConfidence(text, type, importance) {
659
- const inferredTaskType = this.inferTaskType(text);
660
- let confidence = 0.72;
661
- if (importance >= 0.8) confidence += 0.12;
662
- if (type === "decision" || type === "commitment" || type === "milestone") confidence += 0.06;
663
- if (inferredTaskType) confidence += 0.06;
664
- if (/\b(?:decided|chose|committed|deadline|released|merged)\b/i.test(text)) {
665
- confidence += 0.05;
666
- }
667
- return this.clamp01(confidence);
668
- }
669
- isCriticalContent(text) {
670
- return CRITICAL_RE.test(text) || DEADLINE_WITH_DATE_RE.test(text);
671
- }
672
- isNotableContent(text) {
673
- return NOTABLE_RE.test(text);
674
- }
675
- inferTaskType(text) {
676
- if (UNRESOLVED_COMMITMENT_RE.test(text)) {
677
- return "commitment-unresolved";
678
- }
679
- if (TODO_SIGNAL_RE.test(text)) {
680
- return "todo";
681
- }
682
- if (COMMITMENT_TASK_SIGNAL_RE.test(text) || DEADLINE_SIGNAL_RE.test(text)) {
683
- return "task";
684
- }
685
- return null;
686
- }
687
- normalizeText(text) {
688
- return text.replace(/\s+/g, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim().slice(0, 280);
689
- }
690
- extractDate(text) {
691
- const match = text.match(/\b(\d{4}-\d{2}-\d{2})\b/);
692
- return match?.[1] ?? null;
693
- }
694
- extractTime(text) {
695
- const match = text.match(/\b([01]\d|2[0-3]):([0-5]\d)\b/);
696
- if (!match) {
697
- return null;
698
- }
699
- return `${match[1]}:${match[2]}`;
700
- }
701
- formatDate(date) {
702
- return date.toISOString().split("T")[0];
703
- }
704
- formatTime(date) {
705
- return date.toISOString().slice(11, 16);
706
- }
707
- clamp01(value) {
708
- if (!Number.isFinite(value)) return 0;
709
- if (value < 0) return 0;
710
- if (value > 1) return 1;
711
- return value;
712
- }
713
- };
714
-
715
44
  // src/observer/reflector.ts
716
- var DATE_HEADING_RE2 = /^##\s+(\d{4}-\d{2}-\d{2})\s*$/;
45
+ var DATE_HEADING_RE = /^##\s+(\d{4}-\d{2}-\d{2})\s*$/;
717
46
  var OBSERVATION_LINE_RE = /^(🔴|🟡|🟢)\s+(.+)$/u;
718
47
  var Reflector = class {
719
48
  now;
@@ -773,7 +102,7 @@ var Reflector = class {
773
102
  const sections = /* @__PURE__ */ new Map();
774
103
  let currentDate = null;
775
104
  for (const rawLine of markdown.split(/\r?\n/)) {
776
- const dateMatch = rawLine.match(DATE_HEADING_RE2);
105
+ const dateMatch = rawLine.match(DATE_HEADING_RE);
777
106
  if (dateMatch) {
778
107
  currentDate = dateMatch[1];
779
108
  if (!sections.has(currentDate)) {
@@ -821,9 +150,8 @@ var Reflector = class {
821
150
  };
822
151
 
823
152
  // src/lib/llm-adapter.ts
824
- var GEMINI_FLASH_MODEL = "gemini-2.0-flash";
825
153
  var OLLAMA_DEFAULT_MODEL = "llama3.1:8b";
826
- var OLLAMA_BASE_URL2 = "http://127.0.0.1:11434";
154
+ var OLLAMA_BASE_URL = "http://127.0.0.1:11434";
827
155
  function createGeminiFlashAdapter(options = {}) {
828
156
  const apiKey = process.env.GEMINI_API_KEY;
829
157
  return {
@@ -834,7 +162,8 @@ function createGeminiFlashAdapter(options = {}) {
834
162
  return requestLlmCompletion({
835
163
  prompt,
836
164
  provider: "gemini",
837
- model: options.model ?? GEMINI_FLASH_MODEL,
165
+ model: options.model,
166
+ tier: options.tier ?? "complex",
838
167
  temperature: options.temperature ?? 0.1,
839
168
  maxTokens: options.maxTokens ?? 2e3,
840
169
  fetchImpl: options.fetchImpl
@@ -853,7 +182,7 @@ function createOllamaAdapter(options = {}) {
853
182
  const fetchFn = options.fetchImpl ?? globalThis.fetch;
854
183
  return {
855
184
  async call(prompt) {
856
- const resp = await fetchFn(`${OLLAMA_BASE_URL2}/api/generate`, {
185
+ const resp = await fetchFn(`${OLLAMA_BASE_URL}/api/generate`, {
857
186
  method: "POST",
858
187
  headers: { "Content-Type": "application/json" },
859
188
  body: JSON.stringify({
@@ -891,6 +220,7 @@ function createDefaultAdapter(options = {}) {
891
220
  prompt,
892
221
  provider: resolvedProvider,
893
222
  model: options.model,
223
+ tier: options.tier ?? "default",
894
224
  temperature: options.temperature ?? 0.1,
895
225
  maxTokens: options.maxTokens ?? 2e3,
896
226
  fetchImpl: options.fetchImpl
@@ -905,18 +235,22 @@ function createDefaultAdapter(options = {}) {
905
235
  };
906
236
  }
907
237
  function createFactExtractionAdapter(options = {}) {
238
+ const factExtractionOptions = {
239
+ ...options,
240
+ tier: options.tier ?? "complex"
241
+ };
908
242
  if (options.provider) {
909
- return createDefaultAdapter(options);
243
+ return createDefaultAdapter(factExtractionOptions);
910
244
  }
911
- const geminiAdapter = createGeminiFlashAdapter(options);
245
+ const geminiAdapter = createGeminiFlashAdapter(factExtractionOptions);
912
246
  if (geminiAdapter.isAvailable()) {
913
247
  return geminiAdapter;
914
248
  }
915
- const ollamaAdapter = createOllamaAdapter(options);
249
+ const ollamaAdapter = createOllamaAdapter(factExtractionOptions);
916
250
  if (ollamaAdapter.isAvailable()) {
917
251
  return ollamaAdapter;
918
252
  }
919
- return createDefaultAdapter(options);
253
+ return createDefaultAdapter(factExtractionOptions);
920
254
  }
921
255
  function createLlmFunction(adapter) {
922
256
  if (!adapter.isAvailable()) {
@@ -1806,14 +1140,13 @@ function readCompressionConfig(vaultPath) {
1806
1140
  const root = asRecord(config);
1807
1141
  const observer = asRecord(root?.observer);
1808
1142
  const compression = asRecord(observer?.compression);
1809
- if (!compression) {
1810
- return {};
1811
- }
1143
+ const models = asRecord(root?.models);
1144
+ const backgroundTierModel = asNonEmptyString(models?.background);
1812
1145
  return {
1813
- provider: asCompressionProvider(compression.provider),
1814
- model: asNonEmptyString(compression.model),
1815
- baseUrl: asNonEmptyString(compression.baseUrl),
1816
- apiKey: asNonEmptyString(compression.apiKey)
1146
+ provider: asCompressionProvider(compression?.provider),
1147
+ model: asNonEmptyString(compression?.model) ?? backgroundTierModel,
1148
+ baseUrl: asNonEmptyString(compression?.baseUrl),
1149
+ apiKey: asNonEmptyString(compression?.apiKey)
1817
1150
  };
1818
1151
  } catch {
1819
1152
  return {};
@@ -2015,7 +1348,6 @@ var Observer = class {
2015
1348
  };
2016
1349
 
2017
1350
  export {
2018
- Compressor,
2019
1351
  Reflector,
2020
1352
  createGeminiFlashAdapter,
2021
1353
  createDefaultAdapter,