@loreai/core 0.10.1 → 0.11.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 (72) hide show
  1. package/dist/bun/config.d.ts +8 -0
  2. package/dist/bun/config.d.ts.map +1 -1
  3. package/dist/bun/db.d.ts.map +1 -1
  4. package/dist/bun/distillation.d.ts +74 -2
  5. package/dist/bun/distillation.d.ts.map +1 -1
  6. package/dist/bun/embedding.d.ts.map +1 -1
  7. package/dist/bun/gradient.d.ts +72 -0
  8. package/dist/bun/gradient.d.ts.map +1 -1
  9. package/dist/bun/index.d.ts +4 -2
  10. package/dist/bun/index.d.ts.map +1 -1
  11. package/dist/bun/index.js +530 -67
  12. package/dist/bun/index.js.map +4 -4
  13. package/dist/bun/prompt.d.ts +8 -2
  14. package/dist/bun/prompt.d.ts.map +1 -1
  15. package/dist/bun/temporal.d.ts +31 -0
  16. package/dist/bun/temporal.d.ts.map +1 -1
  17. package/dist/bun/types.d.ts +9 -0
  18. package/dist/bun/types.d.ts.map +1 -1
  19. package/dist/bun/worker-model.d.ts +90 -0
  20. package/dist/bun/worker-model.d.ts.map +1 -0
  21. package/dist/node/config.d.ts +8 -0
  22. package/dist/node/config.d.ts.map +1 -1
  23. package/dist/node/db.d.ts.map +1 -1
  24. package/dist/node/distillation.d.ts +74 -2
  25. package/dist/node/distillation.d.ts.map +1 -1
  26. package/dist/node/embedding.d.ts.map +1 -1
  27. package/dist/node/gradient.d.ts +72 -0
  28. package/dist/node/gradient.d.ts.map +1 -1
  29. package/dist/node/index.d.ts +4 -2
  30. package/dist/node/index.d.ts.map +1 -1
  31. package/dist/node/index.js +530 -67
  32. package/dist/node/index.js.map +4 -4
  33. package/dist/node/prompt.d.ts +8 -2
  34. package/dist/node/prompt.d.ts.map +1 -1
  35. package/dist/node/temporal.d.ts +31 -0
  36. package/dist/node/temporal.d.ts.map +1 -1
  37. package/dist/node/types.d.ts +9 -0
  38. package/dist/node/types.d.ts.map +1 -1
  39. package/dist/node/worker-model.d.ts +90 -0
  40. package/dist/node/worker-model.d.ts.map +1 -0
  41. package/dist/types/config.d.ts +8 -0
  42. package/dist/types/config.d.ts.map +1 -1
  43. package/dist/types/db.d.ts.map +1 -1
  44. package/dist/types/distillation.d.ts +74 -2
  45. package/dist/types/distillation.d.ts.map +1 -1
  46. package/dist/types/embedding.d.ts.map +1 -1
  47. package/dist/types/gradient.d.ts +72 -0
  48. package/dist/types/gradient.d.ts.map +1 -1
  49. package/dist/types/index.d.ts +4 -2
  50. package/dist/types/index.d.ts.map +1 -1
  51. package/dist/types/prompt.d.ts +8 -2
  52. package/dist/types/prompt.d.ts.map +1 -1
  53. package/dist/types/temporal.d.ts +31 -0
  54. package/dist/types/temporal.d.ts.map +1 -1
  55. package/dist/types/types.d.ts +9 -0
  56. package/dist/types/types.d.ts.map +1 -1
  57. package/dist/types/worker-model.d.ts +90 -0
  58. package/dist/types/worker-model.d.ts.map +1 -0
  59. package/package.json +1 -1
  60. package/src/config.ts +53 -6
  61. package/src/db.ts +57 -1
  62. package/src/distillation.ts +225 -28
  63. package/src/embedding.ts +7 -0
  64. package/src/gradient.ts +262 -8
  65. package/src/index.ts +16 -0
  66. package/src/lat-reader.ts +4 -4
  67. package/src/ltm.ts +17 -17
  68. package/src/prompt.ts +101 -0
  69. package/src/recall.ts +4 -4
  70. package/src/temporal.ts +41 -10
  71. package/src/types.ts +9 -0
  72. package/src/worker-model.ts +264 -0
