@cortexkit/pi-magic-context 0.23.0 → 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();
@@ -143852,10 +144542,10 @@ function mergeDefs(...defs) {
143852
144542
  function cloneDef(schema) {
143853
144543
  return mergeDefs(schema._zod.def);
143854
144544
  }
143855
- function getElementAtPath(obj, path3) {
143856
- if (!path3)
144545
+ function getElementAtPath(obj, path4) {
144546
+ if (!path4)
143857
144547
  return obj;
143858
- return path3.reduce((acc, key) => acc?.[key], obj);
144548
+ return path4.reduce((acc, key) => acc?.[key], obj);
143859
144549
  }
143860
144550
  function promiseAllObject(promisesObj) {
143861
144551
  const keys = Object.keys(promisesObj);
@@ -144263,11 +144953,11 @@ function explicitlyAborted(x, startIndex = 0) {
144263
144953
  }
144264
144954
  return false;
144265
144955
  }
144266
- function prefixIssues(path3, issues) {
144956
+ function prefixIssues(path4, issues) {
144267
144957
  return issues.map((iss) => {
144268
144958
  var _a2;
144269
144959
  (_a2 = iss).path ?? (_a2.path = []);
144270
- iss.path.unshift(path3);
144960
+ iss.path.unshift(path4);
144271
144961
  return iss;
144272
144962
  });
144273
144963
  }
@@ -144414,16 +145104,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
144414
145104
  }
144415
145105
  function formatError(error, mapper = (issue2) => issue2.message) {
144416
145106
  const fieldErrors = { _errors: [] };
144417
- const processError = (error2, path3 = []) => {
145107
+ const processError = (error2, path4 = []) => {
144418
145108
  for (const issue2 of error2.issues) {
144419
145109
  if (issue2.code === "invalid_union" && issue2.errors.length) {
144420
- issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
145110
+ issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
144421
145111
  } else if (issue2.code === "invalid_key") {
144422
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
145112
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
144423
145113
  } else if (issue2.code === "invalid_element") {
144424
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
145114
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
144425
145115
  } else {
144426
- const fullpath = [...path3, ...issue2.path];
145116
+ const fullpath = [...path4, ...issue2.path];
144427
145117
  if (fullpath.length === 0) {
144428
145118
  fieldErrors._errors.push(mapper(issue2));
144429
145119
  } else {
@@ -144450,17 +145140,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
144450
145140
  }
144451
145141
  function treeifyError(error, mapper = (issue2) => issue2.message) {
144452
145142
  const result = { errors: [] };
144453
- const processError = (error2, path3 = []) => {
145143
+ const processError = (error2, path4 = []) => {
144454
145144
  var _a2, _b;
144455
145145
  for (const issue2 of error2.issues) {
144456
145146
  if (issue2.code === "invalid_union" && issue2.errors.length) {
144457
- issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
145147
+ issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
144458
145148
  } else if (issue2.code === "invalid_key") {
144459
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
145149
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
144460
145150
  } else if (issue2.code === "invalid_element") {
144461
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
145151
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
144462
145152
  } else {
144463
- const fullpath = [...path3, ...issue2.path];
145153
+ const fullpath = [...path4, ...issue2.path];
144464
145154
  if (fullpath.length === 0) {
144465
145155
  result.errors.push(mapper(issue2));
144466
145156
  continue;
@@ -144492,8 +145182,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
144492
145182
  }
144493
145183
  function toDotPath(_path) {
144494
145184
  const segs = [];
144495
- const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
144496
- for (const seg of path3) {
145185
+ const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
145186
+ for (const seg of path4) {
144497
145187
  if (typeof seg === "number")
144498
145188
  segs.push(`[${seg}]`);
144499
145189
  else if (typeof seg === "symbol")
@@ -156952,13 +157642,13 @@ function resolveRef(ref, ctx) {
156952
157642
  if (!ref.startsWith("#")) {
156953
157643
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
156954
157644
  }
156955
- const path3 = ref.slice(1).split("/").filter(Boolean);
156956
- if (path3.length === 0) {
157645
+ const path4 = ref.slice(1).split("/").filter(Boolean);
157646
+ if (path4.length === 0) {
156957
157647
  return ctx.rootSchema;
156958
157648
  }
156959
157649
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
156960
- if (path3[0] === defsKey) {
156961
- const key = path3[1];
157650
+ if (path4[0] === defsKey) {
157651
+ const key = path4[1];
156962
157652
  if (!key || !ctx.defs[key]) {
156963
157653
  throw new Error(`Reference not found: ${ref}`);
156964
157654
  }
@@ -157446,7 +158136,8 @@ var BaseEmbeddingConfigSchema = exports_external.object({
157446
158136
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
157447
158137
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
157448
158138
  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."),
157449
- 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.")
158139
+ 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."),
158140
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
157450
158141
  }).superRefine((data, ctx) => {
157451
158142
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
157452
158143
  ctx.addIssue({
@@ -157467,7 +158158,8 @@ var EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data) => {
157467
158158
  if (data.provider === "local") {
157468
158159
  return {
157469
158160
  provider: "local",
157470
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
158161
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
158162
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
157471
158163
  };
157472
158164
  }
157473
158165
  if (data.provider === "openai-compatible") {
@@ -157480,7 +158172,8 @@ var EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data) => {
157480
158172
  endpoint: data.endpoint?.trim() ?? "",
157481
158173
  ...apiKey ? { api_key: apiKey } : {},
157482
158174
  ...inputType ? { input_type: inputType } : {},
157483
- ...truncate ? { truncate } : {}
158175
+ ...truncate ? { truncate } : {},
158176
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
157484
158177
  };
157485
158178
  }
157486
158179
  return { provider: "off" };
@@ -157726,31 +158419,31 @@ function getUserConfigPaths() {
157726
158419
  return [`${basePath}.jsonc`, `${basePath}.json`];
157727
158420
  }
157728
158421
  function resolveFirstExisting(paths) {
157729
- return paths.find((path3) => existsSync5(path3));
158422
+ return paths.find((path4) => existsSync5(path4));
157730
158423
  }
157731
- function loadConfigFile(path3, scope) {
158424
+ function loadConfigFile(path4, scope) {
157732
158425
  try {
157733
- const rawText = readFileSync3(path3, "utf-8");
158426
+ const rawText = readFileSync3(path4, "utf-8");
157734
158427
  const substituted = substituteConfigVariables({
157735
158428
  text: rawText,
157736
- configPath: path3,
158429
+ configPath: path4,
157737
158430
  isProjectConfig: scope === "project"
157738
158431
  });
157739
158432
  return {
157740
- path: path3,
158433
+ path: path4,
157741
158434
  scope,
157742
158435
  config: import_comment_json.parse(substituted.text),
157743
- warnings: substituted.warnings.map((warning) => `${path3}: ${warning}`),
158436
+ warnings: substituted.warnings.map((warning) => `${path4}: ${warning}`),
157744
158437
  loadOutcome: substituted.warnings.length > 0 ? "substitution-failure" : "ok"
157745
158438
  };
157746
158439
  } catch (error51) {
157747
158440
  const message = error51 instanceof Error ? error51.message : String(error51);
157748
158441
  return {
157749
- path: path3,
158442
+ path: path4,
157750
158443
  scope,
157751
158444
  config: {},
157752
158445
  warnings: [
157753
- `${path3}: failed to load config: ${message}; using defaults for this file.`
158446
+ `${path4}: failed to load config: ${message}; using defaults for this file.`
157754
158447
  ],
157755
158448
  loadOutcome: typeof error51.code === "string" ? "project-file-io-error" : "project-file-parse-error"
157756
158449
  };
@@ -157916,9 +158609,9 @@ function bindSubstitutionFailures(loaded) {
157916
158609
  }
157917
158610
  const emptyPaths = collectEmptyStringPaths(loaded.config);
157918
158611
  return loaded.warnings.map((message) => {
157919
- const matchedPath = emptyPaths.find((path3) => {
157920
- const tail = path3.split(".").at(-1) ?? path3;
157921
- return message.includes(path3) || message.toLowerCase().includes(tail.toLowerCase());
158612
+ const matchedPath = emptyPaths.find((path4) => {
158613
+ const tail = path4.split(".").at(-1) ?? path4;
158614
+ return message.includes(path4) || message.toLowerCase().includes(tail.toLowerCase());
157922
158615
  });
157923
158616
  return {
157924
158617
  keyPath: matchedPath ?? "<unknown>",
@@ -158006,6 +158699,441 @@ function loadPiConfigDetailed(opts = {}) {
158006
158699
  // ../plugin/src/features/magic-context/memory/embedding.ts
158007
158700
  init_logger();
158008
158701
 
158702
+ // ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
158703
+ import { createHash as createHash4 } from "node:crypto";
158704
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512;
158705
+ var loadFtsRowsStatements = new WeakMap;
158706
+ var existingHashStatements = new WeakMap;
158707
+ var existingHashByProjectStatements = new WeakMap;
158708
+ var deleteByCompartmentStatements = new WeakMap;
158709
+ var insertEmbeddingStatements = new WeakMap;
158710
+ var distinctModelStatements = new WeakMap;
158711
+ var clearProjectStatements = new WeakMap;
158712
+ var clearProjectModelStatements = new WeakMap;
158713
+ var searchRowsStatements = new WeakMap;
158714
+ var searchRowsByModelStatements = new WeakMap;
158715
+ var backfillCandidateStatements = new WeakMap;
158716
+ function getLoadFtsRowsStatement(db) {
158717
+ let stmt = loadFtsRowsStatements.get(db);
158718
+ if (!stmt) {
158719
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
158720
+ FROM message_history_fts
158721
+ WHERE session_id = ?
158722
+ AND message_ordinal >= ?
158723
+ AND message_ordinal <= ?
158724
+ AND role IN ('user', 'assistant')
158725
+ ORDER BY message_ordinal ASC`);
158726
+ loadFtsRowsStatements.set(db, stmt);
158727
+ }
158728
+ return stmt;
158729
+ }
158730
+ function getExistingHashStatement(db, scopedToProject) {
158731
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
158732
+ let stmt = map2.get(db);
158733
+ if (!stmt) {
158734
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
158735
+ FROM compartment_chunk_embeddings
158736
+ WHERE compartment_id = ?
158737
+ AND model_id = ?
158738
+ ${scopedToProject ? "AND project_path = ?" : ""}
158739
+ ORDER BY window_index ASC`);
158740
+ map2.set(db, stmt);
158741
+ }
158742
+ return stmt;
158743
+ }
158744
+ function getDeleteByCompartmentStatement(db) {
158745
+ let stmt = deleteByCompartmentStatements.get(db);
158746
+ if (!stmt) {
158747
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
158748
+ deleteByCompartmentStatements.set(db, stmt);
158749
+ }
158750
+ return stmt;
158751
+ }
158752
+ function getInsertEmbeddingStatement(db) {
158753
+ let stmt = insertEmbeddingStatements.get(db);
158754
+ if (!stmt) {
158755
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
158756
+ compartment_id, session_id, project_path, harness, window_index,
158757
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
158758
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
158759
+ insertEmbeddingStatements.set(db, stmt);
158760
+ }
158761
+ return stmt;
158762
+ }
158763
+ function getDistinctModelStatement(db) {
158764
+ let stmt = distinctModelStatements.get(db);
158765
+ if (!stmt) {
158766
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
158767
+ FROM compartment_chunk_embeddings
158768
+ WHERE project_path = ?`);
158769
+ distinctModelStatements.set(db, stmt);
158770
+ }
158771
+ return stmt;
158772
+ }
158773
+ function getClearProjectStatement(db) {
158774
+ let stmt = clearProjectStatements.get(db);
158775
+ if (!stmt) {
158776
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
158777
+ clearProjectStatements.set(db, stmt);
158778
+ }
158779
+ return stmt;
158780
+ }
158781
+ function getClearProjectModelStatement(db) {
158782
+ let stmt = clearProjectModelStatements.get(db);
158783
+ if (!stmt) {
158784
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
158785
+ clearProjectModelStatements.set(db, stmt);
158786
+ }
158787
+ return stmt;
158788
+ }
158789
+ function getSearchRowsStatement(db, withModel) {
158790
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
158791
+ let stmt = map2.get(db);
158792
+ if (!stmt) {
158793
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
158794
+ e.session_id AS sessionId,
158795
+ c.title AS title,
158796
+ c.start_message AS compartmentStart,
158797
+ c.end_message AS compartmentEnd,
158798
+ e.window_index AS windowIndex,
158799
+ e.start_ordinal AS windowStart,
158800
+ e.end_ordinal AS windowEnd,
158801
+ e.chunk_hash AS chunkHash,
158802
+ e.model_id AS modelId,
158803
+ e.dims AS dims,
158804
+ e.vector AS vector
158805
+ FROM compartment_chunk_embeddings e
158806
+ JOIN compartments c ON c.id = e.compartment_id
158807
+ WHERE e.session_id = ?
158808
+ AND e.project_path = ?
158809
+ ${withModel ? "AND e.model_id = ?" : ""}
158810
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
158811
+ map2.set(db, stmt);
158812
+ }
158813
+ return stmt;
158814
+ }
158815
+ function isFinitePositiveInteger(value) {
158816
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
158817
+ }
158818
+ function normalizeCompartmentChunkMaxInputTokens(value) {
158819
+ if (!isFinitePositiveInteger(value)) {
158820
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
158821
+ }
158822
+ return Math.max(1, Math.floor(value));
158823
+ }
158824
+ function normalizeContent(text) {
158825
+ return text.replace(/\s+/g, " ").trim();
158826
+ }
158827
+ function formatOrdinalRange(start, end) {
158828
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
158829
+ }
158830
+ function rolePrefix(role) {
158831
+ if (role === "user")
158832
+ return "U";
158833
+ if (role === "assistant")
158834
+ return "A";
158835
+ return null;
158836
+ }
158837
+ function parseOrdinal(value) {
158838
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
158839
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
158840
+ }
158841
+ function parseCanonicalLineRange(line) {
158842
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
158843
+ if (!match)
158844
+ return null;
158845
+ const start = Number.parseInt(match[1], 10);
158846
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
158847
+ if (!Number.isFinite(start) || !Number.isFinite(end))
158848
+ return null;
158849
+ return { start, end };
158850
+ }
158851
+ function hashChunkText(text) {
158852
+ return createHash4("sha256").update(text).digest("hex");
158853
+ }
158854
+ function vectorBlob(vector) {
158855
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
158856
+ }
158857
+ function toFloat32Array(blob) {
158858
+ if (blob instanceof Uint8Array) {
158859
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
158860
+ return new Float32Array(buffer2);
158861
+ }
158862
+ return new Float32Array(blob.slice(0));
158863
+ }
158864
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
158865
+ if (endOrdinal < startOrdinal)
158866
+ return "";
158867
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
158868
+ const lines = [];
158869
+ let current = null;
158870
+ const flush2 = () => {
158871
+ if (!current || current.parts.length === 0)
158872
+ return;
158873
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
158874
+ current = null;
158875
+ };
158876
+ for (const row of rows) {
158877
+ const ordinal = parseOrdinal(row.messageOrdinal);
158878
+ const prefix = rolePrefix(row.role);
158879
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
158880
+ if (ordinal === null || prefix === null || content.length === 0)
158881
+ continue;
158882
+ if (current && current.role === prefix) {
158883
+ current.end = ordinal;
158884
+ current.parts.push(content);
158885
+ continue;
158886
+ }
158887
+ flush2();
158888
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
158889
+ }
158890
+ flush2();
158891
+ return lines.join(`
158892
+ `);
158893
+ }
158894
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
158895
+ const lines = [];
158896
+ for (const rawLine of chunkText.split(/\r?\n/)) {
158897
+ const line = rawLine.trim();
158898
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
158899
+ if (!match)
158900
+ continue;
158901
+ const lineStart = Number.parseInt(match[2], 10);
158902
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
158903
+ if (startOrdinal != null && lineEnd < startOrdinal)
158904
+ continue;
158905
+ if (endOrdinal != null && lineStart > endOrdinal)
158906
+ continue;
158907
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
158908
+ const ordinalSpan = lineEnd - lineStart + 1;
158909
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
158910
+ if (ordinalSpan === rawParts.length) {
158911
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
158912
+ if (part.startsWith("TC:"))
158913
+ return false;
158914
+ if (startOrdinal != null && ordinal < startOrdinal)
158915
+ return false;
158916
+ if (endOrdinal != null && ordinal > endOrdinal)
158917
+ return false;
158918
+ return true;
158919
+ });
158920
+ if (retained.length === 0)
158921
+ continue;
158922
+ const retainedStart = retained[0].ordinal;
158923
+ const retainedEnd = retained[retained.length - 1].ordinal;
158924
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
158925
+ continue;
158926
+ }
158927
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
158928
+ if (parts.length === 0)
158929
+ continue;
158930
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
158931
+ }
158932
+ return lines.join(`
158933
+ `);
158934
+ }
158935
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
158936
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
158937
+ if (lines.length === 0 || endOrdinal < startOrdinal)
158938
+ return [];
158939
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
158940
+ const fullText = lines.join(`
158941
+ `);
158942
+ if (estimateTokens(fullText) <= normalizedMax) {
158943
+ return [
158944
+ {
158945
+ windowIndex: 0,
158946
+ startOrdinal,
158947
+ endOrdinal,
158948
+ text: fullText,
158949
+ chunkHash: hashChunkText(fullText)
158950
+ }
158951
+ ];
158952
+ }
158953
+ const windows = [];
158954
+ let currentLines = [];
158955
+ let currentStart = null;
158956
+ let currentEnd = null;
158957
+ let currentTokens = 0;
158958
+ const flush2 = () => {
158959
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
158960
+ return;
158961
+ const text = currentLines.join(`
158962
+ `);
158963
+ windows.push({
158964
+ windowIndex: windows.length + 1,
158965
+ startOrdinal: currentStart,
158966
+ endOrdinal: currentEnd,
158967
+ text,
158968
+ chunkHash: hashChunkText(text)
158969
+ });
158970
+ currentLines = [];
158971
+ currentStart = null;
158972
+ currentEnd = null;
158973
+ currentTokens = 0;
158974
+ };
158975
+ for (const line of lines) {
158976
+ const range = parseCanonicalLineRange(line);
158977
+ const lineStart = range?.start ?? startOrdinal;
158978
+ const lineEnd = range?.end ?? lineStart;
158979
+ const lineTokens = estimateTokens(line);
158980
+ if (currentLines.length > 0 && currentTokens + lineTokens > normalizedMax) {
158981
+ flush2();
158982
+ }
158983
+ if (currentLines.length === 0) {
158984
+ currentStart = lineStart;
158985
+ }
158986
+ currentLines.push(line);
158987
+ currentEnd = lineEnd;
158988
+ currentTokens += lineTokens;
158989
+ }
158990
+ flush2();
158991
+ return windows;
158992
+ }
158993
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
158994
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
158995
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
158996
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
158997
+ }
158998
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
158999
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
159000
+ if (existing.size !== windows.length)
159001
+ return false;
159002
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
159003
+ }
159004
+ function replaceCompartmentChunkEmbeddings(db, rows) {
159005
+ if (rows.length === 0)
159006
+ return;
159007
+ const compartmentId = rows[0].compartmentId;
159008
+ const now = Date.now();
159009
+ db.transaction(() => {
159010
+ getDeleteByCompartmentStatement(db).run(compartmentId);
159011
+ const insert = getInsertEmbeddingStatement(db);
159012
+ for (const row of rows) {
159013
+ 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);
159014
+ }
159015
+ })();
159016
+ }
159017
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
159018
+ const rows = getDistinctModelStatement(db).all(projectPath);
159019
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
159020
+ }
159021
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
159022
+ if (modelId) {
159023
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
159024
+ }
159025
+ return getClearProjectStatement(db).run(projectPath).changes;
159026
+ }
159027
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
159028
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
159029
+ 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) => ({
159030
+ compartmentId: row.compartmentId,
159031
+ sessionId: row.sessionId,
159032
+ title: row.title,
159033
+ startOrdinal: row.compartmentStart,
159034
+ endOrdinal: row.compartmentEnd,
159035
+ windowIndex: row.windowIndex,
159036
+ windowStartOrdinal: row.windowStart,
159037
+ windowEndOrdinal: row.windowEnd,
159038
+ chunkHash: row.chunkHash,
159039
+ modelId: row.modelId,
159040
+ dims: row.dims,
159041
+ vector: toFloat32Array(row.vector)
159042
+ }));
159043
+ }
159044
+ function mapBackfillCandidateRows(rows) {
159045
+ return rows.filter((row) => {
159046
+ if (row === null || typeof row !== "object")
159047
+ return false;
159048
+ const candidate = row;
159049
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
159050
+ }).map((row) => ({
159051
+ id: row.id,
159052
+ sessionId: row.sessionId,
159053
+ startMessage: row.startMessage,
159054
+ endMessage: row.endMessage,
159055
+ title: row.title
159056
+ }));
159057
+ }
159058
+ var sessionBackfillCandidateStatements = new WeakMap;
159059
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
159060
+ if (excludeIds && excludeIds.length > 0) {
159061
+ const placeholders2 = excludeIds.map(() => "?").join(", ");
159062
+ const stmt2 = db.prepare(`SELECT c.id AS id,
159063
+ c.session_id AS sessionId,
159064
+ c.start_message AS startMessage,
159065
+ c.end_message AS endMessage,
159066
+ c.title AS title
159067
+ FROM compartments c
159068
+ JOIN session_projects sp
159069
+ ON sp.session_id = c.session_id
159070
+ AND sp.harness = c.harness
159071
+ AND sp.project_path = ?
159072
+ WHERE c.session_id = ?
159073
+ AND c.start_message IS NOT NULL
159074
+ AND c.end_message IS NOT NULL
159075
+ AND c.id NOT IN (${placeholders2})
159076
+ AND NOT EXISTS (
159077
+ SELECT 1
159078
+ FROM compartment_chunk_embeddings current
159079
+ WHERE current.compartment_id = c.id
159080
+ AND current.project_path = ?
159081
+ AND current.model_id = ?
159082
+ )
159083
+ ORDER BY c.start_message ASC, c.id ASC
159084
+ LIMIT ?`);
159085
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
159086
+ return mapBackfillCandidateRows(rows2);
159087
+ }
159088
+ let stmt = sessionBackfillCandidateStatements.get(db);
159089
+ if (!stmt) {
159090
+ stmt = db.prepare(`SELECT c.id AS id,
159091
+ c.session_id AS sessionId,
159092
+ c.start_message AS startMessage,
159093
+ c.end_message AS endMessage,
159094
+ c.title AS title
159095
+ FROM compartments c
159096
+ JOIN session_projects sp
159097
+ ON sp.session_id = c.session_id
159098
+ AND sp.harness = c.harness
159099
+ AND sp.project_path = ?
159100
+ WHERE c.session_id = ?
159101
+ AND c.start_message IS NOT NULL
159102
+ AND c.end_message IS NOT NULL
159103
+ AND NOT EXISTS (
159104
+ SELECT 1
159105
+ FROM compartment_chunk_embeddings current
159106
+ WHERE current.compartment_id = c.id
159107
+ AND current.project_path = ?
159108
+ AND current.model_id = ?
159109
+ )
159110
+ ORDER BY c.start_message ASC, c.id ASC
159111
+ LIMIT ?`);
159112
+ sessionBackfillCandidateStatements.set(db, stmt);
159113
+ }
159114
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
159115
+ return mapBackfillCandidateRows(rows);
159116
+ }
159117
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
159118
+ const row = db.prepare(`SELECT COUNT(*) AS n
159119
+ FROM compartments c
159120
+ JOIN session_projects sp
159121
+ ON sp.session_id = c.session_id
159122
+ AND sp.harness = c.harness
159123
+ AND sp.project_path = ?
159124
+ WHERE c.session_id = ?
159125
+ AND c.start_message IS NOT NULL
159126
+ AND c.end_message IS NOT NULL
159127
+ AND NOT EXISTS (
159128
+ SELECT 1
159129
+ FROM compartment_chunk_embeddings current
159130
+ WHERE current.compartment_id = c.id
159131
+ AND current.project_path = ?
159132
+ AND current.model_id = ?
159133
+ )`).get(projectPath, sessionId, projectPath, modelId);
159134
+ return typeof row?.n === "number" ? row.n : 0;
159135
+ }
159136
+
158009
159137
  // ../plugin/src/features/magic-context/memory/cosine-similarity.ts
158010
159138
  function cosineSimilarity(a, b) {
158011
159139
  if (a.length !== b.length) {
@@ -158024,13 +159152,13 @@ function cosineSimilarity(a, b) {
158024
159152
  }
158025
159153
 
158026
159154
  // ../plugin/src/features/magic-context/memory/normalize-hash.ts
158027
- import { createHash as createHash2 } from "node:crypto";
159155
+ import { createHash as createHash5 } from "node:crypto";
158028
159156
  function normalizeMemoryContent(content) {
158029
159157
  return content.toLowerCase().replace(/\s+/g, " ").trim();
158030
159158
  }
158031
159159
  function computeNormalizedHash(content) {
158032
159160
  const normalized = normalizeMemoryContent(content);
158033
- return createHash2("md5").update(normalized).digest("hex");
159161
+ return createHash5("md5").update(normalized).digest("hex");
158034
159162
  }
158035
159163
 
158036
159164
  // ../plugin/src/features/magic-context/memory/embedding-identity.ts
@@ -158174,19 +159302,19 @@ function isArrayLikeNumber(value) {
158174
159302
  }
158175
159303
  return arr.length === 0 || typeof arr[0] === "number";
158176
159304
  }
158177
- function toFloat32Array(values) {
159305
+ function toFloat32Array2(values) {
158178
159306
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
158179
159307
  }
158180
159308
  function extractBatchEmbeddings(result, expectedCount) {
158181
159309
  const { data } = result;
158182
159310
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
158183
- return data.map((entry) => toFloat32Array(entry));
159311
+ return data.map((entry) => toFloat32Array2(entry));
158184
159312
  }
158185
159313
  if (!isArrayLikeNumber(data)) {
158186
159314
  log("[magic-context] embedding batch returned unexpected data shape");
158187
159315
  return Array.from({ length: expectedCount }, () => null);
158188
159316
  }
158189
- const flatData = toFloat32Array(data);
159317
+ const flatData = toFloat32Array2(data);
158190
159318
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
158191
159319
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
158192
159320
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -158201,6 +159329,7 @@ function extractBatchEmbeddings(result, expectedCount) {
158201
159329
 
158202
159330
  class LocalEmbeddingProvider {
158203
159331
  modelId;
159332
+ maxInputTokens;
158204
159333
  model;
158205
159334
  pipeline = null;
158206
159335
  initPromise = null;
@@ -158208,8 +159337,9 @@ class LocalEmbeddingProvider {
158208
159337
  disposing = false;
158209
159338
  disposePromise = null;
158210
159339
  inFlightWaiters = [];
158211
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
159340
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
158212
159341
  this.model = model;
159342
+ this.maxInputTokens = maxInputTokens;
158213
159343
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
158214
159344
  }
158215
159345
  async initialize() {
@@ -158464,6 +159594,7 @@ var FETCH_TIMEOUT_MS = 30000;
158464
159594
 
158465
159595
  class OpenAICompatibleEmbeddingProvider {
158466
159596
  modelId;
159597
+ maxInputTokens;
158467
159598
  endpoint;
158468
159599
  model;
158469
159600
  apiKey;
@@ -158480,11 +159611,13 @@ class OpenAICompatibleEmbeddingProvider {
158480
159611
  this.apiKey = options.apiKey?.trim() ?? "";
158481
159612
  this.inputType = options.inputType?.trim() ?? "";
158482
159613
  this.truncate = options.truncate?.trim() ?? "";
159614
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
158483
159615
  this.modelId = getEmbeddingProviderIdentity({
158484
159616
  provider: "openai-compatible",
158485
159617
  endpoint: this.endpoint,
158486
159618
  model: this.model,
158487
- ...this.apiKey ? { api_key: this.apiKey } : {}
159619
+ ...this.apiKey ? { api_key: this.apiKey } : {},
159620
+ ...this.inputType ? { input_type: this.inputType } : {}
158488
159621
  });
158489
159622
  }
158490
159623
  async initialize() {
@@ -158688,9 +159821,9 @@ function isEmbeddingRow(row) {
158688
159821
  if (row === null || typeof row !== "object")
158689
159822
  return false;
158690
159823
  const candidate = row;
158691
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
159824
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
158692
159825
  }
158693
- function toFloat32Array2(blob) {
159826
+ function toFloat32Array3(blob) {
158694
159827
  if (blob instanceof Uint8Array) {
158695
159828
  const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
158696
159829
  return new Float32Array(buffer2);
@@ -158708,7 +159841,7 @@ function getSaveEmbeddingStatement(db) {
158708
159841
  function getLoadAllEmbeddingsStatement(db) {
158709
159842
  let stmt = loadAllEmbeddingsStatements.get(db);
158710
159843
  if (!stmt) {
158711
- 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");
159844
+ 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");
158712
159845
  loadAllEmbeddingsStatements.set(db, stmt);
158713
159846
  }
158714
159847
  return stmt;
@@ -158737,7 +159870,10 @@ function loadAllEmbeddings(db, projectPath) {
158737
159870
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
158738
159871
  const embeddings = new Map;
158739
159872
  for (const row of rows) {
158740
- embeddings.set(row.memoryId, toFloat32Array2(row.embedding));
159873
+ embeddings.set(row.memoryId, {
159874
+ embedding: toFloat32Array3(row.embedding),
159875
+ modelId: row.modelId
159876
+ });
158741
159877
  }
158742
159878
  return embeddings;
158743
159879
  }
@@ -158750,7 +159886,7 @@ function getDistinctStoredModelIds(db, projectPath) {
158750
159886
  }
158751
159887
 
158752
159888
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
158753
- import { createHash as createHash3, randomUUID } from "node:crypto";
159889
+ import { createHash as createHash6, randomUUID } from "node:crypto";
158754
159890
  init_logger();
158755
159891
 
158756
159892
  // ../plugin/src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
@@ -158758,7 +159894,7 @@ var saveStatements = new WeakMap;
158758
159894
  var loadProjectStatements = new WeakMap;
158759
159895
  var loadUnembeddedStatements = new WeakMap;
158760
159896
  var countEmbeddedStatements = new WeakMap;
158761
- var clearProjectStatements = new WeakMap;
159897
+ var clearProjectStatements2 = new WeakMap;
158762
159898
  var distinctModelIdStatements = new WeakMap;
158763
159899
  function getSaveStatement(db) {
158764
159900
  let stmt = saveStatements.get(db);
@@ -158806,12 +159942,12 @@ function getCountEmbeddedStatement(db) {
158806
159942
  }
158807
159943
  return stmt;
158808
159944
  }
158809
- function getClearProjectStatement(db) {
158810
- let stmt = clearProjectStatements.get(db);
159945
+ function getClearProjectStatement2(db) {
159946
+ let stmt = clearProjectStatements2.get(db);
158811
159947
  if (!stmt) {
158812
159948
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
158813
159949
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
158814
- clearProjectStatements.set(db, stmt);
159950
+ clearProjectStatements2.set(db, stmt);
158815
159951
  }
158816
159952
  return stmt;
158817
159953
  }
@@ -158847,7 +159983,7 @@ function countEmbeddedCommits(db, projectPath) {
158847
159983
  return row?.count ?? 0;
158848
159984
  }
158849
159985
  function clearProjectCommitEmbeddings(db, projectPath) {
158850
- return getClearProjectStatement(db).run(projectPath).changes;
159986
+ return getClearProjectStatement2(db).run(projectPath).changes;
158851
159987
  }
158852
159988
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
158853
159989
  const rows = getDistinctModelIdStatement(db).all(projectPath);
@@ -159011,9 +160147,83 @@ function invalidateMemory(projectPath, memoryId) {
159011
160147
  cached2?.embeddings.delete(memoryId);
159012
160148
  }
159013
160149
 
160150
+ // ../plugin/src/features/magic-context/session-project-storage.ts
160151
+ var upsertSessionProjectStatements = new WeakMap;
160152
+ var repairSessionChunkProjectStatements = new WeakMap;
160153
+ var repairProjectChunkProjectStatements = new WeakMap;
160154
+ function getUpsertSessionProjectStatement(db) {
160155
+ let stmt = upsertSessionProjectStatements.get(db);
160156
+ if (!stmt) {
160157
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
160158
+ VALUES (?, ?, ?, ?)
160159
+ ON CONFLICT(session_id, harness) DO UPDATE SET
160160
+ project_path = excluded.project_path,
160161
+ updated_at = excluded.updated_at
160162
+ WHERE session_projects.project_path <> excluded.project_path`);
160163
+ upsertSessionProjectStatements.set(db, stmt);
160164
+ }
160165
+ return stmt;
160166
+ }
160167
+ function getRepairSessionChunkProjectStatement(db) {
160168
+ let stmt = repairSessionChunkProjectStatements.get(db);
160169
+ if (!stmt) {
160170
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
160171
+ SET project_path = ?
160172
+ WHERE session_id = ?
160173
+ AND harness = ?
160174
+ AND project_path <> ?`);
160175
+ repairSessionChunkProjectStatements.set(db, stmt);
160176
+ }
160177
+ return stmt;
160178
+ }
160179
+ function getRepairProjectChunkProjectStatement(db) {
160180
+ let stmt = repairProjectChunkProjectStatements.get(db);
160181
+ if (!stmt) {
160182
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
160183
+ SET project_path = (
160184
+ SELECT sp.project_path
160185
+ FROM session_projects sp
160186
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
160187
+ AND sp.harness = compartment_chunk_embeddings.harness
160188
+ LIMIT 1
160189
+ )
160190
+ WHERE EXISTS (
160191
+ SELECT 1
160192
+ FROM session_projects sp
160193
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
160194
+ AND sp.harness = compartment_chunk_embeddings.harness
160195
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
160196
+ AND (
160197
+ sp.project_path = ?
160198
+ OR compartment_chunk_embeddings.project_path = ?
160199
+ )
160200
+ )`);
160201
+ repairProjectChunkProjectStatements.set(db, stmt);
160202
+ }
160203
+ return stmt;
160204
+ }
160205
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
160206
+ if (!sessionId || !projectPath)
160207
+ return;
160208
+ const harness = getHarness();
160209
+ const now = Date.now();
160210
+ db.transaction(() => {
160211
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
160212
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
160213
+ })();
160214
+ }
160215
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
160216
+ if (!projectPath)
160217
+ return 0;
160218
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
160219
+ }
160220
+
159014
160221
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
159015
160222
  var OFF_PROVIDER_IDENTITY = "embedding-provider:off";
159016
160223
  var SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
160224
+ var CHUNK_DRAIN_BATCH_SIZE = 8;
160225
+ var MAX_WINDOWS_PER_EMBED_CALL = 16;
160226
+ var SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
159017
160227
  var projectRegistrations = new Map;
159018
160228
  var loadUnembeddedMemoriesStatements = new WeakMap;
159019
160229
  var globalRegistrationGeneration = 0;
@@ -159022,7 +160232,10 @@ function resolveEmbeddingConfig(config2) {
159022
160232
  if (!config2 || config2.provider === "local") {
159023
160233
  return {
159024
160234
  provider: "local",
159025
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
160235
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
160236
+ ...config2?.max_input_tokens ? {
160237
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
160238
+ } : {}
159026
160239
  };
159027
160240
  }
159028
160241
  if (config2.provider === "openai-compatible") {
@@ -159035,7 +160248,10 @@ function resolveEmbeddingConfig(config2) {
159035
160248
  endpoint: config2.endpoint.trim(),
159036
160249
  ...apiKey ? { api_key: apiKey } : {},
159037
160250
  ...inputType ? { input_type: inputType } : {},
159038
- ...truncate ? { truncate } : {}
160251
+ ...truncate ? { truncate } : {},
160252
+ ...config2.max_input_tokens ? {
160253
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
160254
+ } : {}
159039
160255
  };
159040
160256
  }
159041
160257
  return { provider: "off" };
@@ -159053,10 +160269,11 @@ function createProvider(config2) {
159053
160269
  model: config2.model,
159054
160270
  apiKey: config2.api_key,
159055
160271
  inputType: config2.input_type,
159056
- truncate: config2.truncate
160272
+ truncate: config2.truncate,
160273
+ maxInputTokens: config2.max_input_tokens
159057
160274
  });
159058
160275
  }
159059
- return new LocalEmbeddingProvider(config2.model);
160276
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
159060
160277
  }
159061
160278
  function stableStringify2(value) {
159062
160279
  if (Array.isArray(value)) {
@@ -159069,7 +160286,7 @@ function stableStringify2(value) {
159069
160286
  return JSON.stringify(value);
159070
160287
  }
159071
160288
  function sha256Prefix(value, length = 16) {
159072
- return createHash3("sha256").update(value).digest("hex").slice(0, length);
160289
+ return createHash6("sha256").update(value).digest("hex").slice(0, length);
159073
160290
  }
159074
160291
  function getRuntimeFingerprint(config2) {
159075
160292
  if (config2.provider === "off") {
@@ -159077,6 +160294,18 @@ function getRuntimeFingerprint(config2) {
159077
160294
  }
159078
160295
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
159079
160296
  }
160297
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
160298
+ if (config2.provider === "off") {
160299
+ return OFF_PROVIDER_IDENTITY;
160300
+ }
160301
+ const chunkIdentity = {
160302
+ providerIdentity,
160303
+ chunkerVersion: 1,
160304
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
160305
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
160306
+ };
160307
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
160308
+ }
159080
160309
  function sameFeatures(a, b) {
159081
160310
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
159082
160311
  }
@@ -159093,7 +160322,8 @@ function snapshotFor(registration) {
159093
160322
  features: { ...registration.features },
159094
160323
  enabled,
159095
160324
  gitCommitEnabled,
159096
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
160325
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
160326
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
159097
160327
  };
159098
160328
  }
159099
160329
  function disposeProvider(provider) {
@@ -159113,7 +160343,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
159113
160343
  }
159114
160344
  return false;
159115
160345
  }
159116
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
160346
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
159117
160347
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
159118
160348
  return false;
159119
160349
  }
@@ -159134,6 +160364,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
159134
160364
  wiped = true;
159135
160365
  }
159136
160366
  }
160367
+ if (features.memoryEnabled) {
160368
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
160369
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
160370
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
160371
+ clearChunkEmbeddingsForProject(db, projectIdentity);
160372
+ wiped = true;
160373
+ }
160374
+ }
159137
160375
  })();
159138
160376
  return wiped;
159139
160377
  }
@@ -159141,10 +160379,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
159141
160379
  const resolvedConfig = resolveEmbeddingConfig(config2);
159142
160380
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
159143
160381
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
160382
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
159144
160383
  const prior = projectRegistrations.get(projectIdentity);
159145
160384
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
159146
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
159147
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
160385
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
160386
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
159148
160387
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
159149
160388
  const registration = {
159150
160389
  projectIdentity,
@@ -159156,6 +160395,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
159156
160395
  generation,
159157
160396
  features: { ...features },
159158
160397
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
160398
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
159159
160399
  observationMode: false
159160
160400
  };
159161
160401
  projectRegistrations.set(projectIdentity, registration);
@@ -159178,6 +160418,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
159178
160418
  generation,
159179
160419
  features: { memoryEnabled: false, gitCommitEnabled: false },
159180
160420
  modelId: "off",
160421
+ chunkModelId: "off",
159181
160422
  observationMode: true
159182
160423
  };
159183
160424
  projectRegistrations.set(projectIdentity, registration);
@@ -159188,6 +160429,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
159188
160429
  const registration = projectRegistrations.get(projectIdentity);
159189
160430
  return registration ? snapshotFor(registration) : null;
159190
160431
  }
160432
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
160433
+ const registration = projectRegistrations.get(projectIdentity);
160434
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
160435
+ }
160436
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
160437
+ const registration = projectRegistrations.get(projectIdentity);
160438
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
160439
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
160440
+ }
159191
160441
  function getOrCreateProjectProvider(registration) {
159192
160442
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
159193
160443
  return null;
@@ -159282,6 +160532,131 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
159282
160532
  return 0;
159283
160533
  }
159284
160534
  }
160535
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
160536
+ const noWork = [];
160537
+ if (candidates.length === 0)
160538
+ return { embedded: 0, noWork };
160539
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
160540
+ const prepared = [];
160541
+ for (const candidate of candidates) {
160542
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
160543
+ if (canonicalText.length === 0) {
160544
+ noWork.push(candidate.id);
160545
+ continue;
160546
+ }
160547
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
160548
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
160549
+ noWork.push(candidate.id);
160550
+ continue;
160551
+ }
160552
+ prepared.push({ candidate, windows });
160553
+ }
160554
+ if (prepared.length === 0)
160555
+ return { embedded: 0, noWork };
160556
+ let embedded = 0;
160557
+ let i = 0;
160558
+ while (i < prepared.length) {
160559
+ if (signal?.aborted)
160560
+ break;
160561
+ const slice = [];
160562
+ let windowCount = 0;
160563
+ do {
160564
+ const item = prepared[i];
160565
+ slice.push(item);
160566
+ windowCount += item.windows.length;
160567
+ i += 1;
160568
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
160569
+ const texts = [];
160570
+ for (const item of slice)
160571
+ texts.push(...item.windows.map((w) => w.text));
160572
+ try {
160573
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
160574
+ if (!result)
160575
+ continue;
160576
+ if (signal?.aborted)
160577
+ break;
160578
+ let offset = 0;
160579
+ for (const item of slice) {
160580
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
160581
+ offset += item.windows.length;
160582
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
160583
+ continue;
160584
+ }
160585
+ const rows = item.windows.map((window, index) => ({
160586
+ compartmentId: item.candidate.id,
160587
+ sessionId: item.candidate.sessionId,
160588
+ projectPath: projectIdentity,
160589
+ window,
160590
+ modelId,
160591
+ vector: vectors[index]
160592
+ }));
160593
+ replaceCompartmentChunkEmbeddings(db, rows);
160594
+ embedded += 1;
160595
+ }
160596
+ } catch (error51) {
160597
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
160598
+ }
160599
+ }
160600
+ return { embedded, noWork };
160601
+ }
160602
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
160603
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
160604
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
160605
+ return { status: "disabled", embedded: 0, total: 0 };
160606
+ }
160607
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
160608
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
160609
+ if (total === 0)
160610
+ return { status: "nothing", embedded: 0, total: 0 };
160611
+ const holderId = `session-embed-${randomUUID()}`;
160612
+ const lease = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
160613
+ if (!lease.acquired)
160614
+ return { status: "busy", embedded: 0, total };
160615
+ const renewal = setInterval(() => {
160616
+ try {
160617
+ renewGitSweepLease(db, projectIdentity, holderId);
160618
+ } catch {}
160619
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
160620
+ renewal.unref?.();
160621
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
160622
+ const skipIds = [];
160623
+ let embedded = 0;
160624
+ let aborted2 = false;
160625
+ let providerStalled = false;
160626
+ try {
160627
+ options?.onProgress?.({ embedded, total });
160628
+ for (;; ) {
160629
+ if (options?.signal?.aborted) {
160630
+ aborted2 = true;
160631
+ break;
160632
+ }
160633
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
160634
+ if (candidates.length === 0)
160635
+ break;
160636
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
160637
+ for (const id of noWork)
160638
+ skipIds.push(id);
160639
+ if (n === 0 && noWork.length === 0) {
160640
+ providerStalled = true;
160641
+ break;
160642
+ }
160643
+ embedded += n;
160644
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
160645
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
160646
+ }
160647
+ } finally {
160648
+ clearInterval(renewal);
160649
+ releaseGitSweepLease(db, projectIdentity, holderId);
160650
+ }
160651
+ if (aborted2)
160652
+ return { status: "aborted", embedded, total };
160653
+ if (providerStalled) {
160654
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
160655
+ if (remaining > 0)
160656
+ return { status: "stalled", embedded, total, remaining };
160657
+ }
160658
+ return { status: "done", embedded, total };
160659
+ }
159285
160660
 
159286
160661
  // ../plugin/src/features/magic-context/memory/embedding.ts
159287
160662
  var DEFAULT_EMBEDDING_CONFIG = {
@@ -159301,10 +160676,11 @@ function createProvider2(config2) {
159301
160676
  model: config2.model,
159302
160677
  apiKey: config2.api_key,
159303
160678
  inputType: config2.input_type,
159304
- truncate: config2.truncate
160679
+ truncate: config2.truncate,
160680
+ maxInputTokens: config2.max_input_tokens
159305
160681
  });
159306
160682
  }
159307
- return new LocalEmbeddingProvider(config2.model);
160683
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
159308
160684
  }
159309
160685
  function getOrCreateProvider() {
159310
160686
  if (provider) {
@@ -159328,186 +160704,8 @@ async function embedText(text, signal) {
159328
160704
  }
159329
160705
  var SWEEP_MAX_WALL_CLOCK_MS2 = 10 * 60 * 1000;
159330
160706
 
159331
- // ../plugin/src/features/magic-context/memory/project-identity.ts
159332
- import { execFileSync } from "node:child_process";
159333
- import { createHash as createHash4 } from "node:crypto";
159334
- import { statSync } from "node:fs";
159335
- import path3 from "node:path";
159336
- var GIT_TIMEOUT_MS = 5000;
159337
- var identityCache = new Map;
159338
- var directoryFallbackCache = new Map;
159339
-
159340
- class ProjectIdentityError extends Error {
159341
- errorClass;
159342
- rawDirectory;
159343
- constructor(errorClass, rawDirectory, message, cause) {
159344
- super(message);
159345
- this.name = "ProjectIdentityError";
159346
- this.errorClass = errorClass;
159347
- this.rawDirectory = rawDirectory;
159348
- if (cause) {
159349
- this.cause = cause;
159350
- }
159351
- }
159352
- }
159353
- function asError(error51) {
159354
- return error51 instanceof Error ? error51 : undefined;
159355
- }
159356
- function getErrorCode(error51) {
159357
- if (error51 === null || typeof error51 !== "object" || !("code" in error51)) {
159358
- return;
159359
- }
159360
- const code = error51.code;
159361
- return typeof code === "string" ? code : undefined;
159362
- }
159363
- function getErrorSignal(error51) {
159364
- if (error51 === null || typeof error51 !== "object" || !("signal" in error51)) {
159365
- return;
159366
- }
159367
- const signal = error51.signal;
159368
- return typeof signal === "string" ? signal : undefined;
159369
- }
159370
- function getErrorKilled(error51) {
159371
- if (error51 === null || typeof error51 !== "object" || !("killed" in error51)) {
159372
- return false;
159373
- }
159374
- return error51.killed === true;
159375
- }
159376
- function getErrorStderr(error51) {
159377
- if (error51 === null || typeof error51 !== "object" || !("stderr" in error51)) {
159378
- return "";
159379
- }
159380
- const stderr = error51.stderr;
159381
- if (typeof stderr === "string") {
159382
- return stderr;
159383
- }
159384
- if (Buffer.isBuffer(stderr)) {
159385
- return stderr.toString("utf8");
159386
- }
159387
- return "";
159388
- }
159389
- function directoryFallback(directory) {
159390
- const canonical = path3.resolve(directory);
159391
- const hash2 = createHash4("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
159392
- return `dir:${hash2}`;
159393
- }
159394
- function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
159395
- try {
159396
- const stat2 = statSync(canonicalDirectory);
159397
- if (!stat2.isDirectory()) {
159398
- throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
159399
- }
159400
- } catch (error51) {
159401
- if (error51 instanceof ProjectIdentityError) {
159402
- throw error51;
159403
- }
159404
- const code = getErrorCode(error51);
159405
- if (code === "EACCES" || code === "EPERM") {
159406
- throw new ProjectIdentityError("permission_denied", rawDirectory, `Permission denied while accessing project directory: ${canonicalDirectory}`, asError(error51));
159407
- }
159408
- throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error51));
159409
- }
159410
- }
159411
- function isGitTimeoutError(error51) {
159412
- const code = getErrorCode(error51);
159413
- const signal = getErrorSignal(error51);
159414
- return code === "ETIMEDOUT" || signal === "SIGTERM" || signal === "SIGKILL" || getErrorKilled(error51);
159415
- }
159416
- function classifyGitError(error51, rawDirectory) {
159417
- if (isGitTimeoutError(error51)) {
159418
- return new ProjectIdentityError("git_timeout", rawDirectory, `git rev-list timed out after ${GIT_TIMEOUT_MS}ms`, asError(error51));
159419
- }
159420
- const code = getErrorCode(error51);
159421
- if (code === "ENOENT") {
159422
- return new ProjectIdentityError("git_missing", rawDirectory, "git binary is not available in PATH", asError(error51));
159423
- }
159424
- if (code === "EACCES" || code === "EPERM") {
159425
- return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error51));
159426
- }
159427
- const stderr = getErrorStderr(error51).toLowerCase();
159428
- 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")) {
159429
- return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error51));
159430
- }
159431
- return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error51));
159432
- }
159433
- function resolveProjectIdentityStrict(directory) {
159434
- const canonical = path3.resolve(directory);
159435
- const cached2 = identityCache.get(canonical);
159436
- if (cached2 !== undefined) {
159437
- return cached2;
159438
- }
159439
- assertDirectoryUsable(canonical, directory);
159440
- let output;
159441
- try {
159442
- output = execFileSync("git", ["rev-list", "--max-parents=0", "HEAD"], {
159443
- cwd: canonical,
159444
- encoding: "utf8",
159445
- env: { ...process.env, LC_ALL: "C", LANG: "C" },
159446
- stdio: ["ignore", "pipe", "pipe"],
159447
- timeout: GIT_TIMEOUT_MS
159448
- });
159449
- } catch (error51) {
159450
- throw classifyGitError(error51, directory);
159451
- }
159452
- const firstLine = output.split(`
159453
- `)[0]?.trim() ?? "";
159454
- const rootCommit = firstLine.slice(0, 64);
159455
- if (rootCommit.length < 7) {
159456
- throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
159457
- }
159458
- const identity = `git:${rootCommit}`;
159459
- identityCache.set(canonical, identity);
159460
- return identity;
159461
- }
159462
- function shouldUseDirectoryFallback(error51) {
159463
- return error51.errorClass === "not_git_repo" || error51.errorClass === "unknown" && error51.message.startsWith("Unable to access project directory:");
159464
- }
159465
- function resolveProjectIdentity(directory) {
159466
- const canonical = path3.resolve(directory);
159467
- const cachedFallback = directoryFallbackCache.get(canonical);
159468
- if (cachedFallback !== undefined) {
159469
- if (!hasGitDir(canonical)) {
159470
- return cachedFallback;
159471
- }
159472
- directoryFallbackCache.delete(canonical);
159473
- }
159474
- try {
159475
- return resolveProjectIdentityStrict(directory);
159476
- } catch (error51) {
159477
- if (error51 instanceof ProjectIdentityError && shouldUseDirectoryFallback(error51)) {
159478
- const fallback = directoryFallback(canonical);
159479
- if (!hasGitDir(canonical)) {
159480
- directoryFallbackCache.set(canonical, fallback);
159481
- }
159482
- return fallback;
159483
- }
159484
- throw error51;
159485
- }
159486
- }
159487
- function hasGitDir(canonical) {
159488
- try {
159489
- statSync(path3.join(canonical, ".git"));
159490
- return true;
159491
- } catch {
159492
- return false;
159493
- }
159494
- }
159495
- function normalizeStoredProjectPath(rawOrStored) {
159496
- if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
159497
- return rawOrStored;
159498
- }
159499
- try {
159500
- return resolveProjectIdentity(rawOrStored);
159501
- } catch {
159502
- return directoryFallback(rawOrStored);
159503
- }
159504
- }
159505
- function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
159506
- return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
159507
- }
159508
-
159509
160707
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
159510
- import { createHash as createHash5 } from "node:crypto";
160708
+ import { createHash as createHash7 } from "node:crypto";
159511
160709
  init_logger();
