@wolfx/pi-magic-context 0.23.1 → 0.24.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.
@@ -8164,9 +8164,470 @@ function isRelativeProjectFile(projectPath, relativePath) {
8164
8164
 
8165
8165
  // ../plugin/src/features/magic-context/migrations.ts
8166
8166
  init_logger();
8167
- function tableExists(db, name) {
8167
+
8168
+ // ../plugin/src/features/magic-context/workspaces.ts
8169
+ import { createHash as createHash3 } from "node:crypto";
8170
+
8171
+ // ../plugin/src/features/magic-context/memory/constants.ts
8172
+ var V2_MEMORY_CATEGORIES = [
8173
+ "PROJECT_RULES",
8174
+ "ARCHITECTURE",
8175
+ "CONSTRAINTS",
8176
+ "CONFIG_VALUES",
8177
+ "NAMING"
8178
+ ];
8179
+ var PROMOTABLE_CATEGORIES = [
8180
+ "PROJECT_RULES",
8181
+ "ARCHITECTURE",
8182
+ "CONSTRAINTS",
8183
+ "CONFIG_VALUES",
8184
+ "NAMING",
8185
+ "ARCHITECTURE_DECISIONS",
8186
+ "CONFIG_DEFAULTS",
8187
+ "USER_PREFERENCES",
8188
+ "USER_DIRECTIVES",
8189
+ "ENVIRONMENT",
8190
+ "WORKFLOW_RULES",
8191
+ "KNOWN_ISSUES"
8192
+ ];
8193
+ var CATEGORY_PRIORITY = [
8194
+ "PROJECT_RULES",
8195
+ "ARCHITECTURE",
8196
+ "CONSTRAINTS",
8197
+ "CONFIG_VALUES",
8198
+ "NAMING",
8199
+ "USER_DIRECTIVES",
8200
+ "USER_PREFERENCES",
8201
+ "CONFIG_DEFAULTS",
8202
+ "ARCHITECTURE_DECISIONS",
8203
+ "ENVIRONMENT",
8204
+ "WORKFLOW_RULES",
8205
+ "KNOWN_ISSUES"
8206
+ ];
8207
+ var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
8208
+ var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
8209
+ acc[category] = index;
8210
+ return acc;
8211
+ }, {});
8212
+ var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
8213
+ var CATEGORY_DEFAULT_TTL = {
8214
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
8215
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
8216
+ };
8217
+
8218
+ // ../plugin/src/features/magic-context/memory/project-identity.ts
8219
+ import { execFileSync } from "node:child_process";
8220
+ import { createHash as createHash2 } from "node:crypto";
8221
+ import { statSync } from "node:fs";
8222
+ import path3 from "node:path";
8223
+ var GIT_TIMEOUT_MS = 5000;
8224
+ var identityCache = new Map;
8225
+ var directoryFallbackCache = new Map;
8226
+
8227
+ class ProjectIdentityError extends Error {
8228
+ errorClass;
8229
+ rawDirectory;
8230
+ constructor(errorClass, rawDirectory, message, cause) {
8231
+ super(message);
8232
+ this.name = "ProjectIdentityError";
8233
+ this.errorClass = errorClass;
8234
+ this.rawDirectory = rawDirectory;
8235
+ if (cause) {
8236
+ this.cause = cause;
8237
+ }
8238
+ }
8239
+ }
8240
+ function asError(error) {
8241
+ return error instanceof Error ? error : undefined;
8242
+ }
8243
+ function getErrorCode(error) {
8244
+ if (error === null || typeof error !== "object" || !("code" in error)) {
8245
+ return;
8246
+ }
8247
+ const code = error.code;
8248
+ return typeof code === "string" ? code : undefined;
8249
+ }
8250
+ function getErrorSignal(error) {
8251
+ if (error === null || typeof error !== "object" || !("signal" in error)) {
8252
+ return;
8253
+ }
8254
+ const signal = error.signal;
8255
+ return typeof signal === "string" ? signal : undefined;
8256
+ }
8257
+ function getErrorKilled(error) {
8258
+ if (error === null || typeof error !== "object" || !("killed" in error)) {
8259
+ return false;
8260
+ }
8261
+ return error.killed === true;
8262
+ }
8263
+ function getErrorStderr(error) {
8264
+ if (error === null || typeof error !== "object" || !("stderr" in error)) {
8265
+ return "";
8266
+ }
8267
+ const stderr = error.stderr;
8268
+ if (typeof stderr === "string") {
8269
+ return stderr;
8270
+ }
8271
+ if (Buffer.isBuffer(stderr)) {
8272
+ return stderr.toString("utf8");
8273
+ }
8274
+ return "";
8275
+ }
8276
+ function directoryFallback(directory) {
8277
+ const canonical = path3.resolve(directory);
8278
+ const hash = createHash2("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
8279
+ return `dir:${hash}`;
8280
+ }
8281
+ function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
8282
+ try {
8283
+ const stat = statSync(canonicalDirectory);
8284
+ if (!stat.isDirectory()) {
8285
+ throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
8286
+ }
8287
+ } catch (error) {
8288
+ if (error instanceof ProjectIdentityError) {
8289
+ throw error;
8290
+ }
8291
+ const code = getErrorCode(error);
8292
+ if (code === "EACCES" || code === "EPERM") {
8293
+ throw new ProjectIdentityError("permission_denied", rawDirectory, `Permission denied while accessing project directory: ${canonicalDirectory}`, asError(error));
8294
+ }
8295
+ throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error));
8296
+ }
8297
+ }
8298
+ function isGitTimeoutError(error) {
8299
+ const code = getErrorCode(error);
8300
+ const signal = getErrorSignal(error);
8301
+ return code === "ETIMEDOUT" || signal === "SIGTERM" || signal === "SIGKILL" || getErrorKilled(error);
8302
+ }
8303
+ function classifyGitError(error, rawDirectory) {
8304
+ if (isGitTimeoutError(error)) {
8305
+ return new ProjectIdentityError("git_timeout", rawDirectory, `git rev-list timed out after ${GIT_TIMEOUT_MS}ms`, asError(error));
8306
+ }
8307
+ const code = getErrorCode(error);
8308
+ if (code === "ENOENT") {
8309
+ return new ProjectIdentityError("git_missing", rawDirectory, "git binary is not available in PATH", asError(error));
8310
+ }
8311
+ if (code === "EACCES" || code === "EPERM") {
8312
+ return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
8313
+ }
8314
+ const stderr = getErrorStderr(error).toLowerCase();
8315
+ if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
8316
+ return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
8317
+ }
8318
+ return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
8319
+ }
8320
+ function resolveProjectIdentityStrict(directory) {
8321
+ const canonical = path3.resolve(directory);
8322
+ const cached = identityCache.get(canonical);
8323
+ if (cached !== undefined) {
8324
+ return cached;
8325
+ }
8326
+ assertDirectoryUsable(canonical, directory);
8327
+ let output;
8328
+ try {
8329
+ output = execFileSync("git", ["rev-list", "--max-parents=0", "HEAD"], {
8330
+ cwd: canonical,
8331
+ encoding: "utf8",
8332
+ env: { ...process.env, LC_ALL: "C", LANG: "C" },
8333
+ stdio: ["ignore", "pipe", "pipe"],
8334
+ timeout: GIT_TIMEOUT_MS
8335
+ });
8336
+ } catch (error) {
8337
+ throw classifyGitError(error, directory);
8338
+ }
8339
+ const firstLine = output.split(`
8340
+ `)[0]?.trim() ?? "";
8341
+ const rootCommit = firstLine.slice(0, 64);
8342
+ if (rootCommit.length < 7) {
8343
+ throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
8344
+ }
8345
+ const identity = `git:${rootCommit}`;
8346
+ identityCache.set(canonical, identity);
8347
+ return identity;
8348
+ }
8349
+ function shouldUseDirectoryFallback(error) {
8350
+ return error.errorClass === "not_git_repo" || error.errorClass === "unknown" && error.message.startsWith("Unable to access project directory:");
8351
+ }
8352
+ function resolveProjectIdentity(directory) {
8353
+ const canonical = path3.resolve(directory);
8354
+ const cachedFallback = directoryFallbackCache.get(canonical);
8355
+ if (cachedFallback !== undefined) {
8356
+ if (!hasGitDir(canonical)) {
8357
+ return cachedFallback;
8358
+ }
8359
+ directoryFallbackCache.delete(canonical);
8360
+ }
8361
+ try {
8362
+ return resolveProjectIdentityStrict(directory);
8363
+ } catch (error) {
8364
+ if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
8365
+ const fallback = directoryFallback(canonical);
8366
+ if (!hasGitDir(canonical)) {
8367
+ directoryFallbackCache.set(canonical, fallback);
8368
+ }
8369
+ return fallback;
8370
+ }
8371
+ throw error;
8372
+ }
8373
+ }
8374
+ function hasGitDir(canonical) {
8375
+ try {
8376
+ statSync(path3.join(canonical, ".git"));
8377
+ return true;
8378
+ } catch {
8379
+ return false;
8380
+ }
8381
+ }
8382
+ function normalizeStoredProjectPath(rawOrStored) {
8383
+ if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
8384
+ return rawOrStored;
8385
+ }
8386
+ try {
8387
+ return resolveProjectIdentity(rawOrStored);
8388
+ } catch {
8389
+ return directoryFallback(rawOrStored);
8390
+ }
8391
+ }
8392
+ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
8393
+ return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
8394
+ }
8395
+ // ../plugin/src/features/magic-context/workspaces.ts
8396
+ var VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
8397
+ function tableExists(db, tableName) {
8398
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
8399
+ return Boolean(row);
8400
+ }
8401
+ function columnExists(db, tableName, columnName) {
8402
+ const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
8403
+ return rows.some((row) => row.name === columnName);
8404
+ }
8405
+ function uniqueSorted(values) {
8406
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
8407
+ }
8408
+ function placeholders(values) {
8409
+ return values.map(() => "?").join(", ");
8410
+ }
8411
+ function normalizeShareCategories(raw) {
8412
+ if (raw === null || raw === undefined)
8413
+ return null;
8414
+ if (typeof raw !== "string")
8415
+ return null;
8416
+ let parsed;
8417
+ try {
8418
+ parsed = JSON.parse(raw);
8419
+ } catch {
8420
+ return null;
8421
+ }
8422
+ if (!Array.isArray(parsed))
8423
+ return null;
8424
+ const categories = [];
8425
+ for (const value of parsed) {
8426
+ if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
8427
+ return null;
8428
+ }
8429
+ if (!categories.includes(value))
8430
+ categories.push(value);
8431
+ }
8432
+ return categories.sort((left, right) => left.localeCompare(right));
8433
+ }
8434
+ function selectWorkspaceShareCategories(db, identities) {
8435
+ const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
8436
+ if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
8437
+ return null;
8438
+ }
8439
+ const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
8440
+ FROM workspace_members AS member
8441
+ JOIN workspaces AS workspace ON workspace.id = member.workspace_id
8442
+ WHERE member.project_path IN (${placeholders(candidates)})
8443
+ ORDER BY workspace.id ASC
8444
+ LIMIT 1`).get(...candidates);
8445
+ return normalizeShareCategories(row?.shareCategories ?? null);
8446
+ }
8447
+ function resolveWorkspaceShareCategories(db, projectIdentity) {
8448
+ return selectWorkspaceShareCategories(db, [projectIdentity]);
8449
+ }
8450
+ function resolveWorkspaceIdentitySet(db, projectIdentity) {
8451
+ if (!tableExists(db, "workspace_members")) {
8452
+ return { identities: [projectIdentity], namesByIdentity: new Map };
8453
+ }
8454
+ const rows = db.prepare(`SELECT member.project_path AS identity, member.display_name AS displayName
8455
+ FROM workspace_members AS anchor
8456
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
8457
+ WHERE anchor.project_path = ?
8458
+ ORDER BY member.display_name ASC, member.project_path ASC`).all(projectIdentity);
8459
+ if (rows.length === 0) {
8460
+ return { identities: [projectIdentity], namesByIdentity: new Map };
8461
+ }
8462
+ const namesByIdentity = new Map;
8463
+ const identities = [];
8464
+ for (const row of rows) {
8465
+ if (typeof row.identity !== "string" || row.identity.length === 0)
8466
+ continue;
8467
+ if (identities.includes(row.identity))
8468
+ continue;
8469
+ identities.push(row.identity);
8470
+ if (typeof row.displayName === "string" && row.displayName.length > 0) {
8471
+ namesByIdentity.set(row.identity, row.displayName);
8472
+ }
8473
+ }
8474
+ return identities.length > 0 ? { identities, namesByIdentity } : { identities: [projectIdentity], namesByIdentity: new Map };
8475
+ }
8476
+ function expandWorkspaceIdentitySetWithAliases(db, identities) {
8477
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
8478
+ const expanded = new Set(canonical);
8479
+ const canonicalIdentityByStoredPath = new Map;
8480
+ for (const identity of canonical) {
8481
+ canonicalIdentityByStoredPath.set(identity, identity);
8482
+ }
8483
+ if (canonical.length === 0 || !tableExists(db, "v22_identity_rekey_map")) {
8484
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
8485
+ }
8486
+ const rows = db.prepare(`SELECT old_project_path AS oldProjectPath, new_project_path AS newProjectPath
8487
+ FROM v22_identity_rekey_map
8488
+ WHERE new_project_path IN (${placeholders(canonical)})
8489
+ ORDER BY old_project_path ASC`).all(...canonical);
8490
+ for (const row of rows) {
8491
+ if (typeof row.oldProjectPath !== "string" || typeof row.newProjectPath !== "string") {
8492
+ continue;
8493
+ }
8494
+ if (!canonicalIdentityByStoredPath.has(row.newProjectPath))
8495
+ continue;
8496
+ expanded.add(row.oldProjectPath);
8497
+ canonicalIdentityByStoredPath.set(row.oldProjectPath, row.newProjectPath);
8498
+ }
8499
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
8500
+ }
8501
+ function resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) {
8502
+ const direct = canonicalIdentityByStoredPath.get(storedProjectPath);
8503
+ if (direct)
8504
+ return direct;
8505
+ const normalized = normalizeStoredProjectPath(storedProjectPath);
8506
+ const normalizedDirect = canonicalIdentityByStoredPath.get(normalized);
8507
+ if (normalizedDirect)
8508
+ return normalizedDirect;
8509
+ if (memberIdentities.includes(normalized))
8510
+ return normalized;
8511
+ for (const identity of memberIdentities) {
8512
+ if (storedPathBelongsToIdentity(storedProjectPath, identity)) {
8513
+ return identity;
8514
+ }
8515
+ }
8516
+ return null;
8517
+ }
8518
+ function storedPathBelongsToWorkspace(storedProjectPath, memberIdentities, expandedIdentities, canonicalIdentityByStoredPath) {
8519
+ if (expandedIdentities.includes(storedProjectPath))
8520
+ return true;
8521
+ return resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) !== null;
8522
+ }
8523
+ function sourceNameForMemory(storedProjectPath, ownIdentity, memberIdentities, namesByIdentity, canonicalIdentityByStoredPath) {
8524
+ const canonicalIdentity = resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath);
8525
+ if (!canonicalIdentity || canonicalIdentity === ownIdentity)
8526
+ return;
8527
+ return namesByIdentity.get(canonicalIdentity);
8528
+ }
8529
+ function getEpochMap(db, identities) {
8530
+ if (identities.length === 0)
8531
+ return new Map;
8532
+ const rows = db.prepare(`SELECT project_path AS projectPath, project_memory_epoch AS epoch
8533
+ FROM project_state
8534
+ WHERE project_path IN (${placeholders(identities)})`).all(...identities);
8535
+ const epochs = new Map;
8536
+ for (const row of rows) {
8537
+ if (typeof row.projectPath !== "string" || typeof row.epoch !== "number")
8538
+ continue;
8539
+ epochs.set(row.projectPath, row.epoch);
8540
+ }
8541
+ return epochs;
8542
+ }
8543
+ function computeWorkspaceEpochFingerprint(db, identities) {
8544
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
8545
+ const epochs = getEpochMap(db, canonical);
8546
+ const shareCategories = selectWorkspaceShareCategories(db, canonical);
8547
+ const hash = createHash3("sha256");
8548
+ hash.update("share_categories", "utf8");
8549
+ hash.update("\x00");
8550
+ hash.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
8551
+ hash.update(`
8552
+ `);
8553
+ for (const identity of canonical) {
8554
+ hash.update(identity, "utf8");
8555
+ hash.update("\x00");
8556
+ hash.update(String(epochs.get(identity) ?? 0), "utf8");
8557
+ hash.update(`
8558
+ `);
8559
+ }
8560
+ return hash.digest("hex");
8561
+ }
8562
+ function isInTransaction(db) {
8563
+ const candidate = db;
8564
+ return candidate.inTransaction === true || candidate.isTransaction === true;
8565
+ }
8566
+ function workspaceMembersForIdentity(db, identity) {
8567
+ if (!tableExists(db, "workspace_members"))
8568
+ return [identity];
8569
+ const rows = db.prepare(`SELECT member.project_path AS identity
8570
+ FROM workspace_members AS anchor
8571
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
8572
+ WHERE anchor.project_path = ?
8573
+ ORDER BY member.project_path ASC`).all(identity);
8574
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((value) => value.length > 0);
8575
+ return identities.length > 0 ? uniqueSorted(identities) : [identity];
8576
+ }
8577
+ function bumpEpochRows(db, identities, now) {
8578
+ const stmt = db.prepare(`INSERT INTO project_state
8579
+ (project_path, project_memory_epoch, project_user_profile_version, updated_at)
8580
+ VALUES (?, 1, 0, ?)
8581
+ ON CONFLICT(project_path) DO UPDATE SET
8582
+ project_memory_epoch = project_memory_epoch + 1,
8583
+ updated_at = excluded.updated_at`);
8584
+ for (const identity of uniqueSorted(identities)) {
8585
+ stmt.run(identity, now);
8586
+ }
8587
+ }
8588
+ function bumpEpochsForWorkspaceMembers(db, identity, now = Date.now()) {
8589
+ const run = () => bumpEpochRows(db, workspaceMembersForIdentity(db, identity), now);
8590
+ if (isInTransaction(db)) {
8591
+ run();
8592
+ return;
8593
+ }
8594
+ db.exec("BEGIN IMMEDIATE");
8595
+ try {
8596
+ run();
8597
+ db.exec("COMMIT");
8598
+ } catch (error) {
8599
+ try {
8600
+ db.exec("ROLLBACK");
8601
+ } catch {}
8602
+ throw error;
8603
+ }
8604
+ }
8605
+ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
8606
+ const run = () => bumpEpochRows(db, identities, now);
8607
+ if (isInTransaction(db)) {
8608
+ run();
8609
+ return;
8610
+ }
8611
+ db.exec("BEGIN IMMEDIATE");
8612
+ try {
8613
+ run();
8614
+ db.exec("COMMIT");
8615
+ } catch (error) {
8616
+ try {
8617
+ db.exec("ROLLBACK");
8618
+ } catch {}
8619
+ throw error;
8620
+ }
8621
+ }
8622
+
8623
+ // ../plugin/src/features/magic-context/migrations.ts
8624
+ function tableExists2(db, name) {
8168
8625
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
8169
8626
  }
8627
+ function columnExists2(db, table, column) {
8628
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
8629
+ return rows.some((row) => row.name === column);
8630
+ }
8170
8631
  var MIGRATIONS = [
8171
8632
  {
8172
8633
  version: 1,
@@ -8625,9 +9086,9 @@ var MIGRATIONS = [
8625
9086
  version: 22,
8626
9087
  description: "v2.0 cache architecture schema foundation",
8627
9088
  up: (db) => {
8628
- const hasSessionMetaTable = tableExists(db, "session_meta");
8629
- const hasCompartmentsTable = tableExists(db, "compartments");
8630
- const hasMemoriesTable = tableExists(db, "memories");
9089
+ const hasSessionMetaTable = tableExists2(db, "session_meta");
9090
+ const hasCompartmentsTable = tableExists2(db, "compartments");
9091
+ const hasMemoriesTable = tableExists2(db, "memories");
8631
9092
  if (hasSessionMetaTable) {
8632
9093
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
8633
9094
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
@@ -8652,7 +9113,7 @@ var MIGRATIONS = [
8652
9113
  ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
8653
9114
  ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
8654
9115
  }
8655
- const hasRecompCompartmentsTable = tableExists(db, "recomp_compartments");
9116
+ const hasRecompCompartmentsTable = tableExists2(db, "recomp_compartments");
8656
9117
  if (hasRecompCompartmentsTable) {
8657
9118
  ensureColumn(db, "recomp_compartments", "p1", "TEXT");
8658
9119
  ensureColumn(db, "recomp_compartments", "p2", "TEXT");
@@ -8963,6 +9424,156 @@ var MIGRATIONS = [
8963
9424
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
8964
9425
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
8965
9426
  }
9427
+ },
9428
+ {
9429
+ version: 33,
9430
+ description: "Compartment chunk embeddings for semantic message-history search",
9431
+ up: (db) => {
9432
+ db.exec(`
9433
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
9434
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9435
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
9436
+ session_id TEXT NOT NULL,
9437
+ project_path TEXT NOT NULL,
9438
+ harness TEXT NOT NULL DEFAULT 'opencode',
9439
+ window_index INTEGER NOT NULL DEFAULT 0,
9440
+ start_ordinal INTEGER NOT NULL,
9441
+ end_ordinal INTEGER NOT NULL,
9442
+ chunk_hash TEXT NOT NULL,
9443
+ model_id TEXT NOT NULL,
9444
+ dims INTEGER NOT NULL,
9445
+ vector BLOB NOT NULL,
9446
+ created_at INTEGER NOT NULL,
9447
+ UNIQUE(compartment_id, window_index)
9448
+ );
9449
+ CREATE INDEX IF NOT EXISTS idx_cce_session
9450
+ ON compartment_chunk_embeddings(session_id);
9451
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model
9452
+ ON compartment_chunk_embeddings(project_path, model_id);
9453
+ `);
9454
+ }
9455
+ },
9456
+ {
9457
+ version: 34,
9458
+ description: "workspace tables and m[0] workspace fingerprint cache reset",
9459
+ up: (db) => {
9460
+ db.exec(`
9461
+ CREATE TABLE IF NOT EXISTS workspaces (
9462
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9463
+ name TEXT NOT NULL UNIQUE,
9464
+ created_at INTEGER NOT NULL,
9465
+ updated_at INTEGER NOT NULL
9466
+ );
9467
+ CREATE TABLE IF NOT EXISTS workspace_members (
9468
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
9469
+ project_path TEXT NOT NULL,
9470
+ display_name TEXT NOT NULL,
9471
+ display_path TEXT NOT NULL,
9472
+ added_at INTEGER NOT NULL,
9473
+ PRIMARY KEY (workspace_id, project_path)
9474
+ );
9475
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique
9476
+ ON workspace_members(project_path);
9477
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name
9478
+ ON workspace_members(workspace_id, display_name);
9479
+ `);
9480
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
9481
+ if (!hasSessionMeta)
9482
+ return;
9483
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
9484
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
9485
+ const clears = [
9486
+ ["cached_m0_bytes", null],
9487
+ ["cached_m1_bytes", null],
9488
+ ["cached_m0_project_memory_epoch", null],
9489
+ ["cached_m0_workspace_fingerprint", null],
9490
+ ["cached_m0_project_user_profile_version", null],
9491
+ ["cached_m0_max_compartment_seq", null],
9492
+ ["cached_m0_max_memory_id", null],
9493
+ ["cached_m0_max_mutation_id", null],
9494
+ ["cached_m0_max_memory_mutation_id", null],
9495
+ ["cached_m0_project_docs_hash", null],
9496
+ ["cached_m0_materialized_at", null],
9497
+ ["cached_m0_session_facts_version", null],
9498
+ ["cached_m0_upgrade_state", null],
9499
+ ["cached_m0_system_hash", null],
9500
+ ["cached_m0_tool_set_hash", null],
9501
+ ["cached_m0_model_key", null],
9502
+ ["cached_m0_last_baseline_end_message_id", null],
9503
+ ["memory_block_cache", ""],
9504
+ ["memory_block_ids", ""],
9505
+ ["memory_block_count", 0]
9506
+ ];
9507
+ const setClauses = [];
9508
+ const values = [];
9509
+ for (const [column, value] of clears) {
9510
+ if (!columns.has(column))
9511
+ continue;
9512
+ setClauses.push(`${column} = ?`);
9513
+ values.push(value);
9514
+ }
9515
+ if (setClauses.length > 0) {
9516
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")}`).run(...values);
9517
+ }
9518
+ }
9519
+ },
9520
+ {
9521
+ version: 35,
9522
+ description: "workspace per-category share defaults and epoch refresh",
9523
+ up: (db) => {
9524
+ db.exec(`
9525
+ CREATE TABLE IF NOT EXISTS workspaces (
9526
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9527
+ name TEXT NOT NULL UNIQUE,
9528
+ created_at INTEGER NOT NULL,
9529
+ updated_at INTEGER NOT NULL,
9530
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
9531
+ );
9532
+ `);
9533
+ if (!columnExists2(db, "workspaces", "share_categories")) {
9534
+ db.exec(`ALTER TABLE workspaces ADD COLUMN share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
9535
+ }
9536
+ db.prepare(`UPDATE workspaces
9537
+ SET share_categories = '["CONSTRAINTS"]'
9538
+ WHERE share_categories IS NULL OR share_categories = ''`).run();
9539
+ if (!tableExists2(db, "workspace_members"))
9540
+ return;
9541
+ const rows = db.prepare(`SELECT DISTINCT project_path AS identity
9542
+ FROM workspace_members
9543
+ WHERE project_path IS NOT NULL AND project_path <> ''
9544
+ ORDER BY project_path ASC`).all();
9545
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((identity) => identity.length > 0);
9546
+ if (identities.length > 0) {
9547
+ bumpEpochsForWorkspaceMemberSet(db, identities, Date.now());
9548
+ }
9549
+ }
9550
+ },
9551
+ {
9552
+ version: 36,
9553
+ description: "session project ownership map for compartment chunk backfill scoping",
9554
+ up: (db) => {
9555
+ db.exec(`
9556
+ CREATE TABLE IF NOT EXISTS session_projects (
9557
+ session_id TEXT NOT NULL,
9558
+ harness TEXT NOT NULL DEFAULT 'opencode',
9559
+ project_path TEXT NOT NULL,
9560
+ updated_at INTEGER NOT NULL,
9561
+ PRIMARY KEY(session_id, harness)
9562
+ );
9563
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
9564
+ ON session_projects(project_path);
9565
+ `);
9566
+ const hasChunkTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartment_chunk_embeddings'").get();
9567
+ if (hasChunkTable) {
9568
+ db.exec(`
9569
+ INSERT OR IGNORE INTO session_projects (session_id, harness, project_path, updated_at)
9570
+ SELECT session_id, harness, MIN(project_path), 0
9571
+ FROM compartment_chunk_embeddings
9572
+ GROUP BY session_id, harness
9573
+ HAVING COUNT(DISTINCT project_path) = 1;
9574
+ `);
9575
+ }
9576
+ }
8966
9577
  }
8967
9578
  ];
8968
9579
  var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -141940,7 +142551,7 @@ var databases = new Map;
141940
142551
  var persistenceByDatabase = new WeakMap;
141941
142552
  var persistenceErrorByDatabase = new WeakMap;
141942
142553
  var lastSchemaFenceRejection = null;
141943
- var LATEST_SUPPORTED_VERSION = 32;
142554
+ var LATEST_SUPPORTED_VERSION = 36;
141944
142555
  function resolveDatabasePath(dbPathOverride) {
141945
142556
  if (dbPathOverride) {
141946
142557
  return { dbDir: dirname2(dbPathOverride), dbPath: dbPathOverride };
@@ -142106,6 +142717,35 @@ function initializeDatabase(db) {
142106
142717
  );
142107
142718
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
142108
142719
 
142720
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
142721
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
142722
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
142723
+ session_id TEXT NOT NULL,
142724
+ project_path TEXT NOT NULL,
142725
+ harness TEXT NOT NULL DEFAULT 'opencode',
142726
+ window_index INTEGER NOT NULL DEFAULT 0,
142727
+ start_ordinal INTEGER NOT NULL,
142728
+ end_ordinal INTEGER NOT NULL,
142729
+ chunk_hash TEXT NOT NULL,
142730
+ model_id TEXT NOT NULL,
142731
+ dims INTEGER NOT NULL,
142732
+ vector BLOB NOT NULL,
142733
+ created_at INTEGER NOT NULL,
142734
+ UNIQUE(compartment_id, window_index)
142735
+ );
142736
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
142737
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
142738
+
142739
+ CREATE TABLE IF NOT EXISTS session_projects (
142740
+ session_id TEXT NOT NULL,
142741
+ harness TEXT NOT NULL DEFAULT 'opencode',
142742
+ project_path TEXT NOT NULL,
142743
+ updated_at INTEGER NOT NULL,
142744
+ PRIMARY KEY(session_id, harness)
142745
+ );
142746
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
142747
+ ON session_projects(project_path);
142748
+
142109
142749
  CREATE TABLE IF NOT EXISTS compartment_events (
142110
142750
  id INTEGER PRIMARY KEY AUTOINCREMENT,
142111
142751
  session_id TEXT NOT NULL,
@@ -142291,6 +142931,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142291
142931
  rekeyed_at INTEGER NOT NULL
142292
142932
  );
142293
142933
 
142934
+ CREATE TABLE IF NOT EXISTS workspaces (
142935
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
142936
+ name TEXT NOT NULL UNIQUE,
142937
+ created_at INTEGER NOT NULL,
142938
+ updated_at INTEGER NOT NULL,
142939
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
142940
+ );
142941
+
142942
+ CREATE TABLE IF NOT EXISTS workspace_members (
142943
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
142944
+ project_path TEXT NOT NULL,
142945
+ display_name TEXT NOT NULL,
142946
+ display_path TEXT NOT NULL,
142947
+ added_at INTEGER NOT NULL,
142948
+ PRIMARY KEY (workspace_id, project_path)
142949
+ );
142950
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
142951
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
142952
+
142294
142953
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
142295
142954
  id INTEGER PRIMARY KEY AUTOINCREMENT,
142296
142955
  table_name TEXT NOT NULL,
@@ -142402,6 +143061,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142402
143061
  deferred_execute_state TEXT,
142403
143062
  cached_m0_bytes BLOB,
142404
143063
  cached_m0_project_memory_epoch INTEGER,
143064
+ cached_m0_workspace_fingerprint TEXT,
142405
143065
  cached_m0_project_user_profile_version INTEGER,
142406
143066
  cached_m0_max_compartment_seq INTEGER,
142407
143067
  cached_m0_max_memory_id INTEGER,
@@ -142560,6 +143220,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142560
143220
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
142561
143221
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
142562
143222
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
143223
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
142563
143224
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
142564
143225
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
142565
143226
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -142613,6 +143274,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142613
143274
  ensureColumn(db, "memories", "importance", "INTEGER");
142614
143275
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
142615
143276
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
143277
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
142616
143278
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
142617
143279
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
142618
143280
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -142644,6 +143306,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142644
143306
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
142645
143307
  updated_at INTEGER NOT NULL DEFAULT 0
142646
143308
  );
143309
+ CREATE TABLE IF NOT EXISTS session_projects (
143310
+ session_id TEXT NOT NULL,
143311
+ harness TEXT NOT NULL DEFAULT 'opencode',
143312
+ project_path TEXT NOT NULL,
143313
+ updated_at INTEGER NOT NULL,
143314
+ PRIMARY KEY(session_id, harness)
143315
+ );
143316
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
143317
+ ON session_projects(project_path);
142647
143318
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
142648
143319
  id INTEGER PRIMARY KEY AUTOINCREMENT,
142649
143320
  session_id TEXT NOT NULL,
@@ -142671,6 +143342,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142671
143342
  new_project_path TEXT NOT NULL,
142672
143343
  rekeyed_at INTEGER NOT NULL
142673
143344
  );
143345
+ CREATE TABLE IF NOT EXISTS workspaces (
143346
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
143347
+ name TEXT NOT NULL UNIQUE,
143348
+ created_at INTEGER NOT NULL,
143349
+ updated_at INTEGER NOT NULL,
143350
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
143351
+ );
143352
+ CREATE TABLE IF NOT EXISTS workspace_members (
143353
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
143354
+ project_path TEXT NOT NULL,
143355
+ display_name TEXT NOT NULL,
143356
+ display_path TEXT NOT NULL,
143357
+ added_at INTEGER NOT NULL,
143358
+ PRIMARY KEY (workspace_id, project_path)
143359
+ );
143360
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
143361
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
142674
143362
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
142675
143363
  id INTEGER PRIMARY KEY AUTOINCREMENT,
142676
143364
  table_name TEXT NOT NULL,
@@ -142692,6 +143380,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142692
143380
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
142693
143381
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
142694
143382
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
143383
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
142695
143384
  }
142696
143385
  function healAllNullColumns(db) {
142697
143386
  healNullTextColumns(db);
@@ -142730,6 +143419,7 @@ function healNullTextColumns(db) {
142730
143419
  ["system_prompt_hash", ""],
142731
143420
  ["stripped_placeholder_ids", ""],
142732
143421
  ["stale_reduce_stripped_ids", ""],
143422
+ ["processed_image_stripped_ids", ""],
142733
143423
  ["memory_block_cache", ""],
142734
143424
  ["memory_block_ids", ""],
142735
143425
  ["compaction_marker_state", ""],
@@ -142774,7 +143464,7 @@ function healNullIntegerColumns(db) {
142774
143464
  }
142775
143465
  }
142776
143466
  function ensureColumn(db, table, column, definition) {
142777
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
143467
+ if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
142778
143468
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
142779
143469
  }
142780
143470
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -143856,10 +144546,10 @@ function mergeDefs(...defs) {
143856
144546
  function cloneDef(schema) {
143857
144547
  return mergeDefs(schema._zod.def);
143858
144548
  }
143859
- function getElementAtPath(obj, path3) {
143860
- if (!path3)
144549
+ function getElementAtPath(obj, path4) {
144550
+ if (!path4)
143861
144551
  return obj;
143862
- return path3.reduce((acc, key) => acc?.[key], obj);
144552
+ return path4.reduce((acc, key) => acc?.[key], obj);
143863
144553
  }
143864
144554
  function promiseAllObject(promisesObj) {
143865
144555
  const keys = Object.keys(promisesObj);
@@ -144267,11 +144957,11 @@ function explicitlyAborted(x, startIndex = 0) {
144267
144957
  }
144268
144958
  return false;
144269
144959
  }
144270
- function prefixIssues(path3, issues) {
144960
+ function prefixIssues(path4, issues) {
144271
144961
  return issues.map((iss) => {
144272
144962
  var _a2;
144273
144963
  (_a2 = iss).path ?? (_a2.path = []);
144274
- iss.path.unshift(path3);
144964
+ iss.path.unshift(path4);
144275
144965
  return iss;
144276
144966
  });
144277
144967
  }
@@ -144418,16 +145108,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
144418
145108
  }
144419
145109
  function formatError(error, mapper = (issue2) => issue2.message) {
144420
145110
  const fieldErrors = { _errors: [] };
144421
- const processError = (error2, path3 = []) => {
145111
+ const processError = (error2, path4 = []) => {
144422
145112
  for (const issue2 of error2.issues) {
144423
145113
  if (issue2.code === "invalid_union" && issue2.errors.length) {
144424
- issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
145114
+ issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
144425
145115
  } else if (issue2.code === "invalid_key") {
144426
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
145116
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
144427
145117
  } else if (issue2.code === "invalid_element") {
144428
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
145118
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
144429
145119
  } else {
144430
- const fullpath = [...path3, ...issue2.path];
145120
+ const fullpath = [...path4, ...issue2.path];
144431
145121
  if (fullpath.length === 0) {
144432
145122
  fieldErrors._errors.push(mapper(issue2));
144433
145123
  } else {
@@ -144454,17 +145144,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
144454
145144
  }
144455
145145
  function treeifyError(error, mapper = (issue2) => issue2.message) {
144456
145146
  const result = { errors: [] };
144457
- const processError = (error2, path3 = []) => {
145147
+ const processError = (error2, path4 = []) => {
144458
145148
  var _a2, _b;
144459
145149
  for (const issue2 of error2.issues) {
144460
145150
  if (issue2.code === "invalid_union" && issue2.errors.length) {
144461
- issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
145151
+ issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
144462
145152
  } else if (issue2.code === "invalid_key") {
144463
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
145153
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
144464
145154
  } else if (issue2.code === "invalid_element") {
144465
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
145155
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
144466
145156
  } else {
144467
- const fullpath = [...path3, ...issue2.path];
145157
+ const fullpath = [...path4, ...issue2.path];
144468
145158
  if (fullpath.length === 0) {
144469
145159
  result.errors.push(mapper(issue2));
144470
145160
  continue;
@@ -144496,8 +145186,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
144496
145186
  }
144497
145187
  function toDotPath(_path) {
144498
145188
  const segs = [];
144499
- const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
144500
- for (const seg of path3) {
145189
+ const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
145190
+ for (const seg of path4) {
144501
145191
  if (typeof seg === "number")
144502
145192
  segs.push(`[${seg}]`);
144503
145193
  else if (typeof seg === "symbol")
@@ -156956,13 +157646,13 @@ function resolveRef(ref, ctx) {
156956
157646
  if (!ref.startsWith("#")) {
156957
157647
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
156958
157648
  }
156959
- const path3 = ref.slice(1).split("/").filter(Boolean);
156960
- if (path3.length === 0) {
157649
+ const path4 = ref.slice(1).split("/").filter(Boolean);
157650
+ if (path4.length === 0) {
156961
157651
  return ctx.rootSchema;
156962
157652
  }
156963
157653
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
156964
- if (path3[0] === defsKey) {
156965
- const key = path3[1];
157654
+ if (path4[0] === defsKey) {
157655
+ const key = path4[1];
156966
157656
  if (!key || !ctx.defs[key]) {
156967
157657
  throw new Error(`Reference not found: ${ref}`);
156968
157658
  }
@@ -157450,7 +158140,8 @@ var BaseEmbeddingConfigSchema = exports_external.object({
157450
158140
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
157451
158141
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
157452
158142
  input_type: exports_external.string().optional().describe("Optional input_type sent in the embedding request body. Required by some openai-compatible providers (e.g. NVIDIA NIM expects 'query' or 'passage'). Omitted from the request when unset."),
157453
- truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset.")
158143
+ truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset."),
158144
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
157454
158145
  }).superRefine((data, ctx) => {
157455
158146
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
157456
158147
  ctx.addIssue({
@@ -157471,7 +158162,8 @@ var EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data) => {
157471
158162
  if (data.provider === "local") {
157472
158163
  return {
157473
158164
  provider: "local",
157474
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
158165
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
158166
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
157475
158167
  };
157476
158168
  }
157477
158169
  if (data.provider === "openai-compatible") {
@@ -157484,7 +158176,8 @@ var EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data) => {
157484
158176
  endpoint: data.endpoint?.trim() ?? "",
157485
158177
  ...apiKey ? { api_key: apiKey } : {},
157486
158178
  ...inputType ? { input_type: inputType } : {},
157487
- ...truncate ? { truncate } : {}
158179
+ ...truncate ? { truncate } : {},
158180
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
157488
158181
  };
157489
158182
  }
157490
158183
  return { provider: "off" };
@@ -157733,31 +158426,31 @@ function getUserConfigPaths() {
157733
158426
  return [`${basePath}.jsonc`, `${basePath}.json`];
157734
158427
  }
157735
158428
  function resolveFirstExisting(paths) {
157736
- return paths.find((path3) => existsSync5(path3));
158429
+ return paths.find((path4) => existsSync5(path4));
157737
158430
  }
157738
- function loadConfigFile(path3, scope) {
158431
+ function loadConfigFile(path4, scope) {
157739
158432
  try {
157740
- const rawText = readFileSync3(path3, "utf-8");
158433
+ const rawText = readFileSync3(path4, "utf-8");
157741
158434
  const substituted = substituteConfigVariables({
157742
158435
  text: rawText,
157743
- configPath: path3,
158436
+ configPath: path4,
157744
158437
  isProjectConfig: scope === "project"
157745
158438
  });
157746
158439
  return {
157747
- path: path3,
158440
+ path: path4,
157748
158441
  scope,
157749
158442
  config: import_comment_json.parse(substituted.text),
157750
- warnings: substituted.warnings.map((warning) => `${path3}: ${warning}`),
158443
+ warnings: substituted.warnings.map((warning) => `${path4}: ${warning}`),
157751
158444
  loadOutcome: substituted.warnings.length > 0 ? "substitution-failure" : "ok"
157752
158445
  };
157753
158446
  } catch (error51) {
157754
158447
  const message = error51 instanceof Error ? error51.message : String(error51);
157755
158448
  return {
157756
- path: path3,
158449
+ path: path4,
157757
158450
  scope,
157758
158451
  config: {},
157759
158452
  warnings: [
157760
- `${path3}: failed to load config: ${message}; using defaults for this file.`
158453
+ `${path4}: failed to load config: ${message}; using defaults for this file.`
157761
158454
  ],
157762
158455
  loadOutcome: typeof error51.code === "string" ? "project-file-io-error" : "project-file-parse-error"
157763
158456
  };
@@ -157923,9 +158616,9 @@ function bindSubstitutionFailures(loaded) {
157923
158616
  }
157924
158617
  const emptyPaths = collectEmptyStringPaths(loaded.config);
157925
158618
  return loaded.warnings.map((message) => {
157926
- const matchedPath = emptyPaths.find((path3) => {
157927
- const tail = path3.split(".").at(-1) ?? path3;
157928
- return message.includes(path3) || message.toLowerCase().includes(tail.toLowerCase());
158619
+ const matchedPath = emptyPaths.find((path4) => {
158620
+ const tail = path4.split(".").at(-1) ?? path4;
158621
+ return message.includes(path4) || message.toLowerCase().includes(tail.toLowerCase());
157929
158622
  });
157930
158623
  return {
157931
158624
  keyPath: matchedPath ?? "<unknown>",
@@ -158013,6 +158706,441 @@ function loadPiConfigDetailed(opts = {}) {
158013
158706
  // ../plugin/src/features/magic-context/memory/embedding.ts
158014
158707
  init_logger();
158015
158708
 
158709
+ // ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
158710
+ import { createHash as createHash4 } from "node:crypto";
158711
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512;
158712
+ var loadFtsRowsStatements = new WeakMap;
158713
+ var existingHashStatements = new WeakMap;
158714
+ var existingHashByProjectStatements = new WeakMap;
158715
+ var deleteByCompartmentStatements = new WeakMap;
158716
+ var insertEmbeddingStatements = new WeakMap;
158717
+ var distinctModelStatements = new WeakMap;
158718
+ var clearProjectStatements = new WeakMap;
158719
+ var clearProjectModelStatements = new WeakMap;
158720
+ var searchRowsStatements = new WeakMap;
158721
+ var searchRowsByModelStatements = new WeakMap;
158722
+ var backfillCandidateStatements = new WeakMap;
158723
+ function getLoadFtsRowsStatement(db) {
158724
+ let stmt = loadFtsRowsStatements.get(db);
158725
+ if (!stmt) {
158726
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
158727
+ FROM message_history_fts
158728
+ WHERE session_id = ?
158729
+ AND message_ordinal >= ?
158730
+ AND message_ordinal <= ?
158731
+ AND role IN ('user', 'assistant')
158732
+ ORDER BY message_ordinal ASC`);
158733
+ loadFtsRowsStatements.set(db, stmt);
158734
+ }
158735
+ return stmt;
158736
+ }
158737
+ function getExistingHashStatement(db, scopedToProject) {
158738
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
158739
+ let stmt = map2.get(db);
158740
+ if (!stmt) {
158741
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
158742
+ FROM compartment_chunk_embeddings
158743
+ WHERE compartment_id = ?
158744
+ AND model_id = ?
158745
+ ${scopedToProject ? "AND project_path = ?" : ""}
158746
+ ORDER BY window_index ASC`);
158747
+ map2.set(db, stmt);
158748
+ }
158749
+ return stmt;
158750
+ }
158751
+ function getDeleteByCompartmentStatement(db) {
158752
+ let stmt = deleteByCompartmentStatements.get(db);
158753
+ if (!stmt) {
158754
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
158755
+ deleteByCompartmentStatements.set(db, stmt);
158756
+ }
158757
+ return stmt;
158758
+ }
158759
+ function getInsertEmbeddingStatement(db) {
158760
+ let stmt = insertEmbeddingStatements.get(db);
158761
+ if (!stmt) {
158762
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
158763
+ compartment_id, session_id, project_path, harness, window_index,
158764
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
158765
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
158766
+ insertEmbeddingStatements.set(db, stmt);
158767
+ }
158768
+ return stmt;
158769
+ }
158770
+ function getDistinctModelStatement(db) {
158771
+ let stmt = distinctModelStatements.get(db);
158772
+ if (!stmt) {
158773
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
158774
+ FROM compartment_chunk_embeddings
158775
+ WHERE project_path = ?`);
158776
+ distinctModelStatements.set(db, stmt);
158777
+ }
158778
+ return stmt;
158779
+ }
158780
+ function getClearProjectStatement(db) {
158781
+ let stmt = clearProjectStatements.get(db);
158782
+ if (!stmt) {
158783
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
158784
+ clearProjectStatements.set(db, stmt);
158785
+ }
158786
+ return stmt;
158787
+ }
158788
+ function getClearProjectModelStatement(db) {
158789
+ let stmt = clearProjectModelStatements.get(db);
158790
+ if (!stmt) {
158791
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
158792
+ clearProjectModelStatements.set(db, stmt);
158793
+ }
158794
+ return stmt;
158795
+ }
158796
+ function getSearchRowsStatement(db, withModel) {
158797
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
158798
+ let stmt = map2.get(db);
158799
+ if (!stmt) {
158800
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
158801
+ e.session_id AS sessionId,
158802
+ c.title AS title,
158803
+ c.start_message AS compartmentStart,
158804
+ c.end_message AS compartmentEnd,
158805
+ e.window_index AS windowIndex,
158806
+ e.start_ordinal AS windowStart,
158807
+ e.end_ordinal AS windowEnd,
158808
+ e.chunk_hash AS chunkHash,
158809
+ e.model_id AS modelId,
158810
+ e.dims AS dims,
158811
+ e.vector AS vector
158812
+ FROM compartment_chunk_embeddings e
158813
+ JOIN compartments c ON c.id = e.compartment_id
158814
+ WHERE e.session_id = ?
158815
+ AND e.project_path = ?
158816
+ ${withModel ? "AND e.model_id = ?" : ""}
158817
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
158818
+ map2.set(db, stmt);
158819
+ }
158820
+ return stmt;
158821
+ }
158822
+ function isFinitePositiveInteger(value) {
158823
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
158824
+ }
158825
+ function normalizeCompartmentChunkMaxInputTokens(value) {
158826
+ if (!isFinitePositiveInteger(value)) {
158827
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
158828
+ }
158829
+ return Math.max(1, Math.floor(value));
158830
+ }
158831
+ function normalizeContent(text) {
158832
+ return text.replace(/\s+/g, " ").trim();
158833
+ }
158834
+ function formatOrdinalRange(start, end) {
158835
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
158836
+ }
158837
+ function rolePrefix(role) {
158838
+ if (role === "user")
158839
+ return "U";
158840
+ if (role === "assistant")
158841
+ return "A";
158842
+ return null;
158843
+ }
158844
+ function parseOrdinal(value) {
158845
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
158846
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
158847
+ }
158848
+ function parseCanonicalLineRange(line) {
158849
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
158850
+ if (!match)
158851
+ return null;
158852
+ const start = Number.parseInt(match[1], 10);
158853
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
158854
+ if (!Number.isFinite(start) || !Number.isFinite(end))
158855
+ return null;
158856
+ return { start, end };
158857
+ }
158858
+ function hashChunkText(text) {
158859
+ return createHash4("sha256").update(text).digest("hex");
158860
+ }
158861
+ function vectorBlob(vector) {
158862
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
158863
+ }
158864
+ function toFloat32Array(blob) {
158865
+ if (blob instanceof Uint8Array) {
158866
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
158867
+ return new Float32Array(buffer2);
158868
+ }
158869
+ return new Float32Array(blob.slice(0));
158870
+ }
158871
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
158872
+ if (endOrdinal < startOrdinal)
158873
+ return "";
158874
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
158875
+ const lines = [];
158876
+ let current = null;
158877
+ const flush2 = () => {
158878
+ if (!current || current.parts.length === 0)
158879
+ return;
158880
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
158881
+ current = null;
158882
+ };
158883
+ for (const row of rows) {
158884
+ const ordinal = parseOrdinal(row.messageOrdinal);
158885
+ const prefix = rolePrefix(row.role);
158886
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
158887
+ if (ordinal === null || prefix === null || content.length === 0)
158888
+ continue;
158889
+ if (current && current.role === prefix) {
158890
+ current.end = ordinal;
158891
+ current.parts.push(content);
158892
+ continue;
158893
+ }
158894
+ flush2();
158895
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
158896
+ }
158897
+ flush2();
158898
+ return lines.join(`
158899
+ `);
158900
+ }
158901
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
158902
+ const lines = [];
158903
+ for (const rawLine of chunkText.split(/\r?\n/)) {
158904
+ const line = rawLine.trim();
158905
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
158906
+ if (!match)
158907
+ continue;
158908
+ const lineStart = Number.parseInt(match[2], 10);
158909
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
158910
+ if (startOrdinal != null && lineEnd < startOrdinal)
158911
+ continue;
158912
+ if (endOrdinal != null && lineStart > endOrdinal)
158913
+ continue;
158914
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
158915
+ const ordinalSpan = lineEnd - lineStart + 1;
158916
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
158917
+ if (ordinalSpan === rawParts.length) {
158918
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
158919
+ if (part.startsWith("TC:"))
158920
+ return false;
158921
+ if (startOrdinal != null && ordinal < startOrdinal)
158922
+ return false;
158923
+ if (endOrdinal != null && ordinal > endOrdinal)
158924
+ return false;
158925
+ return true;
158926
+ });
158927
+ if (retained.length === 0)
158928
+ continue;
158929
+ const retainedStart = retained[0].ordinal;
158930
+ const retainedEnd = retained[retained.length - 1].ordinal;
158931
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
158932
+ continue;
158933
+ }
158934
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
158935
+ if (parts.length === 0)
158936
+ continue;
158937
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
158938
+ }
158939
+ return lines.join(`
158940
+ `);
158941
+ }
158942
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
158943
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
158944
+ if (lines.length === 0 || endOrdinal < startOrdinal)
158945
+ return [];
158946
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
158947
+ const fullText = lines.join(`
158948
+ `);
158949
+ if (estimateTokens(fullText) <= normalizedMax) {
158950
+ return [
158951
+ {
158952
+ windowIndex: 0,
158953
+ startOrdinal,
158954
+ endOrdinal,
158955
+ text: fullText,
158956
+ chunkHash: hashChunkText(fullText)
158957
+ }
158958
+ ];
158959
+ }
158960
+ const windows = [];
158961
+ let currentLines = [];
158962
+ let currentStart = null;
158963
+ let currentEnd = null;
158964
+ let currentTokens = 0;
158965
+ const flush2 = () => {
158966
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
158967
+ return;
158968
+ const text = currentLines.join(`
158969
+ `);
158970
+ windows.push({
158971
+ windowIndex: windows.length + 1,
158972
+ startOrdinal: currentStart,
158973
+ endOrdinal: currentEnd,
158974
+ text,
158975
+ chunkHash: hashChunkText(text)
158976
+ });
158977
+ currentLines = [];
158978
+ currentStart = null;
158979
+ currentEnd = null;
158980
+ currentTokens = 0;
158981
+ };
158982
+ for (const line of lines) {
158983
+ const range = parseCanonicalLineRange(line);
158984
+ const lineStart = range?.start ?? startOrdinal;
158985
+ const lineEnd = range?.end ?? lineStart;
158986
+ const lineTokens = estimateTokens(line);
158987
+ if (currentLines.length > 0 && currentTokens + lineTokens > normalizedMax) {
158988
+ flush2();
158989
+ }
158990
+ if (currentLines.length === 0) {
158991
+ currentStart = lineStart;
158992
+ }
158993
+ currentLines.push(line);
158994
+ currentEnd = lineEnd;
158995
+ currentTokens += lineTokens;
158996
+ }
158997
+ flush2();
158998
+ return windows;
158999
+ }
159000
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
159001
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
159002
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
159003
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
159004
+ }
159005
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
159006
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
159007
+ if (existing.size !== windows.length)
159008
+ return false;
159009
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
159010
+ }
159011
+ function replaceCompartmentChunkEmbeddings(db, rows) {
159012
+ if (rows.length === 0)
159013
+ return;
159014
+ const compartmentId = rows[0].compartmentId;
159015
+ const now = Date.now();
159016
+ db.transaction(() => {
159017
+ getDeleteByCompartmentStatement(db).run(compartmentId);
159018
+ const insert = getInsertEmbeddingStatement(db);
159019
+ for (const row of rows) {
159020
+ insert.run(row.compartmentId, row.sessionId, row.projectPath, getHarness(), row.window.windowIndex, row.window.startOrdinal, row.window.endOrdinal, row.window.chunkHash, row.modelId, row.vector.length, vectorBlob(row.vector), row.createdAt ?? now);
159021
+ }
159022
+ })();
159023
+ }
159024
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
159025
+ const rows = getDistinctModelStatement(db).all(projectPath);
159026
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
159027
+ }
159028
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
159029
+ if (modelId) {
159030
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
159031
+ }
159032
+ return getClearProjectStatement(db).run(projectPath).changes;
159033
+ }
159034
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
159035
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
159036
+ return rows.filter((row) => typeof row.compartmentId === "number" && typeof row.sessionId === "string" && typeof row.title === "string" && typeof row.compartmentStart === "number" && typeof row.compartmentEnd === "number" && typeof row.windowIndex === "number" && typeof row.windowStart === "number" && typeof row.windowEnd === "number" && typeof row.chunkHash === "string" && typeof row.modelId === "string" && typeof row.dims === "number" && (row.vector instanceof Uint8Array || row.vector instanceof ArrayBuffer)).map((row) => ({
159037
+ compartmentId: row.compartmentId,
159038
+ sessionId: row.sessionId,
159039
+ title: row.title,
159040
+ startOrdinal: row.compartmentStart,
159041
+ endOrdinal: row.compartmentEnd,
159042
+ windowIndex: row.windowIndex,
159043
+ windowStartOrdinal: row.windowStart,
159044
+ windowEndOrdinal: row.windowEnd,
159045
+ chunkHash: row.chunkHash,
159046
+ modelId: row.modelId,
159047
+ dims: row.dims,
159048
+ vector: toFloat32Array(row.vector)
159049
+ }));
159050
+ }
159051
+ function mapBackfillCandidateRows(rows) {
159052
+ return rows.filter((row) => {
159053
+ if (row === null || typeof row !== "object")
159054
+ return false;
159055
+ const candidate = row;
159056
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
159057
+ }).map((row) => ({
159058
+ id: row.id,
159059
+ sessionId: row.sessionId,
159060
+ startMessage: row.startMessage,
159061
+ endMessage: row.endMessage,
159062
+ title: row.title
159063
+ }));
159064
+ }
159065
+ var sessionBackfillCandidateStatements = new WeakMap;
159066
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
159067
+ if (excludeIds && excludeIds.length > 0) {
159068
+ const placeholders2 = excludeIds.map(() => "?").join(", ");
159069
+ const stmt2 = db.prepare(`SELECT c.id AS id,
159070
+ c.session_id AS sessionId,
159071
+ c.start_message AS startMessage,
159072
+ c.end_message AS endMessage,
159073
+ c.title AS title
159074
+ FROM compartments c
159075
+ JOIN session_projects sp
159076
+ ON sp.session_id = c.session_id
159077
+ AND sp.harness = c.harness
159078
+ AND sp.project_path = ?
159079
+ WHERE c.session_id = ?
159080
+ AND c.start_message IS NOT NULL
159081
+ AND c.end_message IS NOT NULL
159082
+ AND c.id NOT IN (${placeholders2})
159083
+ AND NOT EXISTS (
159084
+ SELECT 1
159085
+ FROM compartment_chunk_embeddings current
159086
+ WHERE current.compartment_id = c.id
159087
+ AND current.project_path = ?
159088
+ AND current.model_id = ?
159089
+ )
159090
+ ORDER BY c.start_message ASC, c.id ASC
159091
+ LIMIT ?`);
159092
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
159093
+ return mapBackfillCandidateRows(rows2);
159094
+ }
159095
+ let stmt = sessionBackfillCandidateStatements.get(db);
159096
+ if (!stmt) {
159097
+ stmt = db.prepare(`SELECT c.id AS id,
159098
+ c.session_id AS sessionId,
159099
+ c.start_message AS startMessage,
159100
+ c.end_message AS endMessage,
159101
+ c.title AS title
159102
+ FROM compartments c
159103
+ JOIN session_projects sp
159104
+ ON sp.session_id = c.session_id
159105
+ AND sp.harness = c.harness
159106
+ AND sp.project_path = ?
159107
+ WHERE c.session_id = ?
159108
+ AND c.start_message IS NOT NULL
159109
+ AND c.end_message IS NOT NULL
159110
+ AND NOT EXISTS (
159111
+ SELECT 1
159112
+ FROM compartment_chunk_embeddings current
159113
+ WHERE current.compartment_id = c.id
159114
+ AND current.project_path = ?
159115
+ AND current.model_id = ?
159116
+ )
159117
+ ORDER BY c.start_message ASC, c.id ASC
159118
+ LIMIT ?`);
159119
+ sessionBackfillCandidateStatements.set(db, stmt);
159120
+ }
159121
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
159122
+ return mapBackfillCandidateRows(rows);
159123
+ }
159124
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
159125
+ const row = db.prepare(`SELECT COUNT(*) AS n
159126
+ FROM compartments c
159127
+ JOIN session_projects sp
159128
+ ON sp.session_id = c.session_id
159129
+ AND sp.harness = c.harness
159130
+ AND sp.project_path = ?
159131
+ WHERE c.session_id = ?
159132
+ AND c.start_message IS NOT NULL
159133
+ AND c.end_message IS NOT NULL
159134
+ AND NOT EXISTS (
159135
+ SELECT 1
159136
+ FROM compartment_chunk_embeddings current
159137
+ WHERE current.compartment_id = c.id
159138
+ AND current.project_path = ?
159139
+ AND current.model_id = ?
159140
+ )`).get(projectPath, sessionId, projectPath, modelId);
159141
+ return typeof row?.n === "number" ? row.n : 0;
159142
+ }
159143
+
158016
159144
  // ../plugin/src/features/magic-context/memory/cosine-similarity.ts
