@loreai/core 0.10.2 → 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
@@ -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,
@@ -436,6 +438,58 @@ var MIGRATIONS = [
436
438
  to_id TEXT NOT NULL REFERENCES knowledge(id) ON DELETE CASCADE,
437
439
  PRIMARY KEY (from_id, to_id)
438
440
  );
441
+ `,
442
+ `
443
+ -- Version 11: F3b -- unambiguous chunk terminator in temporal_messages.content.
444
+ --
445
+ -- Pre-F3b, partsToText joined chunks with a newline. Tool-output payloads
446
+ -- can contain newlines too, so the boundary between a tool envelope and a
447
+ -- following plain-text or [reasoning] chunk was structurally ambiguous.
448
+ -- This caused two known limitations in the F3 distill-input truncator:
449
+ -- trailing text could be swallowed into a tool payload, and embedded
450
+ -- literal envelope strings inside a payload (e.g. when reading AGENTS.md)
451
+ -- could fabricate fake boundaries.
452
+ --
453
+ -- F3b switches the chunk separator to newline plus ASCII Unit Separator
454
+ -- (char 31). The Unit Separator is non-word so FTS5's unicode61 tokenizer
455
+ -- ignores it (zero BM25 impact). New rows are written via the post-F3b
456
+ -- partsToText. Existing rows are rewritten in place by the UPDATE below,
457
+ -- which uses pure SQL replace() to inject the Unit Separator after every
458
+ -- legacy chunk-prefix sequence -- the same boundary patterns the legacy
459
+ -- F3 reader was already trying to recover.
460
+ --
461
+ -- Trade-off (acceptable): any embedded legacy chunk-prefix sequence
462
+ -- inside a tool payload becomes a structural boundary post-migration.
463
+ -- This matches what the legacy F3 reader did at read-time anyway, baked
464
+ -- into the row permanently. The migration runs once per machine.
465
+ --
466
+ -- Idempotent: a row that already contains the Unit Separator before a
467
+ -- chunk prefix no longer matches the search literal (the separator
468
+ -- interposes), so re-running the UPDATE is a no-op for migrated rows.
469
+ -- (Important: migrate() in db.ts runs each migration via database.exec()
470
+ -- with no explicit BEGIN/COMMIT around the whole loop. SQLite makes this
471
+ -- single UPDATE statement atomic per-statement, so partial progress on
472
+ -- crash is safe to retry thanks to the idempotency above.)
473
+ --
474
+ -- char(10) = newline, char(31) = Unit Separator. SQLite has no native
475
+ -- regex, but two nested replace() calls on the literal prefixes are
476
+ -- sufficient because both legacy chunk prefixes match at line-start.
477
+ --
478
+ -- Each row UPDATE fires the temporal_fts_update trigger once; because
479
+ -- the Unit Separator is a non-word character, the re-indexed content
480
+ -- tokenizes identically -- net no-op for FTS scoring.
481
+ UPDATE temporal_messages
482
+ SET content = replace(
483
+ replace(
484
+ content,
485
+ char(10) || '[tool:',
486
+ char(10) || char(31) || '[tool:'
487
+ ),
488
+ char(10) || '[reasoning] ',
489
+ char(10) || char(31) || '[reasoning] '
490
+ )
491
+ WHERE content LIKE '%' || char(10) || '[tool:%'
492
+ OR content LIKE '%' || char(10) || '[reasoning] %';
439
493
  `
440
494
  ];
441
495
  function dataDir() {
@@ -459,6 +513,7 @@ function db() {
459
513
  instance = new Database(path);
460
514
  instance.exec("PRAGMA journal_mode = WAL");
461
515
  instance.exec("PRAGMA foreign_keys = ON");
516
+ instance.exec("PRAGMA busy_timeout = 5000");
462
517
  instance.exec("PRAGMA auto_vacuum = INCREMENTAL");
463
518
  migrate(instance);
464
519
  return instance;
@@ -10765,12 +10820,27 @@ EXACT NUMBERS: When two segments report different numbers for what seems like th
10765
10820
 
10766
10821
  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.
10767
10822
 
10823
+ 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.
10824
+
10768
10825
  Output ONLY an <observations> block with the consolidated observations.`;