159512
160710
  var EMBEDDING_AFFECTING_KEYS = new Set([
159513
160711
  "embedding.api_key",
@@ -159529,7 +160727,7 @@ var EMBEDDING_WARNING_TERMS = [
159529
160727
  ];
159530
160728
  var loggedFailureSignatures = new Map;
159531
160729
  function sha256Prefix2(value, length = 16) {
159532
- return createHash5("sha256").update(value).digest("hex").slice(0, length);
160730
+ return createHash7("sha256").update(value).digest("hex").slice(0, length);
159533
160731
  }
159534
160732
  function warningLooksEmbeddingRelated(message) {
159535
160733
  const lower = message.toLowerCase();
@@ -159690,6 +160888,7 @@ var SESSION_META_SELECT_COLUMNS = [
159690
160888
  "cached_m0_bytes",
159691
160889
  "cached_m1_bytes",
159692
160890
  "cached_m0_project_memory_epoch",
160891
+ "cached_m0_workspace_fingerprint",
159693
160892
  "cached_m0_project_user_profile_version",
159694
160893
  "cached_m0_max_compartment_seq",
159695
160894
  "cached_m0_max_memory_id",
@@ -159737,6 +160936,7 @@ var META_COLUMNS = {
159737
160936
  cachedM0Bytes: "cached_m0_bytes",
159738
160937
  cachedM1Bytes: "cached_m1_bytes",
159739
160938
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
160939
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
159740
160940
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
159741
160941
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
159742
160942
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -159766,6 +160966,7 @@ var NULL_BIND_META_KEYS = new Set([
159766
160966
  "cachedM0Bytes",
159767
160967
  "cachedM1Bytes",
159768
160968
  "cachedM0ProjectMemoryEpoch",
160969
+ "cachedM0WorkspaceFingerprint",
159769
160970
  "cachedM0ProjectUserProfileVersion",
159770
160971
  "cachedM0MaxCompartmentSeq",
159771
160972
  "cachedM0MaxMemoryId",
@@ -159799,7 +161000,7 @@ function isSessionMetaRow(row) {
159799
161000
  if (row === null || typeof row !== "object")
159800
161001
  return false;
159801
161002
  const r = row;
159802
- 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);
161003
+ 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);
159803
161004
  }
159804
161005
  function getDefaultSessionMeta(sessionId) {
159805
161006
  return {
@@ -159826,6 +161027,7 @@ function getDefaultSessionMeta(sessionId) {
159826
161027
  cachedM0Bytes: null,
159827
161028
  cachedM1Bytes: null,
159828
161029
  cachedM0ProjectMemoryEpoch: null,
161030
+ cachedM0WorkspaceFingerprint: null,
159829
161031
  cachedM0ProjectUserProfileVersion: null,
159830
161032
  cachedM0MaxCompartmentSeq: null,
159831
161033
  cachedM0MaxMemoryId: null,
@@ -159888,6 +161090,7 @@ function toSessionMeta(row) {
159888
161090
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
159889
161091
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
159890
161092
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
161093
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
159891
161094
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
159892
161095
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
159893
161096
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -159918,6 +161121,7 @@ function persistCachedM0(db, sessionId, payload) {
159918
161121
  db.prepare(`UPDATE session_meta SET
159919
161122
  cached_m0_bytes = ?,
159920
161123
  cached_m0_project_memory_epoch = ?,
161124
+ cached_m0_workspace_fingerprint = ?,
159921
161125
  cached_m0_project_user_profile_version = ?,
159922
161126
  cached_m0_max_compartment_seq = ?,
159923
161127
  cached_m0_max_memory_id = ?,
@@ -159929,9 +161133,8 @@ function persistCachedM0(db, sessionId, payload) {
159929
161133
  cached_m0_session_facts_version = ?,
159930
161134
  cached_m0_upgrade_state = ?,
159931
161135
  cached_m0_system_hash = ?,
159932
- cached_m0_tool_set_hash = ?,
159933
161136
  cached_m0_model_key = ?
159934
- 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.toolSetHash ?? "", payload.modelKey ?? "", sessionId);
161137
+ 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);
159935
161138
  }
159936
161139
  function clearCachedM0M1(db, sessionId) {
159937
161140
  ensureSessionMetaRow(db, sessionId);
@@ -159940,6 +161143,7 @@ function clearCachedM0M1(db, sessionId) {
159940
161143
  ["cached_m0_bytes", null],
159941
161144
  ["cached_m1_bytes", null],
159942
161145
  ["cached_m0_project_memory_epoch", null],
161146
+ ["cached_m0_workspace_fingerprint", null],
159943
161147
  ["cached_m0_project_user_profile_version", null],
159944
161148
  ["cached_m0_max_compartment_seq", null],
159945
161149
  ["cached_m0_max_memory_id", null],
@@ -160275,8 +161479,8 @@ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
160275
161479
  const CHUNK = 800;
160276
161480
  for (let i = 0;i < ids.length; i += CHUNK) {
160277
161481
  const slice = ids.slice(i, i + CHUNK);
160278
- const placeholders = slice.map(() => "?").join(",");
160279
- 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);
161482
+ const placeholders2 = slice.map(() => "?").join(",");
161483
+ 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);
160280
161484
  for (const part of partRows) {
160281
161485
  const list = partsByMessageId.get(part.message_id) ?? [];
160282
161486
  list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
@@ -165173,53 +166377,6 @@ function createCtxExpandTool(deps) {
165173
166377
  }
165174
166378
  };
165175
166379
  }
165176
-
165177
- // ../plugin/src/features/magic-context/memory/constants.ts
165178
- var V2_MEMORY_CATEGORIES = [
165179
- "PROJECT_RULES",
165180
- "ARCHITECTURE",
165181
- "CONSTRAINTS",
165182
- "CONFIG_VALUES",
165183
- "NAMING"
165184
- ];
165185
- var PROMOTABLE_CATEGORIES = [
165186
- "PROJECT_RULES",
165187
- "ARCHITECTURE",
165188
- "CONSTRAINTS",
165189
- "CONFIG_VALUES",
165190
- "NAMING",
165191
- "ARCHITECTURE_DECISIONS",
165192
- "CONFIG_DEFAULTS",
165193
- "USER_PREFERENCES",
165194
- "USER_DIRECTIVES",
165195
- "ENVIRONMENT",
165196
- "WORKFLOW_RULES",
165197
- "KNOWN_ISSUES"
165198
- ];
165199
- var CATEGORY_PRIORITY = [
165200
- "PROJECT_RULES",
165201
- "ARCHITECTURE",
165202
- "CONSTRAINTS",
165203
- "CONFIG_VALUES",
165204
- "NAMING",
165205
- "USER_DIRECTIVES",
165206
- "USER_PREFERENCES",
165207
- "CONFIG_DEFAULTS",
165208
- "ARCHITECTURE_DECISIONS",
165209
- "ENVIRONMENT",
165210
- "WORKFLOW_RULES",
165211
- "KNOWN_ISSUES"
165212
- ];
165213
- var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
165214
- var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
165215
- acc[category] = index;
165216
- return acc;
165217
- }, {});
165218
- var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
165219
- var CATEGORY_DEFAULT_TTL = {
165220
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
165221
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
165222
- };
165223
166380
  // ../plugin/src/features/magic-context/memory/embedding-backfill.ts
165224
166381
  init_logger();
165225
166382
  async function ensureMemoryEmbeddings(args) {
@@ -165244,7 +166401,7 @@ async function ensureMemoryEmbeddings(args) {
165244
166401
  continue;
165245
166402
  }
165246
166403
  saveEmbedding(args.db, memory2.id, embedding, result.modelId);
165247
- staged.set(memory2.id, embedding);
166404
+ staged.set(memory2.id, { embedding, modelId: result.modelId });
165248
166405
  }
165249
166406
  })();
165250
166407
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -165447,8 +166604,8 @@ function getMemoriesByProjectStatement(db, statuses) {
165447
166604
  }
165448
166605
  let stmt = statements.get(db);
165449
166606
  if (!stmt) {
165450
- const placeholders = statuses.map(() => "?").join(", ");
165451
- 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`);
166607
+ const placeholders2 = statuses.map(() => "?").join(", ");
166608
+ 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`);
165452
166609
  statements.set(db, stmt);
165453
166610
  }
165454
166611
  return stmt;
@@ -165593,6 +166750,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
165593
166750
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
165594
166751
  return rows.map(toMemory);
165595
166752
  }
166753
+ function sqlPlaceholders(values) {
166754
+ return values.map(() => "?").join(", ");
166755
+ }
166756
+ function uniqueValues(values) {
166757
+ return [...new Set(values.filter((value) => value.length > 0))];
166758
+ }
166759
+ function buildWorkspaceMemorySqlFilter(args) {
166760
+ if (args.shareCategories === null || args.shareCategories === undefined) {
166761
+ return { clause: "", params: [], active: false };
166762
+ }
166763
+ const identities = uniqueValues(args.identities);
166764
+ const identitySet = new Set(identities);
166765
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
166766
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
166767
+ if (foreignIdentities.length === 0) {
166768
+ return { clause: "", params: [], active: false };
166769
+ }
166770
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
166771
+ const shareCategories = uniqueValues([...args.shareCategories]);
166772
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
166773
+ const predicates = [];
166774
+ const params = [];
166775
+ if (ownIdentities.length > 0) {
166776
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
166777
+ params.push(...ownIdentities);
166778
+ }
166779
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
166780
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
166781
+ params.push(...foreignIdentities, ...shareCategories);
166782
+ }
166783
+ if (predicates.length === 0) {
166784
+ return { clause: " AND 0 = 1", params: [], active: true };
166785
+ }
166786
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
166787
+ }
166788
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
166789
+ const identities = uniqueValues(projectPaths);
166790
+ if (identities.length === 0 || statuses.length === 0)
166791
+ return [];
166792
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
166793
+ identities,
166794
+ ownIdentities,
166795
+ shareCategories
166796
+ });
166797
+ if (identities.length === 1 && !sharingFilter.active) {
166798
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
166799
+ }
166800
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
166801
+ FROM memories
166802
+ WHERE project_path IN (${sqlPlaceholders(identities)})
166803
+ AND status IN (${sqlPlaceholders(statuses)})
166804
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
166805
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
166806
+ return rows.map(toMemory);
166807
+ }
166808
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
166809
+ const identities = uniqueValues(projectPaths);
166810
+ if (identities.length === 0)
166811
+ return 0;
166812
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
166813
+ identities,
166814
+ ownIdentities,
166815
+ shareCategories
166816
+ });
166817
+ if (identities.length === 1 && !sharingFilter.active) {
166818
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
166819
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
166820
+ }
166821
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
166822
+ FROM memories
166823
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
166824
+ return typeof row?.max_id === "number" ? row.max_id : 0;
166825
+ }
166826
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
166827
+ const identities = uniqueValues(projectPaths);
166828
+ if (identities.length === 0)
166829
+ return [];
166830
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
166831
+ identities,
166832
+ ownIdentities,
166833
+ shareCategories
166834
+ });
166835
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
166836
+ FROM memories
166837
+ WHERE project_path IN (${sqlPlaceholders(identities)})
166838
+ AND id > ?
166839
+ AND status IN ('active', 'permanent')
166840
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
166841
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
166842
+ return rows.map(toMemory);
166843
+ }
165596
166844
  function getAllActiveMemoriesForMigration(db, projectPath) {
165597
166845
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
165598
166846
  return rows.map(toMemory);
@@ -165760,6 +167008,7 @@ async function embedAndStoreMemory(db, sessionId, projectPath, memoryId, content
165760
167008
  // ../plugin/src/features/magic-context/memory/storage-memory-fts.ts
165761
167009
  var DEFAULT_SEARCH_LIMIT = 10;
165762
167010
  var searchStatements = new WeakMap;
167011
+ var unionSearchStatements = new Map;
165763
167012
  function getSearchStatement(db) {
165764
167013
  let stmt = searchStatements.get(db);
165765
167014
  if (!stmt) {
@@ -165768,6 +167017,23 @@ function getSearchStatement(db) {
165768
167017
  }
165769
167018
  return stmt;
165770
167019
  }
167020
+ function getUnionSearchStatement(db, arity) {
167021
+ let statements = unionSearchStatements.get(arity);
167022
+ if (!statements) {
167023
+ statements = new WeakMap;
167024
+ unionSearchStatements.set(arity, statements);
167025
+ }
167026
+ let stmt = statements.get(db);
167027
+ if (!stmt) {
167028
+ const placeholders2 = Array.from({ length: arity }, () => "?").join(", ");
167029
+ 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 ?`);
167030
+ statements.set(db, stmt);
167031
+ }
167032
+ return stmt;
167033
+ }
167034
+ function uniqueProjectPaths(projectPaths) {
167035
+ return [...new Set(projectPaths.filter((path4) => path4.length > 0))];
167036
+ }
165771
167037
  function sanitizeFtsQuery(query) {
165772
167038
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
165773
167039
  if (tokens.length === 0)
@@ -165786,6 +167052,28 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
165786
167052
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
165787
167053
  return rows.map(toMemory);
165788
167054
  }
167055
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
167056
+ const identities = uniqueProjectPaths(projectPaths);
167057
+ if (identities.length === 0)
167058
+ return [];
167059
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
167060
+ identities,
167061
+ ownIdentities,
167062
+ shareCategories,
167063
+ tableName: "memories"
167064
+ });
167065
+ if (identities.length === 1 && !sharingFilter.active) {
167066
+ return searchMemoriesFTS(db, identities[0], query, limit);
167067
+ }
167068
+ const trimmedQuery = query.trim();
167069
+ if (trimmedQuery.length === 0 || limit <= 0)
167070
+ return [];
167071
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
167072
+ if (sanitized.length === 0)
167073
+ return [];
167074
+ 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);
167075
+ return rows.map(toMemory);
167076
+ }
165789
167077
  // ../plugin/src/features/magic-context/message-index.ts
165790
167078
  var lastIndexedStatements = new WeakMap;
165791
167079
  var insertMessageStatements = new WeakMap;
@@ -165932,7 +167220,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
165932
167220
  return inserted;
165933
167221
  }
165934
167222
  // ../plugin/src/features/magic-context/project-docs-hash.ts
165935
- import { createHash as createHash6 } from "node:crypto";
167223
+ import { createHash as createHash8 } from "node:crypto";
165936
167224
  import { lstatSync, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
165937
167225
  import path4 from "node:path";
165938
167226
  var PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
@@ -166030,7 +167318,7 @@ function hashCanonicalPieces(hashPieces) {
166030
167318
  if (hashPieces.length === 0) {
166031
167319
  return "";
166032
167320
  }
166033
- return createHash6("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
167321
+ return createHash8("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
166034
167322
  }
166035
167323
  function readProjectDocsCanonical(projectDirectory) {
166036
167324
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -166136,18 +167424,13 @@ function getMemoryMutation(db, id) {
166136
167424
  WHERE id = ?`).get(id);
166137
167425
  return row ? toMemoryMutation(row) : null;
166138
167426
  }
166139
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
166140
- if (renderedMemoryIds.length === 0)
166141
- return [];
166142
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
166143
- const placeholders = uniqueIds.map(() => "?").join(", ");
166144
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
166145
- superseded_by_id, category, new_content, queued_at
166146
- FROM memory_mutation_log
166147
- WHERE project_path = ?
166148
- AND id > ?
166149
- AND target_memory_id IN (${placeholders})
166150
- ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
167427
+ function uniqueProjectPaths2(projectPaths) {
167428
+ return [...new Set(projectPaths.filter((path5) => path5.length > 0))];
167429
+ }
167430
+ function placeholders2(values) {
167431
+ return values.map(() => "?").join(", ");
167432
+ }
167433
+ function coalesceMutations(rows) {
166151
167434
  const chosenByTarget = new Map;
166152
167435
  for (const dbRow of rows) {
166153
167436
  const candidate = toMemoryMutation(dbRow);
@@ -166168,10 +167451,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
166168
167451
  }
166169
167452
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
166170
167453
  }
167454
+ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
167455
+ if (renderedMemoryIds.length === 0)
167456
+ return [];
167457
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
167458
+ const placeholders3 = uniqueIds.map(() => "?").join(", ");
167459
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
167460
+ superseded_by_id, category, new_content, queued_at
167461
+ FROM memory_mutation_log
167462
+ WHERE project_path = ?
167463
+ AND id > ?
167464
+ AND target_memory_id IN (${placeholders3})
167465
+ ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
167466
+ return coalesceMutations(rows);
167467
+ }
167468
+ function getMemoryMutationsForRenderByProjects(db, projectPaths, afterId, renderedMemoryIds) {
167469
+ if (renderedMemoryIds.length === 0)
167470
+ return [];
167471
+ const identities = uniqueProjectPaths2(projectPaths);
167472
+ if (identities.length === 0)
167473
+ return [];
167474
+ if (identities.length === 1) {
167475
+ return getMemoryMutationsForRender(db, identities[0], afterId, renderedMemoryIds);
167476
+ }
167477
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
167478
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
167479
+ superseded_by_id, category, new_content, queued_at
167480
+ FROM memory_mutation_log
167481
+ WHERE project_path IN (${placeholders2(identities)})
167482
+ AND id > ?
167483
+ AND target_memory_id IN (${placeholders2(uniqueIds)})
167484
+ ORDER BY id ASC`).all(...identities, afterId ?? 0, ...uniqueIds);
167485
+ return coalesceMutations(rows);
167486
+ }
166171
167487
  function getMaxMemoryMutationId(db, projectPath) {
166172
167488
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
166173
167489
  return row?.max_id ?? null;
166174
167490
  }
167491
+ function getMaxMemoryMutationIdForProjects(db, projectPaths) {
167492
+ const identities = uniqueProjectPaths2(projectPaths);
167493
+ if (identities.length === 0)
167494
+ return null;
167495
+ if (identities.length === 1)
167496
+ return getMaxMemoryMutationId(db, identities[0]);
167497
+ const row = db.prepare(`SELECT MAX(id) AS max_id
167498
+ FROM memory_mutation_log
167499
+ WHERE project_path IN (${placeholders2(identities)})`).get(...identities);
167500
+ return row?.max_id ?? null;
167501
+ }
166175
167502
  // ../plugin/src/features/magic-context/storage-meta-persisted.ts
166176
167503
  init_logger();
166177
167504
  var CAS_RETRY_LIMIT = 5;
@@ -167017,9 +168344,9 @@ function buildStatusClause(status) {
167017
168344
  if (statuses.length === 0) {
167018
168345
  return null;
167019
168346
  }
167020
- const placeholders = statuses.map(() => "?").join(", ");
168347
+ const placeholders3 = statuses.map(() => "?").join(", ");
167021
168348
  return {
167022
- sql: `status IN (${placeholders})`,
168349
+ sql: `status IN (${placeholders3})`,
167023
168350
  params: statuses
167024
168351
  };
167025
168352
  }
@@ -167215,19 +168542,6 @@ function getProjectState(db, projectPath) {
167215
168542
  WHERE project_path = ?`).get(projectPath);
167216
168543
  return row ? toProjectState(row) : null;
167217
168544
  }
167218
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
167219
- db.prepare(`INSERT INTO project_state
167220
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
167221
- VALUES (?, 1, 0, ?)
167222
- ON CONFLICT(project_path) DO UPDATE SET
167223
- project_memory_epoch = project_memory_epoch + 1,
167224
- updated_at = excluded.updated_at`).run(projectPath, now);
167225
- const state = getProjectState(db, projectPath);
167226
- if (!state) {
167227
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
167228
- }
167229
- return state;
167230
- }
167231
168545
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
167232
168546
  db.prepare(`INSERT INTO project_state
167233
168547
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -167261,8 +168575,8 @@ function getSourceContents(db, sessionId, tagIds) {
167261
168575
  if (tagIds.length === 0) {
167262
168576
  return new Map;
167263
168577
  }
167264
- const placeholders = tagIds.map(() => "?").join(", ");
167265
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
168578
+ const placeholders3 = tagIds.map(() => "?").join(", ");
168579
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders3})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
167266
168580
  const sources = new Map;
167267
168581
  for (const row of rows) {
167268
168582
  sources.set(row.tag_id, row.content);
@@ -167552,8 +168866,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
167552
168866
  }
167553
168867
  return all;
167554
168868
  }
167555
- const placeholders = tagNumbers.map(() => "?").join(",");
167556
- 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);
168869
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
168870
+ 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);
167557
168871
  return rows.map(toTagEntry);
167558
168872
  }
167559
168873
  var getToolTagNumberByOwnerStatements = new WeakMap;
@@ -167743,6 +169057,23 @@ function createCtxMemoryTool(deps) {
167743
169057
  }
167744
169058
  const projectIdentity = resolveProjectIdentity(ctx.cwd);
167745
169059
  await deps.ensureProjectRegistered?.(ctx.cwd, deps.db);
169060
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectIdentity);
169061
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
169062
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
169063
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? normalizeStoredProjectPath(rawProjectPath) : normalizeStoredProjectPath(rawProjectPath);
169064
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectIdentity) : null;
169065
+ const memoryVisibleToTool = (memory2) => {
169066
+ if (workspaceIdentitySet.identities.length <= 1) {
169067
+ return storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
169068
+ }
169069
+ if (!storedPathBelongsToWorkspace(memory2.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
169070
+ return false;
169071
+ }
169072
+ const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
169073
+ if (isOwn)
169074
+ return true;
169075
+ return toolShareCategories === null || toolShareCategories.includes(memory2.category);
169076
+ };
167746
169077
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
167747
169078
  if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
167748
169079
  return err2("Cross-session memory is disabled for this project.");
@@ -167789,28 +169120,34 @@ function createCtxMemoryTool(deps) {
167789
169120
  return err2("Error: 'content' is required when action is 'update'.");
167790
169121
  }
167791
169122
  const memory2 = getMemoryById(deps.db, updateId);
167792
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
169123
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
167793
169124
  return err2(`Error: Memory with ID ${updateId} was not found.`);
167794
169125
  }