158017
159145
  function cosineSimilarity(a, b) {
158018
159146
  if (a.length !== b.length) {
@@ -158031,13 +159159,13 @@ function cosineSimilarity(a, b) {
158031
159159
  }
158032
159160
 
158033
159161
  // ../plugin/src/features/magic-context/memory/normalize-hash.ts
158034
- import { createHash as createHash2 } from "node:crypto";
159162
+ import { createHash as createHash5 } from "node:crypto";
158035
159163
  function normalizeMemoryContent(content) {
158036
159164
  return content.toLowerCase().replace(/\s+/g, " ").trim();
158037
159165
  }
158038
159166
  function computeNormalizedHash(content) {
158039
159167
  const normalized = normalizeMemoryContent(content);
158040
- return createHash2("md5").update(normalized).digest("hex");
159168
+ return createHash5("md5").update(normalized).digest("hex");
158041
159169
  }
158042
159170
 
158043
159171
  // ../plugin/src/features/magic-context/memory/embedding-identity.ts
@@ -158181,19 +159309,19 @@ function isArrayLikeNumber(value) {
158181
159309
  }
158182
159310
  return arr.length === 0 || typeof arr[0] === "number";
158183
159311
  }
158184
- function toFloat32Array(values) {
159312
+ function toFloat32Array2(values) {
158185
159313
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
158186
159314
  }
158187
159315
  function extractBatchEmbeddings(result, expectedCount) {
158188
159316
  const { data } = result;
158189
159317
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
158190
- return data.map((entry) => toFloat32Array(entry));
159318
+ return data.map((entry) => toFloat32Array2(entry));
158191
159319
  }
158192
159320
  if (!isArrayLikeNumber(data)) {
158193
159321
  log("[magic-context] embedding batch returned unexpected data shape");
158194
159322
  return Array.from({ length: expectedCount }, () => null);
158195
159323
  }
158196
- const flatData = toFloat32Array(data);
159324
+ const flatData = toFloat32Array2(data);
158197
159325
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
158198
159326
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
158199
159327
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -158208,6 +159336,7 @@ function extractBatchEmbeddings(result, expectedCount) {
158208
159336
 
158209
159337
  class LocalEmbeddingProvider {
158210
159338
  modelId;
159339
+ maxInputTokens;
158211
159340
  model;
158212
159341
  pipeline = null;
158213
159342
  initPromise = null;
@@ -158215,8 +159344,9 @@ class LocalEmbeddingProvider {
158215
159344
  disposing = false;
158216
159345
  disposePromise = null;
158217
159346
  inFlightWaiters = [];
158218
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
159347
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
158219
159348
  this.model = model;
159349
+ this.maxInputTokens = maxInputTokens;
158220
159350
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
158221
159351
  }
158222
159352
  async initialize() {
@@ -158471,6 +159601,7 @@ var FETCH_TIMEOUT_MS = 30000;
158471
159601
 
158472
159602
  class OpenAICompatibleEmbeddingProvider {
158473
159603
  modelId;
159604
+ maxInputTokens;
158474
159605
  endpoint;
158475
159606
  model;
158476
159607
  apiKey;
@@ -158487,11 +159618,13 @@ class OpenAICompatibleEmbeddingProvider {
158487
159618
  this.apiKey = options.apiKey?.trim() ?? "";
158488
159619
  this.inputType = options.inputType?.trim() ?? "";
158489
159620
  this.truncate = options.truncate?.trim() ?? "";
159621
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
158490
159622
  this.modelId = getEmbeddingProviderIdentity({
158491
159623
  provider: "openai-compatible",
158492
159624
  endpoint: this.endpoint,
158493
159625
  model: this.model,
158494
- ...this.apiKey ? { api_key: this.apiKey } : {}
159626
+ ...this.apiKey ? { api_key: this.apiKey } : {},
159627
+ ...this.inputType ? { input_type: this.inputType } : {}
158495
159628
  });
158496
159629
  }
158497
159630
  async initialize() {
@@ -158695,9 +159828,9 @@ function isEmbeddingRow(row) {
158695
159828
  if (row === null || typeof row !== "object")
158696
159829
  return false;
158697
159830
  const candidate = row;
158698
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
159831
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
158699
159832
  }
158700
- function toFloat32Array2(blob) {
159833
+ function toFloat32Array3(blob) {
158701
159834
  if (blob instanceof Uint8Array) {
158702
159835
  const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
158703
159836
  return new Float32Array(buffer2);
@@ -158715,7 +159848,7 @@ function getSaveEmbeddingStatement(db) {
158715
159848
  function getLoadAllEmbeddingsStatement(db) {
158716
159849
  let stmt = loadAllEmbeddingsStatements.get(db);
158717
159850
  if (!stmt) {
158718
- stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
159851
+ stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding, memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
158719
159852
  loadAllEmbeddingsStatements.set(db, stmt);
158720
159853
  }
158721
159854
  return stmt;
@@ -158744,7 +159877,10 @@ function loadAllEmbeddings(db, projectPath) {
158744
159877
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
158745
159878
  const embeddings = new Map;
158746
159879
  for (const row of rows) {
158747
- embeddings.set(row.memoryId, toFloat32Array2(row.embedding));
159880
+ embeddings.set(row.memoryId, {
159881
+ embedding: toFloat32Array3(row.embedding),
159882
+ modelId: row.modelId
159883
+ });
158748
159884
  }
158749
159885
  return embeddings;
158750
159886
  }
@@ -158757,7 +159893,7 @@ function getDistinctStoredModelIds(db, projectPath) {
158757
159893
  }
158758
159894
 
158759
159895
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
158760
- import { createHash as createHash3, randomUUID } from "node:crypto";
159896
+ import { createHash as createHash6, randomUUID } from "node:crypto";
158761
159897
  init_logger();
158762
159898
 
158763
159899
  // ../plugin/src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
@@ -158765,7 +159901,7 @@ var saveStatements = new WeakMap;
158765
159901
  var loadProjectStatements = new WeakMap;
158766
159902
  var loadUnembeddedStatements = new WeakMap;
158767
159903
  var countEmbeddedStatements = new WeakMap;
158768
- var clearProjectStatements = new WeakMap;
159904
+ var clearProjectStatements2 = new WeakMap;
158769
159905
  var distinctModelIdStatements = new WeakMap;
158770
159906
  function getSaveStatement(db) {
158771
159907
  let stmt = saveStatements.get(db);
@@ -158813,12 +159949,12 @@ function getCountEmbeddedStatement(db) {
158813
159949
  }
158814
159950
  return stmt;
158815
159951
  }
158816
- function getClearProjectStatement(db) {
158817
- let stmt = clearProjectStatements.get(db);
159952
+ function getClearProjectStatement2(db) {
159953
+ let stmt = clearProjectStatements2.get(db);
158818
159954
  if (!stmt) {
158819
159955
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
158820
159956
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
158821
- clearProjectStatements.set(db, stmt);
159957
+ clearProjectStatements2.set(db, stmt);
158822
159958
  }
158823
159959
  return stmt;
158824
159960
  }
@@ -158854,7 +159990,7 @@ function countEmbeddedCommits(db, projectPath) {
158854
159990
  return row?.count ?? 0;
158855
159991
  }
158856
159992
  function clearProjectCommitEmbeddings(db, projectPath) {
158857
- return getClearProjectStatement(db).run(projectPath).changes;
159993
+ return getClearProjectStatement2(db).run(projectPath).changes;
158858
159994
  }
158859
159995
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
158860
159996
  const rows = getDistinctModelIdStatement(db).all(projectPath);
@@ -159018,9 +160154,83 @@ function invalidateMemory(projectPath, memoryId) {
159018
160154
  cached2?.embeddings.delete(memoryId);
159019
160155
  }
159020
160156
 
160157
+ // ../plugin/src/features/magic-context/session-project-storage.ts
160158
+ var upsertSessionProjectStatements = new WeakMap;
160159
+ var repairSessionChunkProjectStatements = new WeakMap;
160160
+ var repairProjectChunkProjectStatements = new WeakMap;
160161
+ function getUpsertSessionProjectStatement(db) {
160162
+ let stmt = upsertSessionProjectStatements.get(db);
160163
+ if (!stmt) {
160164
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
160165
+ VALUES (?, ?, ?, ?)
160166
+ ON CONFLICT(session_id, harness) DO UPDATE SET
160167
+ project_path = excluded.project_path,
160168
+ updated_at = excluded.updated_at
160169
+ WHERE session_projects.project_path <> excluded.project_path`);
160170
+ upsertSessionProjectStatements.set(db, stmt);
160171
+ }
160172
+ return stmt;
160173
+ }
160174
+ function getRepairSessionChunkProjectStatement(db) {
160175
+ let stmt = repairSessionChunkProjectStatements.get(db);
160176
+ if (!stmt) {
160177
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
160178
+ SET project_path = ?
160179
+ WHERE session_id = ?
160180
+ AND harness = ?
160181
+ AND project_path <> ?`);
160182
+ repairSessionChunkProjectStatements.set(db, stmt);
160183
+ }
160184
+ return stmt;
160185
+ }
160186
+ function getRepairProjectChunkProjectStatement(db) {
160187
+ let stmt = repairProjectChunkProjectStatements.get(db);
160188
+ if (!stmt) {
160189
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
160190
+ SET project_path = (
160191
+ SELECT sp.project_path
160192
+ FROM session_projects sp
160193
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
160194
+ AND sp.harness = compartment_chunk_embeddings.harness
160195
+ LIMIT 1
160196
+ )
160197
+ WHERE EXISTS (
160198
+ SELECT 1
160199
+ FROM session_projects sp
160200
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
160201
+ AND sp.harness = compartment_chunk_embeddings.harness
160202
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
160203
+ AND (
160204
+ sp.project_path = ?
160205
+ OR compartment_chunk_embeddings.project_path = ?
160206
+ )
160207
+ )`);
160208
+ repairProjectChunkProjectStatements.set(db, stmt);
160209
+ }
160210
+ return stmt;
160211
+ }
160212
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
160213
+ if (!sessionId || !projectPath)
160214
+ return;
160215
+ const harness = getHarness();
160216
+ const now = Date.now();
160217
+ db.transaction(() => {
160218
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
160219
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
160220
+ })();
160221
+ }
160222
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
160223
+ if (!projectPath)
160224
+ return 0;
160225
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
160226
+ }
160227
+
159021
160228
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
159022
160229
  var OFF_PROVIDER_IDENTITY = "embedding-provider:off";
159023
160230
  var SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
160231
+ var CHUNK_DRAIN_BATCH_SIZE = 8;
160232
+ var MAX_WINDOWS_PER_EMBED_CALL = 16;
160233
+ var SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
159024
160234
  var projectRegistrations = new Map;
159025
160235
  var loadUnembeddedMemoriesStatements = new WeakMap;
159026
160236
  var globalRegistrationGeneration = 0;
@@ -159029,7 +160239,10 @@ function resolveEmbeddingConfig(config2) {
159029
160239
  if (!config2 || config2.provider === "local") {
159030
160240
  return {
159031
160241
  provider: "local",
159032
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
160242
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
160243
+ ...config2?.max_input_tokens ? {
160244
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
160245
+ } : {}
159033
160246
  };
159034
160247
  }
159035
160248
  if (config2.provider === "openai-compatible") {
@@ -159042,7 +160255,10 @@ function resolveEmbeddingConfig(config2) {
159042
160255
  endpoint: config2.endpoint.trim(),
159043
160256
  ...apiKey ? { api_key: apiKey } : {},
159044
160257
  ...inputType ? { input_type: inputType } : {},
159045
- ...truncate ? { truncate } : {}
160258
+ ...truncate ? { truncate } : {},
160259
+ ...config2.max_input_tokens ? {
160260
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
160261
+ } : {}
159046
160262
  };
159047
160263
  }
159048
160264
  return { provider: "off" };
@@ -159060,10 +160276,11 @@ function createProvider(config2) {
159060
160276
  model: config2.model,
159061
160277
  apiKey: config2.api_key,
159062
160278
  inputType: config2.input_type,
159063
- truncate: config2.truncate
160279
+ truncate: config2.truncate,
160280
+ maxInputTokens: config2.max_input_tokens
159064
160281
  });
159065
160282
  }
159066
- return new LocalEmbeddingProvider(config2.model);
160283
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
159067
160284
  }
159068
160285
  function stableStringify2(value) {
159069
160286
  if (Array.isArray(value)) {
@@ -159076,7 +160293,7 @@ function stableStringify2(value) {
159076
160293
  return JSON.stringify(value);
159077
160294
  }
159078
160295
  function sha256Prefix(value, length = 16) {
159079
- return createHash3("sha256").update(value).digest("hex").slice(0, length);
160296
+ return createHash6("sha256").update(value).digest("hex").slice(0, length);
159080
160297
  }
159081
160298
  function getRuntimeFingerprint(config2) {
159082
160299
  if (config2.provider === "off") {
@@ -159084,6 +160301,18 @@ function getRuntimeFingerprint(config2) {
159084
160301
  }
159085
160302
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
159086
160303
  }
160304
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
160305
+ if (config2.provider === "off") {
160306
+ return OFF_PROVIDER_IDENTITY;
160307
+ }
160308
+ const chunkIdentity = {
160309
+ providerIdentity,
160310
+ chunkerVersion: 1,
160311
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
160312
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
160313
+ };
160314
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
160315
+ }
159087
160316
  function sameFeatures(a, b) {
159088
160317
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
159089
160318
  }
@@ -159100,7 +160329,8 @@ function snapshotFor(registration) {
159100
160329
  features: { ...registration.features },
159101
160330
  enabled,
159102
160331
  gitCommitEnabled,
159103
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
160332
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
160333
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
159104
160334
  };
159105
160335
  }
159106
160336
  function disposeProvider(provider) {
@@ -159120,7 +160350,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
159120
160350
  }
159121
160351
  return false;
159122
160352
  }
159123
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
160353
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
159124
160354
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
159125
160355
  return false;
159126
160356
  }
@@ -159141,6 +160371,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
159141
160371
  wiped = true;
159142
160372
  }
159143
160373
  }
160374
+ if (features.memoryEnabled) {
160375
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
160376
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
160377
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
160378
+ clearChunkEmbeddingsForProject(db, projectIdentity);
160379
+ wiped = true;
160380
+ }
160381
+ }
159144
160382
  })();
159145
160383
  return wiped;
159146
160384
  }
@@ -159148,10 +160386,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
159148
160386
  const resolvedConfig = resolveEmbeddingConfig(config2);
159149
160387
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
159150
160388
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
160389
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
159151
160390
  const prior = projectRegistrations.get(projectIdentity);
159152
160391
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
159153
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
159154
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
160392
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
160393
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
159155
160394
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
159156
160395
  const registration = {
159157
160396
  projectIdentity,
@@ -159163,6 +160402,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
159163
160402
  generation,
159164
160403
  features: { ...features },
159165
160404
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
160405
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
159166
160406
  observationMode: false
159167
160407
  };
159168
160408
  projectRegistrations.set(projectIdentity, registration);
@@ -159185,6 +160425,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
159185
160425
  generation,
159186
160426
  features: { memoryEnabled: false, gitCommitEnabled: false },
159187
160427
  modelId: "off",
160428
+ chunkModelId: "off",
159188
160429
  observationMode: true
159189
160430
  };
159190
160431
  projectRegistrations.set(projectIdentity, registration);
@@ -159195,6 +160436,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
159195
160436
  const registration = projectRegistrations.get(projectIdentity);
159196
160437
  return registration ? snapshotFor(registration) : null;
159197
160438
  }
160439
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
160440
+ const registration = projectRegistrations.get(projectIdentity);
160441
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
160442
+ }
160443
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
160444
+ const registration = projectRegistrations.get(projectIdentity);
160445
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
160446
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
160447
+ }
159198
160448
  function getOrCreateProjectProvider(registration) {
159199
160449
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
159200
160450
  return null;
@@ -159289,6 +160539,131 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
159289
160539
  return 0;
159290
160540
  }
159291
160541
  }
160542
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
160543
+ const noWork = [];
160544
+ if (candidates.length === 0)
160545
+ return { embedded: 0, noWork };
160546
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
160547
+ const prepared = [];
160548
+ for (const candidate of candidates) {
160549
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
160550
+ if (canonicalText.length === 0) {
160551
+ noWork.push(candidate.id);
160552
+ continue;
160553
+ }
160554
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
160555
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
160556
+ noWork.push(candidate.id);
160557
+ continue;
160558
+ }
160559
+ prepared.push({ candidate, windows });
160560
+ }
160561
+ if (prepared.length === 0)
160562
+ return { embedded: 0, noWork };
160563
+ let embedded = 0;
160564
+ let i = 0;
160565
+ while (i < prepared.length) {
160566
+ if (signal?.aborted)
160567
+ break;
160568
+ const slice = [];
160569
+ let windowCount = 0;
160570
+ do {
160571
+ const item = prepared[i];
160572
+ slice.push(item);
160573
+ windowCount += item.windows.length;
160574
+ i += 1;
160575
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
160576
+ const texts = [];
160577
+ for (const item of slice)
160578
+ texts.push(...item.windows.map((w) => w.text));
160579
+ try {
160580
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
160581
+ if (!result)
160582
+ continue;
160583
+ if (signal?.aborted)
160584
+ break;
160585
+ let offset = 0;
160586
+ for (const item of slice) {
160587
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
160588
+ offset += item.windows.length;
160589
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
160590
+ continue;
160591
+ }
160592
+ const rows = item.windows.map((window, index) => ({
160593
+ compartmentId: item.candidate.id,
160594
+ sessionId: item.candidate.sessionId,
160595
+ projectPath: projectIdentity,
160596
+ window,
160597
+ modelId,
160598
+ vector: vectors[index]
160599
+ }));
160600
+ replaceCompartmentChunkEmbeddings(db, rows);
160601
+ embedded += 1;
160602
+ }
160603
+ } catch (error51) {
160604
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
160605
+ }
160606
+ }
160607
+ return { embedded, noWork };
160608
+ }
160609
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
160610
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
160611
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
160612
+ return { status: "disabled", embedded: 0, total: 0 };
160613
+ }
160614
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
160615
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
160616
+ if (total === 0)
160617
+ return { status: "nothing", embedded: 0, total: 0 };
160618
+ const holderId = `session-embed-${randomUUID()}`;
160619
+ const lease = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
160620
+ if (!lease.acquired)
160621
+ return { status: "busy", embedded: 0, total };
160622
+ const renewal = setInterval(() => {
160623
+ try {
160624
+ renewGitSweepLease(db, projectIdentity, holderId);
160625
+ } catch {}
160626
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
160627
+ renewal.unref?.();
160628
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
160629
+ const skipIds = [];
160630
+ let embedded = 0;
160631
+ let aborted2 = false;
160632
+ let providerStalled = false;
160633
+ try {
160634
+ options?.onProgress?.({ embedded, total });
160635
+ for (;; ) {
160636
+ if (options?.signal?.aborted) {
160637
+ aborted2 = true;
160638
+ break;
160639
+ }
160640
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
160641
+ if (candidates.length === 0)
160642
+ break;
160643
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
160644
+ for (const id of noWork)
160645
+ skipIds.push(id);
160646
+ if (n === 0 && noWork.length === 0) {
160647
+ providerStalled = true;
160648
+ break;
160649
+ }
160650
+ embedded += n;
160651
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
160652
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
160653
+ }
160654
+ } finally {
160655
+ clearInterval(renewal);
160656
+ releaseGitSweepLease(db, projectIdentity, holderId);
160657
+ }
160658
+ if (aborted2)
160659
+ return { status: "aborted", embedded, total };
160660
+ if (providerStalled) {
160661
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
160662
+ if (remaining > 0)
160663
+ return { status: "stalled", embedded, total, remaining };
160664
+ }
160665
+ return { status: "done", embedded, total };
160666
+ }
159292
160667
 
159293
160668
  // ../plugin/src/features/magic-context/memory/embedding.ts
159294
160669
  var DEFAULT_EMBEDDING_CONFIG = {
@@ -159308,10 +160683,11 @@ function createProvider2(config2) {
159308
160683
  model: config2.model,
159309
160684
  apiKey: config2.api_key,
159310
160685
  inputType: config2.input_type,
159311
- truncate: config2.truncate
160686
+ truncate: config2.truncate,
160687
+ maxInputTokens: config2.max_input_tokens
159312
160688
  });
159313
160689
  }
159314
- return new LocalEmbeddingProvider(config2.model);
160690
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
159315
160691
  }
159316
160692
  function getOrCreateProvider() {
159317
160693
  if (provider) {
@@ -159335,186 +160711,8 @@ async function embedText(text, signal) {
159335
160711
  }
159336
160712
  var SWEEP_MAX_WALL_CLOCK_MS2 = 10 * 60 * 1000;
159337
160713
 
159338
- // ../plugin/src/features/magic-context/memory/project-identity.ts
159339
- import { execFileSync } from "node:child_process";
159340
- import { createHash as createHash4 } from "node:crypto";
159341
- import { statSync } from "node:fs";
159342
- import path3 from "node:path";
159343
- var GIT_TIMEOUT_MS = 5000;
159344
- var identityCache = new Map;
159345
- var directoryFallbackCache = new Map;
159346
-
159347
- class ProjectIdentityError extends Error {
159348
- errorClass;
159349
- rawDirectory;
159350
- constructor(errorClass, rawDirectory, message, cause) {
159351
- super(message);
159352
- this.name = "ProjectIdentityError";
159353
- this.errorClass = errorClass;
159354
- this.rawDirectory = rawDirectory;
159355
- if (cause) {
159356
- this.cause = cause;
159357
- }
159358
- }
159359
- }
159360
- function asError(error51) {
159361
- return error51 instanceof Error ? error51 : undefined;
159362
- }
159363
- function getErrorCode(error51) {
159364
- if (error51 === null || typeof error51 !== "object" || !("code" in error51)) {
159365
- return;
159366
- }
159367
- const code = error51.code;
159368
- return typeof code === "string" ? code : undefined;
159369
- }
159370
- function getErrorSignal(error51) {
159371
- if (error51 === null || typeof error51 !== "object" || !("signal" in error51)) {
159372
- return;
159373
- }
159374
- const signal = error51.signal;
159375
- return typeof signal === "string" ? signal : undefined;
159376
- }
159377
- function getErrorKilled(error51) {
159378
- if (error51 === null || typeof error51 !== "object" || !("killed" in error51)) {
159379
- return false;
159380
- }
159381
- return error51.killed === true;
159382
- }
159383
- function getErrorStderr(error51) {
159384
- if (error51 === null || typeof error51 !== "object" || !("stderr" in error51)) {
159385
- return "";
159386
- }
159387
- const stderr = error51.stderr;
159388
- if (typeof stderr === "string") {
159389
- return stderr;
159390
- }
159391
- if (Buffer.isBuffer(stderr)) {
159392
- return stderr.toString("utf8");
159393
- }
159394
- return "";
159395
- }
159396
- function directoryFallback(directory) {
159397
- const canonical = path3.resolve(directory);
159398
- const hash2 = createHash4("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
159399
- return `dir:${hash2}`;
159400
- }
159401
- function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
159402
- try {
159403
- const stat2 = statSync(canonicalDirectory);
159404
- if (!stat2.isDirectory()) {
159405
- throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
159406
- }
159407
- } catch (error51) {
159408
- if (error51 instanceof ProjectIdentityError) {
159409
- throw error51;
159410
- }
159411
- const code = getErrorCode(error51);
159412
- if (code === "EACCES" || code === "EPERM") {
159413
- throw new ProjectIdentityError("permission_denied", rawDirectory, `Permission denied while accessing project directory: ${canonicalDirectory}`, asError(error51));
159414
- }
159415
- throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error51));
159416
- }
159417
- }
159418
- function isGitTimeoutError(error51) {
159419
- const code = getErrorCode(error51);
159420
- const signal = getErrorSignal(error51);
159421
- return code === "ETIMEDOUT" || signal === "SIGTERM" || signal === "SIGKILL" || getErrorKilled(error51);
159422
- }
159423
- function classifyGitError(error51, rawDirectory) {
159424
- if (isGitTimeoutError(error51)) {
159425
- return new ProjectIdentityError("git_timeout", rawDirectory, `git rev-list timed out after ${GIT_TIMEOUT_MS}ms`, asError(error51));
159426
- }
159427
- const code = getErrorCode(error51);
159428
- if (code === "ENOENT") {
159429
- return new ProjectIdentityError("git_missing", rawDirectory, "git binary is not available in PATH", asError(error51));
159430
- }
159431
- if (code === "EACCES" || code === "EPERM") {
159432
- return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error51));
159433
- }
159434
- const stderr = getErrorStderr(error51).toLowerCase();
159435
- if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
159436
- return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error51));
159437
- }
159438
- return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error51));
159439
- }
159440
- function resolveProjectIdentityStrict(directory) {
159441
- const canonical = path3.resolve(directory);
159442
- const cached2 = identityCache.get(canonical);
159443
- if (cached2 !== undefined) {
159444
- return cached2;
159445
- }
159446
- assertDirectoryUsable(canonical, directory);
159447
- let output;
159448
- try {
159449
- output = execFileSync("git", ["rev-list", "--max-parents=0", "HEAD"], {
159450
- cwd: canonical,
159451
- encoding: "utf8",
159452
- env: { ...process.env, LC_ALL: "C", LANG: "C" },
159453
- stdio: ["ignore", "pipe", "pipe"],
159454
- timeout: GIT_TIMEOUT_MS
159455
- });
159456
- } catch (error51) {
159457
- throw classifyGitError(error51, directory);
159458
- }
159459
- const firstLine = output.split(`
159460
- `)[0]?.trim() ?? "";
159461
- const rootCommit = firstLine.slice(0, 64);
159462
- if (rootCommit.length < 7) {
159463
- throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
159464
- }
159465
- const identity = `git:${rootCommit}`;
159466
- identityCache.set(canonical, identity);
159467
- return identity;
159468
- }
159469
- function shouldUseDirectoryFallback(error51) {
159470
- return error51.errorClass === "not_git_repo" || error51.errorClass === "unknown" && error51.message.startsWith("Unable to access project directory:");
159471
- }
159472
- function resolveProjectIdentity(directory) {
159473
- const canonical = path3.resolve(directory);
159474
- const cachedFallback = directoryFallbackCache.get(canonical);
159475
- if (cachedFallback !== undefined) {
159476
- if (!hasGitDir(canonical)) {
159477
- return cachedFallback;
159478
- }
159479
- directoryFallbackCache.delete(canonical);
159480
- }
159481
- try {
159482
- return resolveProjectIdentityStrict(directory);
159483
- } catch (error51) {
159484
- if (error51 instanceof ProjectIdentityError && shouldUseDirectoryFallback(error51)) {
159485
- const fallback = directoryFallback(canonical);
159486
- if (!hasGitDir(canonical)) {
159487
- directoryFallbackCache.set(canonical, fallback);
159488
- }
159489
- return fallback;
159490
- }
159491
- throw error51;
159492
- }
159493
- }
159494
- function hasGitDir(canonical) {
159495
- try {
159496
- statSync(path3.join(canonical, ".git"));
159497
- return true;
159498
- } catch {
159499
- return false;
159500
- }
159501
- }
159502
- function normalizeStoredProjectPath(rawOrStored) {
159503
- if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
159504
- return rawOrStored;
159505
- }
159506
- try {
159507
- return resolveProjectIdentity(rawOrStored);
159508
- } catch {
159509
- return directoryFallback(rawOrStored);
159510
- }
159511
- }
159512
- function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
159513
- return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
159514
- }
159515
-
159516
160714
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
159517
- import { createHash as createHash5 } from "node:crypto";
160715
+ import { createHash as createHash7 } from "node:crypto";
159518
160716
  init_logger();
159519
160717
  var EMBEDDING_AFFECTING_KEYS = new Set([
159520
160718
  "embedding.api_key",
@@ -159536,7 +160734,7 @@ var EMBEDDING_WARNING_TERMS = [
159536
160734
  ];
159537
160735
  var loggedFailureSignatures = new Map;
159538
160736
  function sha256Prefix2(value, length = 16) {
159539
- return createHash5("sha256").update(value).digest("hex").slice(0, length);
160737
+ return createHash7("sha256").update(value).digest("hex").slice(0, length);
159540
160738
  }
159541
160739
  function warningLooksEmbeddingRelated(message) {
159542
160740
  const lower = message.toLowerCase();
@@ -159697,6 +160895,7 @@ var SESSION_META_SELECT_COLUMNS = [
159697
160895
  "cached_m0_bytes",
159698
160896
  "cached_m1_bytes",
159699
160897
  "cached_m0_project_memory_epoch",
160898
+ "cached_m0_workspace_fingerprint",
159700
160899
  "cached_m0_project_user_profile_version",
159701
160900
  "cached_m0_max_compartment_seq",
159702
160901
  "cached_m0_max_memory_id",
@@ -159744,6 +160943,7 @@ var META_COLUMNS = {
159744
160943
  cachedM0Bytes: "cached_m0_bytes",
159745
160944
  cachedM1Bytes: "cached_m1_bytes",
159746
160945
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
160946
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
159747
160947
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
159748
160948
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
159749
160949
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -159773,6 +160973,7 @@ var NULL_BIND_META_KEYS = new Set([
159773
160973
  "cachedM0Bytes",
159774
160974
  "cachedM1Bytes",
159775
160975
  "cachedM0ProjectMemoryEpoch",
160976
+ "cachedM0WorkspaceFingerprint",
159776
160977
  "cachedM0ProjectUserProfileVersion",
159777
160978
  "cachedM0MaxCompartmentSeq",
159778
160979
  "cachedM0MaxMemoryId",
@@ -159806,7 +161007,7 @@ function isSessionMetaRow(row) {
159806
161007
  if (row === null || typeof row !== "object")
159807
161008
  return false;
159808
161009
  const r = row;
159809
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
161010
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
159810
161011
  }
159811
161012
  function getDefaultSessionMeta(sessionId) {
159812
161013
  return {
@@ -159833,6 +161034,7 @@ function getDefaultSessionMeta(sessionId) {
159833
161034
  cachedM0Bytes: null,
159834
161035
  cachedM1Bytes: null,
159835
161036
  cachedM0ProjectMemoryEpoch: null,
161037
+ cachedM0WorkspaceFingerprint: null,
159836
161038
  cachedM0ProjectUserProfileVersion: null,
159837
161039
  cachedM0MaxCompartmentSeq: null,
159838
161040
  cachedM0MaxMemoryId: null,
@@ -159895,6 +161097,7 @@ function toSessionMeta(row) {
159895
161097
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
159896
161098
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
159897
161099
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
161100
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
159898
161101
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
159899
161102
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
159900
161103
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -159925,6 +161128,7 @@ function persistCachedM0(db, sessionId, payload) {
159925
161128
  db.prepare(`UPDATE session_meta SET
159926
161129
  cached_m0_bytes = ?,
159927
161130
  cached_m0_project_memory_epoch = ?,
161131
+ cached_m0_workspace_fingerprint = ?,
159928
161132
  cached_m0_project_user_profile_version = ?,
159929
161133
  cached_m0_max_compartment_seq = ?,
159930
161134
  cached_m0_max_memory_id = ?,
@@ -159937,7 +161141,7 @@ function persistCachedM0(db, sessionId, payload) {
159937
161141
  cached_m0_upgrade_state = ?,
159938
161142
  cached_m0_system_hash = ?,
159939
161143
  cached_m0_model_key = ?
159940
- WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.modelKey ?? "", sessionId);
161144
+ WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.workspaceFingerprint ?? null, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.modelKey ?? "", sessionId);
159941
161145
  }
159942
161146
  function clearCachedM0M1(db, sessionId) {
159943
161147
  ensureSessionMetaRow(db, sessionId);
@@ -159946,6 +161150,7 @@ function clearCachedM0M1(db, sessionId) {
159946
161150
  ["cached_m0_bytes", null],
159947
161151
  ["cached_m1_bytes", null],
159948
161152
  ["cached_m0_project_memory_epoch", null],
161153
+ ["cached_m0_workspace_fingerprint", null],
159949
161154
  ["cached_m0_project_user_profile_version", null],
159950
161155
  ["cached_m0_max_compartment_seq", null],
159951
161156
  ["cached_m0_max_memory_id", null],
@@ -160281,8 +161486,8 @@ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
160281
161486
  const CHUNK = 800;
160282
161487
  for (let i = 0;i < ids.length; i += CHUNK) {
160283
161488
  const slice = ids.slice(i, i + CHUNK);
160284
- const placeholders = slice.map(() => "?").join(",");
160285
- const partRows = db.prepare(`SELECT message_id, data, time_updated FROM part WHERE session_id = ? AND message_id IN (${placeholders}) ORDER BY time_created ASC, id ASC`).all(sessionId, ...slice).filter(isRawPartRow);
161489
+ const placeholders2 = slice.map(() => "?").join(",");
161490
+ const partRows = db.prepare(`SELECT message_id, data, time_updated FROM part WHERE session_id = ? AND message_id IN (${placeholders2}) ORDER BY time_created ASC, id ASC`).all(sessionId, ...slice).filter(isRawPartRow);
160286
161491
  for (const part of partRows) {
160287
161492
  const list = partsByMessageId.get(part.message_id) ?? [];
160288
161493
  list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
@@ -165179,53 +166384,6 @@ function createCtxExpandTool(deps) {
165179
166384
  }
165180
166385
  };
165181
166386
  }
165182
-
165183
- // ../plugin/src/features/magic-context/memory/constants.ts
165184
- var V2_MEMORY_CATEGORIES = [
165185
- "PROJECT_RULES",
165186
- "ARCHITECTURE",
165187
- "CONSTRAINTS",
165188
- "CONFIG_VALUES",
165189
- "NAMING"
165190
- ];
165191
- var PROMOTABLE_CATEGORIES = [
165192
- "PROJECT_RULES",
165193
- "ARCHITECTURE",
165194
- "CONSTRAINTS",
165195
- "CONFIG_VALUES",
165196
- "NAMING",
165197
- "ARCHITECTURE_DECISIONS",
165198
- "CONFIG_DEFAULTS",
165199
- "USER_PREFERENCES",
165200
- "USER_DIRECTIVES",
165201
- "ENVIRONMENT",
165202
- "WORKFLOW_RULES",
165203
- "KNOWN_ISSUES"
165204
- ];
165205
- var CATEGORY_PRIORITY = [
165206
- "PROJECT_RULES",
165207
- "ARCHITECTURE",
165208
- "CONSTRAINTS",
165209
- "CONFIG_VALUES",
165210
- "NAMING",
165211
- "USER_DIRECTIVES",
165212
- "USER_PREFERENCES",
165213
- "CONFIG_DEFAULTS",
165214
- "ARCHITECTURE_DECISIONS",
165215
- "ENVIRONMENT",
165216
- "WORKFLOW_RULES",
165217
- "KNOWN_ISSUES"
165218
- ];
165219
- var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
165220
- var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
165221
- acc[category] = index;
165222
- return acc;
165223
- }, {});
165224
- var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
165225
- var CATEGORY_DEFAULT_TTL = {
165226
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
165227
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
165228
- };
165229
166387
  // ../plugin/src/features/magic-context/memory/embedding-backfill.ts
165230
166388
  init_logger();
165231
166389
  async function ensureMemoryEmbeddings(args) {
@@ -165250,7 +166408,7 @@ async function ensureMemoryEmbeddings(args) {
165250
166408
  continue;
165251
166409
  }
165252
166410
  saveEmbedding(args.db, memory2.id, embedding, result.modelId);
165253
- staged.set(memory2.id, embedding);
166411
+ staged.set(memory2.id, { embedding, modelId: result.modelId });
165254
166412
  }
165255
166413
  })();
165256
166414
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -165453,8 +166611,8 @@ function getMemoriesByProjectStatement(db, statuses) {
165453
166611
  }
165454
166612
  let stmt = statements.get(db);
165455
166613
  if (!stmt) {
165456
- const placeholders = statuses.map(() => "?").join(", ");
165457
- stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
166614
+ const placeholders2 = statuses.map(() => "?").join(", ");
166615
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders2}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
165458
166616
  statements.set(db, stmt);
165459
166617
  }
165460
166618
  return stmt;
@@ -165599,6 +166757,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
165599
166757
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
165600
166758
  return rows.map(toMemory);
165601
166759
  }
166760
+ function sqlPlaceholders(values) {
166761
+ return values.map(() => "?").join(", ");
166762
+ }
166763
+ function uniqueValues(values) {
166764
+ return [...new Set(values.filter((value) => value.length > 0))];
166765
+ }
166766
+ function buildWorkspaceMemorySqlFilter(args) {
166767
+ if (args.shareCategories === null || args.shareCategories === undefined) {
166768
+ return { clause: "", params: [], active: false };
166769
+ }
166770
+ const identities = uniqueValues(args.identities);
166771
+ const identitySet = new Set(identities);
166772
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
166773
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
166774
+ if (foreignIdentities.length === 0) {
166775
+ return { clause: "", params: [], active: false };
166776
+ }
166777
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
166778
+ const shareCategories = uniqueValues([...args.shareCategories]);
166779
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
166780
+ const predicates = [];
166781
+ const params = [];
166782
+ if (ownIdentities.length > 0) {
166783
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
166784
+ params.push(...ownIdentities);
166785
+ }
166786
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
166787
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
166788
+ params.push(...foreignIdentities, ...shareCategories);
166789
+ }
166790
+ if (predicates.length === 0) {
166791
+ return { clause: " AND 0 = 1", params: [], active: true };
166792
+ }
166793
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
166794
+ }
166795
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
166796
+ const identities = uniqueValues(projectPaths);
166797
+ if (identities.length === 0 || statuses.length === 0)
166798
+ return [];
166799
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
166800
+ identities,
166801
+ ownIdentities,
166802
+ shareCategories
166803
+ });
166804
+ if (identities.length === 1 && !sharingFilter.active) {
166805
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
166806
+ }
166807
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
166808
+ FROM memories
166809
+ WHERE project_path IN (${sqlPlaceholders(identities)})
166810
+ AND status IN (${sqlPlaceholders(statuses)})
166811
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
166812
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
166813
+ return rows.map(toMemory);
166814
+ }
166815
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
166816
+ const identities = uniqueValues(projectPaths);
166817
+ if (identities.length === 0)
166818
+ return 0;
166819
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
166820
+ identities,
166821
+ ownIdentities,
166822
+ shareCategories
166823
+ });
166824
+ if (identities.length === 1 && !sharingFilter.active) {
166825
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
166826
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
166827
+ }
166828
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
166829
+ FROM memories
166830
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
166831
+ return typeof row?.max_id === "number" ? row.max_id : 0;
166832
+ }
166833
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
166834
+ const identities = uniqueValues(projectPaths);
166835
+ if (identities.length === 0)
166836
+ return [];
166837
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
166838
+ identities,
166839
+ ownIdentities,
166840
+ shareCategories
166841
+ });
166842
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
166843
+ FROM memories
166844
+ WHERE project_path IN (${sqlPlaceholders(identities)})
166845
+ AND id > ?
166846
+ AND status IN ('active', 'permanent')
166847
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
166848
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
166849
+ return rows.map(toMemory);
166850
+ }
165602
166851
  function getAllActiveMemoriesForMigration(db, projectPath) {
165603
166852
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
165604
166853
  return rows.map(toMemory);
@@ -165766,6 +167015,7 @@ async function embedAndStoreMemory(db, sessionId, projectPath, memoryId, content
165766
167015
  // ../plugin/src/features/magic-context/memory/storage-memory-fts.ts
165767
167016
  var DEFAULT_SEARCH_LIMIT = 10;
165768
167017
  var searchStatements = new WeakMap;
167018
+ var unionSearchStatements = new Map;
165769
167019
  function getSearchStatement(db) {
165770
167020
  let stmt = searchStatements.get(db);
165771
167021
  if (!stmt) {
@@ -165774,6 +167024,23 @@ function getSearchStatement(db) {
165774
167024
  }
165775
167025
  return stmt;
165776
167026
  }
167027
+ function getUnionSearchStatement(db, arity) {
167028
+ let statements = unionSearchStatements.get(arity);
167029
+ if (!statements) {
167030
+ statements = new WeakMap;
167031
+ unionSearchStatements.set(arity, statements);
167032
+ }
167033
+ let stmt = statements.get(db);
167034
+ if (!stmt) {
167035
+ const placeholders2 = Array.from({ length: arity }, () => "?").join(", ");
167036
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${placeholders2}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
167037
+ statements.set(db, stmt);
167038
+ }
167039
+ return stmt;
167040
+ }
167041
+ function uniqueProjectPaths(projectPaths) {
167042
+ return [...new Set(projectPaths.filter((path4) => path4.length > 0))];
167043
+ }
165777
167044
  function sanitizeFtsQuery(query) {
165778
167045
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
165779
167046
  if (tokens.length === 0)
@@ -165792,6 +167059,28 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
165792
167059
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
165793
167060
  return rows.map(toMemory);
165794
167061
  }
167062
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
167063
+ const identities = uniqueProjectPaths(projectPaths);
167064
+ if (identities.length === 0)
167065
+ return [];
167066
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
167067
+ identities,
167068
+ ownIdentities,
167069
+ shareCategories,
167070
+ tableName: "memories"
167071
+ });
167072
+ if (identities.length === 1 && !sharingFilter.active) {
167073
+ return searchMemoriesFTS(db, identities[0], query, limit);
167074
+ }
167075
+ const trimmedQuery = query.trim();
167076
+ if (trimmedQuery.length === 0 || limit <= 0)
167077
+ return [];
167078
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
167079
+ if (sanitized.length === 0)
167080
+ return [];
167081
+ const rows = sharingFilter.active ? db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${identities.map(() => "?").join(", ")}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ?${sharingFilter.clause} ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`).all(...identities, Date.now(), sanitized, ...sharingFilter.params, limit).filter(isMemoryRow) : getUnionSearchStatement(db, identities.length).all(...identities, Date.now(), sanitized, limit).filter(isMemoryRow);
167082
+ return rows.map(toMemory);
167083
+ }
165795
167084
  // ../plugin/src/features/magic-context/message-index.ts
165796
167085
  var lastIndexedStatements = new WeakMap;
165797
167086
  var insertMessageStatements = new WeakMap;
@@ -165938,7 +167227,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
165938
167227
  return inserted;
165939
167228
  }
165940
167229
  // ../plugin/src/features/magic-context/project-docs-hash.ts
165941
- import { createHash as createHash6 } from "node:crypto";
167230
+ import { createHash as createHash8 } from "node:crypto";
165942
167231
  import { lstatSync, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
165943
167232
  import path4 from "node:path";
165944
167233
  var PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
@@ -166036,7 +167325,7 @@ function hashCanonicalPieces(hashPieces) {
166036
167325
  if (hashPieces.length === 0) {
166037
167326
  return "";
166038
167327
  }
166039
- return createHash6("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
167328
+ return createHash8("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
166040
167329
  }
166041
167330
  function readProjectDocsCanonical(projectDirectory) {
166042
167331
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -166142,18 +167431,13 @@ function getMemoryMutation(db, id) {
166142
167431
  WHERE id = ?`).get(id);
