omegon 0.6.8 → 0.6.10
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/extensions/cleave/dispatcher.ts +32 -6
- package/extensions/cleave/index.ts +21 -3
- package/extensions/cleave/subprocess-tracker.ts +72 -0
- package/extensions/project-memory/extraction-v2.ts +206 -434
- package/extensions/project-memory/index.ts +89 -182
- package/extensions/project-memory/llm-direct.ts +260 -0
- package/extensions/project-memory/types.ts +3 -3
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/README.md +3 -0
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/clipboard.linux-x64-gnu.node +0 -0
- package/node_modules/@mariozechner/{clipboard-darwin-arm64 → clipboard-linux-x64-gnu}/package.json +8 -5
- package/node_modules/@styrene-lab/pi-ai/dist/cli.js +0 -0
- package/node_modules/@types/node/README.md +2 -2
- package/node_modules/@types/node/assert/strict.d.ts +5 -11
- package/node_modules/@types/node/assert.d.ts +11 -171
- package/node_modules/@types/node/async_hooks.d.ts +8 -8
- package/node_modules/@types/node/buffer.buffer.d.ts +1 -7
- package/node_modules/@types/node/buffer.d.ts +44 -168
- package/node_modules/@types/node/child_process.d.ts +21 -64
- package/node_modules/@types/node/cluster.d.ts +240 -332
- package/node_modules/@types/node/console.d.ts +49 -351
- package/node_modules/@types/node/constants.d.ts +3 -4
- package/node_modules/@types/node/crypto.d.ts +287 -1639
- package/node_modules/@types/node/dgram.d.ts +15 -51
- package/node_modules/@types/node/diagnostics_channel.d.ts +4 -4
- package/node_modules/@types/node/dns/promises.d.ts +3 -3
- package/node_modules/@types/node/dns.d.ts +131 -132
- package/node_modules/@types/node/domain.d.ts +13 -17
- package/node_modules/@types/node/events.d.ts +866 -795
- package/node_modules/@types/node/fs/promises.d.ts +7 -8
- package/node_modules/@types/node/fs.d.ts +419 -455
- package/node_modules/@types/node/globals.d.ts +6 -26
- package/node_modules/@types/node/globals.typedarray.d.ts +60 -0
- package/node_modules/@types/node/http.d.ts +310 -276
- package/node_modules/@types/node/http2.d.ts +528 -804
- package/node_modules/@types/node/https.d.ts +63 -243
- package/node_modules/@types/node/index.d.ts +15 -1
- package/node_modules/@types/node/inspector/promises.d.ts +41 -0
- package/node_modules/@types/node/inspector.d.ts +42 -50
- package/node_modules/@types/node/inspector.generated.d.ts +572 -417
- package/node_modules/@types/node/module.d.ts +47 -195
- package/node_modules/@types/node/net.d.ts +68 -189
- package/node_modules/@types/node/os.d.ts +6 -6
- package/node_modules/@types/node/package.json +3 -3
- package/node_modules/@types/node/path/posix.d.ts +8 -0
- package/node_modules/@types/node/path/win32.d.ts +8 -0
- package/node_modules/@types/node/path.d.ts +120 -133
- package/node_modules/@types/node/perf_hooks.d.ts +244 -643
- package/node_modules/@types/node/process.d.ts +219 -127
- package/node_modules/@types/node/punycode.d.ts +5 -5
- package/node_modules/@types/node/querystring.d.ts +4 -4
- package/node_modules/@types/node/quic.d.ts +910 -0
- package/node_modules/@types/node/readline/promises.d.ts +3 -3
- package/node_modules/@types/node/readline.d.ts +68 -120
- package/node_modules/@types/node/repl.d.ts +75 -98
- package/node_modules/@types/node/sea.d.ts +1 -1
- package/node_modules/@types/node/sqlite.d.ts +137 -30
- package/node_modules/@types/node/stream/consumers.d.ts +10 -10
- package/node_modules/@types/node/stream/promises.d.ts +136 -15
- package/node_modules/@types/node/stream/web.d.ts +176 -453
- package/node_modules/@types/node/stream.d.ts +565 -478
- package/node_modules/@types/node/string_decoder.d.ts +4 -4
- package/node_modules/@types/node/test/reporters.d.ts +96 -0
- package/node_modules/@types/node/test.d.ts +134 -199
- package/node_modules/@types/node/timers/promises.d.ts +4 -4
- package/node_modules/@types/node/timers.d.ts +4 -130
- package/node_modules/@types/node/tls.d.ts +109 -179
- package/node_modules/@types/node/trace_events.d.ts +9 -9
- package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +1 -7
- package/node_modules/@types/node/ts5.6/index.d.ts +15 -1
- package/node_modules/@types/node/ts5.7/index.d.ts +15 -1
- package/node_modules/@types/node/tty.d.ts +58 -16
- package/node_modules/@types/node/url.d.ts +78 -594
- package/node_modules/@types/node/util/types.d.ts +558 -0
- package/node_modules/@types/node/util.d.ts +150 -796
- package/node_modules/@types/node/v8.d.ts +31 -21
- package/node_modules/@types/node/vm.d.ts +43 -30
- package/node_modules/@types/node/wasi.d.ts +4 -4
- package/node_modules/@types/node/web-globals/abortcontroller.d.ts +27 -2
- package/node_modules/@types/node/web-globals/blob.d.ts +23 -0
- package/node_modules/@types/node/web-globals/console.d.ts +9 -0
- package/node_modules/@types/node/web-globals/crypto.d.ts +7 -0
- package/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
- package/node_modules/@types/node/web-globals/events.d.ts +9 -0
- package/node_modules/@types/node/web-globals/fetch.d.ts +7 -0
- package/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
- package/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
- package/node_modules/@types/node/web-globals/performance.d.ts +45 -0
- package/node_modules/@types/node/web-globals/streams.d.ts +93 -0
- package/node_modules/@types/node/web-globals/timers.d.ts +44 -0
- package/node_modules/@types/node/web-globals/url.d.ts +24 -0
- package/node_modules/@types/node/worker_threads.d.ts +186 -363
- package/node_modules/@types/node/zlib.d.ts +8 -71
- package/node_modules/cliui/CHANGELOG.md +121 -0
- package/node_modules/color-convert/CHANGELOG.md +54 -0
- package/node_modules/esprima/ChangeLog +235 -0
- package/node_modules/mz/HISTORY.md +66 -0
- package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
- package/node_modules/source-map/CHANGELOG.md +301 -0
- package/node_modules/thenify/History.md +11 -0
- package/node_modules/thenify-all/History.md +11 -0
- package/node_modules/undici-types/agent.d.ts +1 -1
- package/node_modules/undici-types/api.d.ts +2 -2
- package/node_modules/undici-types/balanced-pool.d.ts +2 -1
- package/node_modules/undici-types/cache-interceptor.d.ts +1 -0
- package/node_modules/undici-types/client.d.ts +1 -1
- package/node_modules/undici-types/connector.d.ts +2 -2
- package/node_modules/undici-types/diagnostics-channel.d.ts +2 -2
- package/node_modules/undici-types/dispatcher.d.ts +12 -12
- package/node_modules/undici-types/fetch.d.ts +4 -4
- package/node_modules/undici-types/formdata.d.ts +1 -1
- package/node_modules/undici-types/h2c-client.d.ts +1 -1
- package/node_modules/undici-types/index.d.ts +9 -1
- package/node_modules/undici-types/interceptors.d.ts +36 -2
- package/node_modules/undici-types/package.json +1 -1
- package/node_modules/undici-types/pool.d.ts +1 -1
- package/node_modules/undici-types/readable.d.ts +2 -2
- package/node_modules/undici-types/round-robin-pool.d.ts +41 -0
- package/node_modules/undici-types/websocket.d.ts +9 -9
- package/node_modules/which/CHANGELOG.md +166 -0
- package/node_modules/y18n/CHANGELOG.md +100 -0
- package/node_modules/yargs/CHANGELOG.md +88 -0
- package/node_modules/yargs-parser/CHANGELOG.md +263 -0
- package/package.json +5 -5
- package/extensions/render/composition/package-lock.json +0 -534
- package/node_modules/@mariozechner/clipboard-darwin-arm64/README.md +0 -3
- package/node_modules/@mariozechner/clipboard-darwin-arm64/clipboard.darwin-arm64.node +0 -0
- package/node_modules/@mariozechner/clipboard-darwin-universal/README.md +0 -3
- package/node_modules/@mariozechner/clipboard-darwin-universal/clipboard.darwin-universal.node +0 -0
- package/node_modules/@mariozechner/clipboard-darwin-universal/package.json +0 -19
- /package/node_modules/{p-retry/node_modules/@types → @types}/retry/LICENSE +0 -0
- /package/node_modules/{p-retry/node_modules/@types → @types}/retry/README.md +0 -0
- /package/node_modules/{p-retry/node_modules/@types → @types}/retry/index.d.ts +0 -0
- /package/node_modules/{p-retry/node_modules/@types → @types}/retry/package.json +0 -0
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
createTriggerState,
|
|
68
68
|
shouldExtract,
|
|
69
69
|
} from "./triggers.ts";
|
|
70
|
-
import { runExtractionV2, runGlobalExtraction, killActiveExtraction, killAllSubprocesses,
|
|
70
|
+
import { runExtractionV2, runGlobalExtraction, killActiveExtraction, killAllSubprocesses, generateEpisodeDirect, generateEpisodeWithFallback, buildTemplateEpisode, runSectionPruningPass, type SessionTelemetry } from "./extraction-v2.ts";
|
|
71
71
|
import { migrateToFactStore, needsMigration, markMigrated } from "./migration.ts";
|
|
72
72
|
import { SECTIONS } from "./template.ts";
|
|
73
73
|
import { serializeConversation, convertToLlm } from "@styrene-lab/pi-coding-agent";
|
|
@@ -156,23 +156,17 @@ Summarize the prefix to provide context for the retained suffix:
|
|
|
156
156
|
Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
157
157
|
|
|
158
158
|
// ---------------------------------------------------------------------------
|
|
159
|
-
//
|
|
159
|
+
// Local model discovery for compaction fallback
|
|
160
160
|
// ---------------------------------------------------------------------------
|
|
161
161
|
|
|
162
|
-
const OLLAMA_URL = () => process.env.OLLAMA_HOST || process.env.LOCAL_INFERENCE_URL || "http://localhost:11434";
|
|
163
|
-
|
|
164
|
-
/** Embedding model names that must not be used for chat completions */
|
|
165
|
-
const EMBEDDING_MODEL_PATTERN = /embed|embedding/i;
|
|
166
|
-
|
|
167
|
-
/** Preferred models for summarization, in priority order */
|
|
168
|
-
// Canonical preference list + family prefix catch-alls from shared registry.
|
|
169
|
-
// Specific tags first (largest/best wins via startsWith); families catch any
|
|
170
|
-
// installed variant not explicitly listed (e.g. qwen3:14b-q4_k_m).
|
|
171
|
-
// Edit extensions/lib/local-models.ts to update model preferences.
|
|
172
162
|
import {
|
|
173
163
|
PREFERRED_ORDER as LOCAL_MODELS_ORDER,
|
|
174
164
|
PREFERRED_FAMILIES,
|
|
175
165
|
} from "../lib/local-models.ts";
|
|
166
|
+
import { chatDirect } from "./llm-direct.ts";
|
|
167
|
+
|
|
168
|
+
const OLLAMA_URL = () => process.env.OLLAMA_HOST || process.env.LOCAL_INFERENCE_URL || "http://localhost:11434";
|
|
169
|
+
const EMBEDDING_MODEL_PATTERN = /embed|embedding/i;
|
|
176
170
|
const PREFERRED_CHAT_MODELS = [...LOCAL_MODELS_ORDER, ...PREFERRED_FAMILIES];
|
|
177
171
|
|
|
178
172
|
/**
|
|
@@ -187,49 +181,16 @@ async function discoverLocalChatModel(): Promise<string | null> {
|
|
|
187
181
|
const available = (data.data?.map((m: { id: string }) => m.id) ?? [])
|
|
188
182
|
.filter((id: string) => !EMBEDDING_MODEL_PATTERN.test(id));
|
|
189
183
|
if (available.length === 0) return null;
|
|
190
|
-
|
|
191
|
-
// Try preferred models first (startsWith for exact matching)
|
|
192
184
|
for (const pref of PREFERRED_CHAT_MODELS) {
|
|
193
185
|
const found = available.find((id: string) => id.startsWith(pref));
|
|
194
186
|
if (found) return found;
|
|
195
187
|
}
|
|
196
|
-
return available[0];
|
|
188
|
+
return available[0];
|
|
197
189
|
} catch {
|
|
198
190
|
return null;
|
|
199
191
|
}
|
|
200
192
|
}
|
|
201
193
|
|
|
202
|
-
/**
|
|
203
|
-
* Send a chat completion to Ollama. Returns trimmed content or null.
|
|
204
|
-
*/
|
|
205
|
-
async function ollamaChat(
|
|
206
|
-
model: string,
|
|
207
|
-
systemPrompt: string,
|
|
208
|
-
userPrompt: string,
|
|
209
|
-
opts: { maxTokens?: number; signal?: AbortSignal },
|
|
210
|
-
): Promise<string | null> {
|
|
211
|
-
const resp = await fetch(`${OLLAMA_URL()}/v1/chat/completions`, {
|
|
212
|
-
method: "POST",
|
|
213
|
-
headers: { "Content-Type": "application/json" },
|
|
214
|
-
body: JSON.stringify({
|
|
215
|
-
model,
|
|
216
|
-
messages: [
|
|
217
|
-
{ role: "system", content: systemPrompt },
|
|
218
|
-
{ role: "user", content: userPrompt },
|
|
219
|
-
],
|
|
220
|
-
max_tokens: opts.maxTokens ?? 4096,
|
|
221
|
-
temperature: 0.3,
|
|
222
|
-
// Request a reasonable context window for the local model
|
|
223
|
-
num_ctx: 32768,
|
|
224
|
-
}),
|
|
225
|
-
signal: opts.signal,
|
|
226
|
-
});
|
|
227
|
-
if (!resp.ok) return null;
|
|
228
|
-
const data = await resp.json() as { choices?: Array<{ message?: { content?: string } }> };
|
|
229
|
-
const content = data.choices?.[0]?.message?.content?.trim();
|
|
230
|
-
return content || null;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
194
|
/**
|
|
234
195
|
* Format file operations for appending to compaction summary.
|
|
235
196
|
* Mirrors pi core's formatFileOperations but inlined since it's not exported.
|
|
@@ -362,6 +323,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
362
323
|
let exitEpisodeDone = false;
|
|
363
324
|
/** Pending embed promises — tracked so shutdown can await them before DB close */
|
|
364
325
|
const pendingEmbeds = new Set<Promise<unknown>>();
|
|
326
|
+
/** Concurrency guard — prevents pruning, indexing, and extraction from overlapping */
|
|
327
|
+
let backgroundTaskRunning = false;
|
|
365
328
|
let consecutiveExtractionFailures = 0;
|
|
366
329
|
let memoryDir = "";
|
|
367
330
|
|
|
@@ -770,14 +733,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
770
733
|
}
|
|
771
734
|
|
|
772
735
|
// --- Decay sweep + per-section pruning (background, non-blocking) ---
|
|
773
|
-
//
|
|
774
|
-
// Converts passive decay (lower score on read) into active archival.
|
|
775
|
-
// Without this, decayed facts accumulate forever as active but invisible.
|
|
776
|
-
// 2. When any section exceeds 60 facts, run a targeted LLM archival pass
|
|
777
|
-
// to bring it back under the ceiling. This prevents monotonic accumulation.
|
|
778
|
-
// Runs fire-and-forget so it doesn't block session startup.
|
|
736
|
+
// Guarded by backgroundTaskRunning to prevent overlap with indexing/extraction.
|
|
779
737
|
if (store) {
|
|
780
738
|
(async () => {
|
|
739
|
+
if (backgroundTaskRunning) return;
|
|
740
|
+
backgroundTaskRunning = true;
|
|
781
741
|
try {
|
|
782
742
|
const mind = activeMind();
|
|
783
743
|
|
|
@@ -792,38 +752,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
792
752
|
const sectionCounts = store!.getSectionCounts(mind);
|
|
793
753
|
for (const [section, count] of sectionCounts) {
|
|
794
754
|
if (count <= SECTION_CEILING) continue;
|
|
795
|
-
// Skip Recent Work — decay sweep handles it (fast decay, no LLM needed)
|
|
796
755
|
if (section === "Recent Work") continue;
|
|
797
756
|
|
|
798
|
-
// Single query — all subsequent phases operate on this in-memory list.
|
|
799
|
-
// getFactsBySection already computes confidence via computeConfidence
|
|
800
|
-
// (canonical formula from core.ts), so sorted.confidence is correct.
|
|
801
757
|
const facts = store!.getFactsBySection(mind, section);
|
|
802
758
|
const excess = count - SECTION_CEILING;
|
|
803
759
|
let archived = 0;
|
|
804
760
|
const archivedIds = new Set<string>();
|
|
805
761
|
|
|
806
|
-
// Phase 2a: Deterministic cull —
|
|
807
|
-
// below a hard floor. This protects semantically important facts that
|
|
808
|
-
// happen to have low confidence purely due to age. Facts above the
|
|
809
|
-
// floor are sent to the LLM for judgment (phase 2b).
|
|
762
|
+
// Phase 2a: Deterministic cull — below confidence floor
|
|
810
763
|
const CONFIDENCE_FLOOR = 0.25;
|
|
811
764
|
const sorted = [...facts].sort((a, b) => a.confidence - b.confidence);
|
|
812
765
|
for (const f of sorted) {
|
|
813
|
-
if (archived >= excess) break;
|
|
814
|
-
if (f.confidence > CONFIDENCE_FLOOR) break;
|
|
766
|
+
if (archived >= excess) break;
|
|
767
|
+
if (f.confidence > CONFIDENCE_FLOOR) break;
|
|
815
768
|
store!.archiveFact(f.id);
|
|
816
769
|
archivedIds.add(f.id);
|
|
817
770
|
archived++;
|
|
818
771
|
}
|
|
819
772
|
|
|
820
|
-
// Phase 2b: LLM-assisted pruning for
|
|
821
|
-
// Filter the in-memory array (no re-query) and send a manageable
|
|
822
|
-
// batch (up to 80 facts) to the LLM for nuanced review.
|
|
773
|
+
// Phase 2b: LLM-assisted pruning for remaining excess
|
|
823
774
|
const remaining = facts.filter(f => !archivedIds.has(f.id));
|
|
824
775
|
const stillExcess = remaining.length - SECTION_CEILING;
|
|
825
776
|
if (stillExcess > 0) {
|
|
826
|
-
// Send the bottom 80 by confidence for LLM review
|
|
827
777
|
const batch = [...remaining]
|
|
828
778
|
.sort((a, b) => a.confidence - b.confidence)
|
|
829
779
|
.slice(0, Math.min(80, remaining.length));
|
|
@@ -845,96 +795,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
845
795
|
}
|
|
846
796
|
} catch {
|
|
847
797
|
// Best effort — don't interrupt session
|
|
798
|
+
} finally {
|
|
799
|
+
backgroundTaskRunning = false;
|
|
848
800
|
}
|
|
849
801
|
})();
|
|
850
802
|
}
|
|
851
803
|
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
// questions work without waiting for semantic retrieval.
|
|
855
|
-
// Layer 1: last 1 session episode (most recent — use memory_episodes for more)
|
|
856
|
-
// Layer 2: top-15 recently-reinforced facts (recency window, cross-section)
|
|
857
|
-
// Layer 3: Decisions + Constraints + Known Issues always; Architecture capped at 10
|
|
858
|
-
//
|
|
859
|
-
// Architecture is the largest section by fact count. Loading all Architecture
|
|
860
|
-
// facts unconditionally blows context on large projects. The top-10 by recency
|
|
861
|
-
// covers active concerns; older facts are retrievable via memory_recall.
|
|
862
|
-
//
|
|
863
|
-
// This runs asynchronously so it doesn't block the TUI from appearing.
|
|
864
|
-
// The payload is injected as a pre-prompt system message on the first turn.
|
|
865
|
-
if (store) {
|
|
866
|
-
try {
|
|
867
|
-
const mind = activeMind();
|
|
868
|
-
const recentEpisodes = store.getEpisodes(mind, 1);
|
|
869
|
-
const allFacts = store.getActiveFacts(mind);
|
|
870
|
-
|
|
871
|
-
// Recency window: top 15 by last_reinforced (any section)
|
|
872
|
-
const recentFacts = [...allFacts]
|
|
873
|
-
.sort((a, b) => new Date(b.last_reinforced).getTime() - new Date(a.last_reinforced).getTime())
|
|
874
|
-
.slice(0, 15);
|
|
875
|
-
|
|
876
|
-
// Core structural facts: Decisions + Constraints + Known Issues always loaded.
|
|
877
|
-
// Architecture capped at 10 most-recently-reinforced (largest section by volume).
|
|
878
|
-
const coreFacts = allFacts.filter(f =>
|
|
879
|
-
f.section === "Decisions" || f.section === "Constraints" || f.section === "Known Issues"
|
|
880
|
-
);
|
|
881
|
-
const archFacts = [...allFacts]
|
|
882
|
-
.filter(f => f.section === "Architecture")
|
|
883
|
-
.sort((a, b) => new Date(b.last_reinforced).getTime() - new Date(a.last_reinforced).getTime())
|
|
884
|
-
.slice(0, 10);
|
|
885
|
-
|
|
886
|
-
// Merge: recent episodes + recency window + core sections (deduplicated)
|
|
887
|
-
// Budget-capped to prevent context overflow on large fact stores.
|
|
888
|
-
const STARTUP_MAX_CHARS = 12_000; // ~3K tokens — leaves room for system prompt + design-tree
|
|
889
|
-
let startupChars = 0;
|
|
890
|
-
const startupFactIds = new Set<string>();
|
|
891
|
-
const startupFacts: typeof allFacts = [];
|
|
892
|
-
for (const f of [...coreFacts, ...archFacts, ...recentFacts]) {
|
|
893
|
-
if (startupFactIds.has(f.id)) continue;
|
|
894
|
-
const cost = f.content.length + 20;
|
|
895
|
-
if (startupChars + cost > STARTUP_MAX_CHARS) break;
|
|
896
|
-
startupFacts.push(f);
|
|
897
|
-
startupFactIds.add(f.id);
|
|
898
|
-
startupChars += cost;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
if (recentEpisodes.length > 0 || startupFacts.length > 0) {
|
|
902
|
-
const lines: string[] = ["<!-- Startup Context — recent sessions and structural memory -->", ""];
|
|
903
|
-
|
|
904
|
-
if (recentEpisodes.length > 0) {
|
|
905
|
-
lines.push("## Recent Sessions");
|
|
906
|
-
lines.push("_Episodic memory — what happened and why_");
|
|
907
|
-
lines.push("");
|
|
908
|
-
for (const ep of recentEpisodes) {
|
|
909
|
-
lines.push(`### ${ep.date}: ${ep.title}`);
|
|
910
|
-
lines.push(ep.narrative);
|
|
911
|
-
lines.push("");
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
if (startupFacts.length > 0) {
|
|
916
|
-
const factsBySection = new Map<string, typeof startupFacts>();
|
|
917
|
-
for (const f of startupFacts) {
|
|
918
|
-
const sec = factsBySection.get(f.section) ?? [];
|
|
919
|
-
sec.push(f);
|
|
920
|
-
factsBySection.set(f.section, sec);
|
|
921
|
-
}
|
|
922
|
-
for (const [section, facts] of factsBySection) {
|
|
923
|
-
lines.push(`## ${section}`);
|
|
924
|
-
lines.push("");
|
|
925
|
-
for (const f of facts) {
|
|
926
|
-
lines.push(`- ${f.content}`);
|
|
927
|
-
}
|
|
928
|
-
lines.push("");
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
startupInjectionPayload = lines.join("\n");
|
|
933
|
-
}
|
|
934
|
-
} catch {
|
|
935
|
-
// Best effort — don't block startup
|
|
936
|
-
}
|
|
937
|
-
}
|
|
804
|
+
// Startup injection payload is built lazily in before_agent_start (first turn)
|
|
805
|
+
// to avoid loading all facts twice (once here, once in the injection pipeline).
|
|
938
806
|
|
|
939
807
|
updateStatus(ctx);
|
|
940
808
|
});
|
|
@@ -952,11 +820,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
952
820
|
customInstructions: string | undefined,
|
|
953
821
|
signal: AbortSignal
|
|
954
822
|
): Promise<{ summary: string; details: any } | null> {
|
|
955
|
-
// Build summarization prompt (same as existing logic)
|
|
956
823
|
const llmMessages = convertToLlm(prep.messagesToSummarize);
|
|
957
824
|
let conversationText = sanitizeCompactionText(serializeConversation(llmMessages));
|
|
958
825
|
|
|
959
|
-
// Truncate to ~60k chars (~15k tokens) to fit local model context windows
|
|
960
826
|
const MAX_CONVERSATION_CHARS = 60_000;
|
|
961
827
|
if (conversationText.length > MAX_CONVERSATION_CHARS) {
|
|
962
828
|
conversationText = "...[earlier conversation truncated]...\n\n"
|
|
@@ -968,7 +834,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
968
834
|
promptText += `<previous-summary>\n${prep.previousSummary}\n</previous-summary>\n\n`;
|
|
969
835
|
}
|
|
970
836
|
|
|
971
|
-
// Inject project memory context for richer summaries
|
|
972
837
|
if (store) {
|
|
973
838
|
const mind = activeMind();
|
|
974
839
|
const facts = store.getActiveFacts(mind);
|
|
@@ -982,7 +847,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
982
847
|
const basePrompt = prep.previousSummary ? COMPACTION_UPDATE_PROMPT : COMPACTION_INITIAL_PROMPT;
|
|
983
848
|
promptText += customInstructions ? `${basePrompt}\n\nAdditional focus: ${customInstructions}` : basePrompt;
|
|
984
849
|
|
|
985
|
-
// Handle split turn prefix
|
|
850
|
+
// Handle split turn prefix
|
|
986
851
|
let turnPrefixSummary = "";
|
|
987
852
|
if (prep.isSplitTurn && prep.turnPrefixMessages.length > 0) {
|
|
988
853
|
const prefixMessages = convertToLlm(prep.turnPrefixMessages);
|
|
@@ -991,36 +856,45 @@ export default function (pi: ExtensionAPI) {
|
|
|
991
856
|
prefixText = "...[truncated]...\n\n" + prefixText.slice(-MAX_CONVERSATION_CHARS);
|
|
992
857
|
}
|
|
993
858
|
const prefixPrompt = `<conversation>\n${prefixText}\n</conversation>\n\n${COMPACTION_TURN_PREFIX_PROMPT}`;
|
|
994
|
-
|
|
995
859
|
try {
|
|
996
|
-
const
|
|
997
|
-
|
|
860
|
+
const result = await chatDirect({
|
|
861
|
+
model: localModel,
|
|
862
|
+
systemPrompt: COMPACTION_SYSTEM_PROMPT,
|
|
863
|
+
userMessage: prefixPrompt,
|
|
864
|
+
maxTokens: 2048,
|
|
865
|
+
timeout: 30_000,
|
|
866
|
+
signal,
|
|
998
867
|
});
|
|
999
|
-
|
|
868
|
+
turnPrefixSummary = result.content;
|
|
1000
869
|
} catch {
|
|
1001
870
|
// If turn prefix fails, continue without it
|
|
1002
871
|
}
|
|
1003
872
|
}
|
|
1004
873
|
|
|
1005
|
-
// Generate main summary
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
874
|
+
// Generate main summary via direct HTTP (no subprocess)
|
|
875
|
+
try {
|
|
876
|
+
const result = await chatDirect({
|
|
877
|
+
model: localModel,
|
|
878
|
+
systemPrompt: COMPACTION_SYSTEM_PROMPT,
|
|
879
|
+
userMessage: promptText,
|
|
880
|
+
maxTokens: 4096,
|
|
881
|
+
timeout: 45_000,
|
|
882
|
+
signal,
|
|
883
|
+
});
|
|
1009
884
|
|
|
1010
|
-
|
|
885
|
+
let fullSummary = result.content;
|
|
886
|
+
if (turnPrefixSummary) {
|
|
887
|
+
fullSummary += `\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefixSummary}`;
|
|
888
|
+
}
|
|
889
|
+
fullSummary += formatFileOps(prep.fileOps);
|
|
1011
890
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
891
|
+
return {
|
|
892
|
+
summary: fullSummary,
|
|
893
|
+
details: buildFileDetails(prep.fileOps),
|
|
894
|
+
};
|
|
895
|
+
} catch {
|
|
896
|
+
return null;
|
|
1015
897
|
}
|
|
1016
|
-
|
|
1017
|
-
// Append file operations
|
|
1018
|
-
fullSummary += formatFileOps(prep.fileOps);
|
|
1019
|
-
|
|
1020
|
-
return {
|
|
1021
|
-
summary: fullSummary,
|
|
1022
|
-
details: buildFileDetails(prep.fileOps),
|
|
1023
|
-
};
|
|
1024
898
|
}
|
|
1025
899
|
|
|
1026
900
|
/**
|
|
@@ -1671,11 +1545,42 @@ export default function (pi: ExtensionAPI) {
|
|
|
1671
1545
|
}
|
|
1672
1546
|
}
|
|
1673
1547
|
|
|
1674
|
-
// Proactive startup payload —
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1548
|
+
// Proactive startup payload — built lazily on firstTurn from allFacts (already loaded above).
|
|
1549
|
+
// Avoids the old pattern of loading all facts in session_start AND again here.
|
|
1550
|
+
let startupSection = "";
|
|
1551
|
+
if (startupInjectionPayload) {
|
|
1552
|
+
startupSection = `\n\n${startupInjectionPayload}`;
|
|
1553
|
+
startupInjectionPayload = null;
|
|
1554
|
+
} else if (!postCompaction) {
|
|
1555
|
+
// First turn: build startup context from allFacts + episodes (already in memory)
|
|
1556
|
+
try {
|
|
1557
|
+
const recentEpisodes = store.getEpisodes(mind, 1);
|
|
1558
|
+
const lines: string[] = ["<!-- Startup Context — recent sessions and structural memory -->", ""];
|
|
1559
|
+
if (recentEpisodes.length > 0) {
|
|
1560
|
+
lines.push("## Recent Sessions");
|
|
1561
|
+
lines.push("_Episodic memory — what happened and why_");
|
|
1562
|
+
lines.push("");
|
|
1563
|
+
for (const ep of recentEpisodes) {
|
|
1564
|
+
lines.push(`### ${ep.date}: ${ep.title}`);
|
|
1565
|
+
lines.push(ep.narrative);
|
|
1566
|
+
lines.push("");
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
// Decisions section from allFacts (already sorted/injected above — add any not yet included)
|
|
1570
|
+
const decisionsList = allFacts.filter(f => f.section === "Decisions" && !injectedIds.has(f.id)).slice(0, 5);
|
|
1571
|
+
if (decisionsList.length > 0) {
|
|
1572
|
+
lines.push("## Decisions");
|
|
1573
|
+
lines.push("");
|
|
1574
|
+
for (const f of decisionsList) lines.push(`- ${f.content}`);
|
|
1575
|
+
lines.push("");
|
|
1576
|
+
}
|
|
1577
|
+
if (lines.length > 2) {
|
|
1578
|
+
startupSection = `\n\n${lines.join("\n")}`;
|
|
1579
|
+
}
|
|
1580
|
+
} catch {
|
|
1581
|
+
// Best effort
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1679
1584
|
|
|
1680
1585
|
const budgetNote = currentChars >= MAX_MEMORY_CHARS
|
|
1681
1586
|
? ` (budget-capped at ~${Math.round(MAX_MEMORY_CHARS / 1000)}K chars)`
|
|
@@ -1855,8 +1760,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1855
1760
|
|
|
1856
1761
|
if (shouldExtract(triggerState, usage.tokens ?? 0, config, consecutiveExtractionFailures)) {
|
|
1857
1762
|
activeExtractionPromise = (async () => {
|
|
1858
|
-
if (!store || triggerState.isRunning) return;
|
|
1763
|
+
if (!store || triggerState.isRunning || backgroundTaskRunning) return;
|
|
1859
1764
|
triggerState.isRunning = true;
|
|
1765
|
+
backgroundTaskRunning = true;
|
|
1860
1766
|
try {
|
|
1861
1767
|
await runExtractionCycle(ctx, config);
|
|
1862
1768
|
const usage = ctx.getContextUsage();
|
|
@@ -1869,6 +1775,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1869
1775
|
consecutiveExtractionFailures++;
|
|
1870
1776
|
} finally {
|
|
1871
1777
|
triggerState.isRunning = false;
|
|
1778
|
+
backgroundTaskRunning = false;
|
|
1872
1779
|
}
|
|
1873
1780
|
})();
|
|
1874
1781
|
activeExtractionPromise.catch(() => {}).finally(() => { activeExtractionPromise = null; });
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct HTTP LLM chat — zero subprocess overhead.
|
|
3
|
+
*
|
|
4
|
+
* Replaces spawnExtraction() which launched a full Omegon runtime just to
|
|
5
|
+
* make a single chat completion. Supports Anthropic, OpenAI, and Ollama
|
|
6
|
+
* APIs directly via fetch().
|
|
7
|
+
*
|
|
8
|
+
* Provider auto-detection: resolves from model name prefix + available API keys.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Types
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface ChatRequest {
|
|
16
|
+
model: string;
|
|
17
|
+
systemPrompt: string;
|
|
18
|
+
userMessage: string;
|
|
19
|
+
maxTokens?: number;
|
|
20
|
+
temperature?: number;
|
|
21
|
+
timeout?: number;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ChatResult {
|
|
26
|
+
content: string;
|
|
27
|
+
provider: "anthropic" | "openai" | "ollama";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Provider detection
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
const ANTHROPIC_PREFIXES = ["claude-"];
|
|
35
|
+
const OPENAI_PREFIXES = ["gpt-", "o1-", "o3-", "o4-"];
|
|
36
|
+
|
|
37
|
+
type Provider = "anthropic" | "openai" | "ollama";
|
|
38
|
+
|
|
39
|
+
function detectProvider(model: string): Provider {
|
|
40
|
+
for (const p of ANTHROPIC_PREFIXES) {
|
|
41
|
+
if (model.startsWith(p)) return "anthropic";
|
|
42
|
+
}
|
|
43
|
+
for (const p of OPENAI_PREFIXES) {
|
|
44
|
+
if (model.startsWith(p)) return "openai";
|
|
45
|
+
}
|
|
46
|
+
// Everything else assumed local/Ollama
|
|
47
|
+
return "ollama";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getApiKey(provider: Provider): string | null {
|
|
51
|
+
switch (provider) {
|
|
52
|
+
case "anthropic":
|
|
53
|
+
return process.env.ANTHROPIC_API_KEY?.trim() || null;
|
|
54
|
+
case "openai":
|
|
55
|
+
return process.env.OPENAI_API_KEY?.trim() || null;
|
|
56
|
+
case "ollama":
|
|
57
|
+
return null; // No key needed
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getBaseUrl(provider: Provider): string {
|
|
62
|
+
switch (provider) {
|
|
63
|
+
case "anthropic":
|
|
64
|
+
return process.env.ANTHROPIC_BASE_URL?.trim() || "https://api.anthropic.com";
|
|
65
|
+
case "openai":
|
|
66
|
+
return process.env.OPENAI_BASE_URL?.trim() || "https://api.openai.com";
|
|
67
|
+
case "ollama":
|
|
68
|
+
return process.env.OLLAMA_HOST?.trim() || process.env.LOCAL_INFERENCE_URL?.trim() || "http://localhost:11434";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Provider-specific chat implementations
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
async function chatAnthropic(
|
|
77
|
+
baseUrl: string,
|
|
78
|
+
apiKey: string,
|
|
79
|
+
req: ChatRequest,
|
|
80
|
+
signal: AbortSignal,
|
|
81
|
+
): Promise<string> {
|
|
82
|
+
const resp = await fetch(`${baseUrl}/v1/messages`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
"x-api-key": apiKey,
|
|
87
|
+
"anthropic-version": "2023-06-01",
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
model: req.model,
|
|
91
|
+
max_tokens: req.maxTokens ?? 2048,
|
|
92
|
+
temperature: req.temperature ?? 0.2,
|
|
93
|
+
system: req.systemPrompt,
|
|
94
|
+
messages: [{ role: "user", content: req.userMessage }],
|
|
95
|
+
}),
|
|
96
|
+
signal,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!resp.ok) {
|
|
100
|
+
const body = await resp.text().catch(() => "");
|
|
101
|
+
throw new Error(`Anthropic API ${resp.status}: ${body.slice(0, 200)}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const data = await resp.json() as {
|
|
105
|
+
content?: Array<{ type: string; text?: string }>;
|
|
106
|
+
};
|
|
107
|
+
const text = data.content?.find(b => b.type === "text")?.text?.trim();
|
|
108
|
+
if (!text) throw new Error("Anthropic returned empty content");
|
|
109
|
+
return text;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function chatOpenAI(
|
|
113
|
+
baseUrl: string,
|
|
114
|
+
apiKey: string,
|
|
115
|
+
req: ChatRequest,
|
|
116
|
+
signal: AbortSignal,
|
|
117
|
+
): Promise<string> {
|
|
118
|
+
const resp = await fetch(`${baseUrl}/v1/chat/completions`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
model: req.model,
|
|
126
|
+
max_tokens: req.maxTokens ?? 2048,
|
|
127
|
+
temperature: req.temperature ?? 0.2,
|
|
128
|
+
messages: [
|
|
129
|
+
{ role: "system", content: req.systemPrompt },
|
|
130
|
+
{ role: "user", content: req.userMessage },
|
|
131
|
+
],
|
|
132
|
+
}),
|
|
133
|
+
signal,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!resp.ok) {
|
|
137
|
+
const body = await resp.text().catch(() => "");
|
|
138
|
+
throw new Error(`OpenAI API ${resp.status}: ${body.slice(0, 200)}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const data = await resp.json() as {
|
|
142
|
+
choices?: Array<{ message?: { content?: string } }>;
|
|
143
|
+
};
|
|
144
|
+
const text = data.choices?.[0]?.message?.content?.trim();
|
|
145
|
+
if (!text) throw new Error("OpenAI returned empty content");
|
|
146
|
+
return text;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function chatOllama(
|
|
150
|
+
baseUrl: string,
|
|
151
|
+
req: ChatRequest,
|
|
152
|
+
signal: AbortSignal,
|
|
153
|
+
): Promise<string> {
|
|
154
|
+
const resp = await fetch(`${baseUrl}/api/chat`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body: JSON.stringify({
|
|
158
|
+
model: req.model,
|
|
159
|
+
stream: false,
|
|
160
|
+
options: {
|
|
161
|
+
temperature: req.temperature ?? 0.2,
|
|
162
|
+
num_predict: req.maxTokens ?? 2048,
|
|
163
|
+
num_ctx: 32768,
|
|
164
|
+
},
|
|
165
|
+
messages: [
|
|
166
|
+
{ role: "system", content: req.systemPrompt },
|
|
167
|
+
{ role: "user", content: req.userMessage },
|
|
168
|
+
],
|
|
169
|
+
}),
|
|
170
|
+
signal,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (!resp.ok) {
|
|
174
|
+
const body = await resp.text().catch(() => "");
|
|
175
|
+
throw new Error(`Ollama API ${resp.status}: ${body.slice(0, 200)}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const data = await resp.json() as { message?: { content?: string } };
|
|
179
|
+
const text = data.message?.content?.trim();
|
|
180
|
+
if (!text) throw new Error("Ollama returned empty content");
|
|
181
|
+
return text;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Public API
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Clean model output: strip code fences and <think> blocks.
|
|
190
|
+
*/
|
|
191
|
+
export function cleanModelOutput(raw: string): string {
|
|
192
|
+
return raw
|
|
193
|
+
.replace(/^```(?:jsonl?|json)?\n?/, "")
|
|
194
|
+
.replace(/\n?```\s*$/, "")
|
|
195
|
+
.replace(/<think>[\s\S]*?<\/think>\s*/g, "")
|
|
196
|
+
.trim();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Send a chat completion to the appropriate provider.
|
|
201
|
+
* Auto-detects provider from model name. Returns cleaned content.
|
|
202
|
+
* Throws on failure (caller handles fallback).
|
|
203
|
+
*/
|
|
204
|
+
export async function chatDirect(req: ChatRequest): Promise<ChatResult> {
|
|
205
|
+
const provider = detectProvider(req.model);
|
|
206
|
+
const baseUrl = getBaseUrl(provider);
|
|
207
|
+
const timeout = req.timeout ?? 60_000;
|
|
208
|
+
|
|
209
|
+
// Build combined signal: external abort + timeout
|
|
210
|
+
const signals: AbortSignal[] = [AbortSignal.timeout(timeout)];
|
|
211
|
+
if (req.signal) signals.push(req.signal);
|
|
212
|
+
const combinedSignal = typeof AbortSignal.any === "function"
|
|
213
|
+
? AbortSignal.any(signals)
|
|
214
|
+
: signals[0]; // Node <20.3 fallback
|
|
215
|
+
|
|
216
|
+
let content: string;
|
|
217
|
+
|
|
218
|
+
if (provider === "ollama") {
|
|
219
|
+
content = await chatOllama(baseUrl, req, combinedSignal);
|
|
220
|
+
} else {
|
|
221
|
+
const apiKey = getApiKey(provider);
|
|
222
|
+
if (!apiKey) {
|
|
223
|
+
throw new Error(`No API key for ${provider} (set ${provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"})`);
|
|
224
|
+
}
|
|
225
|
+
if (provider === "anthropic") {
|
|
226
|
+
content = await chatAnthropic(baseUrl, apiKey, req, combinedSignal);
|
|
227
|
+
} else {
|
|
228
|
+
content = await chatOpenAI(baseUrl, apiKey, req, combinedSignal);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { content: cleanModelOutput(content), provider };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if a model ID is a cloud model (has API key available).
|
|
237
|
+
* Returns false for Ollama/local models.
|
|
238
|
+
*/
|
|
239
|
+
export function isCloudModel(model: string): boolean {
|
|
240
|
+
const provider = detectProvider(model);
|
|
241
|
+
if (provider === "ollama") return false;
|
|
242
|
+
return getApiKey(provider) !== null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check if any cloud provider is available (has API key configured).
|
|
247
|
+
*/
|
|
248
|
+
export function hasCloudProvider(): boolean {
|
|
249
|
+
return !!(process.env.ANTHROPIC_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim());
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get the best available cloud model for lightweight tasks.
|
|
254
|
+
* Prefers Haiku (cheapest), falls back to GPT models.
|
|
255
|
+
*/
|
|
256
|
+
export function getBudgetCloudModel(): string | null {
|
|
257
|
+
if (process.env.ANTHROPIC_API_KEY?.trim()) return "claude-haiku-4-5";
|
|
258
|
+
if (process.env.OPENAI_API_KEY?.trim()) return "gpt-4.1-mini";
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
@@ -77,9 +77,9 @@ export const DEFAULT_CONFIG: MemoryConfig = {
|
|
|
77
77
|
maxLines: 50,
|
|
78
78
|
embeddingProvider: "voyage",
|
|
79
79
|
embeddingModel: "voyage-3-lite",
|
|
80
|
-
minimumTokensToInit:
|
|
81
|
-
minimumTokensBetweenUpdate:
|
|
82
|
-
toolCallsBetweenUpdates:
|
|
80
|
+
minimumTokensToInit: 15_000,
|
|
81
|
+
minimumTokensBetweenUpdate: 10_000,
|
|
82
|
+
toolCallsBetweenUpdates: 15,
|
|
83
83
|
manualStoreThreshold: 3,
|
|
84
84
|
extractionModel: "gpt-5.3-codex-spark",
|
|
85
85
|
extractionTimeout: 60_000,
|