167795
169126
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167796
169127
  return err2(inactiveMemoryError(updateId, "updating"));
167797
169128
  }
167798
169129
  const normalizedHash = computeNormalizedHash(content);
167799
- const duplicate = getMemoryByHash(deps.db, projectIdentity, memory2.category, normalizedHash);
169130
+ const targetIdentity = targetIdentityForStoredPath(memory2.projectPath);
169131
+ const duplicate = getMemoryByHash(deps.db, targetIdentity, memory2.category, normalizedHash);
167800
169132
  if (duplicate && duplicate.id !== memory2.id) {
167801
169133
  return err2(`Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`);
167802
169134
  }
167803
169135
  deps.db.transaction(() => {
167804
169136
  updateMemoryContent(deps.db, memory2.id, content, normalizedHash);
167805
169137
  queueMemoryMutation(deps.db, {
167806
- projectPath: projectIdentity,
169138
+ projectPath: targetIdentity,
167807
169139
  mutationType: "update",
167808
169140
  targetMemoryId: memory2.id,
167809
169141
  category: memory2.category,
167810
169142
  newContent: content
167811
169143
  });
167812
169144
  })();
167813
- queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
169145
+ queueEmbedding({
169146
+ deps,
169147
+ projectIdentity: targetIdentity,
169148
+ memoryId: memory2.id,
169149
+ content
169150
+ });
167814
169151
  return ok2(`Updated memory [ID: ${memory2.id}] in ${memory2.category}.`);