166143
167432
  return row ? toMemoryMutation(row) : null;
166144
167433
  }
166145
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
166146
- if (renderedMemoryIds.length === 0)
166147
- return [];
166148
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
166149
- const placeholders = uniqueIds.map(() => "?").join(", ");
166150
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
166151
- superseded_by_id, category, new_content, queued_at
166152
- FROM memory_mutation_log
166153
- WHERE project_path = ?
166154
- AND id > ?
166155
- AND target_memory_id IN (${placeholders})
166156
- ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
167434
+ function uniqueProjectPaths2(projectPaths) {
167435
+ return [...new Set(projectPaths.filter((path5) => path5.length > 0))];
167436
+ }
167437
+ function placeholders2(values) {
167438
+ return values.map(() => "?").join(", ");
167439
+ }
167440
+ function coalesceMutations(rows) {
166157
167441
  const chosenByTarget = new Map;
166158
167442
  for (const dbRow of rows) {
166159
167443
  const candidate = toMemoryMutation(dbRow);
@@ -166174,10 +167458,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
166174
167458
  }
166175
167459
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
166176
167460
  }
167461
+ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
167462
+ if (renderedMemoryIds.length === 0)
167463
+ return [];
167464
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
167465
+ const placeholders3 = uniqueIds.map(() => "?").join(", ");
167466
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
167467
+ superseded_by_id, category, new_content, queued_at
167468
+ FROM memory_mutation_log
167469
+ WHERE project_path = ?
167470
+ AND id > ?
167471
+ AND target_memory_id IN (${placeholders3})
167472
+ ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
167473
+ return coalesceMutations(rows);
167474
+ }
167475
+ function getMemoryMutationsForRenderByProjects(db, projectPaths, afterId, renderedMemoryIds) {
167476
+ if (renderedMemoryIds.length === 0)
167477
+ return [];
167478
+ const identities = uniqueProjectPaths2(projectPaths);
167479
+ if (identities.length === 0)
167480
+ return [];
167481
+ if (identities.length === 1) {
167482
+ return getMemoryMutationsForRender(db, identities[0], afterId, renderedMemoryIds);
167483
+ }
167484
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
167485
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
167486
+ superseded_by_id, category, new_content, queued_at
167487
+ FROM memory_mutation_log
167488
+ WHERE project_path IN (${placeholders2(identities)})
167489
+ AND id > ?
167490
+ AND target_memory_id IN (${placeholders2(uniqueIds)})
167491
+ ORDER BY id ASC`).all(...identities, afterId ?? 0, ...uniqueIds);
167492
+ return coalesceMutations(rows);
167493
+ }
166177
167494
  function getMaxMemoryMutationId(db, projectPath) {
166178
167495
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
166179
167496
  return row?.max_id ?? null;
166180
167497
  }
167498
+ function getMaxMemoryMutationIdForProjects(db, projectPaths) {
167499
+ const identities = uniqueProjectPaths2(projectPaths);
167500
+ if (identities.length === 0)
167501
+ return null;
167502
+ if (identities.length === 1)
167503
+ return getMaxMemoryMutationId(db, identities[0]);
167504
+ const row = db.prepare(`SELECT MAX(id) AS max_id
167505
+ FROM memory_mutation_log
167506
+ WHERE project_path IN (${placeholders2(identities)})`).get(...identities);
167507
+ return row?.max_id ?? null;
167508
+ }
166181
167509
  // ../plugin/src/features/magic-context/storage-meta-persisted.ts
166182
167510
  init_logger();
166183
167511
  var CAS_RETRY_LIMIT = 5;
@@ -167023,9 +168351,9 @@ function buildStatusClause(status) {
167023
168351
  if (statuses.length === 0) {
167024
168352
  return null;
167025
168353
  }
167026
- const placeholders = statuses.map(() => "?").join(", ");
168354
+ const placeholders3 = statuses.map(() => "?").join(", ");
167027
168355
  return {
167028
- sql: `status IN (${placeholders})`,
168356
+ sql: `status IN (${placeholders3})`,
167029
168357
  params: statuses
167030
168358
  };
167031
168359
  }
@@ -167221,19 +168549,6 @@ function getProjectState(db, projectPath) {
167221
168549
  WHERE project_path = ?`).get(projectPath);
