scream-code 0.18.8 → 0.18.9

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.
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { an as handleConnectCommand } from "./dispatch-BPfUTccZ.mjs";
6
+ import { on as handleConnectCommand } from "./dispatch-BcRHd4ya.mjs";
7
7
  export { handleConnectCommand };
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  //#region src/main.ts
8
8
  try {
9
- (await import("./app-Db7f8Kja.mjs")).main();
9
+ (await import("./app-D3QT1bx1.mjs")).main();
10
10
  } catch (error) {
11
11
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
12
12
  process.exit(1);
@@ -8,6 +8,13 @@ import { n as require_base64_js, t as XMLParser } from "./XMLParser-DFAnGqkc.mjs
8
8
  import * as fs$1 from "node:fs/promises";
9
9
  import * as path$1 from "node:path";
10
10
  import { createRequire } from "module";
11
+ //#region ../../packages/knowledge/src/markit/mupdf-quiet.ts
12
+ const globalScope = globalThis;
13
+ const moduleConfig = globalScope.$libmupdf_wasm_Module ?? {};
14
+ moduleConfig.print ??= () => {};
15
+ moduleConfig.printErr ??= () => {};
16
+ globalScope.$libmupdf_wasm_Module = moduleConfig;
17
+ //#endregion
11
18
  //#region ../../node_modules/.pnpm/underscore@1.13.8/node_modules/underscore/modules/_setup.js
12
19
  var VERSION, root, ArrayProto, ObjProto, SymbolProto, push, slice, toString, hasOwnProperty, supportsArrayBuffer, supportsDataView, nativeIsArray, nativeKeys, nativeCreate, nativeIsView, _isNaN, _isFinite, hasEnumBug, nonEnumerableProps, MAX_ARRAY_INDEX;
13
20
  var init__setup = __esmMin((() => {
@@ -49718,7 +49725,7 @@ var require_turndown_plugin_gfm_cjs = /* @__PURE__ */ __commonJSMin(((exports) =
49718
49725
  exports.gfm = gfm;
49719
49726
  }));
49720
49727
  //#endregion
49721
- //#region ../../packages/agent-core/src/utils/turndown.ts
49728
+ //#region ../../packages/knowledge/src/markit/utils/turndown.ts
49722
49729
  var import_lib = /* @__PURE__ */ __toESM(require_lib$1(), 1);
49723
49730
  var import_turndown_cjs = /* @__PURE__ */ __toESM(require_turndown_cjs(), 1);
49724
49731
  var import_turndown_plugin_gfm_cjs = require_turndown_plugin_gfm_cjs();
@@ -49792,7 +49799,7 @@ function normalizeTablesHtml(html) {
49792
49799
  return result;
49793
49800
  }
49794
49801
  //#endregion
49795
- //#region ../../packages/agent-core/src/markit/converters/docx.ts
49802
+ //#region ../../packages/knowledge/src/markit/converters/docx.ts
49796
49803
  const EXTENSIONS$4 = [".docx"];
49797
49804
  const MIMETYPES$4 = ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"];
49798
49805
  var DocxConverter = class {
@@ -50265,7 +50272,7 @@ function unzipSync(data, opts) {
50265
50272
  return files;
50266
50273
  }
50267
50274
  //#endregion
50268
- //#region ../../packages/agent-core/src/utils/zip.ts
50275
+ //#region ../../packages/knowledge/src/markit/utils/zip.ts
50269
50276
  /**
50270
50277
  * Minimal ZIP reading for the markit document converters (docx/pptx/xlsx/
50271
50278
  * epub are all ZIP containers). Read-only: stored + deflate entries via
@@ -50285,7 +50292,7 @@ function unzip(bytes) {
50285
50292
  return unzipSync(bytes);
50286
50293
  }
50287
50294
  //#endregion
50288
- //#region ../../packages/agent-core/src/markit/converters/epub.ts
50295
+ //#region ../../packages/knowledge/src/markit/converters/epub.ts
50289
50296
  const EXTENSIONS$3 = [".epub"];
50290
50297
  const MIMETYPES$3 = [
50291
50298
  "application/epub",
@@ -50365,7 +50372,7 @@ var EpubConverter = class {
50365
50372
  }
50366
50373
  };
50367
50374
  //#endregion
50368
- //#region ../../packages/agent-core/src/markit/converters/pdf/columns.ts
50375
+ //#region ../../packages/knowledge/src/markit/converters/pdf/columns.ts
50369
50376
  /**
50370
50377
  * Minimum gap as a fraction of the total text width to consider a column
50371
50378
  * boundary. A two-column layout typically has ~50% gap; we use a lower
@@ -50434,7 +50441,7 @@ function detectColumns(textBoxes) {
50434
50441
  };
50435
50442
  }
50436
50443
  //#endregion
50437
- //#region ../../packages/agent-core/src/markit/converters/pdf/extract.ts
50444
+ //#region ../../packages/knowledge/src/markit/converters/pdf/extract.ts
50438
50445
  let mupdfModule;
50439
50446
  async function loadMupdf() {
50440
50447
  if (!mupdfModule) mupdfModule = await import("mupdf");
@@ -50936,7 +50943,7 @@ async function extractPages(input) {
50936
50943
  return pages;
50937
50944
  }
50938
50945
  //#endregion
50939
- //#region ../../packages/agent-core/src/markit/converters/pdf/grid.ts
50946
+ //#region ../../packages/knowledge/src/markit/converters/pdf/grid.ts
50940
50947
  function castRaysForTextBox(textBox, segments) {
50941
50948
  const cx = (textBox.bounds.left + textBox.bounds.right) / 2;
50942
50949
  const cy = (textBox.bounds.top + textBox.bounds.bottom) / 2;
@@ -51584,7 +51591,7 @@ function resolveTableGrids(pageNumber, textBoxes, segments) {
51584
51591
  };
51585
51592
  }
51586
51593
  //#endregion
51587
- //#region ../../packages/agent-core/src/markit/converters/pdf/headers.ts
51594
+ //#region ../../packages/knowledge/src/markit/converters/pdf/headers.ts
51588
51595
  /** Minimum number of pages to enable header/footer detection. */
51589
51596
  const MIN_PAGES = 5;
51590
51597
  /** Minimum Y position for top zone (from bottom of page in PDF coords). */
@@ -51656,7 +51663,7 @@ function stripHeadersFooters(pages) {
51656
51663
  });
51657
51664
  }
51658
51665
  //#endregion
51659
- //#region ../../packages/agent-core/src/markit/converters/pdf/render.ts
51666
+ //#region ../../packages/knowledge/src/markit/converters/pdf/render.ts
51660
51667
  /** Convert full-width ASCII characters (A→A, !→! etc.) to normal ASCII. */
51661
51668
  function normalizeFullWidthAscii(text) {
51662
51669
  return text.replace(/[!-~]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 65248));
@@ -52051,7 +52058,7 @@ function renderPageContent(freeTextBoxes, tables, imageBlocks = [], allTextBoxes
52051
52058
  return normalizeDetachedFirstColumnTables(mergeParagraphWraps(mergeConsecutiveHeadings(removePageNumbers(blocks), bodyFS), bodyFS)).map((b) => b.content).join("\n\n").trim();
52052
52059
  }
52053
52060
  //#endregion
52054
- //#region ../../packages/agent-core/src/markit/converters/pdf/index.ts
52061
+ //#region ../../packages/knowledge/src/markit/converters/pdf/index.ts
52055
52062
  /**
52056
52063
  * PDF to Markdown converter.
52057
52064
  *
@@ -52145,7 +52152,7 @@ var PdfConverter = class {
52145
52152
  }
52146
52153
  };
52147
52154
  //#endregion
52148
- //#region ../../packages/agent-core/src/markit/converters/pptx.ts
52155
+ //#region ../../packages/knowledge/src/markit/converters/pptx.ts
52149
52156
  const EXTENSIONS$1 = [".pptx"];
52150
52157
  const MIMETYPES$1 = ["application/vnd.openxmlformats-officedocument.presentationml.presentation"];
52151
52158
  var PptxConverter = class {
@@ -52331,7 +52338,7 @@ function toList(val) {
52331
52338
  return Array.isArray(val) ? val : [val];
52332
52339
  }
52333
52340
  //#endregion
52334
- //#region ../../packages/agent-core/src/markit/converters/xlsx.ts
52341
+ //#region ../../packages/knowledge/src/markit/converters/xlsx.ts
52335
52342
  const EXTENSIONS = [".xlsx"];
52336
52343
  const MIMETYPES = ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"];
52337
52344
  var XlsxConverter = class {
@@ -52420,7 +52427,7 @@ function toArray(val) {
52420
52427
  return Array.isArray(val) ? val : [val];
52421
52428
  }
52422
52429
  //#endregion
52423
- //#region ../../packages/agent-core/src/markit/registry.ts
52430
+ //#region ../../packages/knowledge/src/markit/registry.ts
52424
52431
  /**
52425
52432
  * In-house document → markdown engine.
52426
52433
  *
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { n as multiSearchWithTrace } from "./src-ClB_Hzp9.mjs";
6
+ import { n as multiSearchWithTrace } from "./src-DIVvoAYO.mjs";
7
7
  export { multiSearchWithTrace };
@@ -218,7 +218,7 @@ function computeRelevanceScore(memo, query, usageCount = 0, currentProjectDir, p
218
218
  const ti = intent?.temporalBias ?? .3;
219
219
  const fi = intent?.factualBias ?? .6;
220
220
  const factors = {
221
- keywordOverlap: computeKeywordSimilarity(memo, query),
221
+ keywordOverlap: computeKeywordOverlap(memo, query),
222
222
  recency: computeRecency(memo.recordedAt),
223
223
  usageBoost: Math.min(.3, usageCount * .1),
224
224
  projectBoost: computeProjectBoost(memo.projectDir, currentProjectDir),
@@ -416,7 +416,21 @@ function extractKeywords(text) {
416
416
  return [...new Set(tokens)];
417
417
  }
418
418
  function computeKeywordSimilarity(memo, query) {
419
- const memoWords = extractKeywords(`${memo.userNeed} ${memo.approach} ${memo.whatFailed} ${memo.whatWorked}`);
419
+ return jaccardSimilarity(keywordText(memo), query);
420
+ }
421
+ /**
422
+ * Ranking variant: the distilled conclusion and the note are separate
423
+ * retrieval surfaces. Score each field on its own and keep the best match —
424
+ * a short conclusion would otherwise be diluted by the long base text.
425
+ */
426
+ function computeKeywordOverlap(memo, query) {
427
+ return Math.max(jaccardSimilarity(keywordText(memo), query), jaccardSimilarity(memo.distilled ?? "", query), jaccardSimilarity(memo.note ?? "", query));
428
+ }
429
+ function keywordText(memo) {
430
+ return `${memo.userNeed} ${memo.approach} ${memo.whatFailed} ${memo.whatWorked}`;
431
+ }
432
+ function jaccardSimilarity(memoText, query) {
433
+ const memoWords = extractKeywords(memoText);
420
434
  const queryWords = extractKeywords(query);
421
435
  if (memoWords.length === 0 || queryWords.length === 0) return 0;
422
436
  const intersection = memoWords.filter((w) => queryWords.includes(w)).length;
@@ -469,6 +483,10 @@ function createMemoryMemo(partial) {
469
483
  outcome: partial.outcome,
470
484
  whatFailed: partial.whatFailed,
471
485
  whatWorked: partial.whatWorked,
486
+ distilled: partial.distilled,
487
+ origin: partial.origin ?? "session",
488
+ distilledFrom: partial.distilledFrom,
489
+ distilledAt: partial.distilledAt,
472
490
  extractionSource: partial.extractionSource,
473
491
  recordedAt: partial.recordedAt ?? Date.now(),
474
492
  projectDir: partial.projectDir ?? "",
@@ -490,6 +508,10 @@ function toSummary(memo) {
490
508
  outcome: memo.outcome,
491
509
  whatFailed: memo.whatFailed,
492
510
  whatWorked: memo.whatWorked,
511
+ distilled: memo.distilled,
512
+ origin: memo.origin,
513
+ distilledFrom: memo.distilledFrom,
514
+ distilledAt: memo.distilledAt,
493
515
  extractionSource: memo.extractionSource,
494
516
  recordedAt: memo.recordedAt,
495
517
  projectDir: memo.projectDir,
@@ -4113,7 +4135,19 @@ var MemoryMemoStore = class MemoryMemoStore {
4113
4135
  JOIN memos_fts f ON m.rowid = f.rowid
4114
4136
  WHERE f.memos_fts MATCH ? AND (m.project_dir = ? OR m.project_dir = '')
4115
4137
  ORDER BY m.recorded_at DESC LIMIT ?`);
4116
- return (projectDir === void 0 ? stmt.all(ftsQuery, limit) : stmt.all(ftsQuery, projectDir, limit)).map(rowToMemo);
4138
+ const rows = projectDir === void 0 ? stmt.all(ftsQuery, limit) : stmt.all(ftsQuery, projectDir, limit);
4139
+ const like = likePattern(query);
4140
+ const remaining = Math.max(0, limit - rows.length);
4141
+ const extraRows = remaining === 0 ? [] : projectDir === void 0 ? this.db.prepare("SELECT * FROM memos WHERE distilled LIKE ? ESCAPE '\\' OR note LIKE ? ESCAPE '\\' ORDER BY recorded_at DESC LIMIT ?").all(like, like, remaining) : this.db.prepare("SELECT * FROM memos WHERE (distilled LIKE ? ESCAPE '\\' OR note LIKE ? ESCAPE '\\') AND (project_dir = ? OR project_dir = '') ORDER BY recorded_at DESC LIMIT ?").all(like, like, projectDir, remaining);
4142
+ const seen = new Set(rows.map((row) => String(row["id"])));
4143
+ const merged = rows.map(rowToMemo);
4144
+ for (const row of extraRows) {
4145
+ const id = String(row["id"]);
4146
+ if (seen.has(id)) continue;
4147
+ seen.add(id);
4148
+ merged.push(rowToMemo(row));
4149
+ }
4150
+ return merged;
4117
4151
  }
4118
4152
  /** List memos with optional full-text search and pagination. */
4119
4153
  async list(options) {
@@ -4255,7 +4289,12 @@ var MemoryMemoStore = class MemoryMemoStore {
4255
4289
  extraction_source TEXT NOT NULL CHECK(extraction_source IN ('compaction', 'exit', 'manual')),
4256
4290
  recorded_at INTEGER NOT NULL,
4257
4291
  project_dir TEXT NOT NULL DEFAULT '',
4258
- tags TEXT NOT NULL DEFAULT '[]'
4292
+ tags TEXT NOT NULL DEFAULT '[]',
4293
+ note TEXT,
4294
+ distilled TEXT NOT NULL DEFAULT '',
4295
+ origin TEXT NOT NULL DEFAULT 'session',
4296
+ distilled_from TEXT,
4297
+ distilled_at INTEGER
4259
4298
  );
4260
4299
 
4261
4300
  CREATE INDEX IF NOT EXISTS idx_memos_project_dir ON memos(project_dir);
@@ -4285,6 +4324,14 @@ var MemoryMemoStore = class MemoryMemoStore {
4285
4324
  const info = this.db.prepare("PRAGMA table_info(memos)").all();
4286
4325
  if (!info.some((col) => col.name === "project_dir")) this.db.exec("ALTER TABLE memos ADD COLUMN project_dir TEXT NOT NULL DEFAULT ''");
4287
4326
  if (!info.some((col) => col.name === "tags")) this.db.exec("ALTER TABLE memos ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'");
4327
+ if (!info.some((col) => col.name === "note")) this.db.exec("ALTER TABLE memos ADD COLUMN note TEXT");
4328
+ if (!info.some((col) => col.name === "distilled")) this.db.exec("ALTER TABLE memos ADD COLUMN distilled TEXT NOT NULL DEFAULT ''");
4329
+ if (!info.some((col) => col.name === "origin")) {
4330
+ this.db.exec("ALTER TABLE memos ADD COLUMN origin TEXT NOT NULL DEFAULT 'session'");
4331
+ this.db.exec("UPDATE memos SET origin = 'dream' WHERE distilled <> ''");
4332
+ }
4333
+ if (!info.some((col) => col.name === "distilled_from")) this.db.exec("ALTER TABLE memos ADD COLUMN distilled_from TEXT");
4334
+ if (!info.some((col) => col.name === "distilled_at")) this.db.exec("ALTER TABLE memos ADD COLUMN distilled_at INTEGER");
4288
4335
  this.db.exec(`
4289
4336
  CREATE INDEX IF NOT EXISTS idx_memos_project_dir ON memos(project_dir);
4290
4337
  CREATE INDEX IF NOT EXISTS idx_memory_embeddings_created_at ON memory_embeddings(created_at DESC);
@@ -4328,15 +4375,16 @@ var MemoryMemoStore = class MemoryMemoStore {
4328
4375
  if (this.db === void 0 || memos.length === 0) return;
4329
4376
  const insert = this.db.prepare(`INSERT OR IGNORE INTO memos (
4330
4377
  id, source_session_id, source_session_title, user_need, approach,
4331
- outcome, what_failed, what_worked, extraction_source, recorded_at, project_dir, tags
4332
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4378
+ outcome, what_failed, what_worked, extraction_source, recorded_at, project_dir, tags,
4379
+ note, distilled, origin, distilled_from, distilled_at
4380
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4333
4381
  RETURNING rowid`);
4334
4382
  const insertFts = this.db.prepare(`INSERT INTO memos_fts(rowid, user_need, approach, what_failed, what_worked, source_session_title)
4335
4383
  VALUES (?, ?, ?, ?, ?, ?)`);
4336
4384
  this.db.exec("BEGIN TRANSACTION");
4337
4385
  try {
4338
4386
  for (const memo of memos) {
4339
- const row = insert.get(memo.id, memo.sourceSessionId, memo.sourceSessionTitle ?? null, memo.userNeed, memo.approach, memo.outcome, memo.whatFailed, memo.whatWorked, memo.extractionSource, memo.recordedAt, memo.projectDir ?? "", JSON.stringify(memo.tags ?? []));
4387
+ const row = insert.get(memo.id, memo.sourceSessionId, memo.sourceSessionTitle ?? null, memo.userNeed, memo.approach, memo.outcome, memo.whatFailed, memo.whatWorked, memo.extractionSource, memo.recordedAt, memo.projectDir ?? "", JSON.stringify(memo.tags ?? []), memo.note ?? null, memo.distilled ?? "", memo.origin ?? "session", stringifyDistilledFrom(memo.distilledFrom), memo.distilledAt ?? null);
4340
4388
  if (row === void 0) continue;
4341
4389
  insertFts.run(row.rowid, toFtsText$1(memo.userNeed), toFtsText$1(memo.approach), toFtsText$1(memo.whatFailed), toFtsText$1(memo.whatWorked), toFtsText$1(memo.sourceSessionTitle ?? ""));
4342
4390
  }
@@ -4351,14 +4399,15 @@ var MemoryMemoStore = class MemoryMemoStore {
4351
4399
  if (this.db === void 0) return;
4352
4400
  const insert = this.db.prepare(`INSERT INTO memos (
4353
4401
  id, source_session_id, source_session_title, user_need, approach,
4354
- outcome, what_failed, what_worked, extraction_source, recorded_at, project_dir, tags
4355
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4402
+ outcome, what_failed, what_worked, extraction_source, recorded_at, project_dir, tags,
4403
+ note, distilled, origin, distilled_from, distilled_at
4404
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4356
4405
  RETURNING rowid`);
4357
4406
  const insertFts = this.db.prepare(`INSERT INTO memos_fts(rowid, user_need, approach, what_failed, what_worked, source_session_title)
4358
4407
  VALUES (?, ?, ?, ?, ?, ?)`);
4359
4408
  this.db.exec("BEGIN TRANSACTION");
4360
4409
  try {
4361
- const row = insert.get(entry.id, entry.sourceSessionId, entry.sourceSessionTitle ?? null, entry.userNeed, entry.approach, entry.outcome, entry.whatFailed, entry.whatWorked, entry.extractionSource, entry.recordedAt, entry.projectDir ?? "", JSON.stringify(entry.tags ?? []));
4410
+ const row = insert.get(entry.id, entry.sourceSessionId, entry.sourceSessionTitle ?? null, entry.userNeed, entry.approach, entry.outcome, entry.whatFailed, entry.whatWorked, entry.extractionSource, entry.recordedAt, entry.projectDir ?? "", JSON.stringify(entry.tags ?? []), entry.note ?? null, entry.distilled ?? "", entry.origin ?? "session", stringifyDistilledFrom(entry.distilledFrom), entry.distilledAt ?? null);
4362
4411
  insertFts.run(row.rowid, toFtsText$1(entry.userNeed), toFtsText$1(entry.approach), toFtsText$1(entry.whatFailed), toFtsText$1(entry.whatWorked), toFtsText$1(entry.sourceSessionTitle ?? ""));
4363
4412
  this.db.exec("COMMIT");
4364
4413
  this.scheduleEmbedding(entry);
@@ -4394,7 +4443,12 @@ var MemoryMemoStore = class MemoryMemoStore {
4394
4443
  extraction_source = ?,
4395
4444
  recorded_at = ?,
4396
4445
  project_dir = ?,
4397
- tags = ?
4446
+ tags = ?,
4447
+ note = ?,
4448
+ distilled = ?,
4449
+ origin = ?,
4450
+ distilled_from = ?,
4451
+ distilled_at = ?
4398
4452
  WHERE id = ?
4399
4453
  RETURNING rowid`);
4400
4454
  const updateFts = this.db.prepare(`INSERT INTO memos_fts(rowid, user_need, approach, what_failed, what_worked, source_session_title)
@@ -4408,7 +4462,7 @@ var MemoryMemoStore = class MemoryMemoStore {
4408
4462
  this.db.exec("ROLLBACK");
4409
4463
  return false;
4410
4464
  }
4411
- const row = update.get(updated.sourceSessionId, updated.sourceSessionTitle ?? null, updated.userNeed, updated.approach, updated.outcome, updated.whatFailed, updated.whatWorked, updated.extractionSource, updated.recordedAt, updated.projectDir ?? "", JSON.stringify(updated.tags ?? []), id);
4465
+ const row = update.get(updated.sourceSessionId, updated.sourceSessionTitle ?? null, updated.userNeed, updated.approach, updated.outcome, updated.whatFailed, updated.whatWorked, updated.extractionSource, updated.recordedAt, updated.projectDir ?? "", JSON.stringify(updated.tags ?? []), updated.note ?? null, updated.distilled ?? "", updated.origin ?? "session", stringifyDistilledFrom(updated.distilledFrom), updated.distilledAt ?? null, id);
4412
4466
  if (row === void 0) {
4413
4467
  this.db.exec("ROLLBACK");
4414
4468
  return false;
@@ -4656,6 +4710,9 @@ var MemoryMemoStore = class MemoryMemoStore {
4656
4710
  function rowToMemo(row) {
4657
4711
  const sourceSessionTitle = row["source_session_title"];
4658
4712
  const projectDir = row["project_dir"];
4713
+ const distilled = row["distilled"];
4714
+ const note = row["note"];
4715
+ const distilledAt = row["distilled_at"];
4659
4716
  return {
4660
4717
  id: String(row["id"]),
4661
4718
  sourceSessionId: String(row["source_session_id"]),
@@ -4665,12 +4722,36 @@ function rowToMemo(row) {
4665
4722
  outcome: String(row["outcome"]),
4666
4723
  whatFailed: String(row["what_failed"]),
4667
4724
  whatWorked: String(row["what_worked"]),
4725
+ distilled: typeof distilled === "string" && distilled.trim().length > 0 ? distilled : void 0,
4726
+ origin: parseOrigin(row["origin"]),
4727
+ distilledFrom: parseDistilledFrom(row["distilled_from"]),
4728
+ distilledAt: typeof distilledAt === "number" && Number.isFinite(distilledAt) ? distilledAt : void 0,
4668
4729
  extractionSource: row["extraction_source"],
4669
4730
  recordedAt: Number(row["recorded_at"]),
4670
4731
  projectDir: typeof projectDir === "string" ? projectDir : "",
4671
- tags: parseTags(row["tags"])
4732
+ tags: parseTags(row["tags"]),
4733
+ note: typeof note === "string" && note.length > 0 ? note : void 0
4672
4734
  };
4673
4735
  }
4736
+ function parseOrigin(value) {
4737
+ return value === "dream" || value === "user" || value === "session" ? value : "session";
4738
+ }
4739
+ /** Column value for `distilled_from`: undefined when there is nothing to store. */
4740
+ function stringifyDistilledFrom(sources) {
4741
+ return sources === void 0 || sources.length === 0 ? null : JSON.stringify(sources);
4742
+ }
4743
+ /** Parse the `distilled_from` JSON column, dropping entries that fail validation. */
4744
+ function parseDistilledFrom(value) {
4745
+ if (typeof value !== "string" || value.length === 0) return void 0;
4746
+ try {
4747
+ const parsed = JSON.parse(value);
4748
+ if (!Array.isArray(parsed)) return void 0;
4749
+ const sources = parsed.filter((item) => typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.title === "string" && typeof item.at === "number" && Number.isFinite(item.at));
4750
+ return sources.length > 0 ? sources.slice(0, 100) : void 0;
4751
+ } catch {
4752
+ return;
4753
+ }
4754
+ }
4674
4755
  function parseTags(value) {
4675
4756
  if (typeof value !== "string" || value.length === 0) return void 0;
4676
4757
  try {
@@ -4682,12 +4763,17 @@ function parseTags(value) {
4682
4763
  return;
4683
4764
  }
4684
4765
  }
4766
+ function likePattern(value) {
4767
+ return `%${value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
4768
+ }
4685
4769
  function memoMatchesSearch(memo, search) {
4686
4770
  return [
4687
4771
  memo.userNeed,
4688
4772
  memo.approach,
4689
4773
  memo.whatFailed,
4690
4774
  memo.whatWorked,
4775
+ memo.distilled ?? "",
4776
+ memo.note ?? "",
4691
4777
  memo.sourceSessionTitle ?? "",
4692
4778
  ...memo.tags ?? []
4693
4779
  ].join(" ").toLowerCase().replaceAll(/\s+/g, "").includes(search.replaceAll(/\s+/g, ""));
@@ -4861,6 +4947,7 @@ async function buildConsolidationPlan(store, options) {
4861
4947
  async function applyConsolidation(store, plan) {
4862
4948
  let deleted = 0;
4863
4949
  let created = 0;
4950
+ let skipped = 0;
4864
4951
  if (plan.resolved.length > 0) {
4865
4952
  const worked = plan.resolved.filter((m) => m.whatWorked).map((m) => m.whatWorked).join("; ");
4866
4953
  const failed = plan.resolved.filter((m) => m.whatFailed).map((m) => m.whatFailed).join("; ");
@@ -4899,9 +4986,22 @@ async function applyConsolidation(store, plan) {
4899
4986
  created++;
4900
4987
  }
4901
4988
  }
4989
+ const mergeableGroups = [];
4902
4990
  for (const group of plan.duplicateGroups) {
4991
+ const distilledText = (group.merged.distilled ?? "").trim();
4992
+ if (distilledText.length === 0 && group.memos.some(isDistilledRule)) {
4993
+ skipped++;
4994
+ continue;
4995
+ }
4996
+ mergeableGroups.push({
4997
+ group,
4998
+ distilledText
4999
+ });
5000
+ }
5001
+ for (const { group, distilledText } of mergeableGroups) {
4903
5002
  const newest = group.memos.reduce((a, b) => a.recordedAt > b.recordedAt ? a : b);
4904
5003
  const mergedTags = normalizeTags(group.memos.flatMap((m) => m.tags ?? []));
5004
+ const mergedNotes = [...new Set(group.memos.map((memo) => memo.note?.trim() ?? "").filter((note) => note.length > 0))];
4905
5005
  const merged = createMemoryMemo({
4906
5006
  sourceSessionId: newest.sourceSessionId,
4907
5007
  sourceSessionTitle: newest.sourceSessionTitle,
@@ -4910,14 +5010,19 @@ async function applyConsolidation(store, plan) {
4910
5010
  outcome: group.merged.outcome,
4911
5011
  whatFailed: group.merged.whatFailed,
4912
5012
  whatWorked: group.merged.whatWorked,
5013
+ note: mergedNotes.length > 0 ? mergedNotes.join("; ") : void 0,
4913
5014
  tags: group.merged.tags ?? mergedTags,
5015
+ distilled: distilledText.length > 0 ? distilledText : void 0,
4914
5016
  projectDir: newest.projectDir,
4915
- extractionSource: "compaction"
5017
+ extractionSource: "compaction",
5018
+ origin: "dream",
5019
+ distilledAt: distilledText.length > 0 ? Date.now() : void 0,
5020
+ distilledFrom: collectLineage(group.memos)
4916
5021
  });
4917
5022
  await store.append(merged);
4918
5023
  created++;
4919
5024
  }
4920
- for (const group of plan.duplicateGroups) for (const memo of group.memos) {
5025
+ for (const { group } of mergeableGroups) for (const memo of group.memos) {
4921
5026
  await store.delete(memo.id);
4922
5027
  deleted++;
4923
5028
  }
@@ -4931,9 +5036,31 @@ async function applyConsolidation(store, plan) {
4931
5036
  }
4932
5037
  return {
4933
5038
  deleted,
4934
- created
5039
+ created,
5040
+ skipped
4935
5041
  };
4936
5042
  }
5043
+ /**
5044
+ * Build the lineage snapshot for a merged memo: every source memo in the group
5045
+ * plus each source's own lineage, deduped by id so a rule distilled from other
5046
+ * rules keeps its whole ancestry. Sorted oldest-first and capped at 100.
5047
+ */
5048
+ function collectLineage(memos) {
5049
+ const seen = /* @__PURE__ */ new Map();
5050
+ for (const memo of memos) {
5051
+ if (!seen.has(memo.id)) seen.set(memo.id, {
5052
+ id: memo.id,
5053
+ title: clip(memo.userNeed, 80),
5054
+ at: memo.recordedAt
5055
+ });
5056
+ for (const source of memo.distilledFrom ?? []) if (!seen.has(source.id)) seen.set(source.id, source);
5057
+ }
5058
+ return [...seen.values()].toSorted((a, b) => a.at - b.at).slice(-100);
5059
+ }
5060
+ function clip(text, maxLength) {
5061
+ const chars = Array.from(text);
5062
+ return chars.length <= maxLength ? text : chars.slice(0, maxLength).join("");
5063
+ }
4937
5064
  function findDuplicateGroups(memos) {
4938
5065
  const groups = [];
4939
5066
  const used = /* @__PURE__ */ new Set();
@@ -5012,7 +5139,8 @@ function buildDuplicateGroup(cluster) {
5012
5139
  approach: `合并 ${cluster.length} 条相关记录。最新方案: ${newest.approach}`,
5013
5140
  outcome: bestOutcome,
5014
5141
  whatFailed: failures.size > 0 ? [...failures].join("; ") : "none",
5015
- whatWorked: successes.size > 0 ? [...successes].join("; ") : "none"
5142
+ whatWorked: successes.size > 0 ? [...successes].join("; ") : "none",
5143
+ distilled: ""
5016
5144
  },
5017
5145
  reason: `发现 ${cluster.length} 条相似记录(关键词重叠 > ${Math.round(SIMILARITY_THRESHOLD * 100)}%)`
5018
5146
  };
@@ -5075,12 +5203,19 @@ function isOutcomeCompleted(outcome) {
5075
5203
  const lower = outcome.toLowerCase();
5076
5204
  return lower.includes("完成") || lower.includes("done") || lower.includes("completed") || lower.includes("成功") || lower.includes("success");
5077
5205
  }
5206
+ /**
5207
+ * A distilled rule is not a stale experience: pruning it by age would delete
5208
+ * the conclusion /dream exists to accumulate.
5209
+ */
5210
+ function isDistilledRule(memo) {
5211
+ return memo.origin === "dream" || memo.origin === "user" || (memo.distilled ?? "").trim().length > 0;
5212
+ }
5078
5213
  function findResolved(memos) {
5079
- return memos.filter((m) => isOutcomeCompleted(m.outcome) && Date.now() - m.recordedAt > 10080 * 60 * 1e3);
5214
+ return memos.filter((m) => !isDistilledRule(m) && isOutcomeCompleted(m.outcome) && Date.now() - m.recordedAt > 10080 * 60 * 1e3);
5080
5215
  }
5081
5216
  function findStale(memos, staleDays) {
5082
5217
  const threshold = Date.now() - staleDays * 24 * 60 * 60 * 1e3;
5083
- return memos.filter((m) => m.recordedAt < threshold && !isOutcomeCompleted(m.outcome) && !m.outcome.includes("blocked"));
5218
+ return memos.filter((m) => !isDistilledRule(m) && m.recordedAt < threshold && !isOutcomeCompleted(m.outcome) && !m.outcome.includes("blocked"));
5084
5219
  }
5085
5220
  //#endregion
5086
5221
  //#region ../../packages/memory/src/dream.ts
@@ -6717,14 +6852,78 @@ async function embedOrNulls(engine, texts, skipEmbedding) {
6717
6852
  }
6718
6853
  /** LLM concurrency for extraction — callers and comments below refer to this constant. */
6719
6854
  const LLM_CONCURRENCY = 20;
6720
- const SUPPORTED_EXTENSIONS = new Set([
6855
+ /** Formats read directly as UTF-8 text. */
6856
+ const TEXT_EXTENSIONS = new Set([
6721
6857
  ".md",
6722
6858
  ".markdown",
6723
6859
  ".txt"
6724
6860
  ]);
6725
- /** Read a file and return its content. */
6726
- async function readFileContent(filePath) {
6727
- return readFile(filePath, "utf-8");
6861
+ /**
6862
+ * Binary documents run through the markit converter (they become Markdown,
6863
+ * then take the exact same chunking/embedding path as a `.md` file).
6864
+ */
6865
+ const DOCUMENT_EXTENSIONS = new Set([
6866
+ ".pdf",
6867
+ ".docx",
6868
+ ".xlsx"
6869
+ ]);
6870
+ const SUPPORTED_EXTENSIONS = new Set([...TEXT_EXTENSIONS, ...DOCUMENT_EXTENSIONS]);
6871
+ /**
6872
+ * Hard ceiling for a converted document. Every chunk costs one LLM extraction
6873
+ * call, so an unbounded PDF would silently turn into hundreds of requests.
6874
+ */
6875
+ const MAX_DOCUMENT_BYTES = 50 * 1024 * 1024;
6876
+ /** Lazily-loaded converter instance: only binary ingests pay the import cost. */
6877
+ let markitPromise;
6878
+ function getMarkit() {
6879
+ markitPromise ??= import("./markit-CV3BXOV_.mjs").then(({ Markit }) => new Markit()).catch((error) => {
6880
+ markitPromise = void 0;
6881
+ throw error;
6882
+ });
6883
+ return markitPromise;
6884
+ }
6885
+ /**
6886
+ * The converters record images as `<!-- image: id (page N, WxHpt) -->` when
6887
+ * they are not asked to write them out. Those comments carry no searchable
6888
+ * meaning and would otherwise burn tokens in every chunk they land in.
6889
+ */
6890
+ function stripImagePlaceholders(markdown) {
6891
+ return markdown.replaceAll(/<!--\s*image:[^>]*-->/g, "").replaceAll(/\n{3,}/g, "\n\n").trim();
6892
+ }
6893
+ /**
6894
+ * Read a source file and fingerprint it.
6895
+ *
6896
+ * Text files hash the decoded string — exactly what the previous
6897
+ * `sha256(readFile(path, 'utf-8'))` produced, so existing `contentHash` values
6898
+ * keep matching even for non-UTF-8 text. Binary files hash the raw bytes, which
6899
+ * stays stable across converter upgrades.
6900
+ */
6901
+ async function readSource(filePath) {
6902
+ const ext = getFileExtension(filePath);
6903
+ if (!TEXT_EXTENSIONS.has(ext)) {
6904
+ const { size } = await stat(filePath);
6905
+ if (size > MAX_DOCUMENT_BYTES) throw new Error(`文档超过 ${MAX_DOCUMENT_BYTES / 1024 / 1024}MB 上限,已跳过: ${basename$1(filePath)}`);
6906
+ }
6907
+ const bytes = await readFile(filePath);
6908
+ return {
6909
+ bytes,
6910
+ ext,
6911
+ contentHash: TEXT_EXTENSIONS.has(ext) ? createHash("sha256").update(bytes.toString("utf8")).digest("hex") : createHash("sha256").update(bytes).digest("hex")
6912
+ };
6913
+ }
6914
+ /**
6915
+ * Convert a binary document to Markdown. Only called once the bytes are known
6916
+ * to be new or changed — an unchanged PDF is never re-parsed.
6917
+ */
6918
+ async function convertSource(filePath, bytes, ext, onConverting) {
6919
+ onConverting?.(basename$1(filePath));
6920
+ const { markdown } = await (await getMarkit()).convert(bytes, {
6921
+ extension: ext,
6922
+ filename: basename$1(filePath)
6923
+ });
6924
+ const content = stripImagePlaceholders(markdown ?? "");
6925
+ if (content.length === 0) throw new Error(ext === ".pdf" ? "文档未提取到文字(可能是扫描版 PDF),本功能不含 OCR" : "文档未提取到可索引的文字,文件可能已损坏或为空");
6926
+ return content;
6728
6927
  }
6729
6928
  function getFileExtension(filePath) {
6730
6929
  const lastDot = filePath.lastIndexOf(".");
@@ -6792,6 +6991,10 @@ async function ensureEmbeddingModelMatches(store, engine) {
6792
6991
  * Reports progress via callback.
6793
6992
  */
6794
6993
  async function ingestFile(store, llm, filePath, onProgress) {
6994
+ if (!isSupportedFile(filePath)) {
6995
+ const ext = getFileExtension(filePath);
6996
+ throw new Error(`不支持的文件类型: ${ext === "" ? "(无扩展名)" : ext}`);
6997
+ }
6795
6998
  const engine = store.getEmbeddingEngine();
6796
6999
  const skipEmbed = engine === void 0 || !engine.available;
6797
7000
  if (skipEmbed) onProgress?.({
@@ -6803,8 +7006,7 @@ async function ingestFile(store, llm, filePath, onProgress) {
6803
7006
  stage: "chunking",
6804
7007
  message: `读取并切分文件: ${basename$1(filePath)}`
6805
7008
  });
6806
- const content = await readFileContent(filePath);
6807
- const contentHash = createHash("sha256").update(content).digest("hex");
7009
+ const { bytes, ext, contentHash } = await readSource(filePath);
6808
7010
  const existing = await store.findSourceByFilePath(filePath);
6809
7011
  let replacedOldVersion = false;
6810
7012
  if (existing !== void 0) {
@@ -6826,10 +7028,18 @@ async function ingestFile(store, llm, filePath, onProgress) {
6826
7028
  stage: "chunking",
6827
7029
  message: `检测到内容变更,替换旧版本: ${existing.name}`
6828
7030
  });
7031
+ }
7032
+ const content = TEXT_EXTENSIONS.has(ext) ? bytes.toString("utf8") : await convertSource(filePath, bytes, ext, (file) => {
7033
+ onProgress?.({
7034
+ stage: "converting",
7035
+ message: file
7036
+ });
7037
+ });
7038
+ if (existing !== void 0) {
6829
7039
  await store.deleteSource(existing.id);
6830
7040
  replacedOldVersion = true;
6831
7041
  }
6832
- const sections = chunkContent(content, getFileExtension(filePath));
7042
+ const sections = chunkContent(content, ext);
6833
7043
  store.beginTransaction();
6834
7044
  try {
6835
7045
  if (sections.length === 0) {
@@ -7033,6 +7243,7 @@ async function collectSupportedFiles(dirPath) {
7033
7243
  const files = [];
7034
7244
  const entries = await readdir(dirPath, { withFileTypes: true });
7035
7245
  for (const entry of entries) {
7246
+ if (entry.name.startsWith(".") || entry.name.startsWith("~$")) continue;
7036
7247
  const fullPath = join$1(dirPath, entry.name);
7037
7248
  if (entry.isDirectory()) files.push(...await collectSupportedFiles(fullPath));
7038
7249
  else if (entry.isFile() && isSupportedFile(fullPath)) files.push(fullPath);
@@ -7041,7 +7252,7 @@ async function collectSupportedFiles(dirPath) {
7041
7252
  }
7042
7253
  async function ingestDirectory(store, llm, dirPath, onProgress) {
7043
7254
  const files = await collectSupportedFiles(dirPath);
7044
- if (files.length === 0) throw new Error(`目录下没有支持的文件 (.md, .markdown, .txt): ${dirPath}`);
7255
+ if (files.length === 0) throw new Error(`目录下没有支持的文件 (${[...SUPPORTED_EXTENSIONS].join(", ")}): ${dirPath}`);
7045
7256
  const errors = [];
7046
7257
  let succeeded = 0;
7047
7258
  let created = 0;
@@ -7053,6 +7264,10 @@ async function ingestDirectory(store, llm, dirPath, onProgress) {
7053
7264
  for (let i = 0; i < files.length; i++) {
7054
7265
  const filePath = files[i];
7055
7266
  const fileProgress = (progress) => {
7267
+ if (progress.stage === "converting") {
7268
+ onProgress?.(progress);
7269
+ return;
7270
+ }
7056
7271
  const prefix = `批量摄入 ${i + 1}/${files.length}: ${basename$1(filePath)}`;
7057
7272
  onProgress?.({
7058
7273
  ...progress,
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { t as TextInputDialogComponent } from "./text-input-dialog-DHDsHCqR.mjs";
6
+ import { t as TextInputDialogComponent } from "./text-input-dialog-CqagQQtW.mjs";
7
7
  export { TextInputDialogComponent };