167815
169152
  }
167816
169153
  if (params.action === "merge") {
@@ -167830,7 +169167,7 @@ function createCtxMemoryTool(deps) {
167830
169167
  return err2("Error: One or more source memories were not found.");
167831
169168
  }
167832
169169
  if (!dreamerAllowed) {
167833
- const foreign = sourceMemories.find((memory2) => !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity));
169170
+ const foreign = sourceMemories.find((memory2) => !memoryVisibleToTool(memory2));
167834
169171
  if (foreign) {
167835
169172
  return err2(`Error: Memory with ID ${foreign.id} was not found.`);
167836
169173
  }
@@ -167919,26 +169256,36 @@ function createCtxMemoryTool(deps) {
167919
169256
  return ok2(`Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`);
167920
169257
  }
167921
169258
  if (params.action === "archive") {
167922
- const archiveIds = params.ids;
167923
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
169259
+ const rawArchiveIds = params.ids;
169260
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
167924
169261
  return err2("Error: 'ids' must contain at least one integer memory ID when action is 'archive'.");
167925
169262
  }
169263
+ const archiveIds = [...new Set(rawArchiveIds)];
167926
169264
  for (const memoryId of archiveIds) {
167927
169265
  const memory2 = getMemoryById(deps.db, memoryId);
167928
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
169266
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
167929
169267
  return err2(`Error: Memory with ID ${memoryId} was not found.`);
167930
169268
  }
167931
169269
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167932
169270
  return err2(inactiveMemoryError(memoryId, "archiving"));
167933
169271
  }