167222
168550
  return row ? toProjectState(row) : null;
167223
168551
  }
167224
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
167225
- db.prepare(`INSERT INTO project_state
167226
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
167227
- VALUES (?, 1, 0, ?)
167228
- ON CONFLICT(project_path) DO UPDATE SET
167229
- project_memory_epoch = project_memory_epoch + 1,
167230
- updated_at = excluded.updated_at`).run(projectPath, now);
167231
- const state = getProjectState(db, projectPath);
167232
- if (!state) {
167233
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
167234
- }
167235
- return state;
167236
- }
167237
168552
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
167238
168553
  db.prepare(`INSERT INTO project_state
167239
168554
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -167267,8 +168582,8 @@ function getSourceContents(db, sessionId, tagIds) {
167267
168582
  if (tagIds.length === 0) {
167268
168583
  return new Map;
167269
168584
  }
167270
- const placeholders = tagIds.map(() => "?").join(", ");
167271
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
168585
+ const placeholders3 = tagIds.map(() => "?").join(", ");
168586
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders3})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
167272
168587
  const sources = new Map;
167273
168588
  for (const row of rows) {
167274
168589
  sources.set(row.tag_id, row.content);
@@ -167558,8 +168873,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
167558
168873
  }
167559
168874
  return all;
167560
168875
  }
167561
- const placeholders = tagNumbers.map(() => "?").join(",");
167562
- const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
168876
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
168877
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders3}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
167563
168878
  return rows.map(toTagEntry);
167564
168879
  }
167565
168880
  var getToolTagNumberByOwnerStatements = new WeakMap;
@@ -167749,6 +169064,23 @@ function createCtxMemoryTool(deps) {
167749
169064
  }
167750
169065
  const projectIdentity = resolveProjectIdentity(ctx.cwd);
167751
169066
  await deps.ensureProjectRegistered?.(ctx.cwd, deps.db);
169067
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectIdentity);
169068
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
169069
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
169070
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? normalizeStoredProjectPath(rawProjectPath) : normalizeStoredProjectPath(rawProjectPath);
169071
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectIdentity) : null;
169072
+ const memoryVisibleToTool = (memory2) => {
169073
+ if (workspaceIdentitySet.identities.length <= 1) {
169074
+ return storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
169075
+ }
169076
+ if (!storedPathBelongsToWorkspace(memory2.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
169077
+ return false;
169078
+ }
169079
+ const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
169080
+ if (isOwn)
169081
+ return true;
169082
+ return toolShareCategories === null || toolShareCategories.includes(memory2.category);
169083
+ };
167752
169084
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
167753
169085
  if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
167754
169086
  return err2("Cross-session memory is disabled for this project.");
@@ -167795,28 +169127,34 @@ function createCtxMemoryTool(deps) {
167795
169127
  return err2("Error: 'content' is required when action is 'update'.");
167796
169128
  }
167797
169129
  const memory2 = getMemoryById(deps.db, updateId);
167798
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
169130
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
167799
169131
  return err2(`Error: Memory with ID ${updateId} was not found.`);
167800
169132
  }
167801
169133
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167802
169134
  return err2(inactiveMemoryError(updateId, "updating"));
167803
169135
  }
167804
169136
  const normalizedHash = computeNormalizedHash(content);
167805
- const duplicate = getMemoryByHash(deps.db, projectIdentity, memory2.category, normalizedHash);
169137
+ const targetIdentity = targetIdentityForStoredPath(memory2.projectPath);
169138
+ const duplicate = getMemoryByHash(deps.db, targetIdentity, memory2.category, normalizedHash);
167806
169139
  if (duplicate && duplicate.id !== memory2.id) {
167807
169140
  return err2(`Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`);
167808
169141
  }
167809
169142
  deps.db.transaction(() => {
167810
169143
  updateMemoryContent(deps.db, memory2.id, content, normalizedHash);
167811
169144
  queueMemoryMutation(deps.db, {
167812
- projectPath: projectIdentity,
169145
+ projectPath: targetIdentity,
167813
169146
  mutationType: "update",
167814
169147
  targetMemoryId: memory2.id,
167815
169148
  category: memory2.category,
167816
169149
  newContent: content
167817
169150
  });
167818
169151
  })();
167819
- queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
169152
+ queueEmbedding({
169153
+ deps,
169154
+ projectIdentity: targetIdentity,
169155
+ memoryId: memory2.id,
169156
+ content
169157
+ });
167820
169158
  return ok2(`Updated memory [ID: ${memory2.id}] in ${memory2.category}.`);
167821
169159
  }
167822
169160
  if (params.action === "merge") {
@@ -167836,7 +169174,7 @@ function createCtxMemoryTool(deps) {
167836
169174
  return err2("Error: One or more source memories were not found.");
167837
169175
  }
167838
169176
  if (!dreamerAllowed) {
167839
- const foreign = sourceMemories.find((memory2) => !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity));
169177
+ const foreign = sourceMemories.find((memory2) => !memoryVisibleToTool(memory2));
167840
169178
  if (foreign) {
167841
169179
  return err2(`Error: Memory with ID ${foreign.id} was not found.`);
167842
169180
  }
@@ -167925,26 +169263,36 @@ function createCtxMemoryTool(deps) {
167925
169263
  return ok2(`Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`);
167926
169264
  }
167927
169265
  if (params.action === "archive") {
167928
- const archiveIds = params.ids;
167929
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
169266
+ const rawArchiveIds = params.ids;
169267
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
167930
169268
  return err2("Error: 'ids' must contain at least one integer memory ID when action is 'archive'.");
167931
169269
  }
169270
+ const archiveIds = [...new Set(rawArchiveIds)];
167932
169271
  for (const memoryId of archiveIds) {
167933
169272
  const memory2 = getMemoryById(deps.db, memoryId);
167934
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
169273
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
167935
169274
  return err2(`Error: Memory with ID ${memoryId} was not found.`);
167936
169275
  }
167937
169276
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167938
169277
  return err2(inactiveMemoryError(memoryId, "archiving"));
167939
169278
  }
167940
169279
  }
169280
+ const targets = archiveIds.map((memoryId) => {
169281
+ const memory2 = getMemoryById(deps.db, memoryId);
169282
+ if (!memory2)
169283
+ throw new Error(`validated memory ${memoryId} disappeared`);
169284
+ return {
169285
+ memoryId,
169286
+ projectIdentity: targetIdentityForStoredPath(memory2.projectPath)
169287
+ };
169288
+ });
167941
169289
  deps.db.transaction(() => {
167942
- for (const memoryId of archiveIds) {
167943
- archiveMemory(deps.db, memoryId, params.reason);
169290
+ for (const target2 of targets) {
169291
+ archiveMemory(deps.db, target2.memoryId, params.reason);
167944
169292
  queueMemoryMutation(deps.db, {
167945
- projectPath: projectIdentity,
169293
+ projectPath: target2.projectIdentity,
167946
169294
  mutationType: "archive",
167947
- targetMemoryId: memoryId
169295
+ targetMemoryId: target2.memoryId
167948
169296
  });
167949
169297
  }
167950
169298
  })();
@@ -168916,6 +170264,37 @@ function previewText(text) {
168916
170264
  }
168917
170265
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
168918
170266
  }
170267
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
170268
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
170269
+ const isWorkspaced = resolved.identities.length > 1;
170270
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
170271
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
170272
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
170273
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
170274
+ return {
170275
+ identities: resolved.identities,
170276
+ expandedIdentities,
170277
+ ownIdentities,
170278
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
170279
+ namesByIdentity: resolved.namesByIdentity,
170280
+ canonicalIdentityByStoredPath,
170281
+ isWorkspaced
170282
+ };
170283
+ }
170284
+ function memoryWorkspaceIdentity(memory2, workspace) {
170285
+ return resolveStoredPathWorkspaceIdentity(memory2.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
170286
+ }
170287
+ function sourceNamesForSearchMemories(args) {
170288
+ if (!args.workspace.isWorkspaced)
170289
+ return;
170290
+ const sourceNames = new Map;
170291
+ for (const memory2 of args.memories) {
170292
+ const source = sourceNameForMemory(memory2.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
170293
+ if (source)
170294
+ sourceNames.set(memory2.id, source);
170295
+ }
170296
+ return sourceNames.size > 0 ? sourceNames : undefined;
170297
+ }
168919
170298
  function getMessageSearchStatement(db) {
168920
170299
  let stmt = messageSearchStatements.get(db);
168921
170300
  if (!stmt) {
@@ -168924,6 +170303,30 @@ function getMessageSearchStatement(db) {
168924
170303
  }
168925
170304
  return stmt;
168926
170305
  }
170306
+ var ftsRowCountStatements = new WeakMap;
170307
+ var ftsMatchCountStatements = new WeakMap;
170308
+ function getSessionFtsRowCount(db, sessionId) {
170309
+ let stmt = ftsRowCountStatements.get(db);
170310
+ if (!stmt) {
170311
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
170312
+ ftsRowCountStatements.set(db, stmt);
170313
+ }
170314
+ const row = stmt.get(sessionId);
170315
+ return typeof row?.n === "number" ? row.n : 0;
170316
+ }
170317
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
170318
+ let stmt = ftsMatchCountStatements.get(db);
170319
+ if (!stmt) {
170320
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
170321
+ ftsMatchCountStatements.set(db, stmt);
170322
+ }
170323
+ try {
170324
+ const row = stmt.get(sessionId, ftsQuery);
170325
+ return typeof row?.n === "number" ? row.n : 0;
170326
+ } catch {
170327
+ return 0;
170328
+ }
170329
+ }
168927
170330
  function getMessageOrdinal(value) {
168928
170331
  if (typeof value === "number" && Number.isFinite(value)) {
168929
170332
  return value;
@@ -168939,25 +170342,63 @@ async function getSemanticScores(args) {
168939
170342
  if (!args.queryEmbedding || args.memories.length === 0) {
168940
170343
  return semanticScores;
168941
170344
  }
168942
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
168943
- const embeddings = await ensureMemoryEmbeddings({
168944
- db: args.db,
168945
- projectIdentity: args.projectPath,
168946
- memories: args.memories,
168947
- existingEmbeddings: cachedEmbeddings
168948
- });
170345
+ if (!args.workspace?.isWorkspaced) {
170346
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
170347
+ const embeddings = await ensureMemoryEmbeddings({
170348
+ db: args.db,
170349
+ projectIdentity: args.projectPath,
170350
+ memories: args.memories,
170351
+ existingEmbeddings: cachedEmbeddings
170352
+ });
170353
+ for (const memory2 of args.memories) {
170354
+ const memoryEmbedding = embeddings.get(memory2.id);
170355
+ if (!memoryEmbedding) {
170356
+ continue;
170357
+ }
170358
+ semanticScores.set(memory2.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
170359
+ }
170360
+ return semanticScores;
170361
+ }
170362
+ if (!args.queryModelId || args.queryModelId === "off") {
170363
+ return semanticScores;
170364
+ }
170365
+ const workspace = args.workspace;
170366
+ const memoriesByIdentity = new Map;
168949
170367
  for (const memory2 of args.memories) {
168950
- const memoryEmbedding = embeddings.get(memory2.id);
168951
- if (!memoryEmbedding) {
170368
+ const identity = memoryWorkspaceIdentity(memory2, workspace);
170369
+ if (!identity)
170370
+ continue;
170371
+ const list = memoriesByIdentity.get(identity) ?? [];
170372
+ list.push(memory2);
170373
+ memoriesByIdentity.set(identity, list);
170374
+ }
170375
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
170376
+ if (ownMemories.length > 0) {
170377
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
170378
+ await ensureMemoryEmbeddings({
170379
+ db: args.db,
170380
+ projectIdentity: args.projectPath,
170381
+ memories: ownMemories,
170382
+ existingEmbeddings: ownEmbeddings
170383
+ });
170384
+ }
170385
+ for (const identity of workspace.identities) {
170386
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
170387
+ if (memberMemories.length === 0)
168952
170388
  continue;
170389
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
170390
+ for (const memory2 of memberMemories) {
170391
+ const memoryEmbedding = cachedEmbeddings.get(memory2.id);
170392
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
170393
+ continue;
170394
+ semanticScores.set(memory2.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
168953
170395
  }
168954
- semanticScores.set(memory2.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
168955
170396
  }
168956
170397
  return semanticScores;
168957
170398
  }
168958
170399
  function getFtsMatches(args) {
168959
170400
  try {
168960
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
170401
+ return args.workspace?.isWorkspaced ? searchMemoriesFTSUnion(args.db, args.workspace.expandedIdentities, args.query, args.limit, args.workspace.ownIdentities, args.workspace.shareCategories) : searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
168961
170402
  } catch (error51) {
168962
170403
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
168963
170404
  return [];
@@ -168971,8 +170412,11 @@ function selectSemanticCandidates(args) {
168971
170412
  return args.memories;
168972
170413
  }
168973
170414
  const candidateIds = new Set(args.ftsMatches.map((memory2) => memory2.id));
168974
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
168975
- if (cachedEmbeddings) {
170415
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
170416
+ for (const projectPath of embeddingProjects) {
170417
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
170418
+ if (!cachedEmbeddings)
170419
+ continue;
168976
170420
  for (const memoryId of cachedEmbeddings.keys()) {
168977
170421
  candidateIds.add(memoryId);
168978
170422
  }
@@ -169014,7 +170458,8 @@ function mergeMemoryResults(args) {
169014
170458
  score,
169015
170459
  memoryId: memory2.id,
169016
170460
  category: memory2.category,
169017
- matchType
170461
+ matchType,
170462
+ sourceName: args.sourceNameByMemoryId?.get(memory2.id)
169018
170463
  });
169019
170464
  }
169020
170465
  return results.sort((left, right) => {
@@ -169028,7 +170473,7 @@ async function searchMemories(args) {
169028
170473
  if (!args.memoryEnabled) {
169029
170474
  return [];
169030
170475
  }
169031
- const memories = getMemoriesByProject(args.db, args.projectPath);
170476
+ const memories = args.workspace?.isWorkspaced ? getMemoriesByProjects(args.db, args.workspace.expandedIdentities, ["active", "permanent"], Date.now(), args.workspace.ownIdentities, args.workspace.shareCategories) : getMemoriesByProject(args.db, args.projectPath);
169032
170477
  if (memories.length === 0) {
169033
170478
  return [];
169034
170479
  }
@@ -169036,26 +170481,43 @@ async function searchMemories(args) {
169036
170481
  db: args.db,
169037
170482
  projectPath: args.projectPath,
169038
170483
  query: args.query,
169039
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
170484
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
170485
+ workspace: args.workspace
169040
170486
  });
169041
170487
  const ftsScores = getFtsScores(ftsMatches);
169042
170488
  const semanticCandidates = selectSemanticCandidates({
169043
170489
  memories,
169044
170490
  projectPath: args.projectPath,
169045
- ftsMatches
170491
+ ftsMatches,
170492
+ workspace: args.workspace
169046
170493
  });
169047
170494
  const semanticScores = await getSemanticScores({
169048
170495
  db: args.db,
169049
170496
  projectPath: args.projectPath,
169050
170497
  memories: semanticCandidates,
169051
- queryEmbedding: args.queryEmbedding
170498
+ queryEmbedding: args.queryEmbedding,
170499
+ queryModelId: args.queryModelId,
170500
+ workspace: args.workspace
169052
170501
  });
169053
170502
  return mergeMemoryResults({
169054
170503
  memories,
169055
170504
  semanticScores,
169056
170505
  ftsScores,
169057
170506
  limit: args.limit,
169058
- visibleMemoryIds: args.visibleMemoryIds
170507
+ visibleMemoryIds: args.visibleMemoryIds,
170508
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
170509
+ memories,
170510
+ projectPath: args.projectPath,
170511
+ workspace: args.workspace ?? {
170512
+ identities: [args.projectPath],
170513
+ expandedIdentities: [args.projectPath],
170514
+ namesByIdentity: new Map,
170515
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
170516
+ ownIdentities: [args.projectPath],
170517
+ shareCategories: null,
170518
+ isWorkspaced: false
170519
+ }
170520
+ })
169059
170521
  });
169060
170522
  }
169061
170523
  function linearDecayScore(rank, total) {
@@ -169086,7 +170548,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
169086
170548
  return result;
169087
170549
  }
169088
170550
  var RRF_K = 60;
169089
- var VERBATIM_PROBE_BONUS = 0.5;
170551
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
170552
+ var IDF_FALLOFF = 100;
170553
+ function probeDiscriminationWeight(df, corpusSize) {
170554
+ if (corpusSize <= 0 || df <= 0)
170555
+ return 1;
170556
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
170557
+ }
169090
170558
  function searchMessages(args) {
169091
170559
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
169092
170560
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -169103,20 +170571,31 @@ function searchMessages(args) {
169103
170571
  role: row.role
169104
170572
  }));
169105
170573
  }
170574
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
169106
170575
  const queryLists = [];
169107
170576
  if (baseQuery.length > 0) {
169108
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
170577
+ queryLists.push({
170578
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
170579
+ weight: 1
170580
+ });
169109
170581
  }
170582
+ const probeWeights = new Map;
169110
170583
  for (const probe of probes) {
169111
170584
  const probeQuery = sanitizeFtsQuery(probe);
169112
170585
  if (probeQuery.length === 0)
169113
170586
  continue;
169114
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
170587
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
170588
+ const weight = probeDiscriminationWeight(df, corpusSize);
170589
+ probeWeights.set(probe, weight);
170590
+ queryLists.push({
170591
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
170592
+ weight
170593
+ });
169115
170594
  }
169116
170595
  const fused = new Map;
169117
170596
  for (const list of queryLists) {
169118
- list.forEach((row, rank) => {
169119
- const rrf = 1 / (RRF_K + rank);
170597
+ list.rows.forEach((row, rank) => {
170598
+ const rrf = list.weight / (RRF_K + rank);
169120
170599
  const existing = fused.get(row.messageId);
169121
170600
  if (existing) {
169122
170601
  existing.score += rrf;
@@ -169126,26 +170605,107 @@ function searchMessages(args) {
169126
170605
  });
169127
170606
  }
169128
170607
  for (const entry of fused.values()) {
169129
- if (containsProbeVerbatim(entry.row.content, probes)) {
169130
- entry.score += VERBATIM_PROBE_BONUS;
170608
+ let best = 0;
170609
+ for (const probe of probes) {
170610
+ const weight = probeWeights.get(probe) ?? 0;
170611
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
170612
+ best = weight;
170613
+ }
170614
+ }
170615
+ if (best > 0) {
170616
+ entry.score += best * VERBATIM_RANK_BONUS;
169131
170617
  }
169132
170618
  }
169133
170619
  const ranked = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.row.messageOrdinal - b.row.messageOrdinal).slice(0, args.limit);
169134
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
169135
- return ranked.map((entry) => ({
170620
+ return ranked.map((entry, rank) => ({
169136
170621
  source: "message",
169137
170622
  content: previewText(entry.row.content),
169138
- score: maxScore > 0 ? entry.score / maxScore : 0,
170623
+ score: linearDecayScore(rank, ranked.length),
169139
170624
  messageOrdinal: entry.row.messageOrdinal,
169140
170625
  messageId: entry.row.messageId,
169141
170626
  role: entry.row.role
169142
170627
  }));
169143
170628
  }
170629
+ function searchCompartmentChunks(args) {
170630
+ if (!args.queryEmbedding || args.limit <= 0)
170631
+ return [];
170632
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
170633
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
170634
+ if (rows.length === 0)
170635
+ return [];
170636
+ const byCompartment = new Map;
170637
+ for (const row of rows) {
170638
+ if (cutoff !== null && row.endOrdinal > cutoff) {
170639
+ continue;
170640
+ }
170641
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
170642
+ if (score <= 0)
170643
+ continue;
170644
+ const existing = byCompartment.get(row.compartmentId);
170645
+ if (!existing || score > existing.score) {
170646
+ byCompartment.set(row.compartmentId, { row, score });
170647
+ }
170648
+ }
170649
+ return [...byCompartment.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.row.startOrdinal - right.row.startOrdinal).slice(0, args.limit).map(({ row, score }) => ({
170650
+ source: "compartment",
170651
+ content: previewText(row.title),
170652
+ score: score * SINGLE_SOURCE_PENALTY,
170653
+ compartmentId: row.compartmentId,
170654
+ sessionId: row.sessionId,
170655
+ title: row.title,
170656
+ startOrdinal: row.startOrdinal,
170657
+ endOrdinal: row.endOrdinal,
170658
+ matchType: "semantic"
170659
+ }));
170660
+ }
170661
+ function mergeMessageAndCompartmentResults(args) {
170662
+ if (args.compartments.length === 0)
170663
+ return args.messages;
170664
+ if (args.messages.length === 0)
170665
+ return args.compartments;
170666
+ const fused = new Map;
170667
+ const add = (key, result, score, tieOrdinal) => {
170668
+ const existing = fused.get(key);
170669
+ if (existing) {
170670
+ existing.score += score;
170671
+ return existing;
170672
+ }
170673
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
170674
+ fused.set(key, entry);
170675
+ return entry;
170676
+ };
170677
+ args.compartments.forEach((compartment, rank) => {
170678
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
170679
+ });
170680
+ for (const [rank, message] of args.messages.entries()) {
170681
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
170682
+ const contribution = 1 / (RRF_K + rank);
170683
+ if (!containing) {
170684
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
170685
+ continue;
170686
+ }
170687
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
170688
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
170689
+ entry.snippetScore = message.score;
170690
+ entry.result = {
170691
+ ...entry.result,
170692
+ matchType: "hybrid",
170693
+ snippet: message.content
170694
+ };
170695
+ }
170696
+ }
170697
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
170698
+ return ranked.map((entry, rank) => ({
170699
+ ...entry.result,
170700
+ score: linearDecayScore(rank, ranked.length)
170701
+ }));
170702
+ }
169144
170703
  function getSourceBoost(result) {
169145
170704
  switch (result.source) {
169146
170705
  case "memory":
169147
170706
  return MEMORY_SOURCE_BOOST;
169148
170707
  case "message":
170708
+ case "compartment":
169149
170709
  return MESSAGE_SOURCE_BOOST;
169150
170710
  case "git_commit":
169151
170711
  return GIT_COMMIT_SOURCE_BOOST;
@@ -169163,6 +170723,9 @@ function compareUnifiedResults(left, right) {
169163
170723
  if (left.source === "message" && right.source === "message") {
169164
170724
  return left.messageOrdinal - right.messageOrdinal;
169165
170725
  }
170726
+ if (left.source === "compartment" && right.source === "compartment") {
170727
+ return left.startOrdinal - right.startOrdinal;
170728
+ }
169166
170729
  if (left.source === "git_commit" && right.source === "git_commit") {
169167
170730
  return right.committedAtMs - left.committedAtMs;
169168
170731
  }
@@ -169213,10 +170776,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
169213
170776
  const isEmbeddingRuntimeEnabled = options3.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
169214
170777
  const gitCommitsEnabled = options3.gitCommitsEnabled ?? false;
169215
170778
  const activeSources = resolveSources(options3.sources);
169216
- const runMemory = activeSources.has("memory") && (options3.memoryEnabled ?? true);
170779
+ const memoryFeatureEnabled = options3.memoryEnabled ?? true;
170780
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
169217
170781
  const runMessages = activeSources.has("message");
169218
170782
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
169219
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
170783
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
170784
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
169220
170785
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options3.signal).catch((error51) => {
169221
170786
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
169222
170787
  return null;
@@ -169232,6 +170797,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
169232
170797
  probes: messageProbes
169233
170798
  }) : [];
169234
170799
  const queryEmbedding = await queryEmbeddingPromise;
170800
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
170801
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
170802
+ const embeddingModelId = embeddingSnapshot?.modelId;
170803
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
170804
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
170805
+ db,
170806
+ sessionId,
170807
+ projectPath,
170808
+ queryEmbedding,
170809
+ limit: tierLimit,
170810
+ maxOrdinal: options3.maxMessageOrdinal,
170811
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
170812
+ }) : [];
170813
+ const messageLikeResults = mergeMessageAndCompartmentResults({
170814
+ messages: messageResults,
170815
+ compartments: compartmentResults,
170816
+ limit: tierLimit
170817
+ });
169235
170818
  const [memoryResults, gitCommitResults] = await Promise.all([
169236
170819
  runMemory ? searchMemories({
169237
170820
  db,
@@ -169240,6 +170823,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
169240
170823
  limit: tierLimit,
169241
170824
  memoryEnabled: true,
169242
170825
  queryEmbedding,
170826
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
170827
+ workspace,
169243
170828
  visibleMemoryIds: options3.visibleMemoryIds
169244
170829
  }) : Promise.resolve([]),
169245
170830
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -169250,7 +170835,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
169250
170835
  queryEmbedding
169251
170836
  })) : Promise.resolve([])
169252
170837
  ]);
169253
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
170838
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
169254
170839
  const countRetrievals = options3.countRetrievals ?? true;
169255
170840
  if (countRetrievals) {
169256
170841
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -169291,8 +170876,8 @@ function getUserMemoryCandidates(db) {
169291
170876
  function deleteUserMemoryCandidates(db, ids) {
169292
170877
  if (ids.length === 0)
169293
170878
  return;
169294
- const placeholders = ids.map(() => "?").join(",");
169295
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
170879
+ const placeholders3 = ids.map(() => "?").join(",");
170880
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
169296
170881
  }
169297
170882
  function insertUserMemory(db, content, sourceCandidateIds) {
169298
170883
  const now = Date.now();
@@ -169844,26 +171429,40 @@ ${sections.join(`
169844
171429
  var DEFAULT_MEMORY_BUDGET_TOKENS = 8000;
169845
171430
  var MEMORY_BLOCK_WRAPPER_TOKENS = 6;
169846
171431
  var DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000;
171432
+ function memoryCanonicalIdentity(memory2, workspace) {
171433
+ return resolveStoredPathWorkspaceIdentity(memory2.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
171434
+ }
171435
+ function memorySelectionOrder(left, right) {
171436
+ if (left.status === "permanent" && right.status !== "permanent")
171437
+ return -1;
171438
+ if (right.status === "permanent" && left.status !== "permanent")
171439
+ return 1;
171440
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
171441
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
171442
+ const importanceDiff = rightImportance - leftImportance;
171443
+ if (importanceDiff !== 0)
171444
+ return importanceDiff;
171445
+ return left.id - right.id;
171446
+ }
171447
+ function memoryRenderOrder(left, right) {
171448
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
171449
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
171450
+ const categoryDiff = aPriority - bPriority;
171451
+ if (categoryDiff !== 0)
171452
+ return categoryDiff;
171453
+ return left.id - right.id;
171454
+ }
169847
171455
  var maxCompartmentSeqStatements = new WeakMap;
169848
171456
  var maxMemoryIdStatements = new WeakMap;
169849
171457
  var legacyCompartmentCountStatements = new WeakMap;
169850
171458
  var m0CompartmentStatements = new WeakMap;
169851
171459
  var newCompartmentStatements = new WeakMap;
169852
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
169853
- const selectionOrder = [...memories].sort((a, b) => {
169854
- if (a.status === "permanent" && b.status !== "permanent")
169855
- return -1;
169856
- if (b.status === "permanent" && a.status !== "permanent")
169857
- return 1;
169858
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
169859
- if (importanceDiff !== 0)
169860
- return importanceDiff;
169861
- return a.id - b.id;
169862
- });
171460
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
171461
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
169863
171462
  const selected = [];
169864
171463
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
169865
171464
  for (const memory2 of selectionOrder) {
169866
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory2));
171465
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
169867
171466
  if (usedTokens + memoryTokens > budgetTokens)
169868
171467
  continue;
169869
171468
  selected.push(memory2);
@@ -169872,16 +171471,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
169872
171471
  if (selected.length < memories.length) {
169873
171472
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
169874
171473
  }
169875
- const renderOrder = [...selected].sort((a, b) => {
169876
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169877
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169878
- const categoryDiff = aPriority - bPriority;
169879
- if (categoryDiff !== 0)
169880
- return categoryDiff;
169881
- return a.id - b.id;
169882
- });
171474
+ const renderOrder = [...selected].sort(memoryRenderOrder);
169883
171475
  return { selected, renderOrder };
169884
171476
  }