package/dist/bun/index.js CHANGED
@@ -122,9 +122,11 @@ var require_extend = __commonJS({
122
122
  // src/temporal.ts
123
123
  var temporal_exports = {};
124
124
  __export(temporal_exports, {
125
+ CHUNK_TERMINATOR: () => CHUNK_TERMINATOR,
125
126
  bySession: () => bySession,
126
127
  count: () => count,
127
128
  markDistilled: () => markDistilled,
129
+ partsToText: () => partsToText,
128
130
  prune: () => prune,
129
131
  search: () => search2,
130
132
  searchScored: () => searchScored,
@@ -419,6 +421,58 @@ var MIGRATIONS = [
419
421
  to_id TEXT NOT NULL REFERENCES knowledge(id) ON DELETE CASCADE,
420
422
  PRIMARY KEY (from_id, to_id)
421
423
  );
424
+ `,
425
+ `
426
+ -- Version 11: F3b -- unambiguous chunk terminator in temporal_messages.content.
427
+ --
428
+ -- Pre-F3b, partsToText joined chunks with a newline. Tool-output payloads
429
+ -- can contain newlines too, so the boundary between a tool envelope and a
430
+ -- following plain-text or [reasoning] chunk was structurally ambiguous.
431
+ -- This caused two known limitations in the F3 distill-input truncator:
432
+ -- trailing text could be swallowed into a tool payload, and embedded
433
+ -- literal envelope strings inside a payload (e.g. when reading AGENTS.md)
434
+ -- could fabricate fake boundaries.
435
+ --
436
+ -- F3b switches the chunk separator to newline plus ASCII Unit Separator
437
+ -- (char 31). The Unit Separator is non-word so FTS5's unicode61 tokenizer
438
+ -- ignores it (zero BM25 impact). New rows are written via the post-F3b
439
+ -- partsToText. Existing rows are rewritten in place by the UPDATE below,
440
+ -- which uses pure SQL replace() to inject the Unit Separator after every
441
+ -- legacy chunk-prefix sequence -- the same boundary patterns the legacy
442
+ -- F3 reader was already trying to recover.
443
+ --
444
+ -- Trade-off (acceptable): any embedded legacy chunk-prefix sequence
445
+ -- inside a tool payload becomes a structural boundary post-migration.
446
+ -- This matches what the legacy F3 reader did at read-time anyway, baked
447
+ -- into the row permanently. The migration runs once per machine.
448
+ --
449
+ -- Idempotent: a row that already contains the Unit Separator before a
450
+ -- chunk prefix no longer matches the search literal (the separator
451
+ -- interposes), so re-running the UPDATE is a no-op for migrated rows.
452
+ -- (Important: migrate() in db.ts runs each migration via database.exec()
453
+ -- with no explicit BEGIN/COMMIT around the whole loop. SQLite makes this
454
+ -- single UPDATE statement atomic per-statement, so partial progress on
455
+ -- crash is safe to retry thanks to the idempotency above.)
456
+ --
457
+ -- char(10) = newline, char(31) = Unit Separator. SQLite has no native
458
+ -- regex, but two nested replace() calls on the literal prefixes are
459
+ -- sufficient because both legacy chunk prefixes match at line-start.
460
+ --
461
+ -- Each row UPDATE fires the temporal_fts_update trigger once; because
462
+ -- the Unit Separator is a non-word character, the re-indexed content
463
+ -- tokenizes identically -- net no-op for FTS scoring.
464
+ UPDATE temporal_messages
465
+ SET content = replace(
466
+ replace(
467
+ content,
468
+ char(10) || '[tool:',
469
+ char(10) || char(31) || '[tool:'
470
+ ),
471
+ char(10) || '[reasoning] ',
472
+ char(10) || char(31) || '[reasoning] '
473
+ )
474
+ WHERE content LIKE '%' || char(10) || '[tool:%'
475
+ OR content LIKE '%' || char(10) || '[reasoning] %';
422
476
  `
423
477
  ];
424
478
  function dataDir() {
@@ -442,6 +496,7 @@ function db() {
442
496
  instance = new Database(path);
443
497
  instance.exec("PRAGMA journal_mode = WAL");
444
498
  instance.exec("PRAGMA foreign_keys = ON");
499
+ instance.exec("PRAGMA busy_timeout = 5000");
445
500
  instance.exec("PRAGMA auto_vacuum = INCREMENTAL");
446
501
  migrate(instance);
447
502
  return instance;
@@ -10748,12 +10803,27 @@ EXACT NUMBERS: When two segments report different numbers for what seems like th
10748
10803
 
10749
10804
  EARLY-SESSION CONTENT: Bug fixes, code changes, and decisions from the start of a session are just as important as later work. Never drop them just because the segment is short or old. If the first segment contains a specific bug fix with file paths and root cause, it MUST survive into the reflection.
10750
10805
 
10806
+ ANCHORED UPDATES: If the prompt includes a <previous-meta-summary> block, treat it as the current consolidated state. Update it using the NEW observation segments \u2014 preserve still-true details, remove stale details, and merge in new facts. Keep the same section headings. Do NOT re-derive unchanged sections verbatim unless the new segments contradict them.
10807
+
10751
10808
  Output ONLY an <observations> block with the consolidated observations.`;
10752
- function recursiveUser(distillations) {
10809
+ function recursiveUser(distillations, previousMeta) {
10753
10810
  const entries = distillations.map(
10754
10811
  (d, i) => `Segment ${i + 1}:
10755
10812
  ${d.observations}`
10756
10813
  );
10814
+ if (previousMeta) {
10815
+ return `Update the anchored meta-summary below using the NEW observation segments. Preserve still-true details, remove stale details, and merge in new facts. Keep the same section headings.
10816
+
10817
+ <previous-meta-summary>
10818
+ ${previousMeta}
10819
+ </previous-meta-summary>
10820
+
10821
+ ---
10822
+
10823
+ New observation segments to merge (chronological order):
10824
+
10825
+ ${entries.join("\n\n---\n\n")}`;
10826
+ }
10757
10827
  return `Observation segments to consolidate (chronological order):
10758
10828
 
10759
10829
  ${entries.join("\n\n---\n\n")}`;
@@ -10908,6 +10978,61 @@ function formatDistillations(distillations) {
10908
10978
  }
10909
10979
  return sections.join("\n\n");
10910
10980
  }
10981
+ var COMPACT_SUMMARY_TEMPLATE = `Output exactly this Markdown structure. Keep every section in this order, even when empty (use "(none)").
10982
+
10983
+ ---
10984
+ ## Goal
10985
+ - [single-sentence task summary]
10986
+
10987
+ ## Constraints & Preferences
10988
+ - [user constraints, preferences, specs, or "(none)"]
10989
+
10990
+ ## Progress
10991
+ ### Done
10992
+ - [completed work or "(none)"]
10993
+
10994
+ ### In Progress
10995
+ - [current work or "(none)"]
10996
+
10997
+ ### Blocked
10998
+ - [blockers or "(none)"]
10999
+
11000
+ ## Key Decisions
11001
+ - [decision and why, or "(none)"]
11002
+
11003
+ ## Next Steps
11004
+ - [ordered next actions or "(none)"]
11005
+
11006
+ ## Critical Context
11007
+ - [important technical facts, errors, open questions, or "(none)"]
11008
+
11009
+ ## Relevant Files
11010
+ - [file or directory path: why it matters, or "(none)"]
11011
+ ---
11012
+
11013
+ Rules:
11014
+ - Keep every section, even when empty.
11015
+ - Use terse bullets, not prose paragraphs.
11016
+ - Preserve exact file paths, commands, error strings, and identifiers when known.
11017
+ - Do not mention the summary process or that context was compacted.
11018
+ - End with "I'm ready to continue." on its own line after the closing "---".`;
11019
+ function buildCompactPrompt(input) {
11020
+ const distillSection = input.hasDistillations ? "Lore has pre-computed chunked summaries of the session history (injected above as context). Use them as the authoritative source \u2014 do NOT re-read raw conversation messages that conflict with them.\n\n" : "";
11021
+ const anchorBlock = input.previousSummary ? `A prior compacted summary exists for this session. Update it using the conversation history above: preserve still-true details, remove stale details, and merge in new facts. Keep every section in place.
11022
+
11023
+ <previous-summary>
11024
+ ${input.previousSummary}
11025
+ </previous-summary>
11026
+
11027
+ ` : "";
11028
+ const knowledgeBlock = input.knowledge ? `
11029
+ ${input.knowledge}
11030
+ ` : "";
11031
+ return `You are producing a compacted session summary for an AI coding agent. This summary will be the ONLY context available in the next part of the conversation.
11032
+
11033
+ ${distillSection}${anchorBlock}${COMPACT_SUMMARY_TEMPLATE}
11034
+ ${knowledgeBlock}`;
11035
+ }
10911
11036
  function estimateTokens(text4) {
10912
11037
  return Math.ceil(text4.length / 3);
10913
11038
  }
@@ -11160,6 +11285,7 @@ function isToolPart(p3) {
11160
11285
  function estimate(text4) {
11161
11286
  return Math.ceil(text4.length / 3);
11162
11287
  }
11288
+ var CHUNK_TERMINATOR = "";
11163
11289
  function partsToText(parts) {
11164
11290
  const chunks = [];
11165
11291
  for (const part of parts) {
@@ -11169,7 +11295,7 @@ function partsToText(parts) {
11169
11295
  else if (isToolPart(part) && part.state.status === "completed")
11170
11296
  chunks.push(`[tool:${part.tool}] ${part.state.output}`);
11171
11297
  }
11172
- return sanitizeSurrogates(chunks.join("\n"));
11298
+ return sanitizeSurrogates(chunks.join("\n" + CHUNK_TERMINATOR));
11173
11299
  }
11174
11300
  function messageMetadata(info2, parts) {
11175
11301
  const meta3 = {};
@@ -11248,11 +11374,11 @@ function search2(input) {
11248
11374
  const limit = input.limit ?? 20;
11249
11375
  const q = ftsQuery(input.query);
11250
11376
  if (q === EMPTY_QUERY) return [];
11251
- const ftsSQL = input.sessionID ? `SELECT m.* FROM temporal_messages m
11252
- JOIN temporal_fts f ON m.rowid = f.rowid
11377
+ const ftsSQL = input.sessionID ? `SELECT m.* FROM temporal_fts f
11378
+ CROSS JOIN temporal_messages m ON m.rowid = f.rowid
11253
11379
  WHERE f.content MATCH ? AND m.project_id = ? AND m.session_id = ?
11254
- ORDER BY rank LIMIT ?` : `SELECT m.* FROM temporal_messages m
11255
- JOIN temporal_fts f ON m.rowid = f.rowid
11380
+ ORDER BY rank LIMIT ?` : `SELECT m.* FROM temporal_fts f
11381
+ CROSS JOIN temporal_messages m ON m.rowid = f.rowid
11256
11382
  WHERE f.content MATCH ? AND m.project_id = ?
11257
11383
  ORDER BY rank LIMIT ?`;
11258
11384
  const params = input.sessionID ? [q, pid, input.sessionID, limit] : [q, pid, limit];
@@ -11277,11 +11403,11 @@ function searchScored(input) {
11277
11403
  const limit = input.limit ?? 20;
11278
11404
  const q = ftsQuery(input.query);
11279
11405
  if (q === EMPTY_QUERY) return [];
11280
- const ftsSQL = input.sessionID ? `SELECT m.*, rank FROM temporal_messages m
11281
- JOIN temporal_fts f ON m.rowid = f.rowid
11406
+ const ftsSQL = input.sessionID ? `SELECT m.*, rank FROM temporal_fts f
11407
+ CROSS JOIN temporal_messages m ON m.rowid = f.rowid
11282
11408
  WHERE f.content MATCH ? AND m.project_id = ? AND m.session_id = ?
11283
- ORDER BY rank LIMIT ?` : `SELECT m.*, rank FROM temporal_messages m
11284
- JOIN temporal_fts f ON m.rowid = f.rowid
11409
+ ORDER BY rank LIMIT ?` : `SELECT m.*, rank FROM temporal_fts f
11410
+ CROSS JOIN temporal_messages m ON m.rowid = f.rowid
11285
11411
  WHERE f.content MATCH ? AND m.project_id = ?
11286
11412
  ORDER BY rank LIMIT ?`;
11287
11413
  const params = input.sessionID ? [q, pid, input.sessionID, limit] : [q, pid, limit];
@@ -25483,18 +25609,63 @@ var LoreConfig = external_exports.object({
25483
25609
  providerID: external_exports.string(),
25484
25610
  modelID: external_exports.string()
25485
25611
  }).optional(),
25612
+ /** Explicit worker model override. When set, all background workers (distillation,
25613
+ * curation, query expansion) use this model instead of the session model or the
25614
+ * auto-selected worker model. Bypasses dynamic worker model selection entirely. */
25615
+ workerModel: external_exports.object({
25616
+ providerID: external_exports.string(),
25617
+ modelID: external_exports.string()
25618
+ }).optional(),
25486
25619
  budget: external_exports.object({
25487
25620
  distilled: external_exports.number().min(0.05).max(0.5).default(0.25),
25488
25621
  raw: external_exports.number().min(0.1).max(0.7).default(0.4),
25489
25622
  output: external_exports.number().min(0.1).max(0.5).default(0.25),
25490
- /** Max fraction of usable context reserved for LTM system-prompt injection. Default: 0.10 (10%). */
25491
- ltm: external_exports.number().min(0.02).max(0.3).default(0.1)
25492
- }).default({ distilled: 0.25, raw: 0.4, output: 0.25, ltm: 0.1 }),
25623
+ /** Max fraction of usable context reserved for LTM system-prompt injection. Default: 0.05 (5%). */
25624
+ ltm: external_exports.number().min(0.02).max(0.3).default(0.05),
25625
+ /** Per-turn cache-read cost target in dollars. Controls when layer 0 (full
25626
+ * passthrough) escalates to layer 1 (compressed). The cap is derived as:
25627
+ * maxLayer0Tokens = max(target / model.cost.cache.read, 40K).
25628
+ * Lower = cheaper but earlier compression. Default: 0.10. Set to 0 to
25629
+ * disable cost-aware capping (use the model's full context). */
25630
+ targetCacheReadCostPerTurn: external_exports.number().min(0).default(0.1),
25631
+ /** Direct override for the layer-0 token cap. When set, bypasses the
25632
+ * cost-aware formula from targetCacheReadCostPerTurn. 0 = disabled
25633
+ * (no cap, use full context). Default: undefined (use cost-aware auto). */
25634
+ maxLayer0Tokens: external_exports.number().min(0).optional()
25635
+ }).default({ distilled: 0.25, raw: 0.4, output: 0.25, ltm: 0.05, targetCacheReadCostPerTurn: 0.1 }),
25636
+ /**
25637
+ * Cold-cache idle-resume handling.
25638
+ *
25639
+ * Anthropic's prompt cache evicts entries after ~5 min (default tier) /
25640
+ * ~1 hour (extended tier). When a session resumes after the eviction window,
25641
+ * Lore's byte-identity caches (distilled prefix, raw window pin, LTM block)
25642
+ * are providing no value because the underlying provider cache is already
25643
+ * cold. On detection, Lore refreshes those caches so the next turn can
25644
+ * produce a better-fitting window without paying a cache cost it would
25645
+ * otherwise be trying to preserve. Reasoning blocks are NOT touched —
25646
+ * Anthropic's April 23 postmortem identified dropping reasoning blocks as
25647
+ * the root cause of forgetfulness/repetition.
25648
+ *
25649
+ * `idleResumeMinutes` is the threshold in minutes. Default 60 — matches
25650
+ * Anthropic's extended-cache eviction window, conservative across providers.
25651
+ * Set to 0 to disable the feature.
25652
+ */
25653
+ idleResumeMinutes: external_exports.number().min(0).max(24 * 60).default(60),
25493
25654
  distillation: external_exports.object({
25494
- minMessages: external_exports.number().min(3).default(8),
25495
- maxSegment: external_exports.number().min(5).default(50),
25496
- metaThreshold: external_exports.number().min(3).default(10)
25497
- }).default({ minMessages: 8, maxSegment: 50, metaThreshold: 10 }),
25655
+ minMessages: external_exports.number().min(3).default(5),
25656
+ maxSegment: external_exports.number().min(5).default(30),
25657
+ metaThreshold: external_exports.number().min(3).default(10),
25658
+ /** Max chars per tool output when rendering temporal messages for distillation input.
25659
+ * Outputs longer than this are replaced with a compact annotation preserving line
25660
+ * count, error signals, and file paths. Default: 2000 (matches upstream OpenCode's
25661
+ * TOOL_OUTPUT_MAX_CHARS during compaction). Set to 0 to disable. */
25662
+ toolOutputMaxChars: external_exports.number().min(0).default(2e3)
25663
+ }).default({
25664
+ minMessages: 5,
25665
+ maxSegment: 30,
25666
+ metaThreshold: 10,
25667
+ toolOutputMaxChars: 2e3
25668
+ }),
25498
25669
  knowledge: external_exports.object({
25499
25670
  /** Set to false to disable long-term knowledge storage and system-prompt injection.
25500
25671
  * Conversation recall (temporal search, distillation search) and context management
@@ -25599,6 +25770,7 @@ __export(embedding_exports, {
25599
25770
  vectorSearch: () => vectorSearch,
25600
25771
  vectorSearchDistillations: () => vectorSearchDistillations
25601
25772
  });
25773
+ var EMBED_TIMEOUT_MS = 1e4;
25602
25774
  var VOYAGE_API_URL = "https://api.voyageai.com/v1/embeddings";
25603
25775
  var VoyageProvider = class {
25604
25776
  maxBatchSize = 128;
@@ -25622,7 +25794,8 @@ var VoyageProvider = class {
25622
25794
  model: this.model,
25623
25795
  input_type: inputType,
25624
25796
  output_dimension: this.dimensions
25625
- })
25797
+ }),
25798
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS)
25626
25799
  });
25627
25800
  if (!res.ok) {
25628
25801
  const body = await res.text().catch(() => "");
@@ -25658,7 +25831,8 @@ var OpenAIProvider = class {
25658
25831
  "Content-Type": "application/json",
25659
25832
  Authorization: `Bearer ${this.apiKey}`
25660
25833
  },
25661
- body: JSON.stringify(body)
25834
+ body: JSON.stringify(body),
25835
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS)
25662
25836
  });
25663
25837
  if (!res.ok) {
25664
25838
  const responseBody = await res.text().catch(() => "");
@@ -26041,8 +26215,8 @@ function searchScored2(input) {
26041
26215
  const ftsSQL = `SELECT s.id, s.project_id, s.file, s.heading, s.depth, s.content,
26042
26216
  s.content_hash, s.first_paragraph, s.updated_at,
26043
26217
  bm25(lat_sections_fts, 6.0, 2.0) as rank
26044
- FROM lat_sections s
26045
- JOIN lat_sections_fts f ON s.rowid = f.rowid
26218
+ FROM lat_sections_fts f
26219
+ CROSS JOIN lat_sections s ON s.rowid = f.rowid
26046
26220
  WHERE lat_sections_fts MATCH ?
26047
26221
  AND s.project_id = ?
26048
26222
  ORDER BY rank LIMIT ?`;
@@ -26068,8 +26242,8 @@ function scoreForSession(projectPath, sessionContext, maxTokens) {
26068
26242
  `SELECT s.id, s.project_id, s.file, s.heading, s.depth, s.content,
26069
26243
  s.content_hash, s.first_paragraph, s.updated_at,
26070
26244
  bm25(lat_sections_fts, 6.0, 2.0) as rank
26071
- FROM lat_sections s
26072
- JOIN lat_sections_fts f ON s.rowid = f.rowid
26245
+ FROM lat_sections_fts f
26246
+ CROSS JOIN lat_sections s ON s.rowid = f.rowid
26073
26247
  WHERE lat_sections_fts MATCH ?
26074
26248
  AND s.project_id = ?
26075
26249
  ORDER BY rank`
@@ -26198,10 +26372,10 @@ function scoreEntriesFTS(sessionContext) {
26198
26372
  try {
26199
26373
  const results = db().query(
26200
26374
  `SELECT k.id, bm25(knowledge_fts, ?, ?, ?) as rank
26201
- FROM knowledge k
26202
- JOIN knowledge_fts f ON k.rowid = f.rowid
26203
- WHERE knowledge_fts MATCH ?
26204
- AND k.confidence > 0.2`
26375
+ FROM knowledge_fts f
26376
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26377
+ WHERE knowledge_fts MATCH ?
26378
+ AND k.confidence > 0.2`
26205
26379
  ).all(title, content3, category, q);
26206
26380
  if (!results.length) return /* @__PURE__ */ new Map();
26207
26381
  const ranks = results.map((r) => r.rank);
@@ -26335,13 +26509,13 @@ function search3(input) {
26335
26509
  const q = ftsQuery(input.query);
26336
26510
  if (q === EMPTY_QUERY) return [];
26337
26511
  const pid = input.projectPath ? ensureProject(input.projectPath) : null;
26338
- const ftsSQL = pid ? `SELECT ${KNOWLEDGE_COLS_K} FROM knowledge k
26339
- JOIN knowledge_fts f ON k.rowid = f.rowid
26512
+ const ftsSQL = pid ? `SELECT ${KNOWLEDGE_COLS_K} FROM knowledge_fts f
26513
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26340
26514
  WHERE knowledge_fts MATCH ?
26341
26515
  AND (k.project_id = ? OR k.project_id IS NULL OR k.cross_project = 1)
26342
26516
  AND k.confidence > 0.2
26343
- ORDER BY bm25(knowledge_fts, ?, ?, ?) LIMIT ?` : `SELECT ${KNOWLEDGE_COLS_K} FROM knowledge k
26344
- JOIN knowledge_fts f ON k.rowid = f.rowid
26517
+ ORDER BY bm25(knowledge_fts, ?, ?, ?) LIMIT ?` : `SELECT ${KNOWLEDGE_COLS_K} FROM knowledge_fts f
26518
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26345
26519
  WHERE knowledge_fts MATCH ?
26346
26520
  AND k.confidence > 0.2
26347
26521
  ORDER BY bm25(knowledge_fts, ?, ?, ?) LIMIT ?`;
@@ -26368,13 +26542,13 @@ function searchScored3(input) {
26368
26542
  if (q === EMPTY_QUERY) return [];
26369
26543
  const pid = input.projectPath ? ensureProject(input.projectPath) : null;
26370
26544
  const { title, content: content3, category } = ftsWeights();
26371
- const ftsSQL = pid ? `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge k
26372
- JOIN knowledge_fts f ON k.rowid = f.rowid
26545
+ const ftsSQL = pid ? `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge_fts f
26546
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26373
26547
  WHERE knowledge_fts MATCH ?
26374
26548
  AND (k.project_id = ? OR k.project_id IS NULL OR k.cross_project = 1)
26375
26549
  AND k.confidence > 0.2
26376
- ORDER BY rank LIMIT ?` : `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge k
26377
- JOIN knowledge_fts f ON k.rowid = f.rowid
26550
+ ORDER BY rank LIMIT ?` : `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge_fts f
26551
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26378
26552
  WHERE knowledge_fts MATCH ?
26379
26553
  AND k.confidence > 0.2
26380
26554
  ORDER BY rank LIMIT ?`;
@@ -26396,8 +26570,8 @@ function searchScoredOtherProjects(input) {
26396
26570
  if (q === EMPTY_QUERY) return [];
26397
26571
  const excludePid = ensureProject(input.excludeProjectPath);
26398
26572
  const { title, content: content3, category } = ftsWeights();
26399
- const ftsSQL = `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge k
26400
- JOIN knowledge_fts f ON k.rowid = f.rowid
26573
+ const ftsSQL = `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge_fts f
26574
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26401
26575
  WHERE knowledge_fts MATCH ?
26402
26576
  AND k.project_id IS NOT NULL
26403
26577
  AND k.project_id != ?
@@ -26530,8 +26704,8 @@ function check2(projectPath) {
26530
26704
  try {
26531
26705
  const { title, content: content3, category } = config2().search.ftsWeights;
26532
26706
  const matches = db().query(
26533
- `SELECT k.id, k.title FROM knowledge k
26534
- JOIN knowledge_fts f ON k.rowid = f.rowid
26707
+ `SELECT k.id, k.title FROM knowledge_fts f
26708
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26535
26709
  WHERE knowledge_fts MATCH ?
26536
26710
  AND k.id != ?
26537
26711
  AND k.confidence > 0.2
@@ -26565,9 +26739,13 @@ function check2(projectPath) {
26565
26739
  // src/distillation.ts
26566
26740
  var distillation_exports = {};
26567
26741
  __export(distillation_exports, {
26742
+ latestMetaObservations: () => latestMetaObservations,
26568
26743
  loadForSession: () => loadForSession,
26744
+ messagesToText: () => messagesToText,
26745
+ metaDistill: () => metaDistill,
26569
26746
  parseSourceIds: () => parseSourceIds,
26570
26747
  run: () => run,
26748
+ truncateToolOutputsInContent: () => truncateToolOutputsInContent,
26571
26749
  workerSessionIDs: () => workerSessionIDs
26572
26750
  });
26573
26751
 
@@ -26592,6 +26770,8 @@ function estimateMessage(msg) {
26592
26770
  }
26593
26771
  var contextLimit = 2e5;
26594
26772
  var outputReserved = 32e3;
26773
+ var maxLayer0Tokens = 0;
26774
+ var MIN_LAYER0_FLOOR = 4e4;
26595
26775
  var FIRST_TURN_OVERHEAD = 15e3;
26596
26776
  var calibratedOverhead = null;
26597
26777
  function makeSessionState() {
@@ -26605,7 +26785,11 @@ function makeSessionState() {
26605
26785
  forceMinLayer: 0,
26606
26786
  lastTransformEstimate: 0,
26607
26787
  prefixCache: null,
26608
- rawWindowCache: null
26788
+ rawWindowCache: null,
26789
+ lastTurnAt: 0,
26790
+ cameOutOfIdle: false,
26791
+ consecutiveHighLayer: 0,
26792
+ lastPrefixHash: ""
26609
26793
  };
26610
26794
  }
26611
26795
  var sessionStates = /* @__PURE__ */ new Map();
@@ -26618,11 +26802,36 @@ function getSessionState(sessionID) {
26618
26802
  }
26619
26803
  return state;
26620
26804
  }
26805
+ function onIdleResume(sessionID, thresholdMs, now = Date.now()) {
26806
+ if (thresholdMs <= 0) return { triggered: false };
26807
+ const state = getSessionState(sessionID);
26808
+ if (state.lastTurnAt === 0) return { triggered: false };
26809
+ const idleMs = now - state.lastTurnAt;
26810
+ if (idleMs < thresholdMs) return { triggered: false };
26811
+ state.prefixCache = null;
26812
+ state.rawWindowCache = null;
26813
+ state.cameOutOfIdle = true;
26814
+ return { triggered: true, idleMs };
26815
+ }
26816
+ function consumeCameOutOfIdle(sessionID) {
26817
+ const state = sessionStates.get(sessionID);
26818
+ if (!state || !state.cameOutOfIdle) return false;
26819
+ state.cameOutOfIdle = false;
26820
+ return true;
26821
+ }
26621
26822
  var ltmTokens = 0;
26622
26823
  function setModelLimits(limits) {
26623
26824
  contextLimit = limits.context || 2e5;
26624
26825
  outputReserved = Math.min(limits.output || 32e3, 32e3);
26625
26826
  }
26827
+ function setMaxLayer0Tokens(tokens) {
26828
+ maxLayer0Tokens = Math.max(0, Math.floor(tokens));
26829
+ }
26830
+ function computeLayer0Cap(targetCostPerTurn, cacheReadCostPerToken) {
26831
+ if (targetCostPerTurn <= 0 || cacheReadCostPerToken <= 0) return 0;
26832
+ const rawCap = Math.floor(targetCostPerTurn / cacheReadCostPerToken);
26833
+ return Math.max(rawCap, MIN_LAYER0_FLOOR);
26834
+ }
26626
26835
  function setLtmTokens(tokens) {
26627
26836
  ltmTokens = tokens;
26628
26837
  }
@@ -26667,6 +26876,19 @@ function setForceMinLayer(layer, sessionID) {
26667
26876
  }
26668
26877
  }
26669
26878
  }
26879
+ function inspectSessionState(sessionID) {
26880
+ const state = sessionStates.get(sessionID);
26881
+ if (!state) return null;
26882
+ return {
26883
+ hasPrefixCache: state.prefixCache !== null,
26884
+ hasRawWindowCache: state.rawWindowCache !== null,
26885
+ cameOutOfIdle: state.cameOutOfIdle,
26886
+ lastTurnAt: state.lastTurnAt
26887
+ };
26888
+ }
26889
+ function setLastTurnAtForTest(sessionID, ms) {
26890
+ getSessionState(sessionID).lastTurnAt = ms;
26891
+ }
26670
26892
  function loadDistillations(projectPath, sessionID) {
26671
26893
  const pid = ensureProject(projectPath);
26672
26894
  const query = sessionID ? "SELECT id, observations, generation, token_count, created_at, session_id FROM distillations WHERE project_id = ? AND session_id = ? AND archived = 0 ORDER BY created_at ASC" : "SELECT id, observations, generation, token_count, created_at, session_id FROM distillations WHERE project_id = ? AND archived = 0 ORDER BY created_at ASC";
@@ -26699,12 +26921,17 @@ function cleanParts(parts) {
26699
26921
  }
26700
26922
  return filtered.length > 0 ? filtered : parts;
26701
26923
  }
26924
+ var ANNOTATION_PATH_SCAN_LIMIT = 64 * 1024;
26925
+ var PATH_RE = /(?:[\w.-]+\/)+[\w.-]+\.\w{1,5}/g;
26702
26926
  function toolStripAnnotation(toolName, output) {
26703
26927
  const lines = output.split("\n").length;
26704
- const chars = output.length;
26705
26928
  const hasError = /\b(?:error|fail(?:ed|ure)?|exception|panic|traceback)\b/i.test(output);
26706
- const paths = output.match(/(?:[\w.-]+\/)+[\w.-]+\.\w{1,5}/g);
26707
- const uniquePaths = paths ? [...new Set(paths)].slice(0, 5) : [];
26929
+ let uniquePaths = [];
26930
+ if (output.indexOf("/") !== -1) {
26931
+ const pathScan = output.length > ANNOTATION_PATH_SCAN_LIMIT ? output.slice(0, ANNOTATION_PATH_SCAN_LIMIT) : output;
26932
+ const paths = pathScan.match(PATH_RE);
26933
+ if (paths) uniquePaths = [...new Set(paths)].slice(0, 5);
26934
+ }
26708
26935
  let annotation = `[output omitted \u2014 ${toolName}: ${lines} lines`;
26709
26936
  if (hasError) annotation += ", contained errors";
26710
26937
  if (uniquePaths.length > 0) annotation += `, paths: ${uniquePaths.join(", ")}`;
@@ -27135,7 +27362,11 @@ function transformInner(input) {
27135
27362
  expectedInput = messageTokens + overhead + ltmTokens;
27136
27363
  }
27137
27364
  const layer0Input = calibrated ? expectedInput : expectedInput * UNCALIBRATED_SAFETY;
27138
- if (effectiveMinLayer === 0 && layer0Input <= maxInput) {
27365
+ let layer0Ceiling = maxLayer0Tokens > 0 ? Math.min(maxInput, maxLayer0Tokens) : maxInput;
27366
+ if (!calibrated && layer0Ceiling < maxInput) {
27367
+ layer0Ceiling = Math.floor(layer0Ceiling * 0.7);
27368
+ }
27369
+ if (effectiveMinLayer === 0 && layer0Input <= layer0Ceiling) {
27139
27370
  const messageTokens = calibrated ? expectedInput - (ltmTokens - sessState.lastKnownLtm) : expectedInput - overhead - ltmTokens;
27140
27371
  return {
27141
27372
  messages: input.messages,
@@ -27216,14 +27447,31 @@ function transformInner(input) {
27216
27447
  (sum, m) => sum + estimateMessage(m),
27217
27448
  0
27218
27449
  );
27219
- const nuclearRaw = input.messages.slice(-3).map((m) => ({
27450
+ const tailBudget = Math.max(2e3, Math.min(8e3, Math.floor(usable * 0.25)));
27451
+ const nuclearTurnStart = currentTurnStart(input.messages);
27452
+ const currentTurn = input.messages.slice(nuclearTurnStart).map((m) => ({
27220
27453
  info: m.info,
27221
27454
  parts: cleanParts(m.parts)
27222
27455
  }));
27223
- const nuclearRawTokens = nuclearRaw.reduce(
27456
+ const currentTurnTokens = currentTurn.reduce(
27224
27457
  (sum, m) => sum + estimateMessage(m),
27225
27458
  0
27226
27459
  );
27460
+ const olderMessages = [];
27461
+ let olderTokens = 0;
27462
+ const remaining = Math.max(0, tailBudget - currentTurnTokens);
27463
+ for (let i = nuclearTurnStart - 1; i >= 0 && olderTokens < remaining; i--) {
27464
+ const msg = input.messages[i];
27465
+ const est = estimateMessage(msg);
27466
+ if (olderTokens + est > remaining) break;
27467
+ olderMessages.unshift({
27468
+ info: msg.info,
27469
+ parts: cleanParts(msg.parts)
27470
+ });
27471
+ olderTokens += est;
27472
+ }
27473
+ const nuclearRaw = [...olderMessages, ...currentTurn];
27474
+ const nuclearRawTokens = olderTokens + currentTurnTokens;
27227
27475
  return {
27228
27476
  messages: [...nuclearPrefix, ...nuclearRaw],
27229
27477
  layer: 4,
@@ -27245,6 +27493,28 @@ function transform2(input) {
27245
27493
  state.lastTransformEstimate = result.totalTokens;
27246
27494
  state.lastLayer = result.layer;
27247
27495
  state.lastWindowMessageIDs = new Set(result.messages.map((m) => m.info.id));
27496
+ state.lastTurnAt = Date.now();
27497
+ const prefixIds = result.messages.slice(0, 5).map((m) => m.info.id).join(",");
27498
+ const prefixHash = `${result.layer}:${prefixIds}`;
27499
+ if (state.lastPrefixHash && state.lastPrefixHash !== prefixHash) {
27500
+ info(
27501
+ `cache-bust detected: session=${sid} layer=${state.lastLayer}\u2192${result.layer} msgs=${state.lastTransformedCount}\u2192${result.messages.length} prefix=${state.lastPrefixHash.slice(0, 30)}\u2192${prefixHash.slice(0, 30)}`
27502
+ );
27503
+ }
27504
+ state.lastPrefixHash = prefixHash;
27505
+ if (result.layer >= 2) {
27506
+ state.consecutiveHighLayer++;
27507
+ if (state.consecutiveHighLayer === 3) {
27508
+ info(
27509
+ `session ${sid} has been at gradient layer ${result.layer}+ for 3 consecutive turns. Consider running /compact to reset the context window.`
27510
+ );
27511
+ }
27512
+ } else {
27513
+ state.consecutiveHighLayer = 0;
27514
+ }
27515
+ info(
27516
+ `gradient: session=${sid} layer=${result.layer} tokens=${result.totalTokens} (distilled=${result.distilledTokens} raw=${result.rawTokens}) usable=${result.usable} cap=${maxLayer0Tokens || "off"}`
27517
+ );
27248
27518
  }
27249
27519
  return result;
27250
27520
  }
@@ -27338,8 +27608,39 @@ function formatTime(ms) {
27338
27608
  const m = d.getMinutes().toString().padStart(2, "0");
27339
27609
  return `${h3}:${m}`;
27340
27610
  }
27341
- function messagesToText(messages) {
27342
- return messages.map((m) => `[${m.role}] (${formatTime(m.created_at)}) ${m.content}`).join("\n\n");
27611
+ var CHUNK_SEPARATOR = "\n" + CHUNK_TERMINATOR;
27612
+ function truncateToolOutputsInContent(content3, maxChars) {
27613
+ if (maxChars <= 0 || content3.length === 0) return content3;
27614
+ if (content3.indexOf(CHUNK_TERMINATOR) === -1) {
27615
+ return truncateSingleChunk(content3, maxChars);
27616
+ }
27617
+ const chunks = content3.split(CHUNK_SEPARATOR);
27618
+ let anyToolChunk = false;
27619
+ for (const c of chunks) {
27620
+ if (c.startsWith("[tool:")) {
27621
+ anyToolChunk = true;
27622
+ break;
27623
+ }
27624
+ }
27625
+ if (!anyToolChunk) return content3;
27626
+ const out = chunks.map((chunk) => truncateSingleChunk(chunk, maxChars));
27627
+ return out.join(CHUNK_SEPARATOR);
27628
+ }
27629
+ function truncateSingleChunk(chunk, maxChars) {
27630
+ if (!chunk.startsWith("[tool:")) return chunk;
27631
+ const closeBracket = chunk.indexOf("] ");
27632
+ if (closeBracket < 0) return chunk;
27633
+ const toolName = chunk.slice(6, closeBracket);
27634
+ const payload = chunk.slice(closeBracket + 2);
27635
+ if (payload.length <= maxChars) return chunk;
27636
+ return `[tool:${toolName}] ${toolStripAnnotation(toolName, payload)}`;
27637
+ }
27638
+ function messagesToText(messages, toolOutputMaxChars) {
27639
+ const cap = toolOutputMaxChars ?? config2().distillation.toolOutputMaxChars;
27640
+ return messages.map((m) => {
27641
+ const body = m.role === "user" ? m.content : truncateToolOutputsInContent(m.content, cap);
27642
+ return `[${m.role}] (${formatTime(m.created_at)}) ${body}`;
27643
+ }).join("\n\n");
27343
27644
  }
27344
27645
  function parseDistillationResult(text4) {
27345
27646
  const match = text4.match(/<observations>([\s\S]*?)<\/observations>/i);
@@ -27354,6 +27655,19 @@ function latestObservations(projectPath, sessionID) {
27354
27655
  ).get(pid, sessionID);
27355
27656
  return row?.observations || void 0;
27356
27657
  }
27658
+ function latestMetaObservations(projectPath, sessionID) {
27659
+ return latestMeta(projectPath, sessionID)?.observations;
27660
+ }
27661
+ function latestMeta(projectPath, sessionID) {
27662
+ const pid = ensureProject(projectPath);
27663
+ const row = db().query(
27664
+ `SELECT observations, generation FROM distillations
27665
+ WHERE project_id = ? AND session_id = ? AND generation > 0
27666
+ ORDER BY generation DESC, created_at DESC LIMIT 1`
27667
+ ).get(pid, sessionID);
27668
+ if (!row || !row.observations) return void 0;
27669
+ return row;
27670
+ }
27357
27671
  function parseSourceIds(raw) {
27358
27672
  try {
27359
27673
  const parsed = JSON.parse(raw);
@@ -27363,11 +27677,10 @@ function parseSourceIds(raw) {
27363
27677
  return [];
27364
27678
  }
27365
27679
  }
27366
- function loadForSession(projectPath, sessionID) {
27680
+ function loadForSession(projectPath, sessionID, includeArchived = false) {
27367
27681
  const pid = ensureProject(projectPath);
27368
- const rows = db().query(
27369
- "SELECT id, project_id, session_id, observations, source_ids, generation, token_count, created_at FROM distillations WHERE project_id = ? AND session_id = ? ORDER BY created_at ASC"
27370
- ).all(pid, sessionID);
27682
+ const sql = includeArchived ? "SELECT id, project_id, session_id, observations, source_ids, generation, token_count, created_at FROM distillations WHERE project_id = ? AND session_id = ? ORDER BY created_at ASC" : "SELECT id, project_id, session_id, observations, source_ids, generation, token_count, created_at FROM distillations WHERE project_id = ? AND session_id = ? AND archived = 0 ORDER BY created_at ASC";
27683
+ const rows = db().query(sql).all(pid, sessionID);
27371
27684
  return rows.map((r) => ({
27372
27685
  ...r,
27373
27686
  source_ids: parseSourceIds(r.source_ids)
@@ -27532,8 +27845,13 @@ async function distillSegment(input) {
27532
27845
  }
27533
27846
  async function metaDistill(input) {
27534
27847
  const existing = loadGen0(input.projectPath, input.sessionID);
27535
- if (existing.length < 3) return null;
27536
- const userContent = recursiveUser(existing);
27848
+ const priorMeta = latestMeta(input.projectPath, input.sessionID);
27849
+ if (priorMeta) {
27850
+ if (existing.length === 0) return null;
27851
+ } else {
27852
+ if (existing.length < 3) return null;
27853
+ }
27854
+ const userContent = recursiveUser(existing, priorMeta?.observations);
27537
27855
  const model = input.model ?? config2().model;
27538
27856
  const responseText = await input.llm.prompt(
27539
27857
  RECURSIVE_SYSTEM,
@@ -27543,19 +27861,30 @@ async function metaDistill(input) {
27543
27861
  if (!responseText) return null;
27544
27862
  const result = parseDistillationResult(responseText);
27545
27863
  if (!result) return null;
27546
- const maxGen = Math.max(...existing.map((d) => d.generation));
27864
+ const maxGen = Math.max(
27865
+ ...existing.map((d) => d.generation),
27866
+ priorMeta?.generation ?? 0
27867
+ );
27547
27868
  const allSourceIDs = existing.flatMap((d) => d.source_ids);
27548
- const metaId = storeDistillation({
27549
- projectPath: input.projectPath,
27550
- sessionID: input.sessionID,
27551
- observations: result.observations,
27552
- sourceIDs: allSourceIDs,
27553
- generation: maxGen + 1
27554
- });
27869
+ let metaId;
27870
+ db().exec("BEGIN IMMEDIATE");
27871
+ try {
27872
+ metaId = storeDistillation({
27873
+ projectPath: input.projectPath,
27874
+ sessionID: input.sessionID,
27875
+ observations: result.observations,
27876
+ sourceIDs: allSourceIDs,
27877
+ generation: maxGen + 1
27878
+ });
27879
+ archiveDistillations(existing.map((d) => d.id));
27880
+ db().exec("COMMIT");
27881
+ } catch (e) {
27882
+ db().exec("ROLLBACK");
27883
+ throw e;
27884
+ }
27555
27885
  if (isAvailable()) {
27556
27886
  embedDistillation(metaId, result.observations);
27557
27887
  }
27558
- archiveDistillations(existing.map((d) => d.id));
27559
27888
  return result;
27560
27889
  }
27561
27890
 
@@ -27714,13 +28043,13 @@ function searchDistillationsScored(input) {
27714
28043
  const q = ftsQuery(input.query);
27715
28044
  if (q === EMPTY_QUERY) return [];
27716
28045
  const ftsSQL = input.sessionID ? `SELECT d.id, d.observations, d.generation, d.created_at, d.session_id, rank
27717
- FROM distillations d
27718
- JOIN distillation_fts f ON d.rowid = f.rowid
28046
+ FROM distillation_fts f
28047
+ CROSS JOIN distillations d ON d.rowid = f.rowid
27719
28048
  WHERE distillation_fts MATCH ?
27720
28049
  AND d.project_id = ? AND d.session_id = ?
27721
28050
  ORDER BY rank LIMIT ?` : `SELECT d.id, d.observations, d.generation, d.created_at, d.session_id, rank
27722
- FROM distillations d
27723
- JOIN distillation_fts f ON d.rowid = f.rowid
28051
+ FROM distillation_fts f
28052
+ CROSS JOIN distillations d ON d.rowid = f.rowid
27724
28053
  WHERE distillation_fts MATCH ?
27725
28054
  AND d.project_id = ?
27726
28055
  ORDER BY rank LIMIT ?`;
@@ -28163,7 +28492,130 @@ function importFromFile(input) {
28163
28492
  }
28164
28493
  }
28165
28494
  }
28495
+
28496
+ // src/worker-model.ts
28497
+ var worker_model_exports = {};
28498
+ __export(worker_model_exports, {
28499
+ WORKER_JUDGE_SYSTEM: () => WORKER_JUDGE_SYSTEM,
28500
+ computeModelFingerprint: () => computeModelFingerprint,
28501
+ getValidatedWorkerModel: () => getValidatedWorkerModel,
28502
+ isValidationStale: () => isValidationStale,
28503
+ parseJudgeScore: () => parseJudgeScore,
28504
+ resolveWorkerModel: () => resolveWorkerModel,
28505
+ selectWorkerCandidates: () => selectWorkerCandidates,
28506
+ storeValidatedWorkerModel: () => storeValidatedWorkerModel,
28507
+ structuralCheck: () => structuralCheck,
28508
+ workerJudgeUser: () => workerJudgeUser
28509
+ });
28510
+ var KV_PREFIX = "lore:worker_model:";
28511
+ function selectWorkerCandidates(sessionModel, providerModels) {
28512
+ const eligible = providerModels.filter(
28513
+ (m) => m.providerID === sessionModel.providerID && m.status === "active" && m.capabilities.input.text
28514
+ );
28515
+ if (eligible.length === 0) return [];
28516
+ const sorted = [...eligible].sort((a, b) => a.cost.input - b.cost.input);
28517
+ const cheapest = sorted[0];
28518
+ const belowSession = sorted.filter((m) => m.cost.input < sessionModel.cost.input).pop();
28519
+ const candidates = /* @__PURE__ */ new Map();
28520
+ candidates.set(cheapest.id, cheapest);
28521
+ if (belowSession && belowSession.id !== cheapest.id) {
28522
+ candidates.set(belowSession.id, belowSession);
28523
+ }
28524
+ if (cheapest.id === sessionModel.id || cheapest.cost.input >= sessionModel.cost.input) {
28525
+ return [cheapest];
28526
+ }
28527
+ return [...candidates.values()];
28528
+ }
28529
+ function computeModelFingerprint(providerID, sessionModelID, activeModelIDs) {
28530
+ const sorted = [...activeModelIDs].sort();
28531
+ return sha256(
28532
+ JSON.stringify({ providerID, sessionModelID, modelIDs: sorted })
28533
+ );
28534
+ }
28535
+ function getValidatedWorkerModel(providerID) {
28536
+ const row = db().query("SELECT value FROM kv_meta WHERE key = ?").get(`${KV_PREFIX}${providerID}`);
28537
+ if (!row) return null;
28538
+ try {
28539
+ return JSON.parse(row.value);
28540
+ } catch {
28541
+ return null;
28542
+ }
28543
+ }
28544
+ function storeValidatedWorkerModel(result) {
28545
+ const key = `${KV_PREFIX}${result.providerID}`;
28546
+ const value = JSON.stringify(result);
28547
+ db().query(
28548
+ "INSERT INTO kv_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?"
28549
+ ).run(key, value, value);
28550
+ }
28551
+ function isValidationStale(stored, currentFingerprint) {
28552
+ if (!stored) return true;
28553
+ return stored.fingerprint !== currentFingerprint;
28554
+ }
28555
+ function structuralCheck(candidateObservations, referenceObservations) {
28556
+ if (candidateObservations == null || candidateObservations.length === 0) {
28557
+ return { passed: false, observationCount: 0, tokenCount: 0, reason: candidateObservations === null ? "parse_failed" : "empty" };
28558
+ }
28559
+ const countObs = (text4) => text4.split("\n").filter((l) => l.trim().length > 0).length;
28560
+ const refCount = countObs(referenceObservations);
28561
+ const candCount = countObs(candidateObservations);
28562
+ const candTokens = Math.ceil(candidateObservations.length / 3);
28563
+ if (refCount > 0 && (candCount < refCount * 0.5 || candCount > refCount * 1.5)) {
28564
+ return {
28565
+ passed: false,
28566
+ observationCount: candCount,
28567
+ tokenCount: candTokens,
28568
+ reason: `observation_count_${candCount}_vs_ref_${refCount}`
28569
+ };
28570
+ }
28571
+ const refTokens = Math.ceil(referenceObservations.length / 3);
28572
+ if (candTokens === 0) {
28573
+ return { passed: false, observationCount: candCount, tokenCount: candTokens, reason: "empty" };
28574
+ }
28575
+ if (refTokens > 0 && candTokens > refTokens * 3) {
28576
+ return {
28577
+ passed: false,
28578
+ observationCount: candCount,
28579
+ tokenCount: candTokens,
28580
+ reason: `token_count_${candTokens}_vs_ref_${refTokens}_3x`
28581
+ };
28582
+ }
28583
+ return { passed: true, observationCount: candCount, tokenCount: candTokens };
28584
+ }
28585
+ var WORKER_JUDGE_SYSTEM = `You are evaluating distillation quality. You will be given a REFERENCE distillation (produced by a capable model) and a CANDIDATE distillation (produced by a cheaper model) of the same conversation segment.
28586
+
28587
+ Rate the candidate on a scale of 1-5:
28588
+ 5 = Captures all key facts and decisions, equivalent to reference
28589
+ 4 = Captures most facts, minor omissions
28590
+ 3 = Captures the essential facts, some detail loss acceptable
28591
+ 2 = Missing important facts or technical details
28592
+ 1 = Significantly incomplete or inaccurate
28593
+
28594
+ Respond with ONLY a single digit (1-5).`;
28595
+ function workerJudgeUser(reference, candidate) {
28596
+ return `<reference>
28597
+ ${reference}
28598
+ </reference>
28599
+
28600
+ <candidate>
28601
+ ${candidate}
28602
+ </candidate>`;
28603
+ }
28604
+ function parseJudgeScore(response) {
28605
+ const match = response.trim().match(/^([1-5])/);
28606
+ if (!match) return null;
28607
+ return parseInt(match[1], 10);
28608
+ }
28609
+ function resolveWorkerModel(providerID, configWorkerModel, configModel) {
28610
+ if (configWorkerModel) return configWorkerModel;
28611
+ const validated = getValidatedWorkerModel(providerID);
28612
+ if (validated) {
28613
+ return { providerID: validated.providerID, modelID: validated.modelID };
28614
+ }
28615
+ return configModel;
28616
+ }
28166
28617
  export {
28618
+ COMPACT_SUMMARY_TEMPLATE,
28167
28619
  CONSOLIDATION_SYSTEM,
28168
28620
  CURATOR_SYSTEM,
28169
28621
  DISTILLATION_SYSTEM,
@@ -28172,10 +28624,14 @@ export {
28172
28624
  RECALL_PARAM_DESCRIPTIONS,
28173
28625
  RECALL_TOOL_DESCRIPTION,
28174
28626
  RECURSIVE_SYSTEM,
28627
+ WORKER_JUDGE_SYSTEM,
28628
+ buildCompactPrompt,
28175
28629
  calibrate,
28176
28630
  close,
28631
+ computeLayer0Cap,
28177
28632
  config2 as config,
28178
28633
  consolidationUser,
28634
+ consumeCameOutOfIdle,
28179
28635
  curator_exports as curator,
28180
28636
  curatorUser,
28181
28637
  db,
@@ -28197,6 +28653,7 @@ export {
28197
28653
  h,
28198
28654
  importFromFile,
28199
28655
  inline,
28656
+ inspectSessionState,
28200
28657
  isFirstRun,
28201
28658
  isReasoningPart,
28202
28659
  isTextPart,
@@ -28211,6 +28668,7 @@ export {
28211
28668
  ltm_exports as ltm,
28212
28669
  needsUrgentDistillation,
28213
28670
  normalize,
28671
+ onIdleResume,
28214
28672
  p,
28215
28673
  projectId,
28216
28674
  projectName,
@@ -28222,15 +28680,20 @@ export {
28222
28680
  saveForceMinLayer,
28223
28681
  serialize,
28224
28682
  setForceMinLayer,
28683
+ setLastTurnAtForTest,
28225
28684
  setLtmTokens,
28685
+ setMaxLayer0Tokens,
28226
28686
  setModelLimits,
28227
28687
  shouldImport,
28228
28688
  strong2 as strong,
28229
28689
  t,
28230
28690
  temporal_exports as temporal,
28691
+ toolStripAnnotation,
28231
28692
  transform2 as transform,
28232
28693
  ul,
28233
28694
  unescapeMarkdown,
28695
+ workerJudgeUser,
28696
+ worker_model_exports as workerModel,
28234
28697
  workerSessionIDs
28235
28698
  };
28236
28699
  //# sourceMappingURL=index.js.map