10769
- function recursiveUser(distillations) {
10826
+ function recursiveUser(distillations, previousMeta) {
10770
10827
  const entries = distillations.map(
10771
10828
  (d, i) => `Segment ${i + 1}:
10772
10829
  ${d.observations}`
10773
10830
  );
10831
+ if (previousMeta) {
10832
+ 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.
10833
+
10834
+ <previous-meta-summary>
10835
+ ${previousMeta}
10836
+ </previous-meta-summary>
10837
+
10838
+ ---
10839
+
10840
+ New observation segments to merge (chronological order):
10841
+
10842
+ ${entries.join("\n\n---\n\n")}`;
10843
+ }
10774
10844
  return `Observation segments to consolidate (chronological order):
10775
10845
 
10776
10846
  ${entries.join("\n\n---\n\n")}`;
@@ -10925,6 +10995,61 @@ function formatDistillations(distillations) {
10925
10995
  }
10926
10996
  return sections.join("\n\n");
10927
10997
  }
10998
+ var COMPACT_SUMMARY_TEMPLATE = `Output exactly this Markdown structure. Keep every section in this order, even when empty (use "(none)").
10999
+
11000
+ ---
11001
+ ## Goal
11002
+ - [single-sentence task summary]
11003
+
11004
+ ## Constraints & Preferences
11005
+ - [user constraints, preferences, specs, or "(none)"]
11006
+
11007
+ ## Progress
11008
+ ### Done
11009
+ - [completed work or "(none)"]
11010
+
11011
+ ### In Progress
11012
+ - [current work or "(none)"]
11013
+
11014
+ ### Blocked
11015
+ - [blockers or "(none)"]
11016
+
11017
+ ## Key Decisions
11018
+ - [decision and why, or "(none)"]
11019
+
11020
+ ## Next Steps
11021
+ - [ordered next actions or "(none)"]
11022
+
11023
+ ## Critical Context
11024
+ - [important technical facts, errors, open questions, or "(none)"]
11025
+
11026
+ ## Relevant Files
11027
+ - [file or directory path: why it matters, or "(none)"]
11028
+ ---
11029
+
11030
+ Rules:
11031
+ - Keep every section, even when empty.
11032
+ - Use terse bullets, not prose paragraphs.
11033
+ - Preserve exact file paths, commands, error strings, and identifiers when known.
11034
+ - Do not mention the summary process or that context was compacted.
11035
+ - End with "I'm ready to continue." on its own line after the closing "---".`;
11036
+ function buildCompactPrompt(input) {
11037
+ 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" : "";
11038
+ 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.
11039
+
11040
+ <previous-summary>
11041
+ ${input.previousSummary}
11042
+ </previous-summary>
11043
+
11044
+ ` : "";
11045
+ const knowledgeBlock = input.knowledge ? `
11046
+ ${input.knowledge}
11047
+ ` : "";
11048
+ 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.
11049
+
11050
+ ${distillSection}${anchorBlock}${COMPACT_SUMMARY_TEMPLATE}
11051
+ ${knowledgeBlock}`;
11052
+ }
10928
11053
  function estimateTokens(text4) {
10929
11054
  return Math.ceil(text4.length / 3);
10930
11055
  }
@@ -11177,6 +11302,7 @@ function isToolPart(p3) {
11177
11302
  function estimate(text4) {
11178
11303
  return Math.ceil(text4.length / 3);
11179
11304
  }
11305
+ var CHUNK_TERMINATOR = "";
11180
11306
  function partsToText(parts) {
11181
11307
  const chunks = [];
11182
11308
  for (const part of parts) {
@@ -11186,7 +11312,7 @@ function partsToText(parts) {
11186
11312
  else if (isToolPart(part) && part.state.status === "completed")
11187
11313
  chunks.push(`[tool:${part.tool}] ${part.state.output}`);
11188
11314
  }
11189
- return sanitizeSurrogates(chunks.join("\n"));
11315
+ return sanitizeSurrogates(chunks.join("\n" + CHUNK_TERMINATOR));
11190
11316
  }
11191
11317
  function messageMetadata(info2, parts) {
11192
11318
  const meta3 = {};
@@ -11265,11 +11391,11 @@ function search2(input) {
11265
11391
  const limit = input.limit ?? 20;
11266
11392
  const q = ftsQuery(input.query);
11267
11393
  if (q === EMPTY_QUERY) return [];
11268
- const ftsSQL = input.sessionID ? `SELECT m.* FROM temporal_messages m
11269
- JOIN temporal_fts f ON m.rowid = f.rowid
11394
+ const ftsSQL = input.sessionID ? `SELECT m.* FROM temporal_fts f
11395
+ CROSS JOIN temporal_messages m ON m.rowid = f.rowid
11270
11396
  WHERE f.content MATCH ? AND m.project_id = ? AND m.session_id = ?
11271
- ORDER BY rank LIMIT ?` : `SELECT m.* FROM temporal_messages m
11272
- JOIN temporal_fts f ON m.rowid = f.rowid
11397
+ ORDER BY rank LIMIT ?` : `SELECT m.* FROM temporal_fts f
11398
+ CROSS JOIN temporal_messages m ON m.rowid = f.rowid
11273
11399
  WHERE f.content MATCH ? AND m.project_id = ?
11274
11400
  ORDER BY rank LIMIT ?`;
11275
11401
  const params = input.sessionID ? [q, pid, input.sessionID, limit] : [q, pid, limit];
@@ -11294,11 +11420,11 @@ function searchScored(input) {
11294
11420
  const limit = input.limit ?? 20;
11295
11421
  const q = ftsQuery(input.query);
11296
11422
  if (q === EMPTY_QUERY) return [];
11297
- const ftsSQL = input.sessionID ? `SELECT m.*, rank FROM temporal_messages m
11298
- JOIN temporal_fts f ON m.rowid = f.rowid
11423
+ const ftsSQL = input.sessionID ? `SELECT m.*, rank FROM temporal_fts f
11424
+ CROSS JOIN temporal_messages m ON m.rowid = f.rowid
11299
11425
  WHERE f.content MATCH ? AND m.project_id = ? AND m.session_id = ?
11300
- ORDER BY rank LIMIT ?` : `SELECT m.*, rank FROM temporal_messages m
11301
- JOIN temporal_fts f ON m.rowid = f.rowid
11426
+ ORDER BY rank LIMIT ?` : `SELECT m.*, rank FROM temporal_fts f
11427
+ CROSS JOIN temporal_messages m ON m.rowid = f.rowid
11302
11428
  WHERE f.content MATCH ? AND m.project_id = ?
11303
11429
  ORDER BY rank LIMIT ?`;
11304
11430
  const params = input.sessionID ? [q, pid, input.sessionID, limit] : [q, pid, limit];
@@ -25500,18 +25626,63 @@ var LoreConfig = external_exports.object({
25500
25626
  providerID: external_exports.string(),
25501
25627
  modelID: external_exports.string()
25502
25628
  }).optional(),
25629
+ /** Explicit worker model override. When set, all background workers (distillation,
25630
+ * curation, query expansion) use this model instead of the session model or the
25631
+ * auto-selected worker model. Bypasses dynamic worker model selection entirely. */
25632
+ workerModel: external_exports.object({
25633
+ providerID: external_exports.string(),
25634
+ modelID: external_exports.string()
25635
+ }).optional(),
25503
25636
  budget: external_exports.object({
25504
25637
  distilled: external_exports.number().min(0.05).max(0.5).default(0.25),
25505
25638
  raw: external_exports.number().min(0.1).max(0.7).default(0.4),
25506
25639
  output: external_exports.number().min(0.1).max(0.5).default(0.25),
25507
- /** Max fraction of usable context reserved for LTM system-prompt injection. Default: 0.10 (10%). */
25508
- ltm: external_exports.number().min(0.02).max(0.3).default(0.1)
25509
- }).default({ distilled: 0.25, raw: 0.4, output: 0.25, ltm: 0.1 }),
25640
+ /** Max fraction of usable context reserved for LTM system-prompt injection. Default: 0.05 (5%). */
25641
+ ltm: external_exports.number().min(0.02).max(0.3).default(0.05),
25642
+ /** Per-turn cache-read cost target in dollars. Controls when layer 0 (full
25643
+ * passthrough) escalates to layer 1 (compressed). The cap is derived as:
25644
+ * maxLayer0Tokens = max(target / model.cost.cache.read, 40K).
25645
+ * Lower = cheaper but earlier compression. Default: 0.10. Set to 0 to
25646
+ * disable cost-aware capping (use the model's full context). */
25647
+ targetCacheReadCostPerTurn: external_exports.number().min(0).default(0.1),
25648
+ /** Direct override for the layer-0 token cap. When set, bypasses the
25649
+ * cost-aware formula from targetCacheReadCostPerTurn. 0 = disabled
25650
+ * (no cap, use full context). Default: undefined (use cost-aware auto). */
25651
+ maxLayer0Tokens: external_exports.number().min(0).optional()
25652
+ }).default({ distilled: 0.25, raw: 0.4, output: 0.25, ltm: 0.05, targetCacheReadCostPerTurn: 0.1 }),
25653
+ /**
25654
+ * Cold-cache idle-resume handling.
25655
+ *
25656
+ * Anthropic's prompt cache evicts entries after ~5 min (default tier) /
25657
+ * ~1 hour (extended tier). When a session resumes after the eviction window,
25658
+ * Lore's byte-identity caches (distilled prefix, raw window pin, LTM block)
25659
+ * are providing no value because the underlying provider cache is already
25660
+ * cold. On detection, Lore refreshes those caches so the next turn can
25661
+ * produce a better-fitting window without paying a cache cost it would
25662
+ * otherwise be trying to preserve. Reasoning blocks are NOT touched —
25663
+ * Anthropic's April 23 postmortem identified dropping reasoning blocks as
25664
+ * the root cause of forgetfulness/repetition.
25665
+ *
25666
+ * `idleResumeMinutes` is the threshold in minutes. Default 60 — matches
25667
+ * Anthropic's extended-cache eviction window, conservative across providers.
25668
+ * Set to 0 to disable the feature.
25669
+ */
25670
+ idleResumeMinutes: external_exports.number().min(0).max(24 * 60).default(60),
25510
25671
  distillation: external_exports.object({
25511
- minMessages: external_exports.number().min(3).default(8),
25512
- maxSegment: external_exports.number().min(5).default(50),
25513
- metaThreshold: external_exports.number().min(3).default(10)
25514
- }).default({ minMessages: 8, maxSegment: 50, metaThreshold: 10 }),
25672
+ minMessages: external_exports.number().min(3).default(5),
25673
+ maxSegment: external_exports.number().min(5).default(30),
25674
+ metaThreshold: external_exports.number().min(3).default(10),
25675
+ /** Max chars per tool output when rendering temporal messages for distillation input.
25676
+ * Outputs longer than this are replaced with a compact annotation preserving line
25677
+ * count, error signals, and file paths. Default: 2000 (matches upstream OpenCode's
25678
+ * TOOL_OUTPUT_MAX_CHARS during compaction). Set to 0 to disable. */
25679
+ toolOutputMaxChars: external_exports.number().min(0).default(2e3)
25680
+ }).default({
25681
+ minMessages: 5,
25682
+ maxSegment: 30,
25683
+ metaThreshold: 10,
25684
+ toolOutputMaxChars: 2e3
25685
+ }),
25515
25686
  knowledge: external_exports.object({
25516
25687
  /** Set to false to disable long-term knowledge storage and system-prompt injection.
25517
25688
  * Conversation recall (temporal search, distillation search) and context management
@@ -25616,6 +25787,7 @@ __export(embedding_exports, {
25616
25787
  vectorSearch: () => vectorSearch,
25617
25788
  vectorSearchDistillations: () => vectorSearchDistillations
25618
25789
  });
25790
+ var EMBED_TIMEOUT_MS = 1e4;
25619
25791
  var VOYAGE_API_URL = "https://api.voyageai.com/v1/embeddings";
25620
25792
  var VoyageProvider = class {
25621
25793
  maxBatchSize = 128;
@@ -25639,7 +25811,8 @@ var VoyageProvider = class {
25639
25811
  model: this.model,
25640
25812
  input_type: inputType,
25641
25813
  output_dimension: this.dimensions
25642
- })
25814
+ }),
25815
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS)
25643
25816
  });
25644
25817
  if (!res.ok) {
25645
25818
  const body = await res.text().catch(() => "");
@@ -25675,7 +25848,8 @@ var OpenAIProvider = class {
25675
25848
  "Content-Type": "application/json",
25676
25849
  Authorization: `Bearer ${this.apiKey}`
25677
25850
  },
25678
- body: JSON.stringify(body)
25851
+ body: JSON.stringify(body),
25852
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS)
25679
25853
  });
25680
25854
  if (!res.ok) {
25681
25855
  const responseBody = await res.text().catch(() => "");
@@ -26058,8 +26232,8 @@ function searchScored2(input) {
26058
26232
  const ftsSQL = `SELECT s.id, s.project_id, s.file, s.heading, s.depth, s.content,
26059
26233
  s.content_hash, s.first_paragraph, s.updated_at,
26060
26234
  bm25(lat_sections_fts, 6.0, 2.0) as rank
26061
- FROM lat_sections s
26062
- JOIN lat_sections_fts f ON s.rowid = f.rowid
26235
+ FROM lat_sections_fts f
26236
+ CROSS JOIN lat_sections s ON s.rowid = f.rowid
26063
26237
  WHERE lat_sections_fts MATCH ?
26064
26238
  AND s.project_id = ?
26065
26239
  ORDER BY rank LIMIT ?`;
@@ -26085,8 +26259,8 @@ function scoreForSession(projectPath, sessionContext, maxTokens) {
26085
26259
  `SELECT s.id, s.project_id, s.file, s.heading, s.depth, s.content,
26086
26260
  s.content_hash, s.first_paragraph, s.updated_at,
26087
26261
  bm25(lat_sections_fts, 6.0, 2.0) as rank
26088
- FROM lat_sections s
26089
- JOIN lat_sections_fts f ON s.rowid = f.rowid
26262
+ FROM lat_sections_fts f
26263
+ CROSS JOIN lat_sections s ON s.rowid = f.rowid
26090
26264
  WHERE lat_sections_fts MATCH ?
26091
26265
  AND s.project_id = ?
26092
26266
  ORDER BY rank`
@@ -26215,10 +26389,10 @@ function scoreEntriesFTS(sessionContext) {
26215
26389
  try {
26216
26390
  const results = db().query(
26217
26391
  `SELECT k.id, bm25(knowledge_fts, ?, ?, ?) as rank
26218
- FROM knowledge k
26219
- JOIN knowledge_fts f ON k.rowid = f.rowid
26220
- WHERE knowledge_fts MATCH ?
26221
- AND k.confidence > 0.2`
26392
+ FROM knowledge_fts f
26393
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26394
+ WHERE knowledge_fts MATCH ?
26395
+ AND k.confidence > 0.2`
26222
26396
  ).all(title, content3, category, q);
26223
26397
  if (!results.length) return /* @__PURE__ */ new Map();
26224
26398
  const ranks = results.map((r) => r.rank);
@@ -26352,13 +26526,13 @@ function search3(input) {
26352
26526
  const q = ftsQuery(input.query);
26353
26527
  if (q === EMPTY_QUERY) return [];
26354
26528
  const pid = input.projectPath ? ensureProject(input.projectPath) : null;
26355
- const ftsSQL = pid ? `SELECT ${KNOWLEDGE_COLS_K} FROM knowledge k
26356
- JOIN knowledge_fts f ON k.rowid = f.rowid
26529
+ const ftsSQL = pid ? `SELECT ${KNOWLEDGE_COLS_K} FROM knowledge_fts f
26530
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26357
26531
  WHERE knowledge_fts MATCH ?
26358
26532
  AND (k.project_id = ? OR k.project_id IS NULL OR k.cross_project = 1)
26359
26533
  AND k.confidence > 0.2
26360
- ORDER BY bm25(knowledge_fts, ?, ?, ?) LIMIT ?` : `SELECT ${KNOWLEDGE_COLS_K} FROM knowledge k
26361
- JOIN knowledge_fts f ON k.rowid = f.rowid
26534
+ ORDER BY bm25(knowledge_fts, ?, ?, ?) LIMIT ?` : `SELECT ${KNOWLEDGE_COLS_K} FROM knowledge_fts f
26535
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26362
26536
  WHERE knowledge_fts MATCH ?
26363
26537
  AND k.confidence > 0.2
26364
26538
  ORDER BY bm25(knowledge_fts, ?, ?, ?) LIMIT ?`;
@@ -26385,13 +26559,13 @@ function searchScored3(input) {
26385
26559
  if (q === EMPTY_QUERY) return [];
26386
26560
  const pid = input.projectPath ? ensureProject(input.projectPath) : null;
26387
26561
  const { title, content: content3, category } = ftsWeights();
26388
- const ftsSQL = pid ? `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge k
26389
- JOIN knowledge_fts f ON k.rowid = f.rowid
26562
+ const ftsSQL = pid ? `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge_fts f
26563
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26390
26564
  WHERE knowledge_fts MATCH ?
26391
26565
  AND (k.project_id = ? OR k.project_id IS NULL OR k.cross_project = 1)
26392
26566
  AND k.confidence > 0.2
26393
- ORDER BY rank LIMIT ?` : `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge k
26394
- JOIN knowledge_fts f ON k.rowid = f.rowid
26567
+ ORDER BY rank LIMIT ?` : `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge_fts f
26568
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26395
26569
  WHERE knowledge_fts MATCH ?
26396
26570
  AND k.confidence > 0.2
26397
26571
  ORDER BY rank LIMIT ?`;
@@ -26413,8 +26587,8 @@ function searchScoredOtherProjects(input) {
26413
26587
  if (q === EMPTY_QUERY) return [];
26414
26588
  const excludePid = ensureProject(input.excludeProjectPath);
26415
26589
  const { title, content: content3, category } = ftsWeights();
26416
- const ftsSQL = `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge k
26417
- JOIN knowledge_fts f ON k.rowid = f.rowid
26590
+ const ftsSQL = `SELECT ${KNOWLEDGE_COLS_K}, bm25(knowledge_fts, ?, ?, ?) as rank FROM knowledge_fts f
26591
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26418
26592
  WHERE knowledge_fts MATCH ?
26419
26593
  AND k.project_id IS NOT NULL
26420
26594
  AND k.project_id != ?
@@ -26547,8 +26721,8 @@ function check2(projectPath) {
26547
26721
  try {
26548
26722
  const { title, content: content3, category } = config2().search.ftsWeights;
26549
26723
  const matches = db().query(
26550
- `SELECT k.id, k.title FROM knowledge k
26551
- JOIN knowledge_fts f ON k.rowid = f.rowid
26724
+ `SELECT k.id, k.title FROM knowledge_fts f
26725
+ CROSS JOIN knowledge k ON k.rowid = f.rowid
26552
26726
  WHERE knowledge_fts MATCH ?
26553
26727
  AND k.id != ?
26554
26728
  AND k.confidence > 0.2
@@ -26582,9 +26756,13 @@ function check2(projectPath) {
26582
26756
  // src/distillation.ts
26583
26757
  var distillation_exports = {};
26584
26758
  __export(distillation_exports, {
26759
+ latestMetaObservations: () => latestMetaObservations,
26585
26760
  loadForSession: () => loadForSession,
26761
+ messagesToText: () => messagesToText,
26762
+ metaDistill: () => metaDistill,
26586
26763
  parseSourceIds: () => parseSourceIds,
26587
26764
  run: () => run,
26765
+ truncateToolOutputsInContent: () => truncateToolOutputsInContent,
26588
26766
  workerSessionIDs: () => workerSessionIDs
26589
26767
  });
26590
26768
 
@@ -26609,6 +26787,8 @@ function estimateMessage(msg) {
26609
26787
  }
26610
26788
  var contextLimit = 2e5;
26611
26789
  var outputReserved = 32e3;
26790
+ var maxLayer0Tokens = 0;
26791
+ var MIN_LAYER0_FLOOR = 4e4;
26612
26792
  var FIRST_TURN_OVERHEAD = 15e3;
26613
26793
  var calibratedOverhead = null;
26614
26794
  function makeSessionState() {
@@ -26622,7 +26802,11 @@ function makeSessionState() {
26622
26802
  forceMinLayer: 0,
26623
26803
  lastTransformEstimate: 0,
26624
26804
  prefixCache: null,
26625
- rawWindowCache: null
26805
+ rawWindowCache: null,
26806
+ lastTurnAt: 0,
26807
+ cameOutOfIdle: false,
26808
+ consecutiveHighLayer: 0,
26809
+ lastPrefixHash: ""
26626
26810
  };
26627
26811
  }
26628
26812
  var sessionStates = /* @__PURE__ */ new Map();
@@ -26635,11 +26819,36 @@ function getSessionState(sessionID) {
26635
26819
  }
26636
26820
  return state;
26637
26821
  }
26822
+ function onIdleResume(sessionID, thresholdMs, now = Date.now()) {
26823
+ if (thresholdMs <= 0) return { triggered: false };
26824
+ const state = getSessionState(sessionID);
26825
+ if (state.lastTurnAt === 0) return { triggered: false };
26826
+ const idleMs = now - state.lastTurnAt;
26827
+ if (idleMs < thresholdMs) return { triggered: false };
26828
+ state.prefixCache = null;
26829
+ state.rawWindowCache = null;
26830
+ state.cameOutOfIdle = true;
26831
+ return { triggered: true, idleMs };
26832
+ }
26833
+ function consumeCameOutOfIdle(sessionID) {
26834
+ const state = sessionStates.get(sessionID);
26835
+ if (!state || !state.cameOutOfIdle) return false;
26836
+ state.cameOutOfIdle = false;
26837
+ return true;
26838
+ }
26638
26839
  var ltmTokens = 0;
26639
26840
  function setModelLimits(limits) {
26640
26841
  contextLimit = limits.context || 2e5;
26641
26842
  outputReserved = Math.min(limits.output || 32e3, 32e3);
26642
26843
  }
26844
+ function setMaxLayer0Tokens(tokens) {
26845
+ maxLayer0Tokens = Math.max(0, Math.floor(tokens));
26846
+ }
26847
+ function computeLayer0Cap(targetCostPerTurn, cacheReadCostPerToken) {
26848
+ if (targetCostPerTurn <= 0 || cacheReadCostPerToken <= 0) return 0;
26849
+ const rawCap = Math.floor(targetCostPerTurn / cacheReadCostPerToken);
26850
+ return Math.max(rawCap, MIN_LAYER0_FLOOR);
26851
+ }
26643
26852
  function setLtmTokens(tokens) {
26644
26853
  ltmTokens = tokens;
26645
26854
  }
@@ -26684,6 +26893,19 @@ function setForceMinLayer(layer, sessionID) {
26684
26893
  }
26685
26894
  }
26686
26895
  }
26896
+ function inspectSessionState(sessionID) {
26897
+ const state = sessionStates.get(sessionID);
26898
+ if (!state) return null;
26899
+ return {
26900
+ hasPrefixCache: state.prefixCache !== null,
26901
+ hasRawWindowCache: state.rawWindowCache !== null,
26902
+ cameOutOfIdle: state.cameOutOfIdle,
26903
+ lastTurnAt: state.lastTurnAt
26904
+ };
26905
+ }
26906
+ function setLastTurnAtForTest(sessionID, ms) {
26907
+ getSessionState(sessionID).lastTurnAt = ms;
26908
+ }
26687
26909
  function loadDistillations(projectPath, sessionID) {
26688
26910
  const pid = ensureProject(projectPath);
26689
26911
  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";
@@ -26716,12 +26938,17 @@ function cleanParts(parts) {
26716
26938
  }
26717
26939
  return filtered.length > 0 ? filtered : parts;
26718
26940
  }
26941
+ var ANNOTATION_PATH_SCAN_LIMIT = 64 * 1024;
26942
+ var PATH_RE = /(?:[\w.-]+\/)+[\w.-]+\.\w{1,5}/g;
26719
26943
  function toolStripAnnotation(toolName, output) {
26720
26944
  const lines = output.split("\n").length;
26721
- const chars = output.length;
26722
26945
  const hasError = /\b(?:error|fail(?:ed|ure)?|exception|panic|traceback)\b/i.test(output);
26723
- const paths = output.match(/(?:[\w.-]+\/)+[\w.-]+\.\w{1,5}/g);
26724
- const uniquePaths = paths ? [...new Set(paths)].slice(0, 5) : [];
26946
+ let uniquePaths = [];
26947
+ if (output.indexOf("/") !== -1) {
26948
+ const pathScan = output.length > ANNOTATION_PATH_SCAN_LIMIT ? output.slice(0, ANNOTATION_PATH_SCAN_LIMIT) : output;
26949
+ const paths = pathScan.match(PATH_RE);
26950
+ if (paths) uniquePaths = [...new Set(paths)].slice(0, 5);
26951
+ }
26725
26952
  let annotation = `[output omitted \u2014 ${toolName}: ${lines} lines`;
26726
26953
  if (hasError) annotation += ", contained errors";
26727
26954
  if (uniquePaths.length > 0) annotation += `, paths: ${uniquePaths.join(", ")}`;
@@ -27152,7 +27379,11 @@ function transformInner(input) {
27152
27379
  expectedInput = messageTokens + overhead + ltmTokens;
27153
27380
  }
27154
27381
  const layer0Input = calibrated ? expectedInput : expectedInput * UNCALIBRATED_SAFETY;
27155
- if (effectiveMinLayer === 0 && layer0Input <= maxInput) {
27382
+ let layer0Ceiling = maxLayer0Tokens > 0 ? Math.min(maxInput, maxLayer0Tokens) : maxInput;
27383
+ if (!calibrated && layer0Ceiling < maxInput) {
27384
+ layer0Ceiling = Math.floor(layer0Ceiling * 0.7);
27385
+ }
27386
+ if (effectiveMinLayer === 0 && layer0Input <= layer0Ceiling) {
27156
27387
  const messageTokens = calibrated ? expectedInput - (ltmTokens - sessState.lastKnownLtm) : expectedInput - overhead - ltmTokens;
27157
27388
  return {
27158
27389
  messages: input.messages,
@@ -27233,14 +27464,31 @@ function transformInner(input) {
27233
27464
  (sum, m) => sum + estimateMessage(m),
27234
27465
  0
27235
27466
  );
27236
- const nuclearRaw = input.messages.slice(-3).map((m) => ({
27467
+ const tailBudget = Math.max(2e3, Math.min(8e3, Math.floor(usable * 0.25)));
27468
+ const nuclearTurnStart = currentTurnStart(input.messages);
27469
+ const currentTurn = input.messages.slice(nuclearTurnStart).map((m) => ({
27237
27470
  info: m.info,
27238
27471
  parts: cleanParts(m.parts)
27239
27472
  }));
27240
- const nuclearRawTokens = nuclearRaw.reduce(
27473
+ const currentTurnTokens = currentTurn.reduce(
27241
27474
  (sum, m) => sum + estimateMessage(m),
27242
27475
  0
27243
27476
  );
27477
+ const olderMessages = [];
27478
+ let olderTokens = 0;
27479
+ const remaining = Math.max(0, tailBudget - currentTurnTokens);
27480
+ for (let i = nuclearTurnStart - 1; i >= 0 && olderTokens < remaining; i--) {
27481
+ const msg = input.messages[i];
27482
+ const est = estimateMessage(msg);
27483
+ if (olderTokens + est > remaining) break;
27484
+ olderMessages.unshift({
27485
+ info: msg.info,
27486
+ parts: cleanParts(msg.parts)
27487
+ });
27488
+ olderTokens += est;
27489
+ }
27490
+ const nuclearRaw = [...olderMessages, ...currentTurn];
27491
+ const nuclearRawTokens = olderTokens + currentTurnTokens;
27244
27492
  return {
27245
27493
  messages: [...nuclearPrefix, ...nuclearRaw],
27246
27494
  layer: 4,
@@ -27262,6 +27510,28 @@ function transform2(input) {
27262
27510
  state.lastTransformEstimate = result.totalTokens;
27263
27511
  state.lastLayer = result.layer;
27264
27512
  state.lastWindowMessageIDs = new Set(result.messages.map((m) => m.info.id));
27513
+ state.lastTurnAt = Date.now();
27514
+ const prefixIds = result.messages.slice(0, 5).map((m) => m.info.id).join(",");
27515
+ const prefixHash = `${result.layer}:${prefixIds}`;
27516
+ if (state.lastPrefixHash && state.lastPrefixHash !== prefixHash) {
27517
+ info(
27518
+ `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)}`
27519
+ );
27520
+ }
27521
+ state.lastPrefixHash = prefixHash;
27522
+ if (result.layer >= 2) {
27523
+ state.consecutiveHighLayer++;
27524
+ if (state.consecutiveHighLayer === 3) {
27525
+ info(
27526
+ `session ${sid} has been at gradient layer ${result.layer}+ for 3 consecutive turns. Consider running /compact to reset the context window.`
27527
+ );
27528
+ }
27529
+ } else {
27530
+ state.consecutiveHighLayer = 0;
27531
+ }
27532
+ info(
27533
+ `gradient: session=${sid} layer=${result.layer} tokens=${result.totalTokens} (distilled=${result.distilledTokens} raw=${result.rawTokens}) usable=${result.usable} cap=${maxLayer0Tokens || "off"}`
27534
+ );
27265
27535
  }
27266
27536
  return result;
27267
27537
  }
@@ -27355,8 +27625,39 @@ function formatTime(ms) {
27355
27625
  const m = d.getMinutes().toString().padStart(2, "0");
27356
27626
  return `${h3}:${m}`;
27357
27627
  }
27358
- function messagesToText(messages) {
27359
- return messages.map((m) => `[${m.role}] (${formatTime(m.created_at)}) ${m.content}`).join("\n\n");
27628
+ var CHUNK_SEPARATOR = "\n" + CHUNK_TERMINATOR;
27629
+ function truncateToolOutputsInContent(content3, maxChars) {
27630
+ if (maxChars <= 0 || content3.length === 0) return content3;
27631
+ if (content3.indexOf(CHUNK_TERMINATOR) === -1) {
27632
+ return truncateSingleChunk(content3, maxChars);
27633
+ }
27634
+ const chunks = content3.split(CHUNK_SEPARATOR);
27635
+ let anyToolChunk = false;
27636
+ for (const c of chunks) {
27637
+ if (c.startsWith("[tool:")) {
27638
+ anyToolChunk = true;
27639
+ break;
27640
+ }
27641
+ }
27642
+ if (!anyToolChunk) return content3;
27643
+ const out = chunks.map((chunk) => truncateSingleChunk(chunk, maxChars));
27644
+ return out.join(CHUNK_SEPARATOR);
27645
+ }
27646
+ function truncateSingleChunk(chunk, maxChars) {
27647
+ if (!chunk.startsWith("[tool:")) return chunk;
27648
+ const closeBracket = chunk.indexOf("] ");
27649
+ if (closeBracket < 0) return chunk;
27650
+ const toolName = chunk.slice(6, closeBracket);
27651
+ const payload = chunk.slice(closeBracket + 2);
27652
+ if (payload.length <= maxChars) return chunk;
27653
+ return `[tool:${toolName}] ${toolStripAnnotation(toolName, payload)}`;
27654
+ }
27655
+ function messagesToText(messages, toolOutputMaxChars) {
27656
+ const cap = toolOutputMaxChars ?? config2().distillation.toolOutputMaxChars;
27657
+ return messages.map((m) => {
27658
+ const body = m.role === "user" ? m.content : truncateToolOutputsInContent(m.content, cap);
27659
+ return `[${m.role}] (${formatTime(m.created_at)}) ${body}`;
27660
+ }).join("\n\n");
27360
27661
  }
27361
27662
  function parseDistillationResult(text4) {
27362
27663
  const match = text4.match(/<observations>([\s\S]*?)<\/observations>/i);
@@ -27371,6 +27672,19 @@ function latestObservations(projectPath, sessionID) {
27371
27672
  ).get(pid, sessionID);
27372
27673
  return row?.observations || void 0;
27373
27674
  }
27675
+ function latestMetaObservations(projectPath, sessionID) {
27676
+ return latestMeta(projectPath, sessionID)?.observations;
27677
+ }
27678
+ function latestMeta(projectPath, sessionID) {
27679
+ const pid = ensureProject(projectPath);
27680
+ const row = db().query(
27681
+ `SELECT observations, generation FROM distillations
27682
+ WHERE project_id = ? AND session_id = ? AND generation > 0
27683
+ ORDER BY generation DESC, created_at DESC LIMIT 1`
27684
+ ).get(pid, sessionID);
27685
+ if (!row || !row.observations) return void 0;
27686
+ return row;
27687
+ }
27374
27688
  function parseSourceIds(raw) {
27375
27689
  try {
27376
27690
  const parsed = JSON.parse(raw);
@@ -27380,11 +27694,10 @@ function parseSourceIds(raw) {
27380
27694
  return [];
27381
27695
  }
27382
27696
  }
27383
- function loadForSession(projectPath, sessionID) {
27697
+ function loadForSession(projectPath, sessionID, includeArchived = false) {
27384
27698
  const pid = ensureProject(projectPath);
27385
- const rows = db().query(
27386
- "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"
27387
- ).all(pid, sessionID);
27699
+ 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";
27700
+ const rows = db().query(sql).all(pid, sessionID);
27388
27701
  return rows.map((r) => ({
27389
27702
  ...r,
27390
27703
  source_ids: parseSourceIds(r.source_ids)
@@ -27549,8 +27862,13 @@ async function distillSegment(input) {
27549
27862
  }
27550
27863
  async function metaDistill(input) {
27551
27864
  const existing = loadGen0(input.projectPath, input.sessionID);
27552
- if (existing.length < 3) return null;
27553
- const userContent = recursiveUser(existing);
27865
+ const priorMeta = latestMeta(input.projectPath, input.sessionID);
27866
+ if (priorMeta) {
27867
+ if (existing.length === 0) return null;
27868
+ } else {
27869
+ if (existing.length < 3) return null;
27870
+ }
27871
+ const userContent = recursiveUser(existing, priorMeta?.observations);
27554
27872
  const model = input.model ?? config2().model;
27555
27873
  const responseText = await input.llm.prompt(
27556
27874
  RECURSIVE_SYSTEM,
@@ -27560,19 +27878,30 @@ async function metaDistill(input) {
27560
27878
  if (!responseText) return null;
27561
27879
  const result = parseDistillationResult(responseText);
27562
27880
  if (!result) return null;
27563
- const maxGen = Math.max(...existing.map((d) => d.generation));
27881
+ const maxGen = Math.max(
27882
+ ...existing.map((d) => d.generation),
27883
+ priorMeta?.generation ?? 0
27884
+ );
27564
27885
  const allSourceIDs = existing.flatMap((d) => d.source_ids);
27565
- const metaId = storeDistillation({
27566
- projectPath: input.projectPath,
27567
- sessionID: input.sessionID,
27568
- observations: result.observations,
27569
- sourceIDs: allSourceIDs,
27570
- generation: maxGen + 1
27571
- });
27886
+ let metaId;
27887
+ db().exec("BEGIN IMMEDIATE");
27888
+ try {
27889
+ metaId = storeDistillation({
27890
+ projectPath: input.projectPath,
27891
+ sessionID: input.sessionID,
27892
+ observations: result.observations,
27893
+ sourceIDs: allSourceIDs,
27894
+ generation: maxGen + 1
27895
+ });
27896
+ archiveDistillations(existing.map((d) => d.id));
27897
+ db().exec("COMMIT");
27898
+ } catch (e) {
27899
+ db().exec("ROLLBACK");
27900
+ throw e;
27901
+ }
27572
27902
  if (isAvailable()) {
27573
27903
  embedDistillation(metaId, result.observations);
27574
27904
  }
27575
- archiveDistillations(existing.map((d) => d.id));
27576
27905
  return result;
27577
27906
  }
27578
27907
 
@@ -27731,13 +28060,13 @@ function searchDistillationsScored(input) {
27731
28060
  const q = ftsQuery(input.query);
27732
28061
  if (q === EMPTY_QUERY) return [];
27733
28062
  const ftsSQL = input.sessionID ? `SELECT d.id, d.observations, d.generation, d.created_at, d.session_id, rank
27734
- FROM distillations d
27735
- JOIN distillation_fts f ON d.rowid = f.rowid
28063
+ FROM distillation_fts f
28064
+ CROSS JOIN distillations d ON d.rowid = f.rowid
27736
28065
  WHERE distillation_fts MATCH ?
27737
28066
  AND d.project_id = ? AND d.session_id = ?
27738
28067
  ORDER BY rank LIMIT ?` : `SELECT d.id, d.observations, d.generation, d.created_at, d.session_id, rank
27739
- FROM distillations d
27740
- JOIN distillation_fts f ON d.rowid = f.rowid
28068
+ FROM distillation_fts f
28069
+ CROSS JOIN distillations d ON d.rowid = f.rowid
27741
28070
  WHERE distillation_fts MATCH ?
27742
28071
  AND d.project_id = ?
27743
28072
  ORDER BY rank LIMIT ?`;
@@ -28180,7 +28509,130 @@ function importFromFile(input) {
28180
28509
  }
28181
28510
  }
28182
28511
  }
28512
+
28513
+ // src/worker-model.ts
28514
+ var worker_model_exports = {};
28515
+ __export(worker_model_exports, {
28516
+ WORKER_JUDGE_SYSTEM: () => WORKER_JUDGE_SYSTEM,
28517
+ computeModelFingerprint: () => computeModelFingerprint,
28518
+ getValidatedWorkerModel: () => getValidatedWorkerModel,
28519
+ isValidationStale: () => isValidationStale,
28520
+ parseJudgeScore: () => parseJudgeScore,
28521
+ resolveWorkerModel: () => resolveWorkerModel,
28522
+ selectWorkerCandidates: () => selectWorkerCandidates,
28523
+ storeValidatedWorkerModel: () => storeValidatedWorkerModel,
28524
+ structuralCheck: () => structuralCheck,
28525
+ workerJudgeUser: () => workerJudgeUser
28526
+ });
28527
+ var KV_PREFIX = "lore:worker_model:";
28528
+ function selectWorkerCandidates(sessionModel, providerModels) {
28529
+ const eligible = providerModels.filter(
28530
+ (m) => m.providerID === sessionModel.providerID && m.status === "active" && m.capabilities.input.text
28531
+ );
28532
+ if (eligible.length === 0) return [];
28533
+ const sorted = [...eligible].sort((a, b) => a.cost.input - b.cost.input);
28534
+ const cheapest = sorted[0];
28535
+ const belowSession = sorted.filter((m) => m.cost.input < sessionModel.cost.input).pop();
28536
+ const candidates = /* @__PURE__ */ new Map();
28537
+ candidates.set(cheapest.id, cheapest);
28538
+ if (belowSession && belowSession.id !== cheapest.id) {
28539
+ candidates.set(belowSession.id, belowSession);
28540
+ }
28541
+ if (cheapest.id === sessionModel.id || cheapest.cost.input >= sessionModel.cost.input) {
28542
+ return [cheapest];
28543
+ }
28544
+ return [...candidates.values()];
28545
+ }
28546
+ function computeModelFingerprint(providerID, sessionModelID, activeModelIDs) {
28547
+ const sorted = [...activeModelIDs].sort();
28548
+ return sha256(
28549
+ JSON.stringify({ providerID, sessionModelID, modelIDs: sorted })
28550
+ );
28551
+ }
28552
+ function getValidatedWorkerModel(providerID) {
28553
+ const row = db().query("SELECT value FROM kv_meta WHERE key = ?").get(`${KV_PREFIX}${providerID}`);
28554
+ if (!row) return null;
28555
+ try {
28556
+ return JSON.parse(row.value);
28557
+ } catch {
28558
+ return null;
28559
+ }
28560
+ }
28561
+ function storeValidatedWorkerModel(result) {
28562
+ const key = `${KV_PREFIX}${result.providerID}`;
28563
+ const value = JSON.stringify(result);
28564
+ db().query(
28565
+ "INSERT INTO kv_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?"
28566
+ ).run(key, value, value);
28567
+ }
28568
+ function isValidationStale(stored, currentFingerprint) {
28569
+ if (!stored) return true;
28570
+ return stored.fingerprint !== currentFingerprint;
28571
+ }
28572
+ function structuralCheck(candidateObservations, referenceObservations) {
28573
+ if (candidateObservations == null || candidateObservations.length === 0) {
28574
+ return { passed: false, observationCount: 0, tokenCount: 0, reason: candidateObservations === null ? "parse_failed" : "empty" };
28575
+ }
28576
+ const countObs = (text4) => text4.split("\n").filter((l) => l.trim().length > 0).length;
28577
+ const refCount = countObs(referenceObservations);
28578
+ const candCount = countObs(candidateObservations);
28579
+ const candTokens = Math.ceil(candidateObservations.length / 3);
28580
+ if (refCount > 0 && (candCount < refCount * 0.5 || candCount > refCount * 1.5)) {
28581
+ return {
28582
+ passed: false,
28583
+ observationCount: candCount,
28584
+ tokenCount: candTokens,
28585
+ reason: `observation_count_${candCount}_vs_ref_${refCount}`
28586
+ };
28587
+ }
28588
+ const refTokens = Math.ceil(referenceObservations.length / 3);
28589
+ if (candTokens === 0) {
28590
+ return { passed: false, observationCount: candCount, tokenCount: candTokens, reason: "empty" };
28591
+ }
28592
+ if (refTokens > 0 && candTokens > refTokens * 3) {
28593
+ return {
28594
+ passed: false,
28595
+ observationCount: candCount,
28596
+ tokenCount: candTokens,
28597
+ reason: `token_count_${candTokens}_vs_ref_${refTokens}_3x`
28598
+ };
28599
+ }
28600
+ return { passed: true, observationCount: candCount, tokenCount: candTokens };
28601
+ }
28602
+ 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.
28603
+
28604
+ Rate the candidate on a scale of 1-5:
28605
+ 5 = Captures all key facts and decisions, equivalent to reference
28606
+ 4 = Captures most facts, minor omissions
28607
+ 3 = Captures the essential facts, some detail loss acceptable
28608
+ 2 = Missing important facts or technical details
28609
+ 1 = Significantly incomplete or inaccurate
28610
+
28611
+ Respond with ONLY a single digit (1-5).`;
28612
+ function workerJudgeUser(reference, candidate) {
28613
+ return `<reference>
28614
+ ${reference}
28615
+ </reference>
28616
+
28617
+ <candidate>
28618
+ ${candidate}
28619
+ </candidate>`;
28620
+ }
28621
+ function parseJudgeScore(response) {
28622
+ const match = response.trim().match(/^([1-5])/);
28623
+ if (!match) return null;
28624
+ return parseInt(match[1], 10);
28625
+ }
28626
+ function resolveWorkerModel(providerID, configWorkerModel, configModel) {
28627
+ if (configWorkerModel) return configWorkerModel;
28628
+ const validated = getValidatedWorkerModel(providerID);
28629
+ if (validated) {
28630
+ return { providerID: validated.providerID, modelID: validated.modelID };
28631
+ }
28632
+ return configModel;
28633
+ }
28183
28634
  export {
28635
+ COMPACT_SUMMARY_TEMPLATE,
28184
28636
  CONSOLIDATION_SYSTEM,
28185
28637
  CURATOR_SYSTEM,
28186
28638
  DISTILLATION_SYSTEM,
@@ -28189,10 +28641,14 @@ export {
28189
28641
  RECALL_PARAM_DESCRIPTIONS,
28190
28642
  RECALL_TOOL_DESCRIPTION,
28191
28643
  RECURSIVE_SYSTEM,
28644
+ WORKER_JUDGE_SYSTEM,
28645
+ buildCompactPrompt,
28192
28646
  calibrate,
28193
28647
  close,
28648
+ computeLayer0Cap,
28194
28649
  config2 as config,
28195
28650
  consolidationUser,
28651
+ consumeCameOutOfIdle,
28196
28652
  curator_exports as curator,
28197
28653
  curatorUser,
28198
28654
  db,
@@ -28214,6 +28670,7 @@ export {
28214
28670
  h,
28215
28671
  importFromFile,
28216
28672
  inline,
28673
+ inspectSessionState,
28217
28674
  isFirstRun,
28218
28675
  isReasoningPart,
28219
28676
  isTextPart,
@@ -28228,6 +28685,7 @@ export {
28228
28685
  ltm_exports as ltm,
28229
28686
  needsUrgentDistillation,
28230
28687
  normalize,
28688
+ onIdleResume,
28231
28689
  p,
28232
28690
  projectId,
28233
28691
  projectName,
@@ -28239,15 +28697,20 @@ export {
28239
28697
  saveForceMinLayer,
28240
28698
  serialize,
28241
28699
  setForceMinLayer,
28700
+ setLastTurnAtForTest,
28242
28701
  setLtmTokens,
28702
+ setMaxLayer0Tokens,
28243
28703
  setModelLimits,
28244
28704
  shouldImport,
28245
28705
  strong2 as strong,
28246
28706
  t,
28247
28707
  temporal_exports as temporal,
28708
+ toolStripAnnotation,
28248
28709
  transform2 as transform,
28249
28710
  ul,
28250
28711
  unescapeMarkdown,
28712
+ workerJudgeUser,
28713
+ worker_model_exports as workerModel,
28251
28714
  workerSessionIDs
28252
28715
  };
28253
28716
  //# sourceMappingURL=index.js.map