171477
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
171478
+ if (!workspace.isWorkspaced) {
171479
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
171480
+ }
171481
+ const selected = [];
171482
+ const selectedIds = new Set;
171483
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
171484
+ const tokenCost = (memory2) => estimateTokens(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
171485
+ const trySelect = (memory2) => {
171486
+ if (selectedIds.has(memory2.id))
171487
+ return false;
171488
+ const tokens = tokenCost(memory2);
171489
+ if (usedTokens + tokens > budgetTokens)
171490
+ return false;
171491
+ selected.push(memory2);
171492
+ selectedIds.add(memory2.id);
171493
+ usedTokens += tokens;
171494
+ return true;
171495
+ };
171496
+ for (const memory2 of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
171497
+ trySelect(memory2);
171498
+ }
171499
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
171500
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
171501
+ const byIdentity = new Map;
171502
+ for (const memory2 of memories) {
171503
+ if (memory2.status === "permanent")
171504
+ continue;
171505
+ const identity = memoryCanonicalIdentity(memory2, workspace);
171506
+ if (!identity)
171507
+ continue;
171508
+ const list = byIdentity.get(identity) ?? [];
171509
+ list.push(memory2);
171510
+ byIdentity.set(identity, list);
171511
+ }
171512
+ for (const identity of workspace.identities) {
171513
+ let memberTokens = 0;
171514
+ const candidates2 = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
171515
+ for (const memory2 of candidates2) {
171516
+ if (selectedIds.has(memory2.id))
171517
+ continue;
171518
+ const tokens = tokenCost(memory2);
171519
+ if (memberTokens + tokens > floorTokens)
171520
+ continue;
171521
+ if (usedTokens + tokens > budgetTokens)
171522
+ continue;
171523
+ selected.push(memory2);
171524
+ selectedIds.add(memory2.id);
171525
+ usedTokens += tokens;
171526
+ memberTokens += tokens;
171527
+ }
171528
+ }
171529
+ const remaining = memories.filter((memory2) => !selectedIds.has(memory2.id)).sort(memorySelectionOrder);
171530
+ for (const memory2 of remaining) {
171531
+ trySelect(memory2);
171532
+ }
171533
+ if (selected.length < memories.length) {
171534
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
171535
+ }
171536
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
171537
+ }
169885
171538
  function trimUserMemoriesToBudget(memories, budgetTokens) {
169886
171539
  const selected = [];
169887
171540
  let usedTokens = 0;
@@ -169894,15 +171547,16 @@ function trimUserMemoriesToBudget(memories, budgetTokens) {
169894
171547
  }
169895
171548
  return selected;
169896
171549
  }