167934
169272
  }
169273
+ const targets = archiveIds.map((memoryId) => {
169274
+ const memory2 = getMemoryById(deps.db, memoryId);
169275
+ if (!memory2)
169276
+ throw new Error(`validated memory ${memoryId} disappeared`);
169277
+ return {
169278
+ memoryId,
169279
+ projectIdentity: targetIdentityForStoredPath(memory2.projectPath)
169280
+ };
169281
+ });
167935
169282
  deps.db.transaction(() => {
167936
- for (const memoryId of archiveIds) {
167937
- archiveMemory(deps.db, memoryId, params.reason);
169283
+ for (const target2 of targets) {
169284
+ archiveMemory(deps.db, target2.memoryId, params.reason);
167938
169285
  queueMemoryMutation(deps.db, {
167939
- projectPath: projectIdentity,
169286
+ projectPath: target2.projectIdentity,
167940
169287
  mutationType: "archive",
167941
- targetMemoryId: memoryId
169288
+ targetMemoryId: target2.memoryId
167942
169289
  });
167943
169290
  }
167944
169291
  })();
@@ -168910,6 +170257,37 @@ function previewText(text) {
168910
170257
  }
168911
170258
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
168912
170259
  }
170260
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
170261
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
170262
+ const isWorkspaced = resolved.identities.length > 1;
170263
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
170264
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
170265
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
170266
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
170267
+ return {
170268
+ identities: resolved.identities,
170269
+ expandedIdentities,
170270
+ ownIdentities,
170271
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
170272
+ namesByIdentity: resolved.namesByIdentity,
170273
+ canonicalIdentityByStoredPath,
170274
+ isWorkspaced
170275
+ };
170276
+ }
170277
+ function memoryWorkspaceIdentity(memory2, workspace) {
170278
+ return resolveStoredPathWorkspaceIdentity(memory2.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
170279
+ }
170280
+ function sourceNamesForSearchMemories(args) {
170281
+ if (!args.workspace.isWorkspaced)
170282
+ return;
170283
+ const sourceNames = new Map;
170284
+ for (const memory2 of args.memories) {
170285
+ const source = sourceNameForMemory(memory2.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
170286
+ if (source)
170287
+ sourceNames.set(memory2.id, source);
170288
+ }
170289
+ return sourceNames.size > 0 ? sourceNames : undefined;
170290
+ }
168913
170291
  function getMessageSearchStatement(db) {
168914
170292
  let stmt = messageSearchStatements.get(db);
168915
170293
  if (!stmt) {
@@ -168918,6 +170296,30 @@ function getMessageSearchStatement(db) {
168918
170296
  }
168919
170297
  return stmt;
168920
170298
  }
170299
+ var ftsRowCountStatements = new WeakMap;
170300
+ var ftsMatchCountStatements = new WeakMap;
170301
+ function getSessionFtsRowCount(db, sessionId) {
170302
+ let stmt = ftsRowCountStatements.get(db);
170303
+ if (!stmt) {
170304
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
170305
+ ftsRowCountStatements.set(db, stmt);
170306
+ }
170307
+ const row = stmt.get(sessionId);
170308
+ return typeof row?.n === "number" ? row.n : 0;
170309
+ }
170310
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
170311
+ let stmt = ftsMatchCountStatements.get(db);
170312
+ if (!stmt) {
170313
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
170314
+ ftsMatchCountStatements.set(db, stmt);
170315
+ }
170316
+ try {
170317
+ const row = stmt.get(sessionId, ftsQuery);
170318
+ return typeof row?.n === "number" ? row.n : 0;
170319
+ } catch {
170320
+ return 0;
170321
+ }
170322
+ }
168921
170323
  function getMessageOrdinal(value) {
168922
170324
  if (typeof value === "number" && Number.isFinite(value)) {
168923
170325
  return value;
@@ -168933,25 +170335,63 @@ async function getSemanticScores(args) {
168933
170335
  if (!args.queryEmbedding || args.memories.length === 0) {
168934
170336
  return semanticScores;
168935
170337
  }
168936
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
168937
- const embeddings = await ensureMemoryEmbeddings({
168938
- db: args.db,
168939
- projectIdentity: args.projectPath,
168940
- memories: args.memories,
168941
- existingEmbeddings: cachedEmbeddings
168942
- });
170338
+ if (!args.workspace?.isWorkspaced) {
170339
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
170340
+ const embeddings = await ensureMemoryEmbeddings({
170341
+ db: args.db,
170342
+ projectIdentity: args.projectPath,
170343
+ memories: args.memories,
170344
+ existingEmbeddings: cachedEmbeddings
170345
+ });
170346
+ for (const memory2 of args.memories) {
170347
+ const memoryEmbedding = embeddings.get(memory2.id);
170348
+ if (!memoryEmbedding) {
170349
+ continue;
170350
+ }
170351
+ semanticScores.set(memory2.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
170352
+ }
170353
+ return semanticScores;
170354
+ }
170355
+ if (!args.queryModelId || args.queryModelId === "off") {
170356
+ return semanticScores;
170357
+ }
170358
+ const workspace = args.workspace;
170359
+ const memoriesByIdentity = new Map;
168943
170360
  for (const memory2 of args.memories) {
168944
- const memoryEmbedding = embeddings.get(memory2.id);
168945
- if (!memoryEmbedding) {
170361
+ const identity = memoryWorkspaceIdentity(memory2, workspace);
170362
+ if (!identity)
170363
+ continue;
170364
+ const list = memoriesByIdentity.get(identity) ?? [];
170365
+ list.push(memory2);
170366
+ memoriesByIdentity.set(identity, list);
170367
+ }
170368
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
170369
+ if (ownMemories.length > 0) {
170370
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
170371
+ await ensureMemoryEmbeddings({
170372
+ db: args.db,
170373
+ projectIdentity: args.projectPath,
170374
+ memories: ownMemories,
170375
+ existingEmbeddings: ownEmbeddings
170376
+ });
170377
+ }
170378
+ for (const identity of workspace.identities) {
170379
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
170380
+ if (memberMemories.length === 0)
168946
170381
  continue;
170382
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
170383
+ for (const memory2 of memberMemories) {
170384
+ const memoryEmbedding = cachedEmbeddings.get(memory2.id);
170385
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
170386
+ continue;
170387
+ semanticScores.set(memory2.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
168947
170388
  }
168948
- semanticScores.set(memory2.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
168949
170389
  }
168950
170390
  return semanticScores;
168951
170391
  }
168952
170392
  function getFtsMatches(args) {
168953
170393
  try {
168954
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
170394
+ 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);
168955
170395
  } catch (error51) {
168956
170396
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
168957
170397
  return [];
@@ -168965,8 +170405,11 @@ function selectSemanticCandidates(args) {
168965
170405
  return args.memories;
168966
170406
  }
168967
170407
  const candidateIds = new Set(args.ftsMatches.map((memory2) => memory2.id));
168968
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
168969
- if (cachedEmbeddings) {
170408
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
170409
+ for (const projectPath of embeddingProjects) {
170410
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
170411
+ if (!cachedEmbeddings)
170412
+ continue;
168970
170413
  for (const memoryId of cachedEmbeddings.keys()) {
168971
170414
  candidateIds.add(memoryId);
168972
170415
  }
@@ -169008,7 +170451,8 @@ function mergeMemoryResults(args) {
169008
170451
  score,
169009
170452
  memoryId: memory2.id,
169010
170453
  category: memory2.category,
169011
- matchType
170454
+ matchType,
170455
+ sourceName: args.sourceNameByMemoryId?.get(memory2.id)
169012
170456
  });
169013
170457
  }
169014
170458
  return results.sort((left, right) => {
@@ -169022,7 +170466,7 @@ async function searchMemories(args) {
169022
170466
  if (!args.memoryEnabled) {
169023
170467
  return [];
169024
170468
  }
169025
- const memories = getMemoriesByProject(args.db, args.projectPath);
170469
+ 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);
169026
170470
  if (memories.length === 0) {
169027
170471
  return [];
169028
170472
  }
@@ -169030,26 +170474,43 @@ async function searchMemories(args) {
169030
170474
  db: args.db,
169031
170475
  projectPath: args.projectPath,
169032
170476
  query: args.query,
169033
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
170477
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
170478
+ workspace: args.workspace
169034
170479
  });
169035
170480
  const ftsScores = getFtsScores(ftsMatches);
169036
170481
  const semanticCandidates = selectSemanticCandidates({
169037
170482
  memories,
169038
170483
  projectPath: args.projectPath,
169039
- ftsMatches
170484
+ ftsMatches,
170485
+ workspace: args.workspace
169040
170486
  });
169041
170487
  const semanticScores = await getSemanticScores({
169042
170488
  db: args.db,
169043
170489
  projectPath: args.projectPath,
169044
170490
  memories: semanticCandidates,
169045
- queryEmbedding: args.queryEmbedding
170491
+ queryEmbedding: args.queryEmbedding,
170492
+ queryModelId: args.queryModelId,
170493
+ workspace: args.workspace
169046
170494
  });
169047
170495
  return mergeMemoryResults({
169048
170496
  memories,
169049
170497
  semanticScores,
169050
170498
  ftsScores,
169051
170499
  limit: args.limit,
169052
- visibleMemoryIds: args.visibleMemoryIds
170500
+ visibleMemoryIds: args.visibleMemoryIds,
170501
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
170502
+ memories,
170503
+ projectPath: args.projectPath,
170504
+ workspace: args.workspace ?? {
170505
+ identities: [args.projectPath],
170506
+ expandedIdentities: [args.projectPath],
170507
+ namesByIdentity: new Map,
170508
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
170509
+ ownIdentities: [args.projectPath],
170510
+ shareCategories: null,
170511
+ isWorkspaced: false
170512
+ }
170513
+ })
169053
170514
  });
169054
170515
  }
169055
170516
  function linearDecayScore(rank, total) {
@@ -169080,7 +170541,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
169080
170541
  return result;
169081
170542
  }
169082
170543
  var RRF_K = 60;
169083
- var VERBATIM_PROBE_BONUS = 0.5;
170544
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
170545
+ var IDF_FALLOFF = 100;
170546
+ function probeDiscriminationWeight(df, corpusSize) {
170547
+ if (corpusSize <= 0 || df <= 0)
170548
+ return 1;
170549
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
170550
+ }
169084
170551
  function searchMessages(args) {
169085
170552
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
169086
170553
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -169097,20 +170564,31 @@ function searchMessages(args) {
169097
170564
  role: row.role
169098
170565
  }));
169099
170566
  }
170567
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
169100
170568
  const queryLists = [];
169101
170569
  if (baseQuery.length > 0) {
169102
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
170570
+ queryLists.push({
170571
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
170572
+ weight: 1
170573
+ });
169103
170574
  }
170575
+ const probeWeights = new Map;
169104
170576
  for (const probe of probes) {
169105
170577
  const probeQuery = sanitizeFtsQuery(probe);
169106
170578
  if (probeQuery.length === 0)
169107
170579
  continue;
169108
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
170580
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
170581
+ const weight = probeDiscriminationWeight(df, corpusSize);
170582
+ probeWeights.set(probe, weight);
170583
+ queryLists.push({
170584
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
170585
+ weight
170586
+ });
169109
170587
  }
169110
170588
  const fused = new Map;
169111
170589
  for (const list of queryLists) {
169112
- list.forEach((row, rank) => {
169113
- const rrf = 1 / (RRF_K + rank);
170590
+ list.rows.forEach((row, rank) => {
170591
+ const rrf = list.weight / (RRF_K + rank);
169114
170592
  const existing = fused.get(row.messageId);
169115
170593
  if (existing) {
169116
170594
  existing.score += rrf;
@@ -169120,26 +170598,107 @@ function searchMessages(args) {
169120
170598
  });
169121
170599
  }
169122
170600
  for (const entry of fused.values()) {
169123
- if (containsProbeVerbatim(entry.row.content, probes)) {
169124
- entry.score += VERBATIM_PROBE_BONUS;
170601
+ let best = 0;
170602
+ for (const probe of probes) {
170603
+ const weight = probeWeights.get(probe) ?? 0;
170604
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
170605
+ best = weight;
170606
+ }
170607
+ }
170608
+ if (best > 0) {
170609
+ entry.score += best * VERBATIM_RANK_BONUS;
169125
170610
  }
169126
170611
  }
169127
170612
  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);
169128
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
169129
- return ranked.map((entry) => ({
170613
+ return ranked.map((entry, rank) => ({
169130
170614
  source: "message",
169131
170615
  content: previewText(entry.row.content),
169132
- score: maxScore > 0 ? entry.score / maxScore : 0,
170616
+ score: linearDecayScore(rank, ranked.length),
169133
170617
  messageOrdinal: entry.row.messageOrdinal,
169134
170618
  messageId: entry.row.messageId,
169135
170619
  role: entry.row.role
169136
170620
  }));
169137
170621
  }
170622
+ function searchCompartmentChunks(args) {
170623
+ if (!args.queryEmbedding || args.limit <= 0)
170624
+ return [];
170625
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
170626
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
170627
+ if (rows.length === 0)
170628
+ return [];
170629
+ const byCompartment = new Map;
170630
+ for (const row of rows) {
170631
+ if (cutoff !== null && row.endOrdinal > cutoff) {
170632
+ continue;
170633
+ }
170634
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
170635
+ if (score <= 0)
170636
+ continue;
170637
+ const existing = byCompartment.get(row.compartmentId);
170638
+ if (!existing || score > existing.score) {
170639
+ byCompartment.set(row.compartmentId, { row, score });
170640
+ }
170641
+ }
170642
+ 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 }) => ({
170643
+ source: "compartment",
170644
+ content: previewText(row.title),
170645
+ score: score * SINGLE_SOURCE_PENALTY,
170646
+ compartmentId: row.compartmentId,
170647
+ sessionId: row.sessionId,
170648
+ title: row.title,
170649
+ startOrdinal: row.startOrdinal,
170650
+ endOrdinal: row.endOrdinal,
170651
+ matchType: "semantic"
170652
+ }));
170653
+ }
170654
+ function mergeMessageAndCompartmentResults(args) {
170655
+ if (args.compartments.length === 0)
170656
+ return args.messages;
170657
+ if (args.messages.length === 0)
170658
+ return args.compartments;
170659
+ const fused = new Map;
170660
+ const add = (key, result, score, tieOrdinal) => {
170661
+ const existing = fused.get(key);
170662
+ if (existing) {
170663
+ existing.score += score;
170664
+ return existing;
170665
+ }
170666
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
170667
+ fused.set(key, entry);
170668
+ return entry;
170669
+ };
170670
+ args.compartments.forEach((compartment, rank) => {
170671
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
170672
+ });
170673
+ for (const [rank, message] of args.messages.entries()) {
170674
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
170675
+ const contribution = 1 / (RRF_K + rank);
170676
+ if (!containing) {
170677
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
170678
+ continue;
170679
+ }
170680
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
170681
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
170682
+ entry.snippetScore = message.score;
170683
+ entry.result = {
170684
+ ...entry.result,
170685
+ matchType: "hybrid",
170686
+ snippet: message.content
170687
+ };
170688
+ }
170689
+ }
170690
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
170691
+ return ranked.map((entry, rank) => ({
170692
+ ...entry.result,
170693
+ score: linearDecayScore(rank, ranked.length)
170694
+ }));
170695
+ }
169138
170696
  function getSourceBoost(result) {
169139
170697
  switch (result.source) {
169140
170698
  case "memory":
169141
170699
  return MEMORY_SOURCE_BOOST;
169142
170700
  case "message":
170701
+ case "compartment":
169143
170702
  return MESSAGE_SOURCE_BOOST;
169144
170703
  case "git_commit":
169145
170704
  return GIT_COMMIT_SOURCE_BOOST;
@@ -169157,6 +170716,9 @@ function compareUnifiedResults(left, right) {
169157
170716
  if (left.source === "message" && right.source === "message") {
169158
170717
  return left.messageOrdinal - right.messageOrdinal;
169159
170718
  }
170719
+ if (left.source === "compartment" && right.source === "compartment") {
170720
+ return left.startOrdinal - right.startOrdinal;
170721
+ }
169160
170722
  if (left.source === "git_commit" && right.source === "git_commit") {
169161
170723
  return right.committedAtMs - left.committedAtMs;
169162
170724
  }
@@ -169207,10 +170769,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
169207
170769
  const isEmbeddingRuntimeEnabled = options3.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
169208
170770
  const gitCommitsEnabled = options3.gitCommitsEnabled ?? false;
169209
170771
  const activeSources = resolveSources(options3.sources);
169210
- const runMemory = activeSources.has("memory") && (options3.memoryEnabled ?? true);
170772
+ const memoryFeatureEnabled = options3.memoryEnabled ?? true;
170773
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
169211
170774
  const runMessages = activeSources.has("message");
169212
170775
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
169213
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
170776
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
170777
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
169214
170778
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options3.signal).catch((error51) => {
169215
170779
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
169216
170780
  return null;
@@ -169226,6 +170790,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
169226
170790
  probes: messageProbes
169227
170791
  }) : [];
169228
170792
  const queryEmbedding = await queryEmbeddingPromise;
170793
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
170794
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
170795
+ const embeddingModelId = embeddingSnapshot?.modelId;
170796
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
170797
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
170798
+ db,
170799
+ sessionId,
170800
+ projectPath,
170801
+ queryEmbedding,
170802
+ limit: tierLimit,
170803
+ maxOrdinal: options3.maxMessageOrdinal,
170804
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
170805
+ }) : [];
170806
+ const messageLikeResults = mergeMessageAndCompartmentResults({
170807
+ messages: messageResults,
170808
+ compartments: compartmentResults,
170809
+ limit: tierLimit
170810
+ });
169229
170811
  const [memoryResults, gitCommitResults] = await Promise.all([
169230
170812
  runMemory ? searchMemories({
169231
170813
  db,
@@ -169234,6 +170816,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
169234
170816
  limit: tierLimit,
169235
170817
  memoryEnabled: true,
169236
170818
  queryEmbedding,
170819
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
170820
+ workspace,
169237
170821
  visibleMemoryIds: options3.visibleMemoryIds
169238
170822
  }) : Promise.resolve([]),
169239
170823
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -169244,7 +170828,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
169244
170828
  queryEmbedding
169245
170829
  })) : Promise.resolve([])
169246
170830
  ]);
169247
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
170831
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
169248
170832
  const countRetrievals = options3.countRetrievals ?? true;
169249
170833
  if (countRetrievals) {
169250
170834
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -169285,8 +170869,8 @@ function getUserMemoryCandidates(db) {
169285
170869
  function deleteUserMemoryCandidates(db, ids) {
169286
170870
  if (ids.length === 0)
169287
170871
  return;
169288
- const placeholders = ids.map(() => "?").join(",");
169289
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
170872
+ const placeholders3 = ids.map(() => "?").join(",");
170873
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
169290
170874
  }
169291
170875
  function insertUserMemory(db, content, sourceCandidateIds) {
169292
170876
  const now = Date.now();
@@ -169838,26 +171422,40 @@ ${sections.join(`
169838
171422
  var DEFAULT_MEMORY_BUDGET_TOKENS = 8000;
169839
171423
  var MEMORY_BLOCK_WRAPPER_TOKENS = 6;
169840
171424
  var DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000;
171425
+ function memoryCanonicalIdentity(memory2, workspace) {
171426
+ return resolveStoredPathWorkspaceIdentity(memory2.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
171427
+ }
171428
+ function memorySelectionOrder(left, right) {
171429
+ if (left.status === "permanent" && right.status !== "permanent")
171430
+ return -1;
171431
+ if (right.status === "permanent" && left.status !== "permanent")
171432
+ return 1;
171433
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
171434
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
171435
+ const importanceDiff = rightImportance - leftImportance;
171436
+ if (importanceDiff !== 0)
171437
+ return importanceDiff;
171438
+ return left.id - right.id;
171439
+ }
171440
+ function memoryRenderOrder(left, right) {
171441
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
171442
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
171443
+ const categoryDiff = aPriority - bPriority;
171444
+ if (categoryDiff !== 0)
171445
+ return categoryDiff;
171446
+ return left.id - right.id;
171447
+ }
169841
171448
  var maxCompartmentSeqStatements = new WeakMap;
169842
171449
  var maxMemoryIdStatements = new WeakMap;
169843
171450
  var legacyCompartmentCountStatements = new WeakMap;
169844
171451
  var m0CompartmentStatements = new WeakMap;
169845
171452
  var newCompartmentStatements = new WeakMap;
169846
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
169847
- const selectionOrder = [...memories].sort((a, b) => {
169848
- if (a.status === "permanent" && b.status !== "permanent")
169849
- return -1;
169850
- if (b.status === "permanent" && a.status !== "permanent")
169851
- return 1;
169852
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
169853
- if (importanceDiff !== 0)
169854
- return importanceDiff;
169855
- return a.id - b.id;
169856
- });
171453
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
171454
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
169857
171455
  const selected = [];
169858
171456
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
169859
171457
  for (const memory2 of selectionOrder) {
169860
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory2));
171458
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
169861
171459
  if (usedTokens + memoryTokens > budgetTokens)
169862
171460
  continue;
169863
171461
  selected.push(memory2);
@@ -169866,16 +171464,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
169866
171464
  if (selected.length < memories.length) {
169867
171465
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
169868
171466
  }
169869
- const renderOrder = [...selected].sort((a, b) => {
169870
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169871
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169872
- const categoryDiff = aPriority - bPriority;
169873
- if (categoryDiff !== 0)
169874
- return categoryDiff;
169875
- return a.id - b.id;
169876
- });
171467
+ const renderOrder = [...selected].sort(memoryRenderOrder);
169877
171468
  return { selected, renderOrder };
169878
171469
  }
171470
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
171471
+ if (!workspace.isWorkspaced) {
171472
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
171473
+ }
171474
+ const selected = [];
171475
+ const selectedIds = new Set;
171476
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
171477
+ const tokenCost = (memory2) => estimateTokens(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
171478
+ const trySelect = (memory2) => {
171479
+ if (selectedIds.has(memory2.id))
171480
+ return false;
171481
+ const tokens = tokenCost(memory2);
171482
+ if (usedTokens + tokens > budgetTokens)
171483
+ return false;
171484
+ selected.push(memory2);
171485
+ selectedIds.add(memory2.id);
171486
+ usedTokens += tokens;
171487
+ return true;
171488
+ };
171489
+ for (const memory2 of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
171490
+ trySelect(memory2);
171491
+ }
171492
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
171493
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
171494
+ const byIdentity = new Map;
171495
+ for (const memory2 of memories) {
171496
+ if (memory2.status === "permanent")
171497
+ continue;
171498
+ const identity = memoryCanonicalIdentity(memory2, workspace);
171499
+ if (!identity)
171500
+ continue;
171501
+ const list = byIdentity.get(identity) ?? [];
171502
+ list.push(memory2);
171503
+ byIdentity.set(identity, list);
171504
+ }
171505
+ for (const identity of workspace.identities) {
171506
+ let memberTokens = 0;
171507
+ const candidates2 = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
171508
+ for (const memory2 of candidates2) {
171509
+ if (selectedIds.has(memory2.id))
171510
+ continue;
171511
+ const tokens = tokenCost(memory2);
171512
+ if (memberTokens + tokens > floorTokens)
171513
+ continue;
171514
+ if (usedTokens + tokens > budgetTokens)
171515
+ continue;
171516
+ selected.push(memory2);
171517
+ selectedIds.add(memory2.id);
171518
+ usedTokens += tokens;
171519
+ memberTokens += tokens;
171520
+ }
171521
+ }
171522
+ const remaining = memories.filter((memory2) => !selectedIds.has(memory2.id)).sort(memorySelectionOrder);
171523
+ for (const memory2 of remaining) {
171524
+ trySelect(memory2);
171525
+ }
171526
+ if (selected.length < memories.length) {
171527
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
171528
+ }
171529
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
171530
+ }
169879
171531
  function trimUserMemoriesToBudget(memories, budgetTokens) {
169880
171532
  const selected = [];
169881
171533
  let usedTokens = 0;
@@ -169888,15 +171540,16 @@ function trimUserMemoriesToBudget(memories, budgetTokens) {
169888
171540
  }
169889
171541
  return selected;
169890
171542
  }
169891
- function renderMemoryLineV2(memory2) {
169892
- return ` <memory id="${memory2.id}" category="${escapeXmlAttr(memory2.category)}" importance="${memory2.importance ?? 50}">${escapeXmlContent(memory2.content)}</memory>`;
171543
+ function renderMemoryLineV2(memory2, sourceName) {
171544
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
171545
+ return ` <memory id="${memory2.id}" category="${escapeXmlAttr(memory2.category)}"${sourceAttr} importance="${memory2.importance ?? 50}">${escapeXmlContent(memory2.content)}</memory>`;
169893
171546
  }
169894
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
171547
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
169895
171548
  if (memories.length === 0)
169896
171549
  return "";
169897
171550
  const lines = [`<${wrapper}>`];
169898
171551
  for (const memory2 of memories) {
169899
- lines.push(renderMemoryLineV2(memory2));
171552
+ lines.push(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
169900
171553
  }
169901
171554
  lines.push(`</${wrapper}>`);
169902
171555
  return lines.join(`
@@ -169961,8 +171614,9 @@ function formatAge(committedAtMs) {
169961
171614
  }
169962
171615
  function formatResult(result, index) {
169963
171616
  if (result.source === "memory") {
171617
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
169964
171618
  return [
169965
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
171619
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
169966
171620
  result.content
169967
171621
  ].join(`
169968
171622
  `);
@@ -169972,6 +171626,13 @@ function formatResult(result, index) {
169972
171626
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge(result.committedAtMs)} match=${result.matchType}`,
169973
171627
  result.content
169974
171628
  ].join(`
171629
+ `);
171630
+ }
171631
+ if (result.source === "compartment") {
171632
+ return [
171633
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
171634
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
171635
+ ].join(`
169975
171636
  `);
169976
171637
  }
169977
171638
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -169987,7 +171648,7 @@ function formatSearchResults(query, results) {
169987
171648
  return `No results found for "${query}" across memories, git commits, or message history.`;
169988
171649
  }
169989
171650
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
169990
- if (results.some((result) => result.source === "message")) {
171651
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
169991
171652
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
169992
171653
  }
169993
171654
  const body = bodyParts.join(`