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.
- package/README.md +54 -14
- package/bin/clawvault.js +0 -2
- package/bin/command-registration.test.js +13 -1
- package/bin/help-contract.test.js +14 -0
- package/bin/register-core-commands.js +88 -0
- package/bin/register-core-commands.test.js +80 -0
- package/bin/register-maintenance-commands.js +57 -6
- package/bin/register-query-commands.js +10 -28
- package/bin/test-helpers/cli-command-fixtures.js +1 -0
- package/dist/chunk-2PKBIKDH.js +130 -0
- package/dist/{chunk-2JQ3O2YL.js → chunk-5EFSWZO6.js} +3 -3
- package/dist/{chunk-77Q5CSPJ.js → chunk-7SWP5FKU.js} +33 -701
- package/dist/{chunk-URXDAUVH.js → chunk-AXSJIFOJ.js} +174 -1
- package/dist/{chunk-23YDQ3QU.js → chunk-BLQXXX7Q.js} +6 -6
- package/dist/chunk-CSHO3PJB.js +684 -0
- package/dist/{chunk-SLXOR3CC.js → chunk-DOIUYIXV.js} +2 -2
- package/dist/{chunk-NCKFNBHJ.js → chunk-DVOUSOR3.js} +79 -5
- package/dist/{chunk-CLJTREDS.js → chunk-ECGJYWNA.js} +193 -41
- package/dist/{chunk-BUEW6IIK.js → chunk-EL6UBSX5.js} +5 -5
- package/dist/{chunk-6FH3IULF.js → chunk-FZ5I2NF7.js} +1 -1
- package/dist/{chunk-ZN54U2OZ.js → chunk-GFCHWMGD.js} +3 -3
- package/dist/{chunk-GNJL4YGR.js → chunk-GJO3CFUN.js} +30 -6
- package/dist/chunk-H3JZIB5O.js +322 -0
- package/dist/chunk-HEHO7SMV.js +51 -0
- package/dist/{chunk-STCQGCEQ.js → chunk-HGDDW24U.js} +3 -3
- package/dist/chunk-J3YUXVID.js +907 -0
- package/dist/{chunk-Y6VJKXGL.js → chunk-KCYWJDDW.js} +1 -1
- package/dist/{chunk-W4SPAEE7.js → chunk-OFOCU2V4.js} +5 -4
- package/dist/chunk-PTWPPVC7.js +972 -0
- package/dist/{chunk-QSHD36LH.js → chunk-QFWERBDP.js} +2 -2
- package/dist/{chunk-QSRRMEYM.js → chunk-S7N7HI5E.js} +1 -1
- package/dist/{chunk-PBACDKKP.js → chunk-T7E764W3.js} +3 -3
- package/dist/chunk-TDWFBDAQ.js +1016 -0
- package/dist/{chunk-ESVS6K2B.js → chunk-TWMI3SNN.js} +6 -5
- package/dist/{chunk-2RAZ4ZFE.js → chunk-VBILES4B.js} +1 -1
- package/dist/{chunk-ESFLMDRB.js → chunk-VXAGOLDP.js} +3 -3
- package/dist/chunk-YCUVAOFC.js +158 -0
- package/dist/{chunk-SS4B7P7V.js → chunk-YIDV4VV2.js} +1 -1
- package/dist/chunk-ZKWPCBYT.js +600 -0
- package/dist/cli/index.js +24 -24
- package/dist/commands/archive.js +2 -2
- package/dist/commands/benchmark.d.ts +12 -0
- package/dist/commands/benchmark.js +12 -0
- package/dist/commands/context.js +6 -5
- package/dist/commands/doctor.d.ts +8 -3
- package/dist/commands/doctor.js +6 -20
- package/dist/commands/embed.js +5 -4
- package/dist/commands/entities.js +1 -1
- package/dist/commands/graph.js +2 -2
- package/dist/commands/inbox.d.ts +23 -0
- package/dist/commands/inbox.js +11 -0
- package/dist/commands/inject.d.ts +1 -1
- package/dist/commands/inject.js +3 -3
- package/dist/commands/link.js +6 -6
- package/dist/commands/maintain.d.ts +32 -0
- package/dist/commands/maintain.js +12 -0
- package/dist/commands/migrate-observations.js +2 -2
- package/dist/commands/observe.js +9 -8
- package/dist/commands/rebuild-embeddings.js +47 -16
- package/dist/commands/rebuild.js +7 -6
- package/dist/commands/reflect.js +5 -5
- package/dist/commands/replay.js +8 -7
- package/dist/commands/setup.js +3 -2
- package/dist/commands/sleep.d.ts +1 -1
- package/dist/commands/sleep.js +17 -15
- package/dist/commands/status.js +26 -24
- package/dist/commands/sync-bd.js +2 -2
- package/dist/commands/tailscale.js +2 -2
- package/dist/commands/wake.d.ts +1 -1
- package/dist/commands/wake.js +8 -7
- package/dist/index.d.ts +168 -16
- package/dist/index.js +271 -108
- package/dist/{inject-DYUrDqQO.d.ts → inject-DEb_jpLi.d.ts} +3 -1
- package/dist/lib/config.js +1 -1
- package/dist/{types-BbWJoC1c.d.ts → types-DslKvCaj.d.ts} +51 -1
- package/hooks/clawvault/HOOK.md +22 -5
- package/hooks/clawvault/handler.js +213 -78
- package/hooks/clawvault/handler.test.js +109 -43
- package/hooks/clawvault/integrity.js +112 -0
- package/hooks/clawvault/integrity.test.js +32 -0
- package/hooks/clawvault/openclaw.plugin.json +133 -15
- package/openclaw.plugin.json +126 -20
- package/package.json +2 -2
- package/bin/register-workgraph-commands.js +0 -1368
- package/dist/chunk-33VSQP4J.js +0 -37
- package/dist/chunk-4BQTQMJP.js +0 -93
- package/dist/chunk-EK6S23ZB.js +0 -469
- package/dist/chunk-GAOWA7GR.js +0 -501
- package/dist/chunk-GGA32J2R.js +0 -784
- package/dist/chunk-MM6QGW3P.js +0 -207
- package/dist/chunk-QVEERJSP.js +0 -152
- package/dist/chunk-U4O6C46S.js +0 -154
- package/dist/chunk-VSL7KY3M.js +0 -189
- package/dist/chunk-WMGIIABP.js +0 -15
- package/dist/commands/workgraph.d.ts +0 -124
- package/dist/commands/workgraph.js +0 -38
- package/dist/ledger-B7g7jhqG.d.ts +0 -44
- package/dist/registry-BR4326o0.d.ts +0 -30
- package/dist/store-CA-6sKCJ.d.ts +0 -34
- package/dist/thread-B9LhXNU0.d.ts +0 -41
- package/dist/workgraph/index.d.ts +0 -5
- package/dist/workgraph/index.js +0 -23
- package/dist/workgraph/ledger.d.ts +0 -2
- package/dist/workgraph/ledger.js +0 -25
- package/dist/workgraph/registry.d.ts +0 -2
- package/dist/workgraph/registry.js +0 -19
- package/dist/workgraph/store.d.ts +0 -2
- package/dist/workgraph/store.js +0 -25
- package/dist/workgraph/thread.d.ts +0 -2
- package/dist/workgraph/thread.js +0 -25
- package/dist/workgraph/types.d.ts +0 -54
- 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
|
-
|
|
11
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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(`${
|
|
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(
|
|
243
|
+
return createDefaultAdapter(factExtractionOptions);
|
|
910
244
|
}
|
|
911
|
-
const geminiAdapter = createGeminiFlashAdapter(
|
|
245
|
+
const geminiAdapter = createGeminiFlashAdapter(factExtractionOptions);
|
|
912
246
|
if (geminiAdapter.isAvailable()) {
|
|
913
247
|
return geminiAdapter;
|
|
914
248
|
}
|
|
915
|
-
const ollamaAdapter = createOllamaAdapter(
|
|
249
|
+
const ollamaAdapter = createOllamaAdapter(factExtractionOptions);
|
|
916
250
|
if (ollamaAdapter.isAvailable()) {
|
|
917
251
|
return ollamaAdapter;
|
|
918
252
|
}
|
|
919
|
-
return createDefaultAdapter(
|
|
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
|
-
|
|
1810
|
-
|
|
1811
|
-
}
|
|
1143
|
+
const models = asRecord(root?.models);
|
|
1144
|
+
const backgroundTierModel = asNonEmptyString(models?.background);
|
|
1812
1145
|
return {
|
|
1813
|
-
provider: asCompressionProvider(compression
|
|
1814
|
-
model: asNonEmptyString(compression
|
|
1815
|
-
baseUrl: asNonEmptyString(compression
|
|
1816
|
-
apiKey: asNonEmptyString(compression
|
|
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,
|