169897
- function renderMemoryLineV2(memory2) {
169898
- return ` <memory id="${memory2.id}" category="${escapeXmlAttr(memory2.category)}" importance="${memory2.importance ?? 50}">${escapeXmlContent(memory2.content)}</memory>`;
171550
+ function renderMemoryLineV2(memory2, sourceName) {
171551
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
171552
+ return ` <memory id="${memory2.id}" category="${escapeXmlAttr(memory2.category)}"${sourceAttr} importance="${memory2.importance ?? 50}">${escapeXmlContent(memory2.content)}</memory>`;
169899
171553
  }
169900
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
171554
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
169901
171555
  if (memories.length === 0)
169902
171556
  return "";
169903
171557
  const lines = [`<${wrapper}>`];
169904
171558
  for (const memory2 of memories) {
169905
- lines.push(renderMemoryLineV2(memory2));
171559
+ lines.push(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
169906
171560
  }
169907
171561
  lines.push(`</${wrapper}>`);
169908
171562
  return lines.join(`
@@ -169967,8 +171621,9 @@ function formatAge(committedAtMs) {
169967
171621
  }
169968
171622
  function formatResult(result, index) {
169969
171623
  if (result.source === "memory") {
171624
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
169970
171625
  return [
169971
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
171626
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
169972
171627
  result.content
169973
171628
  ].join(`
169974
171629
  `);
@@ -169978,6 +171633,13 @@ function formatResult(result, index) {
169978
171633
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge(result.committedAtMs)} match=${result.matchType}`,
169979
171634
  result.content
169980
171635
  ].join(`
171636
+ `);
171637
+ }
171638
+ if (result.source === "compartment") {
171639
+ return [
171640
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
171641
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
171642
+ ].join(`
169981
171643
  `);
169982
171644
  }
169983
171645
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -169993,7 +171655,7 @@ function formatSearchResults(query, results) {
169993
171655
  return `No results found for "${query}" across memories, git commits, or message history.`;
169994
171656
  }
169995
171657
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
169996
- if (results.some((result) => result.source === "message")) {
171658
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
169997
171659
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
169998
171660
  }
169999
171661
  const body = bodyParts.join(`