@cortexkit/pi-magic-context 0.23.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -141961,6 +141961,7 @@ var SESSION_META_SELECT_COLUMNS = [
141961
141961
  "cached_m0_bytes",
141962
141962
  "cached_m1_bytes",
141963
141963
  "cached_m0_project_memory_epoch",
141964
+ "cached_m0_workspace_fingerprint",
141964
141965
  "cached_m0_project_user_profile_version",
141965
141966
  "cached_m0_max_compartment_seq",
141966
141967
  "cached_m0_max_memory_id",
@@ -142008,6 +142009,7 @@ var META_COLUMNS = {
142008
142009
  cachedM0Bytes: "cached_m0_bytes",
142009
142010
  cachedM1Bytes: "cached_m1_bytes",
142010
142011
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
142012
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
142011
142013
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
142012
142014
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
142013
142015
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -142037,6 +142039,7 @@ var NULL_BIND_META_KEYS = new Set([
142037
142039
  "cachedM0Bytes",
142038
142040
  "cachedM1Bytes",
142039
142041
  "cachedM0ProjectMemoryEpoch",
142042
+ "cachedM0WorkspaceFingerprint",
142040
142043
  "cachedM0ProjectUserProfileVersion",
142041
142044
  "cachedM0MaxCompartmentSeq",
142042
142045
  "cachedM0MaxMemoryId",
@@ -142070,7 +142073,7 @@ function isSessionMetaRow(row) {
142070
142073
  if (row === null || typeof row !== "object")
142071
142074
  return false;
142072
142075
  const r = row;
142073
- 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);
142076
+ 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);
142074
142077
  }
142075
142078
  function getDefaultSessionMeta(sessionId) {
142076
142079
  return {
@@ -142097,6 +142100,7 @@ function getDefaultSessionMeta(sessionId) {
142097
142100
  cachedM0Bytes: null,
142098
142101
  cachedM1Bytes: null,
142099
142102
  cachedM0ProjectMemoryEpoch: null,
142103
+ cachedM0WorkspaceFingerprint: null,
142100
142104
  cachedM0ProjectUserProfileVersion: null,
142101
142105
  cachedM0MaxCompartmentSeq: null,
142102
142106
  cachedM0MaxMemoryId: null,
@@ -142159,6 +142163,7 @@ function toSessionMeta(row) {
142159
142163
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
142160
142164
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
142161
142165
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
142166
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
142162
142167
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
142163
142168
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
142164
142169
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -142189,6 +142194,7 @@ function persistCachedM0(db, sessionId, payload) {
142189
142194
  db.prepare(`UPDATE session_meta SET
142190
142195
  cached_m0_bytes = ?,
142191
142196
  cached_m0_project_memory_epoch = ?,
142197
+ cached_m0_workspace_fingerprint = ?,
142192
142198
  cached_m0_project_user_profile_version = ?,
142193
142199
  cached_m0_max_compartment_seq = ?,
142194
142200
  cached_m0_max_memory_id = ?,
@@ -142201,7 +142207,7 @@ function persistCachedM0(db, sessionId, payload) {
142201
142207
  cached_m0_upgrade_state = ?,
142202
142208
  cached_m0_system_hash = ?,
142203
142209
  cached_m0_model_key = ?
142204
- WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.modelKey ?? "", sessionId);
142210
+ 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);
142205
142211
  }
142206
142212
  function clearCachedM0M1(db, sessionId) {
142207
142213
  ensureSessionMetaRow(db, sessionId);
@@ -142210,6 +142216,7 @@ function clearCachedM0M1(db, sessionId) {
142210
142216
  ["cached_m0_bytes", null],
142211
142217
  ["cached_m1_bytes", null],
142212
142218
  ["cached_m0_project_memory_epoch", null],
142219
+ ["cached_m0_workspace_fingerprint", null],
142213
142220
  ["cached_m0_project_user_profile_version", null],
142214
142221
  ["cached_m0_max_compartment_seq", null],
142215
142222
  ["cached_m0_max_memory_id", null],
@@ -142924,7 +142931,7 @@ var databases = new Map;
142924
142931
  var persistenceByDatabase = new WeakMap;
142925
142932
  var persistenceErrorByDatabase = new WeakMap;
142926
142933
  var lastSchemaFenceRejection = null;
142927
- var LATEST_SUPPORTED_VERSION = 32;
142934
+ var LATEST_SUPPORTED_VERSION = 36;
142928
142935
  function resolveDatabasePath(dbPathOverride) {
142929
142936
  if (dbPathOverride) {
142930
142937
  return { dbDir: dirname2(dbPathOverride), dbPath: dbPathOverride };
@@ -143090,6 +143097,35 @@ function initializeDatabase(db) {
143090
143097
  );
143091
143098
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
143092
143099
 
143100
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
143101
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
143102
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
143103
+ session_id TEXT NOT NULL,
143104
+ project_path TEXT NOT NULL,
143105
+ harness TEXT NOT NULL DEFAULT 'opencode',
143106
+ window_index INTEGER NOT NULL DEFAULT 0,
143107
+ start_ordinal INTEGER NOT NULL,
143108
+ end_ordinal INTEGER NOT NULL,
143109
+ chunk_hash TEXT NOT NULL,
143110
+ model_id TEXT NOT NULL,
143111
+ dims INTEGER NOT NULL,
143112
+ vector BLOB NOT NULL,
143113
+ created_at INTEGER NOT NULL,
143114
+ UNIQUE(compartment_id, window_index)
143115
+ );
143116
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
143117
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
143118
+
143119
+ CREATE TABLE IF NOT EXISTS session_projects (
143120
+ session_id TEXT NOT NULL,
143121
+ harness TEXT NOT NULL DEFAULT 'opencode',
143122
+ project_path TEXT NOT NULL,
143123
+ updated_at INTEGER NOT NULL,
143124
+ PRIMARY KEY(session_id, harness)
143125
+ );
143126
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
143127
+ ON session_projects(project_path);
143128
+
143093
143129
  CREATE TABLE IF NOT EXISTS compartment_events (
143094
143130
  id INTEGER PRIMARY KEY AUTOINCREMENT,
143095
143131
  session_id TEXT NOT NULL,
@@ -143275,6 +143311,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143275
143311
  rekeyed_at INTEGER NOT NULL
143276
143312
  );
143277
143313
 
143314
+ CREATE TABLE IF NOT EXISTS workspaces (
143315
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
143316
+ name TEXT NOT NULL UNIQUE,
143317
+ created_at INTEGER NOT NULL,
143318
+ updated_at INTEGER NOT NULL,
143319
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
143320
+ );
143321
+
143322
+ CREATE TABLE IF NOT EXISTS workspace_members (
143323
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
143324
+ project_path TEXT NOT NULL,
143325
+ display_name TEXT NOT NULL,
143326
+ display_path TEXT NOT NULL,
143327
+ added_at INTEGER NOT NULL,
143328
+ PRIMARY KEY (workspace_id, project_path)
143329
+ );
143330
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
143331
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
143332
+
143278
143333
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
143279
143334
  id INTEGER PRIMARY KEY AUTOINCREMENT,
143280
143335
  table_name TEXT NOT NULL,
@@ -143386,6 +143441,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143386
143441
  deferred_execute_state TEXT,
143387
143442
  cached_m0_bytes BLOB,
143388
143443
  cached_m0_project_memory_epoch INTEGER,
143444
+ cached_m0_workspace_fingerprint TEXT,
143389
143445
  cached_m0_project_user_profile_version INTEGER,
143390
143446
  cached_m0_max_compartment_seq INTEGER,
143391
143447
  cached_m0_max_memory_id INTEGER,
@@ -143544,6 +143600,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143544
143600
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
143545
143601
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
143546
143602
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
143603
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
143547
143604
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
143548
143605
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
143549
143606
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -143597,6 +143654,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143597
143654
  ensureColumn(db, "memories", "importance", "INTEGER");
143598
143655
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
143599
143656
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
143657
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
143600
143658
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
143601
143659
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
143602
143660
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -143628,6 +143686,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143628
143686
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
143629
143687
  updated_at INTEGER NOT NULL DEFAULT 0
143630
143688
  );
143689
+ CREATE TABLE IF NOT EXISTS session_projects (
143690
+ session_id TEXT NOT NULL,
143691
+ harness TEXT NOT NULL DEFAULT 'opencode',
143692
+ project_path TEXT NOT NULL,
143693
+ updated_at INTEGER NOT NULL,
143694
+ PRIMARY KEY(session_id, harness)
143695
+ );
143696
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
143697
+ ON session_projects(project_path);
143631
143698
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
143632
143699
  id INTEGER PRIMARY KEY AUTOINCREMENT,
143633
143700
  session_id TEXT NOT NULL,
@@ -143655,6 +143722,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143655
143722
  new_project_path TEXT NOT NULL,
143656
143723
  rekeyed_at INTEGER NOT NULL
143657
143724
  );
143725
+ CREATE TABLE IF NOT EXISTS workspaces (
143726
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
143727
+ name TEXT NOT NULL UNIQUE,
143728
+ created_at INTEGER NOT NULL,
143729
+ updated_at INTEGER NOT NULL,
143730
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
143731
+ );
143732
+ CREATE TABLE IF NOT EXISTS workspace_members (
143733
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
143734
+ project_path TEXT NOT NULL,
143735
+ display_name TEXT NOT NULL,
143736
+ display_path TEXT NOT NULL,
143737
+ added_at INTEGER NOT NULL,
143738
+ PRIMARY KEY (workspace_id, project_path)
143739
+ );
143740
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
143741
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
143658
143742
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
143659
143743
  id INTEGER PRIMARY KEY AUTOINCREMENT,
143660
143744
  table_name TEXT NOT NULL,
@@ -143676,6 +143760,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143676
143760
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
143677
143761
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
143678
143762
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
143763
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
143679
143764
  }
143680
143765
  function healAllNullColumns(db) {
143681
143766
  healNullTextColumns(db);
@@ -143714,6 +143799,7 @@ function healNullTextColumns(db) {
143714
143799
  ["system_prompt_hash", ""],
143715
143800
  ["stripped_placeholder_ids", ""],
143716
143801
  ["stale_reduce_stripped_ids", ""],
143802
+ ["processed_image_stripped_ids", ""],
143717
143803
  ["memory_block_cache", ""],
143718
143804
  ["memory_block_ids", ""],
143719
143805
  ["compaction_marker_state", ""],
@@ -143758,7 +143844,7 @@ function healNullIntegerColumns(db) {
143758
143844
  }
143759
143845
  }
143760
143846
  function ensureColumn(db, table, column, definition) {
143761
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
143847
+ if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
143762
143848
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
143763
143849
  }
143764
143850
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -143835,10 +143921,291 @@ function openDatabase(dbPathOrOptions) {
143835
143921
  }
143836
143922
  }
143837
143923
 
143924
+ // ../plugin/src/features/magic-context/workspaces.ts
143925
+ import { createHash as createHash3 } from "node:crypto";
143926
+
143927
+ // ../plugin/src/features/magic-context/memory/constants.ts
143928
+ var V2_MEMORY_CATEGORIES = [
143929
+ "PROJECT_RULES",
143930
+ "ARCHITECTURE",
143931
+ "CONSTRAINTS",
143932
+ "CONFIG_VALUES",
143933
+ "NAMING"
143934
+ ];
143935
+ var PROMOTABLE_CATEGORIES = [
143936
+ "PROJECT_RULES",
143937
+ "ARCHITECTURE",
143938
+ "CONSTRAINTS",
143939
+ "CONFIG_VALUES",
143940
+ "NAMING",
143941
+ "ARCHITECTURE_DECISIONS",
143942
+ "CONFIG_DEFAULTS",
143943
+ "USER_PREFERENCES",
143944
+ "USER_DIRECTIVES",
143945
+ "ENVIRONMENT",
143946
+ "WORKFLOW_RULES",
143947
+ "KNOWN_ISSUES"
143948
+ ];
143949
+ var CATEGORY_PRIORITY = [
143950
+ "PROJECT_RULES",
143951
+ "ARCHITECTURE",
143952
+ "CONSTRAINTS",
143953
+ "CONFIG_VALUES",
143954
+ "NAMING",
143955
+ "USER_DIRECTIVES",
143956
+ "USER_PREFERENCES",
143957
+ "CONFIG_DEFAULTS",
143958
+ "ARCHITECTURE_DECISIONS",
143959
+ "ENVIRONMENT",
143960
+ "WORKFLOW_RULES",
143961
+ "KNOWN_ISSUES"
143962
+ ];
143963
+ var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
143964
+ var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
143965
+ acc[category] = index;
143966
+ return acc;
143967
+ }, {});
143968
+ var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
143969
+ var CATEGORY_DEFAULT_TTL = {
143970
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
143971
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
143972
+ };
143973
+ // ../plugin/src/features/magic-context/workspaces.ts
143974
+ var VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
143975
+ function tableExists(db, tableName) {
143976
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
143977
+ return Boolean(row);
143978
+ }
143979
+ function columnExists(db, tableName, columnName) {
143980
+ const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
143981
+ return rows.some((row) => row.name === columnName);
143982
+ }
143983
+ function uniqueSorted(values) {
143984
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
143985
+ }
143986
+ function placeholders(values) {
143987
+ return values.map(() => "?").join(", ");
143988
+ }
143989
+ function normalizeShareCategories(raw) {
143990
+ if (raw === null || raw === undefined)
143991
+ return null;
143992
+ if (typeof raw !== "string")
143993
+ return null;
143994
+ let parsed;
143995
+ try {
143996
+ parsed = JSON.parse(raw);
143997
+ } catch {
143998
+ return null;
143999
+ }
144000
+ if (!Array.isArray(parsed))
144001
+ return null;
144002
+ const categories = [];
144003
+ for (const value of parsed) {
144004
+ if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
144005
+ return null;
144006
+ }
144007
+ if (!categories.includes(value))
144008
+ categories.push(value);
144009
+ }
144010
+ return categories.sort((left, right) => left.localeCompare(right));
144011
+ }
144012
+ function selectWorkspaceShareCategories(db, identities) {
144013
+ const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
144014
+ if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
144015
+ return null;
144016
+ }
144017
+ const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
144018
+ FROM workspace_members AS member
144019
+ JOIN workspaces AS workspace ON workspace.id = member.workspace_id
144020
+ WHERE member.project_path IN (${placeholders(candidates)})
144021
+ ORDER BY workspace.id ASC
144022
+ LIMIT 1`).get(...candidates);
144023
+ return normalizeShareCategories(row?.shareCategories ?? null);
144024
+ }
144025
+ function resolveWorkspaceShareCategories(db, projectIdentity) {
144026
+ return selectWorkspaceShareCategories(db, [projectIdentity]);
144027
+ }
144028
+ function resolveWorkspaceIdentitySet(db, projectIdentity) {
144029
+ if (!tableExists(db, "workspace_members")) {
144030
+ return { identities: [projectIdentity], namesByIdentity: new Map };
144031
+ }
144032
+ const rows = db.prepare(`SELECT member.project_path AS identity, member.display_name AS displayName
144033
+ FROM workspace_members AS anchor
144034
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
144035
+ WHERE anchor.project_path = ?
144036
+ ORDER BY member.display_name ASC, member.project_path ASC`).all(projectIdentity);
144037
+ if (rows.length === 0) {
144038
+ return { identities: [projectIdentity], namesByIdentity: new Map };
144039
+ }
144040
+ const namesByIdentity = new Map;
144041
+ const identities = [];
144042
+ for (const row of rows) {
144043
+ if (typeof row.identity !== "string" || row.identity.length === 0)
144044
+ continue;
144045
+ if (identities.includes(row.identity))
144046
+ continue;
144047
+ identities.push(row.identity);
144048
+ if (typeof row.displayName === "string" && row.displayName.length > 0) {
144049
+ namesByIdentity.set(row.identity, row.displayName);
144050
+ }
144051
+ }
144052
+ return identities.length > 0 ? { identities, namesByIdentity } : { identities: [projectIdentity], namesByIdentity: new Map };
144053
+ }
144054
+ function expandWorkspaceIdentitySetWithAliases(db, identities) {
144055
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
144056
+ const expanded = new Set(canonical);
144057
+ const canonicalIdentityByStoredPath = new Map;
144058
+ for (const identity of canonical) {
144059
+ canonicalIdentityByStoredPath.set(identity, identity);
144060
+ }
144061
+ if (canonical.length === 0 || !tableExists(db, "v22_identity_rekey_map")) {
144062
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
144063
+ }
144064
+ const rows = db.prepare(`SELECT old_project_path AS oldProjectPath, new_project_path AS newProjectPath
144065
+ FROM v22_identity_rekey_map
144066
+ WHERE new_project_path IN (${placeholders(canonical)})
144067
+ ORDER BY old_project_path ASC`).all(...canonical);
144068
+ for (const row of rows) {
144069
+ if (typeof row.oldProjectPath !== "string" || typeof row.newProjectPath !== "string") {
144070
+ continue;
144071
+ }
144072
+ if (!canonicalIdentityByStoredPath.has(row.newProjectPath))
144073
+ continue;
144074
+ expanded.add(row.oldProjectPath);
144075
+ canonicalIdentityByStoredPath.set(row.oldProjectPath, row.newProjectPath);
144076
+ }
144077
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
144078
+ }
144079
+ function resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) {
144080
+ const direct = canonicalIdentityByStoredPath.get(storedProjectPath);
144081
+ if (direct)
144082
+ return direct;
144083
+ const normalized = normalizeStoredProjectPath(storedProjectPath);
144084
+ const normalizedDirect = canonicalIdentityByStoredPath.get(normalized);
144085
+ if (normalizedDirect)
144086
+ return normalizedDirect;
144087
+ if (memberIdentities.includes(normalized))
144088
+ return normalized;
144089
+ for (const identity of memberIdentities) {
144090
+ if (storedPathBelongsToIdentity(storedProjectPath, identity)) {
144091
+ return identity;
144092
+ }
144093
+ }
144094
+ return null;
144095
+ }
144096
+ function storedPathBelongsToWorkspace(storedProjectPath, memberIdentities, expandedIdentities, canonicalIdentityByStoredPath) {
144097
+ if (expandedIdentities.includes(storedProjectPath))
144098
+ return true;
144099
+ return resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) !== null;
144100
+ }
144101
+ function sourceNameForMemory(storedProjectPath, ownIdentity, memberIdentities, namesByIdentity, canonicalIdentityByStoredPath) {
144102
+ const canonicalIdentity = resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath);
144103
+ if (!canonicalIdentity || canonicalIdentity === ownIdentity)
144104
+ return;
144105
+ return namesByIdentity.get(canonicalIdentity);
144106
+ }
144107
+ function getEpochMap(db, identities) {
144108
+ if (identities.length === 0)
144109
+ return new Map;
144110
+ const rows = db.prepare(`SELECT project_path AS projectPath, project_memory_epoch AS epoch
144111
+ FROM project_state
144112
+ WHERE project_path IN (${placeholders(identities)})`).all(...identities);
144113
+ const epochs = new Map;
144114
+ for (const row of rows) {
144115
+ if (typeof row.projectPath !== "string" || typeof row.epoch !== "number")
144116
+ continue;
144117
+ epochs.set(row.projectPath, row.epoch);
144118
+ }
144119
+ return epochs;
144120
+ }
144121
+ function computeWorkspaceEpochFingerprint(db, identities) {
144122
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
144123
+ const epochs = getEpochMap(db, canonical);
144124
+ const shareCategories = selectWorkspaceShareCategories(db, canonical);
144125
+ const hash = createHash3("sha256");
144126
+ hash.update("share_categories", "utf8");
144127
+ hash.update("\x00");
144128
+ hash.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
144129
+ hash.update(`
144130
+ `);
144131
+ for (const identity of canonical) {
144132
+ hash.update(identity, "utf8");
144133
+ hash.update("\x00");
144134
+ hash.update(String(epochs.get(identity) ?? 0), "utf8");
144135
+ hash.update(`
144136
+ `);
144137
+ }
144138
+ return hash.digest("hex");
144139
+ }
144140
+ function isInTransaction(db) {
144141
+ const candidate = db;
144142
+ return candidate.inTransaction === true || candidate.isTransaction === true;
144143
+ }
144144
+ function workspaceMembersForIdentity(db, identity) {
144145
+ if (!tableExists(db, "workspace_members"))
144146
+ return [identity];
144147
+ const rows = db.prepare(`SELECT member.project_path AS identity
144148
+ FROM workspace_members AS anchor
144149
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
144150
+ WHERE anchor.project_path = ?
144151
+ ORDER BY member.project_path ASC`).all(identity);
144152
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((value) => value.length > 0);
144153
+ return identities.length > 0 ? uniqueSorted(identities) : [identity];
144154
+ }
144155
+ function bumpEpochRows(db, identities, now) {
144156
+ const stmt = db.prepare(`INSERT INTO project_state
144157
+ (project_path, project_memory_epoch, project_user_profile_version, updated_at)
144158
+ VALUES (?, 1, 0, ?)
144159
+ ON CONFLICT(project_path) DO UPDATE SET
144160
+ project_memory_epoch = project_memory_epoch + 1,
144161
+ updated_at = excluded.updated_at`);
144162
+ for (const identity of uniqueSorted(identities)) {
144163
+ stmt.run(identity, now);
144164
+ }
144165
+ }
144166
+ function bumpEpochsForWorkspaceMembers(db, identity, now = Date.now()) {
144167
+ const run = () => bumpEpochRows(db, workspaceMembersForIdentity(db, identity), now);
144168
+ if (isInTransaction(db)) {
144169
+ run();
144170
+ return;
144171
+ }
144172
+ db.exec("BEGIN IMMEDIATE");
144173
+ try {
144174
+ run();
144175
+ db.exec("COMMIT");
144176
+ } catch (error) {
144177
+ try {
144178
+ db.exec("ROLLBACK");
144179
+ } catch {}
144180
+ throw error;
144181
+ }
144182
+ }
144183
+ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
144184
+ const run = () => bumpEpochRows(db, identities, now);
144185
+ if (isInTransaction(db)) {
144186
+ run();
144187
+ return;
144188
+ }
144189
+ db.exec("BEGIN IMMEDIATE");
144190
+ try {
144191
+ run();
144192
+ db.exec("COMMIT");
144193
+ } catch (error) {
144194
+ try {
144195
+ db.exec("ROLLBACK");
144196
+ } catch {}
144197
+ throw error;
144198
+ }
144199
+ }
144200
+
143838
144201
  // ../plugin/src/features/magic-context/migrations.ts
143839
- function tableExists(db, name2) {
144202
+ function tableExists2(db, name2) {
143840
144203
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
143841
144204
  }
144205
+ function columnExists2(db, table, column) {
144206
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
144207
+ return rows.some((row) => row.name === column);
144208
+ }
143842
144209
  var MIGRATIONS = [
143843
144210
  {
143844
144211
  version: 1,
@@ -144297,9 +144664,9 @@ var MIGRATIONS = [
144297
144664
  version: 22,
144298
144665
  description: "v2.0 cache architecture schema foundation",
144299
144666
  up: (db) => {
144300
- const hasSessionMetaTable = tableExists(db, "session_meta");
144301
- const hasCompartmentsTable = tableExists(db, "compartments");
144302
- const hasMemoriesTable = tableExists(db, "memories");
144667
+ const hasSessionMetaTable = tableExists2(db, "session_meta");
144668
+ const hasCompartmentsTable = tableExists2(db, "compartments");
144669
+ const hasMemoriesTable = tableExists2(db, "memories");
144303
144670
  if (hasSessionMetaTable) {
144304
144671
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
144305
144672
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
@@ -144324,7 +144691,7 @@ var MIGRATIONS = [
144324
144691
  ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
144325
144692
  ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
144326
144693
  }
144327
- const hasRecompCompartmentsTable = tableExists(db, "recomp_compartments");
144694
+ const hasRecompCompartmentsTable = tableExists2(db, "recomp_compartments");
144328
144695
  if (hasRecompCompartmentsTable) {
144329
144696
  ensureColumn(db, "recomp_compartments", "p1", "TEXT");
144330
144697
  ensureColumn(db, "recomp_compartments", "p2", "TEXT");
@@ -144635,6 +145002,156 @@ var MIGRATIONS = [
144635
145002
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
144636
145003
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
144637
145004
  }
145005
+ },
145006
+ {
145007
+ version: 33,
145008
+ description: "Compartment chunk embeddings for semantic message-history search",
145009
+ up: (db) => {
145010
+ db.exec(`
145011
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
145012
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
145013
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
145014
+ session_id TEXT NOT NULL,
145015
+ project_path TEXT NOT NULL,
145016
+ harness TEXT NOT NULL DEFAULT 'opencode',
145017
+ window_index INTEGER NOT NULL DEFAULT 0,
145018
+ start_ordinal INTEGER NOT NULL,
145019
+ end_ordinal INTEGER NOT NULL,
145020
+ chunk_hash TEXT NOT NULL,
145021
+ model_id TEXT NOT NULL,
145022
+ dims INTEGER NOT NULL,
145023
+ vector BLOB NOT NULL,
145024
+ created_at INTEGER NOT NULL,
145025
+ UNIQUE(compartment_id, window_index)
145026
+ );
145027
+ CREATE INDEX IF NOT EXISTS idx_cce_session
145028
+ ON compartment_chunk_embeddings(session_id);
145029
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model
145030
+ ON compartment_chunk_embeddings(project_path, model_id);
145031
+ `);
145032
+ }
145033
+ },
145034
+ {
145035
+ version: 34,
145036
+ description: "workspace tables and m[0] workspace fingerprint cache reset",
145037
+ up: (db) => {
145038
+ db.exec(`
145039
+ CREATE TABLE IF NOT EXISTS workspaces (
145040
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
145041
+ name TEXT NOT NULL UNIQUE,
145042
+ created_at INTEGER NOT NULL,
145043
+ updated_at INTEGER NOT NULL
145044
+ );
145045
+ CREATE TABLE IF NOT EXISTS workspace_members (
145046
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
145047
+ project_path TEXT NOT NULL,
145048
+ display_name TEXT NOT NULL,
145049
+ display_path TEXT NOT NULL,
145050
+ added_at INTEGER NOT NULL,
145051
+ PRIMARY KEY (workspace_id, project_path)
145052
+ );
145053
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique
145054
+ ON workspace_members(project_path);
145055
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name
145056
+ ON workspace_members(workspace_id, display_name);
145057
+ `);
145058
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
145059
+ if (!hasSessionMeta)
145060
+ return;
145061
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
145062
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
145063
+ const clears = [
145064
+ ["cached_m0_bytes", null],
145065
+ ["cached_m1_bytes", null],
145066
+ ["cached_m0_project_memory_epoch", null],
145067
+ ["cached_m0_workspace_fingerprint", null],
145068
+ ["cached_m0_project_user_profile_version", null],
145069
+ ["cached_m0_max_compartment_seq", null],
145070
+ ["cached_m0_max_memory_id", null],
145071
+ ["cached_m0_max_mutation_id", null],
145072
+ ["cached_m0_max_memory_mutation_id", null],
145073
+ ["cached_m0_project_docs_hash", null],
145074
+ ["cached_m0_materialized_at", null],
145075
+ ["cached_m0_session_facts_version", null],
145076
+ ["cached_m0_upgrade_state", null],
145077
+ ["cached_m0_system_hash", null],
145078
+ ["cached_m0_tool_set_hash", null],
145079
+ ["cached_m0_model_key", null],
145080
+ ["cached_m0_last_baseline_end_message_id", null],
145081
+ ["memory_block_cache", ""],
145082
+ ["memory_block_ids", ""],
145083
+ ["memory_block_count", 0]
145084
+ ];
145085
+ const setClauses = [];
145086
+ const values = [];
145087
+ for (const [column, value] of clears) {
145088
+ if (!columns.has(column))
145089
+ continue;
145090
+ setClauses.push(`${column} = ?`);
145091
+ values.push(value);
145092
+ }
145093
+ if (setClauses.length > 0) {
145094
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")}`).run(...values);
145095
+ }
145096
+ }
145097
+ },
145098
+ {
145099
+ version: 35,
145100
+ description: "workspace per-category share defaults and epoch refresh",
145101
+ up: (db) => {
145102
+ db.exec(`
145103
+ CREATE TABLE IF NOT EXISTS workspaces (
145104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
145105
+ name TEXT NOT NULL UNIQUE,
145106
+ created_at INTEGER NOT NULL,
145107
+ updated_at INTEGER NOT NULL,
145108
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
145109
+ );
145110
+ `);
145111
+ if (!columnExists2(db, "workspaces", "share_categories")) {
145112
+ db.exec(`ALTER TABLE workspaces ADD COLUMN share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
145113
+ }
145114
+ db.prepare(`UPDATE workspaces
145115
+ SET share_categories = '["CONSTRAINTS"]'
145116
+ WHERE share_categories IS NULL OR share_categories = ''`).run();
145117
+ if (!tableExists2(db, "workspace_members"))
145118
+ return;
145119
+ const rows = db.prepare(`SELECT DISTINCT project_path AS identity
145120
+ FROM workspace_members
145121
+ WHERE project_path IS NOT NULL AND project_path <> ''
145122
+ ORDER BY project_path ASC`).all();
145123
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((identity) => identity.length > 0);
145124
+ if (identities.length > 0) {
145125
+ bumpEpochsForWorkspaceMemberSet(db, identities, Date.now());
145126
+ }
145127
+ }
145128
+ },
145129
+ {
145130
+ version: 36,
145131
+ description: "session project ownership map for compartment chunk backfill scoping",
145132
+ up: (db) => {
145133
+ db.exec(`
145134
+ CREATE TABLE IF NOT EXISTS session_projects (
145135
+ session_id TEXT NOT NULL,
145136
+ harness TEXT NOT NULL DEFAULT 'opencode',
145137
+ project_path TEXT NOT NULL,
145138
+ updated_at INTEGER NOT NULL,
145139
+ PRIMARY KEY(session_id, harness)
145140
+ );
145141
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
145142
+ ON session_projects(project_path);
145143
+ `);
145144
+ const hasChunkTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartment_chunk_embeddings'").get();
145145
+ if (hasChunkTable) {
145146
+ db.exec(`
145147
+ INSERT OR IGNORE INTO session_projects (session_id, harness, project_path, updated_at)
145148
+ SELECT session_id, harness, MIN(project_path), 0
145149
+ FROM compartment_chunk_embeddings
145150
+ GROUP BY session_id, harness
145151
+ HAVING COUNT(DISTINCT project_path) = 1;
145152
+ `);
145153
+ }
145154
+ }
144638
145155
  }
144639
145156
  ];
144640
145157
  var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -144704,7 +145221,7 @@ function runMigrations(db) {
144704
145221
  log(`[migrations] schema version now: ${MIGRATIONS[MIGRATIONS.length - 1].version}`);
144705
145222
  }
144706
145223
  // ../plugin/src/features/magic-context/project-docs-hash.ts
144707
- import { createHash as createHash3 } from "node:crypto";
145224
+ import { createHash as createHash4 } from "node:crypto";
144708
145225
  import { lstatSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
144709
145226
  import path4 from "node:path";
144710
145227
  var PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
@@ -144802,7 +145319,7 @@ function hashCanonicalPieces(hashPieces) {
144802
145319
  if (hashPieces.length === 0) {
144803
145320
  return "";
144804
145321
  }
144805
- return createHash3("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
145322
+ return createHash4("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
144806
145323
  }
144807
145324
  function readProjectDocsCanonical(projectDirectory) {
144808
145325
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -144908,18 +145425,13 @@ function getMemoryMutation(db, id) {
144908
145425
  WHERE id = ?`).get(id);
144909
145426
  return row ? toMemoryMutation(row) : null;
144910
145427
  }
144911
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
144912
- if (renderedMemoryIds.length === 0)
144913
- return [];
144914
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
144915
- const placeholders = uniqueIds.map(() => "?").join(", ");
144916
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
144917
- superseded_by_id, category, new_content, queued_at
144918
- FROM memory_mutation_log
144919
- WHERE project_path = ?
144920
- AND id > ?
144921
- AND target_memory_id IN (${placeholders})
144922
- ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
145428
+ function uniqueProjectPaths(projectPaths) {
145429
+ return [...new Set(projectPaths.filter((path5) => path5.length > 0))];
145430
+ }
145431
+ function placeholders2(values) {
145432
+ return values.map(() => "?").join(", ");
145433
+ }
145434
+ function coalesceMutations(rows) {
144923
145435
  const chosenByTarget = new Map;
144924
145436
  for (const dbRow of rows) {
144925
145437
  const candidate = toMemoryMutation(dbRow);
@@ -144940,10 +145452,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
144940
145452
  }
144941
145453
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
144942
145454
  }
145455
+ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
145456
+ if (renderedMemoryIds.length === 0)
145457
+ return [];
145458
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
145459
+ const placeholders3 = uniqueIds.map(() => "?").join(", ");
145460
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
145461
+ superseded_by_id, category, new_content, queued_at
145462
+ FROM memory_mutation_log
145463
+ WHERE project_path = ?
145464
+ AND id > ?
145465
+ AND target_memory_id IN (${placeholders3})
145466
+ ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
145467
+ return coalesceMutations(rows);
145468
+ }
145469
+ function getMemoryMutationsForRenderByProjects(db, projectPaths, afterId, renderedMemoryIds) {
145470
+ if (renderedMemoryIds.length === 0)
145471
+ return [];
145472
+ const identities = uniqueProjectPaths(projectPaths);
145473
+ if (identities.length === 0)
145474
+ return [];
145475
+ if (identities.length === 1) {
145476
+ return getMemoryMutationsForRender(db, identities[0], afterId, renderedMemoryIds);
145477
+ }
145478
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
145479
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
145480
+ superseded_by_id, category, new_content, queued_at
145481
+ FROM memory_mutation_log
145482
+ WHERE project_path IN (${placeholders2(identities)})
145483
+ AND id > ?
145484
+ AND target_memory_id IN (${placeholders2(uniqueIds)})
145485
+ ORDER BY id ASC`).all(...identities, afterId ?? 0, ...uniqueIds);
145486
+ return coalesceMutations(rows);
145487
+ }
144943
145488
  function getMaxMemoryMutationId(db, projectPath) {
144944
145489
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
144945
145490
  return row?.max_id ?? null;
144946
145491
  }
145492
+ function getMaxMemoryMutationIdForProjects(db, projectPaths) {
145493
+ const identities = uniqueProjectPaths(projectPaths);
145494
+ if (identities.length === 0)
145495
+ return null;
145496
+ if (identities.length === 1)
145497
+ return getMaxMemoryMutationId(db, identities[0]);
145498
+ const row = db.prepare(`SELECT MAX(id) AS max_id
145499
+ FROM memory_mutation_log
145500
+ WHERE project_path IN (${placeholders2(identities)})`).get(...identities);
145501
+ return row?.max_id ?? null;
145502
+ }
144947
145503
  // ../plugin/src/features/magic-context/storage-meta-persisted.ts
144948
145504
  init_logger();
144949
145505
  var CAS_RETRY_LIMIT = 5;
@@ -145789,9 +146345,9 @@ function buildStatusClause(status) {
145789
146345
  if (statuses.length === 0) {
145790
146346
  return null;
145791
146347
  }
145792
- const placeholders = statuses.map(() => "?").join(", ");
146348
+ const placeholders3 = statuses.map(() => "?").join(", ");
145793
146349
  return {
145794
- sql: `status IN (${placeholders})`,
146350
+ sql: `status IN (${placeholders3})`,
145795
146351
  params: statuses
145796
146352
  };
145797
146353
  }
@@ -145987,19 +146543,6 @@ function getProjectState(db, projectPath) {
145987
146543
  WHERE project_path = ?`).get(projectPath);
145988
146544
  return row ? toProjectState(row) : null;
145989
146545
  }
145990
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
145991
- db.prepare(`INSERT INTO project_state
145992
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
145993
- VALUES (?, 1, 0, ?)
145994
- ON CONFLICT(project_path) DO UPDATE SET
145995
- project_memory_epoch = project_memory_epoch + 1,
145996
- updated_at = excluded.updated_at`).run(projectPath, now);
145997
- const state = getProjectState(db, projectPath);
145998
- if (!state) {
145999
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
146000
- }
146001
- return state;
146002
- }
146003
146546
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
146004
146547
  db.prepare(`INSERT INTO project_state
146005
146548
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -146033,8 +146576,8 @@ function getSourceContents(db, sessionId, tagIds) {
146033
146576
  if (tagIds.length === 0) {
146034
146577
  return new Map;
146035
146578
  }
146036
- const placeholders = tagIds.map(() => "?").join(", ");
146037
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
146579
+ const placeholders3 = tagIds.map(() => "?").join(", ");
146580
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders3})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
146038
146581
  const sources = new Map;
146039
146582
  for (const row of rows) {
146040
146583
  sources.set(row.tag_id, row.content);
@@ -146324,8 +146867,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
146324
146867
  }
146325
146868
  return all;
146326
146869
  }
146327
- const placeholders = tagNumbers.map(() => "?").join(",");
146328
- 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);
146870
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
146871
+ 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);
146329
146872
  return rows.map(toTagEntry);
146330
146873
  }
146331
146874
  var getToolTagNumberByOwnerStatements = new WeakMap;
@@ -146395,7 +146938,7 @@ var ERROR_CLASSES = new Set([
146395
146938
  ]);
146396
146939
  // ../plugin/src/features/magic-context/v22-deferred-backfill.ts
146397
146940
  init_logger();
146398
- import { createHash as createHash4 } from "node:crypto";
146941
+ import { createHash as createHash5 } from "node:crypto";
146399
146942
  import { realpathSync as realpathSync2 } from "node:fs";
146400
146943
  import path5 from "node:path";
146401
146944
  var BATCH_SIZE = 25;
@@ -146449,7 +146992,7 @@ function computeLegacyRustDirIdentity(rawProjectPath) {
146449
146992
  } catch {
146450
146993
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
146451
146994
  }
146452
- return `dir:${createHash4("sha256").update(canonical, "utf8").digest("hex")}`;
146995
+ return `dir:${createHash5("sha256").update(canonical, "utf8").digest("hex")}`;
146453
146996
  }
146454
146997
  function upsertRekeyMap(db, oldProjectPath, newProjectPath, rekeyedAt) {
146455
146998
  db.prepare(`INSERT INTO v22_identity_rekey_map (old_project_path, new_project_path, rekeyed_at)
@@ -147007,7 +147550,7 @@ function clearNoteNudgeTriggerOnly(db, sessionId) {
147007
147550
  }
147008
147551
 
147009
147552
  // ../plugin/src/hooks/magic-context/todo-view.ts
147010
- import { createHash as createHash5 } from "node:crypto";
147553
+ import { createHash as createHash6 } from "node:crypto";
147011
147554
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
147012
147555
  var TITLE_DONE_STATUSES = new Set(["completed"]);
147013
147556
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -147052,7 +147595,7 @@ function buildSyntheticTodoPart(stateJson) {
147052
147595
  };
147053
147596
  }
147054
147597
  function computeSyntheticCallId(stateJson) {
147055
- const hash = createHash5("sha256").update(stateJson).digest("hex").slice(0, 16);
147598
+ const hash = createHash6("sha256").update(stateJson).digest("hex").slice(0, 16);
147056
147599
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash}`;
147057
147600
  }
147058
147601
  function parseTodoState(stateJson) {
@@ -147193,13 +147736,13 @@ async function maybeSendUpgradeReminder(deps, sessionId) {
147193
147736
  init_data_path();
147194
147737
  import * as fs2 from "node:fs";
147195
147738
  import * as path6 from "node:path";
147196
- var ANNOUNCEMENT_VERSION = "0.23.0";
147739
+ var ANNOUNCEMENT_VERSION = "0.24.0";
147197
147740
  var ANNOUNCEMENT_FEATURES = [
147198
- "Smarter context nudges: gentle <system-reminder> notes on tool outputs replace the old chat nudges — quieter, cache-safe, and they now also help subagents manage their own context.",
147199
- "The agent can now maintain its own project memories: update, archive (batch), and merge the memories it sees — not just write new ones. Memory categories are schema-enforced.",
147200
- "Big-session performance: historian trigger and token math moved off the database hot path — multi-second stalls on large sessions are gone (measured 250ms → 2.4ms per pass).",
147201
- "History compaction unstuck for sparse sessions: the protected-tail boundary is now size-based, so sessions with few user turns compact reliably instead of growing forever (issue #132).",
147202
- "Fixed: session titles no longer fail to generate in fresh directories (issue #129), plus 20+ correctness fixes from three audit rounds."
147741
+ "Searchable session history: ctx_search can now find older discussion by meaning, not just keywords. New history is embedded automatically — to backfill an EXISTING session's older history, run /ctx-embed-history once (it works in the background).",
147742
+ "Cross-project workspaces: group related repos and share project memories across them, with per-category control over what's shared. Set them up in the dashboard's Workspaces panel.",
147743
+ "Pi: fixed sessions overflowing the model context while still showing moderate usage — Pi now sheds context before a tool-heavy turn overflows.",
147744
+ "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
147745
+ "Setup wizard now lists your actual models with type-ahead instead of fixed recommendations, and explains the historian/dreamer roles (issue #144). Plus a GitHub Copilot tool-pairing fix (#135)."
147203
147746
  ];
147204
147747
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
147205
147748
  var STATE_FILENAME = "last_announced_version";
@@ -149082,7 +149625,7 @@ function isEmbeddingRow(row) {
149082
149625
  if (row === null || typeof row !== "object")
149083
149626
  return false;
149084
149627
  const candidate = row;
149085
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
149628
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
149086
149629
  }
149087
149630
  function toFloat32Array(blob) {
149088
149631
  if (blob instanceof Uint8Array) {
@@ -149102,7 +149645,7 @@ function getSaveEmbeddingStatement(db) {
149102
149645
  function getLoadAllEmbeddingsStatement(db) {
149103
149646
  let stmt = loadAllEmbeddingsStatements.get(db);
149104
149647
  if (!stmt) {
149105
- 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");
149648
+ 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");
149106
149649
  loadAllEmbeddingsStatements.set(db, stmt);
149107
149650
  }
149108
149651
  return stmt;
@@ -149131,7 +149674,10 @@ function loadAllEmbeddings(db, projectPath) {
149131
149674
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
149132
149675
  const embeddings = new Map;
149133
149676
  for (const row of rows) {
149134
- embeddings.set(row.memoryId, toFloat32Array(row.embedding));
149677
+ embeddings.set(row.memoryId, {
149678
+ embedding: toFloat32Array(row.embedding),
149679
+ modelId: row.modelId
149680
+ });
149135
149681
  }
149136
149682
  return embeddings;
149137
149683
  }
@@ -149182,13 +149728,13 @@ function invalidateMemory(projectPath, memoryId) {
149182
149728
  }
149183
149729
 
149184
149730
  // ../plugin/src/features/magic-context/memory/normalize-hash.ts
149185
- import { createHash as createHash6 } from "node:crypto";
149731
+ import { createHash as createHash7 } from "node:crypto";
149186
149732
  function normalizeMemoryContent(content) {
149187
149733
  return content.toLowerCase().replace(/\s+/g, " ").trim();
149188
149734
  }
149189
149735
  function computeNormalizedHash(content) {
149190
149736
  const normalized = normalizeMemoryContent(content);
149191
- return createHash6("md5").update(normalized).digest("hex");
149737
+ return createHash7("md5").update(normalized).digest("hex");
149192
149738
  }
149193
149739
 
149194
149740
  // ../plugin/src/features/magic-context/memory/storage-memory.ts
@@ -149376,8 +149922,8 @@ function getMemoriesByProjectStatement(db, statuses) {
149376
149922
  }
149377
149923
  let stmt = statements.get(db);
149378
149924
  if (!stmt) {
149379
- const placeholders = statuses.map(() => "?").join(", ");
149380
- 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`);
149925
+ const placeholders3 = statuses.map(() => "?").join(", ");
149926
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders3}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
149381
149927
  statements.set(db, stmt);
149382
149928
  }
149383
149929
  return stmt;
@@ -149522,6 +150068,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
149522
150068
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
149523
150069
  return rows.map(toMemory);
149524
150070
  }
150071
+ function sqlPlaceholders(values) {
150072
+ return values.map(() => "?").join(", ");
150073
+ }
150074
+ function uniqueValues(values) {
150075
+ return [...new Set(values.filter((value) => value.length > 0))];
150076
+ }
150077
+ function buildWorkspaceMemorySqlFilter(args) {
150078
+ if (args.shareCategories === null || args.shareCategories === undefined) {
150079
+ return { clause: "", params: [], active: false };
150080
+ }
150081
+ const identities = uniqueValues(args.identities);
150082
+ const identitySet = new Set(identities);
150083
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
150084
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
150085
+ if (foreignIdentities.length === 0) {
150086
+ return { clause: "", params: [], active: false };
150087
+ }
150088
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
150089
+ const shareCategories = uniqueValues([...args.shareCategories]);
150090
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
150091
+ const predicates = [];
150092
+ const params = [];
150093
+ if (ownIdentities.length > 0) {
150094
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
150095
+ params.push(...ownIdentities);
150096
+ }
150097
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
150098
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
150099
+ params.push(...foreignIdentities, ...shareCategories);
150100
+ }
150101
+ if (predicates.length === 0) {
150102
+ return { clause: " AND 0 = 1", params: [], active: true };
150103
+ }
150104
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
150105
+ }
150106
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
150107
+ const identities = uniqueValues(projectPaths);
150108
+ if (identities.length === 0 || statuses.length === 0)
150109
+ return [];
150110
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
150111
+ identities,
150112
+ ownIdentities,
150113
+ shareCategories
150114
+ });
150115
+ if (identities.length === 1 && !sharingFilter.active) {
150116
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
150117
+ }
150118
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
150119
+ FROM memories
150120
+ WHERE project_path IN (${sqlPlaceholders(identities)})
150121
+ AND status IN (${sqlPlaceholders(statuses)})
150122
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
150123
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
150124
+ return rows.map(toMemory);
150125
+ }
150126
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
150127
+ const identities = uniqueValues(projectPaths);
150128
+ if (identities.length === 0)
150129
+ return 0;
150130
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
150131
+ identities,
150132
+ ownIdentities,
150133
+ shareCategories
150134
+ });
150135
+ if (identities.length === 1 && !sharingFilter.active) {
150136
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
150137
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
150138
+ }
150139
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
150140
+ FROM memories
150141
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
150142
+ return typeof row?.max_id === "number" ? row.max_id : 0;
150143
+ }
150144
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
150145
+ const identities = uniqueValues(projectPaths);
150146
+ if (identities.length === 0)
150147
+ return [];
150148
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
150149
+ identities,
150150
+ ownIdentities,
150151
+ shareCategories
150152
+ });
150153
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
150154
+ FROM memories
150155
+ WHERE project_path IN (${sqlPlaceholders(identities)})
150156
+ AND id > ?
150157
+ AND status IN ('active', 'permanent')
150158
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
150159
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
150160
+ return rows.map(toMemory);
150161
+ }
149525
150162
  function getAllActiveMemoriesForMigration(db, projectPath) {
149526
150163
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
149527
150164
  return rows.map(toMemory);
@@ -150043,8 +150680,8 @@ function getUserMemoryCandidates(db) {
150043
150680
  function deleteUserMemoryCandidates(db, ids) {
150044
150681
  if (ids.length === 0)
150045
150682
  return;
150046
- const placeholders = ids.map(() => "?").join(",");
150047
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
150683
+ const placeholders3 = ids.map(() => "?").join(",");
150684
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
150048
150685
  }
150049
150686
  function insertUserMemory(db, content, sourceCandidateIds) {
150050
150687
  const now = Date.now();
@@ -165534,7 +166171,8 @@ var BaseEmbeddingConfigSchema = exports_external.object({
165534
166171
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
165535
166172
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
165536
166173
  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."),
165537
- 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.")
166174
+ 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."),
166175
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
165538
166176
  }).superRefine((data, ctx) => {
165539
166177
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
165540
166178
  ctx.addIssue({
@@ -165555,7 +166193,8 @@ var EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data) => {
165555
166193
  if (data.provider === "local") {
165556
166194
  return {
165557
166195
  provider: "local",
165558
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
166196
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
166197
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
165559
166198
  };
165560
166199
  }
165561
166200
  if (data.provider === "openai-compatible") {
@@ -165568,7 +166207,8 @@ var EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data) => {
165568
166207
  endpoint: data.endpoint?.trim() ?? "",
165569
166208
  ...apiKey ? { api_key: apiKey } : {},
165570
166209
  ...inputType ? { input_type: inputType } : {},
165571
- ...truncate ? { truncate } : {}
166210
+ ...truncate ? { truncate } : {},
166211
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
165572
166212
  };
165573
166213
  }
165574
166214
  return { provider: "off" };
@@ -165650,6 +166290,441 @@ var MagicContextConfigSchema = exports_external.object({
165650
166290
  // ../plugin/src/features/magic-context/memory/embedding.ts
165651
166291
  init_logger();
165652
166292
 
166293
+ // ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
166294
+ import { createHash as createHash8 } from "node:crypto";
166295
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512;
166296
+ var loadFtsRowsStatements = new WeakMap;
166297
+ var existingHashStatements = new WeakMap;
166298
+ var existingHashByProjectStatements = new WeakMap;
166299
+ var deleteByCompartmentStatements = new WeakMap;
166300
+ var insertEmbeddingStatements = new WeakMap;
166301
+ var distinctModelStatements = new WeakMap;
166302
+ var clearProjectStatements = new WeakMap;
166303
+ var clearProjectModelStatements = new WeakMap;
166304
+ var searchRowsStatements = new WeakMap;
166305
+ var searchRowsByModelStatements = new WeakMap;
166306
+ var backfillCandidateStatements = new WeakMap;
166307
+ function getLoadFtsRowsStatement(db) {
166308
+ let stmt = loadFtsRowsStatements.get(db);
166309
+ if (!stmt) {
166310
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
166311
+ FROM message_history_fts
166312
+ WHERE session_id = ?
166313
+ AND message_ordinal >= ?
166314
+ AND message_ordinal <= ?
166315
+ AND role IN ('user', 'assistant')
166316
+ ORDER BY message_ordinal ASC`);
166317
+ loadFtsRowsStatements.set(db, stmt);
166318
+ }
166319
+ return stmt;
166320
+ }
166321
+ function getExistingHashStatement(db, scopedToProject) {
166322
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
166323
+ let stmt = map2.get(db);
166324
+ if (!stmt) {
166325
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
166326
+ FROM compartment_chunk_embeddings
166327
+ WHERE compartment_id = ?
166328
+ AND model_id = ?
166329
+ ${scopedToProject ? "AND project_path = ?" : ""}
166330
+ ORDER BY window_index ASC`);
166331
+ map2.set(db, stmt);
166332
+ }
166333
+ return stmt;
166334
+ }
166335
+ function getDeleteByCompartmentStatement(db) {
166336
+ let stmt = deleteByCompartmentStatements.get(db);
166337
+ if (!stmt) {
166338
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
166339
+ deleteByCompartmentStatements.set(db, stmt);
166340
+ }
166341
+ return stmt;
166342
+ }
166343
+ function getInsertEmbeddingStatement(db) {
166344
+ let stmt = insertEmbeddingStatements.get(db);
166345
+ if (!stmt) {
166346
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
166347
+ compartment_id, session_id, project_path, harness, window_index,
166348
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
166349
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
166350
+ insertEmbeddingStatements.set(db, stmt);
166351
+ }
166352
+ return stmt;
166353
+ }
166354
+ function getDistinctModelStatement(db) {
166355
+ let stmt = distinctModelStatements.get(db);
166356
+ if (!stmt) {
166357
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
166358
+ FROM compartment_chunk_embeddings
166359
+ WHERE project_path = ?`);
166360
+ distinctModelStatements.set(db, stmt);
166361
+ }
166362
+ return stmt;
166363
+ }
166364
+ function getClearProjectStatement(db) {
166365
+ let stmt = clearProjectStatements.get(db);
166366
+ if (!stmt) {
166367
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
166368
+ clearProjectStatements.set(db, stmt);
166369
+ }
166370
+ return stmt;
166371
+ }
166372
+ function getClearProjectModelStatement(db) {
166373
+ let stmt = clearProjectModelStatements.get(db);
166374
+ if (!stmt) {
166375
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
166376
+ clearProjectModelStatements.set(db, stmt);
166377
+ }
166378
+ return stmt;
166379
+ }
166380
+ function getSearchRowsStatement(db, withModel) {
166381
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
166382
+ let stmt = map2.get(db);
166383
+ if (!stmt) {
166384
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
166385
+ e.session_id AS sessionId,
166386
+ c.title AS title,
166387
+ c.start_message AS compartmentStart,
166388
+ c.end_message AS compartmentEnd,
166389
+ e.window_index AS windowIndex,
166390
+ e.start_ordinal AS windowStart,
166391
+ e.end_ordinal AS windowEnd,
166392
+ e.chunk_hash AS chunkHash,
166393
+ e.model_id AS modelId,
166394
+ e.dims AS dims,
166395
+ e.vector AS vector
166396
+ FROM compartment_chunk_embeddings e
166397
+ JOIN compartments c ON c.id = e.compartment_id
166398
+ WHERE e.session_id = ?
166399
+ AND e.project_path = ?
166400
+ ${withModel ? "AND e.model_id = ?" : ""}
166401
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
166402
+ map2.set(db, stmt);
166403
+ }
166404
+ return stmt;
166405
+ }
166406
+ function isFinitePositiveInteger(value) {
166407
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
166408
+ }
166409
+ function normalizeCompartmentChunkMaxInputTokens(value) {
166410
+ if (!isFinitePositiveInteger(value)) {
166411
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
166412
+ }
166413
+ return Math.max(1, Math.floor(value));
166414
+ }
166415
+ function normalizeContent(text) {
166416
+ return text.replace(/\s+/g, " ").trim();
166417
+ }
166418
+ function formatOrdinalRange(start, end) {
166419
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
166420
+ }
166421
+ function rolePrefix(role) {
166422
+ if (role === "user")
166423
+ return "U";
166424
+ if (role === "assistant")
166425
+ return "A";
166426
+ return null;
166427
+ }
166428
+ function parseOrdinal(value) {
166429
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
166430
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
166431
+ }
166432
+ function parseCanonicalLineRange(line) {
166433
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
166434
+ if (!match)
166435
+ return null;
166436
+ const start = Number.parseInt(match[1], 10);
166437
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
166438
+ if (!Number.isFinite(start) || !Number.isFinite(end))
166439
+ return null;
166440
+ return { start, end };
166441
+ }
166442
+ function hashChunkText(text) {
166443
+ return createHash8("sha256").update(text).digest("hex");
166444
+ }
166445
+ function vectorBlob(vector) {
166446
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
166447
+ }
166448
+ function toFloat32Array2(blob) {
166449
+ if (blob instanceof Uint8Array) {
166450
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
166451
+ return new Float32Array(buffer2);
166452
+ }
166453
+ return new Float32Array(blob.slice(0));
166454
+ }
166455
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
166456
+ if (endOrdinal < startOrdinal)
166457
+ return "";
166458
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
166459
+ const lines = [];
166460
+ let current = null;
166461
+ const flush2 = () => {
166462
+ if (!current || current.parts.length === 0)
166463
+ return;
166464
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
166465
+ current = null;
166466
+ };
166467
+ for (const row of rows) {
166468
+ const ordinal = parseOrdinal(row.messageOrdinal);
166469
+ const prefix = rolePrefix(row.role);
166470
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
166471
+ if (ordinal === null || prefix === null || content.length === 0)
166472
+ continue;
166473
+ if (current && current.role === prefix) {
166474
+ current.end = ordinal;
166475
+ current.parts.push(content);
166476
+ continue;
166477
+ }
166478
+ flush2();
166479
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
166480
+ }
166481
+ flush2();
166482
+ return lines.join(`
166483
+ `);
166484
+ }
166485
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
166486
+ const lines = [];
166487
+ for (const rawLine of chunkText.split(/\r?\n/)) {
166488
+ const line = rawLine.trim();
166489
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
166490
+ if (!match)
166491
+ continue;
166492
+ const lineStart = Number.parseInt(match[2], 10);
166493
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
166494
+ if (startOrdinal != null && lineEnd < startOrdinal)
166495
+ continue;
166496
+ if (endOrdinal != null && lineStart > endOrdinal)
166497
+ continue;
166498
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
166499
+ const ordinalSpan = lineEnd - lineStart + 1;
166500
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
166501
+ if (ordinalSpan === rawParts.length) {
166502
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
166503
+ if (part.startsWith("TC:"))
166504
+ return false;
166505
+ if (startOrdinal != null && ordinal < startOrdinal)
166506
+ return false;
166507
+ if (endOrdinal != null && ordinal > endOrdinal)
166508
+ return false;
166509
+ return true;
166510
+ });
166511
+ if (retained.length === 0)
166512
+ continue;
166513
+ const retainedStart = retained[0].ordinal;
166514
+ const retainedEnd = retained[retained.length - 1].ordinal;
166515
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
166516
+ continue;
166517
+ }
166518
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
166519
+ if (parts.length === 0)
166520
+ continue;
166521
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
166522
+ }
166523
+ return lines.join(`
166524
+ `);
166525
+ }
166526
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
166527
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
166528
+ if (lines.length === 0 || endOrdinal < startOrdinal)
166529
+ return [];
166530
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
166531
+ const fullText = lines.join(`
166532
+ `);
166533
+ if (estimateTokens(fullText) <= normalizedMax) {
166534
+ return [
166535
+ {
166536
+ windowIndex: 0,
166537
+ startOrdinal,
166538
+ endOrdinal,
166539
+ text: fullText,
166540
+ chunkHash: hashChunkText(fullText)
166541
+ }
166542
+ ];
166543
+ }
166544
+ const windows = [];
166545
+ let currentLines = [];
166546
+ let currentStart = null;
166547
+ let currentEnd = null;
166548
+ let currentTokens = 0;
166549
+ const flush2 = () => {
166550
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
166551
+ return;
166552
+ const text = currentLines.join(`
166553
+ `);
166554
+ windows.push({
166555
+ windowIndex: windows.length + 1,
166556
+ startOrdinal: currentStart,
166557
+ endOrdinal: currentEnd,
166558
+ text,
166559
+ chunkHash: hashChunkText(text)
166560
+ });
166561
+ currentLines = [];
166562
+ currentStart = null;
166563
+ currentEnd = null;
166564
+ currentTokens = 0;
166565
+ };
166566
+ for (const line of lines) {
166567
+ const range = parseCanonicalLineRange(line);
166568
+ const lineStart = range?.start ?? startOrdinal;
166569
+ const lineEnd = range?.end ?? lineStart;
166570
+ const lineTokens = estimateTokens(line);
166571
+ if (currentLines.length > 0 && currentTokens + lineTokens > normalizedMax) {
166572
+ flush2();
166573
+ }
166574
+ if (currentLines.length === 0) {
166575
+ currentStart = lineStart;
166576
+ }
166577
+ currentLines.push(line);
166578
+ currentEnd = lineEnd;
166579
+ currentTokens += lineTokens;
166580
+ }
166581
+ flush2();
166582
+ return windows;
166583
+ }
166584
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
166585
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
166586
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
166587
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
166588
+ }
166589
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
166590
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
166591
+ if (existing.size !== windows.length)
166592
+ return false;
166593
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
166594
+ }
166595
+ function replaceCompartmentChunkEmbeddings(db, rows) {
166596
+ if (rows.length === 0)
166597
+ return;
166598
+ const compartmentId = rows[0].compartmentId;
166599
+ const now = Date.now();
166600
+ db.transaction(() => {
166601
+ getDeleteByCompartmentStatement(db).run(compartmentId);
166602
+ const insert = getInsertEmbeddingStatement(db);
166603
+ for (const row of rows) {
166604
+ 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);
166605
+ }
166606
+ })();
166607
+ }
166608
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
166609
+ const rows = getDistinctModelStatement(db).all(projectPath);
166610
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
166611
+ }
166612
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
166613
+ if (modelId) {
166614
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
166615
+ }
166616
+ return getClearProjectStatement(db).run(projectPath).changes;
166617
+ }
166618
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
166619
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
166620
+ 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) => ({
166621
+ compartmentId: row.compartmentId,
166622
+ sessionId: row.sessionId,
166623
+ title: row.title,
166624
+ startOrdinal: row.compartmentStart,
166625
+ endOrdinal: row.compartmentEnd,
166626
+ windowIndex: row.windowIndex,
166627
+ windowStartOrdinal: row.windowStart,
166628
+ windowEndOrdinal: row.windowEnd,
166629
+ chunkHash: row.chunkHash,
166630
+ modelId: row.modelId,
166631
+ dims: row.dims,
166632
+ vector: toFloat32Array2(row.vector)
166633
+ }));
166634
+ }
166635
+ function mapBackfillCandidateRows(rows) {
166636
+ return rows.filter((row) => {
166637
+ if (row === null || typeof row !== "object")
166638
+ return false;
166639
+ const candidate = row;
166640
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
166641
+ }).map((row) => ({
166642
+ id: row.id,
166643
+ sessionId: row.sessionId,
166644
+ startMessage: row.startMessage,
166645
+ endMessage: row.endMessage,
166646
+ title: row.title
166647
+ }));
166648
+ }
166649
+ var sessionBackfillCandidateStatements = new WeakMap;
166650
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
166651
+ if (excludeIds && excludeIds.length > 0) {
166652
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
166653
+ const stmt2 = db.prepare(`SELECT c.id AS id,
166654
+ c.session_id AS sessionId,
166655
+ c.start_message AS startMessage,
166656
+ c.end_message AS endMessage,
166657
+ c.title AS title
166658
+ FROM compartments c
166659
+ JOIN session_projects sp
166660
+ ON sp.session_id = c.session_id
166661
+ AND sp.harness = c.harness
166662
+ AND sp.project_path = ?
166663
+ WHERE c.session_id = ?
166664
+ AND c.start_message IS NOT NULL
166665
+ AND c.end_message IS NOT NULL
166666
+ AND c.id NOT IN (${placeholders3})
166667
+ AND NOT EXISTS (
166668
+ SELECT 1
166669
+ FROM compartment_chunk_embeddings current
166670
+ WHERE current.compartment_id = c.id
166671
+ AND current.project_path = ?
166672
+ AND current.model_id = ?
166673
+ )
166674
+ ORDER BY c.start_message ASC, c.id ASC
166675
+ LIMIT ?`);
166676
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
166677
+ return mapBackfillCandidateRows(rows2);
166678
+ }
166679
+ let stmt = sessionBackfillCandidateStatements.get(db);
166680
+ if (!stmt) {
166681
+ stmt = db.prepare(`SELECT c.id AS id,
166682
+ c.session_id AS sessionId,
166683
+ c.start_message AS startMessage,
166684
+ c.end_message AS endMessage,
166685
+ c.title AS title
166686
+ FROM compartments c
166687
+ JOIN session_projects sp
166688
+ ON sp.session_id = c.session_id
166689
+ AND sp.harness = c.harness
166690
+ AND sp.project_path = ?
166691
+ WHERE c.session_id = ?
166692
+ AND c.start_message IS NOT NULL
166693
+ AND c.end_message IS NOT NULL
166694
+ AND NOT EXISTS (
166695
+ SELECT 1
166696
+ FROM compartment_chunk_embeddings current
166697
+ WHERE current.compartment_id = c.id
166698
+ AND current.project_path = ?
166699
+ AND current.model_id = ?
166700
+ )
166701
+ ORDER BY c.start_message ASC, c.id ASC
166702
+ LIMIT ?`);
166703
+ sessionBackfillCandidateStatements.set(db, stmt);
166704
+ }
166705
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
166706
+ return mapBackfillCandidateRows(rows);
166707
+ }
166708
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
166709
+ const row = db.prepare(`SELECT COUNT(*) AS n
166710
+ FROM compartments c
166711
+ JOIN session_projects sp
166712
+ ON sp.session_id = c.session_id
166713
+ AND sp.harness = c.harness
166714
+ AND sp.project_path = ?
166715
+ WHERE c.session_id = ?
166716
+ AND c.start_message IS NOT NULL
166717
+ AND c.end_message IS NOT NULL
166718
+ AND NOT EXISTS (
166719
+ SELECT 1
166720
+ FROM compartment_chunk_embeddings current
166721
+ WHERE current.compartment_id = c.id
166722
+ AND current.project_path = ?
166723
+ AND current.model_id = ?
166724
+ )`).get(projectPath, sessionId, projectPath, modelId);
166725
+ return typeof row?.n === "number" ? row.n : 0;
166726
+ }
166727
+
165653
166728
  // ../plugin/src/features/magic-context/memory/cosine-similarity.ts
165654
166729
  function cosineSimilarity(a, b) {
165655
166730
  if (a.length !== b.length) {
@@ -165808,19 +166883,19 @@ function isArrayLikeNumber(value) {
165808
166883
  }
165809
166884
  return arr.length === 0 || typeof arr[0] === "number";
165810
166885
  }
165811
- function toFloat32Array2(values) {
166886
+ function toFloat32Array3(values) {
165812
166887
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
165813
166888
  }
165814
166889
  function extractBatchEmbeddings(result, expectedCount) {
165815
166890
  const { data } = result;
165816
166891
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
165817
- return data.map((entry) => toFloat32Array2(entry));
166892
+ return data.map((entry) => toFloat32Array3(entry));
165818
166893
  }
165819
166894
  if (!isArrayLikeNumber(data)) {
165820
166895
  log("[magic-context] embedding batch returned unexpected data shape");
165821
166896
  return Array.from({ length: expectedCount }, () => null);
165822
166897
  }
165823
- const flatData = toFloat32Array2(data);
166898
+ const flatData = toFloat32Array3(data);
165824
166899
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
165825
166900
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
165826
166901
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -165835,6 +166910,7 @@ function extractBatchEmbeddings(result, expectedCount) {
165835
166910
 
165836
166911
  class LocalEmbeddingProvider {
165837
166912
  modelId;
166913
+ maxInputTokens;
165838
166914
  model;
165839
166915
  pipeline = null;
165840
166916
  initPromise = null;
@@ -165842,8 +166918,9 @@ class LocalEmbeddingProvider {
165842
166918
  disposing = false;
165843
166919
  disposePromise = null;
165844
166920
  inFlightWaiters = [];
165845
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
166921
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
165846
166922
  this.model = model;
166923
+ this.maxInputTokens = maxInputTokens;
165847
166924
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
165848
166925
  }
165849
166926
  async initialize() {
@@ -166098,6 +167175,7 @@ var FETCH_TIMEOUT_MS = 30000;
166098
167175
 
166099
167176
  class OpenAICompatibleEmbeddingProvider {
166100
167177
  modelId;
167178
+ maxInputTokens;
166101
167179
  endpoint;
166102
167180
  model;
166103
167181
  apiKey;
@@ -166114,11 +167192,13 @@ class OpenAICompatibleEmbeddingProvider {
166114
167192
  this.apiKey = options.apiKey?.trim() ?? "";
166115
167193
  this.inputType = options.inputType?.trim() ?? "";
166116
167194
  this.truncate = options.truncate?.trim() ?? "";
167195
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
166117
167196
  this.modelId = getEmbeddingProviderIdentity({
166118
167197
  provider: "openai-compatible",
166119
167198
  endpoint: this.endpoint,
166120
167199
  model: this.model,
166121
- ...this.apiKey ? { api_key: this.apiKey } : {}
167200
+ ...this.apiKey ? { api_key: this.apiKey } : {},
167201
+ ...this.inputType ? { input_type: this.inputType } : {}
166122
167202
  });
166123
167203
  }
166124
167204
  async initialize() {
@@ -166309,7 +167389,7 @@ class OpenAICompatibleEmbeddingProvider {
166309
167389
  }
166310
167390
 
166311
167391
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
166312
- import { createHash as createHash7, randomUUID } from "node:crypto";
167392
+ import { createHash as createHash9, randomUUID } from "node:crypto";
166313
167393
  init_logger();
166314
167394
 
166315
167395
  // ../plugin/src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
@@ -166317,7 +167397,7 @@ var saveStatements = new WeakMap;
166317
167397
  var loadProjectStatements = new WeakMap;
166318
167398
  var loadUnembeddedStatements = new WeakMap;
166319
167399
  var countEmbeddedStatements = new WeakMap;
166320
- var clearProjectStatements = new WeakMap;
167400
+ var clearProjectStatements2 = new WeakMap;
166321
167401
  var distinctModelIdStatements = new WeakMap;
166322
167402
  function getSaveStatement(db) {
166323
167403
  let stmt = saveStatements.get(db);
@@ -166365,12 +167445,12 @@ function getCountEmbeddedStatement(db) {
166365
167445
  }
166366
167446
  return stmt;
166367
167447
  }
166368
- function getClearProjectStatement(db) {
166369
- let stmt = clearProjectStatements.get(db);
167448
+ function getClearProjectStatement2(db) {
167449
+ let stmt = clearProjectStatements2.get(db);
166370
167450
  if (!stmt) {
166371
167451
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
166372
167452
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
166373
- clearProjectStatements.set(db, stmt);
167453
+ clearProjectStatements2.set(db, stmt);
166374
167454
  }
166375
167455
  return stmt;
166376
167456
  }
@@ -166406,7 +167486,7 @@ function countEmbeddedCommits(db, projectPath) {
166406
167486
  return row?.count ?? 0;
166407
167487
  }
166408
167488
  function clearProjectCommitEmbeddings(db, projectPath) {
166409
- return getClearProjectStatement(db).run(projectPath).changes;
167489
+ return getClearProjectStatement2(db).run(projectPath).changes;
166410
167490
  }
166411
167491
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
166412
167492
  const rows = getDistinctModelIdStatement(db).all(projectPath);
@@ -166532,9 +167612,83 @@ function releaseGitSweepLease(db, projectPath, holderId) {
166532
167612
  });
166533
167613
  }
166534
167614
 
167615
+ // ../plugin/src/features/magic-context/session-project-storage.ts
167616
+ var upsertSessionProjectStatements = new WeakMap;
167617
+ var repairSessionChunkProjectStatements = new WeakMap;
167618
+ var repairProjectChunkProjectStatements = new WeakMap;
167619
+ function getUpsertSessionProjectStatement(db) {
167620
+ let stmt = upsertSessionProjectStatements.get(db);
167621
+ if (!stmt) {
167622
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
167623
+ VALUES (?, ?, ?, ?)
167624
+ ON CONFLICT(session_id, harness) DO UPDATE SET
167625
+ project_path = excluded.project_path,
167626
+ updated_at = excluded.updated_at
167627
+ WHERE session_projects.project_path <> excluded.project_path`);
167628
+ upsertSessionProjectStatements.set(db, stmt);
167629
+ }
167630
+ return stmt;
167631
+ }
167632
+ function getRepairSessionChunkProjectStatement(db) {
167633
+ let stmt = repairSessionChunkProjectStatements.get(db);
167634
+ if (!stmt) {
167635
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
167636
+ SET project_path = ?
167637
+ WHERE session_id = ?
167638
+ AND harness = ?
167639
+ AND project_path <> ?`);
167640
+ repairSessionChunkProjectStatements.set(db, stmt);
167641
+ }
167642
+ return stmt;
167643
+ }
167644
+ function getRepairProjectChunkProjectStatement(db) {
167645
+ let stmt = repairProjectChunkProjectStatements.get(db);
167646
+ if (!stmt) {
167647
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
167648
+ SET project_path = (
167649
+ SELECT sp.project_path
167650
+ FROM session_projects sp
167651
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
167652
+ AND sp.harness = compartment_chunk_embeddings.harness
167653
+ LIMIT 1
167654
+ )
167655
+ WHERE EXISTS (
167656
+ SELECT 1
167657
+ FROM session_projects sp
167658
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
167659
+ AND sp.harness = compartment_chunk_embeddings.harness
167660
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
167661
+ AND (
167662
+ sp.project_path = ?
167663
+ OR compartment_chunk_embeddings.project_path = ?
167664
+ )
167665
+ )`);
167666
+ repairProjectChunkProjectStatements.set(db, stmt);
167667
+ }
167668
+ return stmt;
167669
+ }
167670
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
167671
+ if (!sessionId || !projectPath)
167672
+ return;
167673
+ const harness = getHarness();
167674
+ const now = Date.now();
167675
+ db.transaction(() => {
167676
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
167677
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
167678
+ })();
167679
+ }
167680
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
167681
+ if (!projectPath)
167682
+ return 0;
167683
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
167684
+ }
167685
+
166535
167686
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
166536
167687
  var OFF_PROVIDER_IDENTITY = "embedding-provider:off";
166537
167688
  var SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167689
+ var CHUNK_DRAIN_BATCH_SIZE = 8;
167690
+ var MAX_WINDOWS_PER_EMBED_CALL = 16;
167691
+ var SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
166538
167692
  var projectRegistrations = new Map;
166539
167693
  var loadUnembeddedMemoriesStatements = new WeakMap;
166540
167694
  var globalRegistrationGeneration = 0;
@@ -166543,7 +167697,10 @@ function resolveEmbeddingConfig(config2) {
166543
167697
  if (!config2 || config2.provider === "local") {
166544
167698
  return {
166545
167699
  provider: "local",
166546
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
167700
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
167701
+ ...config2?.max_input_tokens ? {
167702
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
167703
+ } : {}
166547
167704
  };
166548
167705
  }
166549
167706
  if (config2.provider === "openai-compatible") {
@@ -166556,7 +167713,10 @@ function resolveEmbeddingConfig(config2) {
166556
167713
  endpoint: config2.endpoint.trim(),
166557
167714
  ...apiKey ? { api_key: apiKey } : {},
166558
167715
  ...inputType ? { input_type: inputType } : {},
166559
- ...truncate ? { truncate } : {}
167716
+ ...truncate ? { truncate } : {},
167717
+ ...config2.max_input_tokens ? {
167718
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
167719
+ } : {}
166560
167720
  };
166561
167721
  }
166562
167722
  return { provider: "off" };
@@ -166574,10 +167734,11 @@ function createProvider(config2) {
166574
167734
  model: config2.model,
166575
167735
  apiKey: config2.api_key,
166576
167736
  inputType: config2.input_type,
166577
- truncate: config2.truncate
167737
+ truncate: config2.truncate,
167738
+ maxInputTokens: config2.max_input_tokens
166578
167739
  });
166579
167740
  }
166580
- return new LocalEmbeddingProvider(config2.model);
167741
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
166581
167742
  }
166582
167743
  function stableStringify2(value) {
166583
167744
  if (Array.isArray(value)) {
@@ -166590,7 +167751,7 @@ function stableStringify2(value) {
166590
167751
  return JSON.stringify(value);
166591
167752
  }
166592
167753
  function sha256Prefix(value, length = 16) {
166593
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
167754
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
166594
167755
  }
166595
167756
  function getRuntimeFingerprint(config2) {
166596
167757
  if (config2.provider === "off") {
@@ -166598,6 +167759,18 @@ function getRuntimeFingerprint(config2) {
166598
167759
  }
166599
167760
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
166600
167761
  }
167762
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
167763
+ if (config2.provider === "off") {
167764
+ return OFF_PROVIDER_IDENTITY;
167765
+ }
167766
+ const chunkIdentity = {
167767
+ providerIdentity,
167768
+ chunkerVersion: 1,
167769
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
167770
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
167771
+ };
167772
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
167773
+ }
166601
167774
  function sameFeatures(a, b) {
166602
167775
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
166603
167776
  }
@@ -166614,7 +167787,8 @@ function snapshotFor(registration) {
166614
167787
  features: { ...registration.features },
166615
167788
  enabled,
166616
167789
  gitCommitEnabled,
166617
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
167790
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
167791
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
166618
167792
  };
166619
167793
  }
166620
167794
  function disposeProvider(provider) {
@@ -166634,7 +167808,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
166634
167808
  }
166635
167809
  return false;
166636
167810
  }
166637
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
167811
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
166638
167812
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
166639
167813
  return false;
166640
167814
  }
@@ -166655,6 +167829,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
166655
167829
  wiped = true;
166656
167830
  }
166657
167831
  }
167832
+ if (features.memoryEnabled) {
167833
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
167834
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
167835
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
167836
+ clearChunkEmbeddingsForProject(db, projectIdentity);
167837
+ wiped = true;
167838
+ }
167839
+ }
166658
167840
  })();
166659
167841
  return wiped;
166660
167842
  }
@@ -166662,10 +167844,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
166662
167844
  const resolvedConfig = resolveEmbeddingConfig(config2);
166663
167845
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
166664
167846
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
167847
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
166665
167848
  const prior = projectRegistrations.get(projectIdentity);
166666
167849
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
166667
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
166668
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
167850
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
167851
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
166669
167852
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
166670
167853
  const registration = {
166671
167854
  projectIdentity,
@@ -166677,6 +167860,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
166677
167860
  generation,
166678
167861
  features: { ...features },
166679
167862
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
167863
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
166680
167864
  observationMode: false
166681
167865
  };
166682
167866
  projectRegistrations.set(projectIdentity, registration);
@@ -166699,6 +167883,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
166699
167883
  generation,
166700
167884
  features: { memoryEnabled: false, gitCommitEnabled: false },
166701
167885
  modelId: "off",
167886
+ chunkModelId: "off",
166702
167887
  observationMode: true
166703
167888
  };
166704
167889
  projectRegistrations.set(projectIdentity, registration);
@@ -166709,6 +167894,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
166709
167894
  const registration = projectRegistrations.get(projectIdentity);
166710
167895
  return registration ? snapshotFor(registration) : null;
166711
167896
  }
167897
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
167898
+ const registration = projectRegistrations.get(projectIdentity);
167899
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
167900
+ }
167901
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
167902
+ const registration = projectRegistrations.get(projectIdentity);
167903
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
167904
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
167905
+ }
166712
167906
  function getOrCreateProjectProvider(registration) {
166713
167907
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
166714
167908
  return null;
@@ -166803,6 +167997,131 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
166803
167997
  return 0;
166804
167998
  }
166805
167999
  }
168000
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
168001
+ const noWork = [];
168002
+ if (candidates.length === 0)
168003
+ return { embedded: 0, noWork };
168004
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
168005
+ const prepared = [];
168006
+ for (const candidate of candidates) {
168007
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
168008
+ if (canonicalText.length === 0) {
168009
+ noWork.push(candidate.id);
168010
+ continue;
168011
+ }
168012
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
168013
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
168014
+ noWork.push(candidate.id);
168015
+ continue;
168016
+ }
168017
+ prepared.push({ candidate, windows });
168018
+ }
168019
+ if (prepared.length === 0)
168020
+ return { embedded: 0, noWork };
168021
+ let embedded = 0;
168022
+ let i = 0;
168023
+ while (i < prepared.length) {
168024
+ if (signal?.aborted)
168025
+ break;
168026
+ const slice = [];
168027
+ let windowCount = 0;
168028
+ do {
168029
+ const item = prepared[i];
168030
+ slice.push(item);
168031
+ windowCount += item.windows.length;
168032
+ i += 1;
168033
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
168034
+ const texts = [];
168035
+ for (const item of slice)
168036
+ texts.push(...item.windows.map((w) => w.text));
168037
+ try {
168038
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
168039
+ if (!result)
168040
+ continue;
168041
+ if (signal?.aborted)
168042
+ break;
168043
+ let offset = 0;
168044
+ for (const item of slice) {
168045
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
168046
+ offset += item.windows.length;
168047
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
168048
+ continue;
168049
+ }
168050
+ const rows = item.windows.map((window, index) => ({
168051
+ compartmentId: item.candidate.id,
168052
+ sessionId: item.candidate.sessionId,
168053
+ projectPath: projectIdentity,
168054
+ window,
168055
+ modelId,
168056
+ vector: vectors[index]
168057
+ }));
168058
+ replaceCompartmentChunkEmbeddings(db, rows);
168059
+ embedded += 1;
168060
+ }
168061
+ } catch (error51) {
168062
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
168063
+ }
168064
+ }
168065
+ return { embedded, noWork };
168066
+ }
168067
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
168068
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
168069
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
168070
+ return { status: "disabled", embedded: 0, total: 0 };
168071
+ }
168072
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
168073
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
168074
+ if (total === 0)
168075
+ return { status: "nothing", embedded: 0, total: 0 };
168076
+ const holderId = `session-embed-${randomUUID()}`;
168077
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
168078
+ if (!lease2.acquired)
168079
+ return { status: "busy", embedded: 0, total };
168080
+ const renewal = setInterval(() => {
168081
+ try {
168082
+ renewGitSweepLease(db, projectIdentity, holderId);
168083
+ } catch {}
168084
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
168085
+ renewal.unref?.();
168086
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
168087
+ const skipIds = [];
168088
+ let embedded = 0;
168089
+ let aborted2 = false;
168090
+ let providerStalled = false;
168091
+ try {
168092
+ options?.onProgress?.({ embedded, total });
168093
+ for (;; ) {
168094
+ if (options?.signal?.aborted) {
168095
+ aborted2 = true;
168096
+ break;
168097
+ }
168098
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
168099
+ if (candidates.length === 0)
168100
+ break;
168101
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
168102
+ for (const id of noWork)
168103
+ skipIds.push(id);
168104
+ if (n === 0 && noWork.length === 0) {
168105
+ providerStalled = true;
168106
+ break;
168107
+ }
168108
+ embedded += n;
168109
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
168110
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
168111
+ }
168112
+ } finally {
168113
+ clearInterval(renewal);
168114
+ releaseGitSweepLease(db, projectIdentity, holderId);
168115
+ }
168116
+ if (aborted2)
168117
+ return { status: "aborted", embedded, total };
168118
+ if (providerStalled) {
168119
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
168120
+ if (remaining > 0)
168121
+ return { status: "stalled", embedded, total, remaining };
168122
+ }
168123
+ return { status: "done", embedded, total };
168124
+ }
166806
168125
 
166807
168126
  // ../plugin/src/features/magic-context/memory/embedding.ts
166808
168127
  var DEFAULT_EMBEDDING_CONFIG = {
@@ -166822,10 +168141,11 @@ function createProvider2(config2) {
166822
168141
  model: config2.model,
166823
168142
  apiKey: config2.api_key,
166824
168143
  inputType: config2.input_type,
166825
- truncate: config2.truncate
168144
+ truncate: config2.truncate,
168145
+ maxInputTokens: config2.max_input_tokens
166826
168146
  });
166827
168147
  }
166828
- return new LocalEmbeddingProvider(config2.model);
168148
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
166829
168149
  }
166830
168150
  function getOrCreateProvider() {
166831
168151
  if (provider) {
@@ -167068,6 +168388,7 @@ init_logger();
167068
168388
  // ../plugin/src/features/magic-context/memory/storage-memory-fts.ts
167069
168389
  var DEFAULT_SEARCH_LIMIT = 10;
167070
168390
  var searchStatements = new WeakMap;
168391
+ var unionSearchStatements = new Map;
167071
168392
  function getSearchStatement(db) {
167072
168393
  let stmt = searchStatements.get(db);
167073
168394
  if (!stmt) {
@@ -167076,6 +168397,23 @@ function getSearchStatement(db) {
167076
168397
  }
167077
168398
  return stmt;
167078
168399
  }
168400
+ function getUnionSearchStatement(db, arity) {
168401
+ let statements = unionSearchStatements.get(arity);
168402
+ if (!statements) {
168403
+ statements = new WeakMap;
168404
+ unionSearchStatements.set(arity, statements);
168405
+ }
168406
+ let stmt = statements.get(db);
168407
+ if (!stmt) {
168408
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
168409
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${placeholders3}) 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 ?`);
168410
+ statements.set(db, stmt);
168411
+ }
168412
+ return stmt;
168413
+ }
168414
+ function uniqueProjectPaths2(projectPaths) {
168415
+ return [...new Set(projectPaths.filter((path7) => path7.length > 0))];
168416
+ }
167079
168417
  function sanitizeFtsQuery(query) {
167080
168418
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
167081
168419
  if (tokens.length === 0)
@@ -167094,6 +168432,28 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
167094
168432
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
167095
168433
  return rows.map(toMemory);
167096
168434
  }
168435
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
168436
+ const identities = uniqueProjectPaths2(projectPaths);
168437
+ if (identities.length === 0)
168438
+ return [];
168439
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
168440
+ identities,
168441
+ ownIdentities,
168442
+ shareCategories,
168443
+ tableName: "memories"
168444
+ });
168445
+ if (identities.length === 1 && !sharingFilter.active) {
168446
+ return searchMemoriesFTS(db, identities[0], query, limit);
168447
+ }
168448
+ const trimmedQuery = query.trim();
168449
+ if (trimmedQuery.length === 0 || limit <= 0)
168450
+ return [];
168451
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
168452
+ if (sanitized.length === 0)
168453
+ return [];
168454
+ 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);
168455
+ return rows.map(toMemory);
168456
+ }
167097
168457
 
167098
168458
  // ../plugin/src/features/magic-context/git-commits/search-git-commits.ts
167099
168459
  var ftsStatements = new WeakMap;
@@ -167386,7 +168746,7 @@ async function sweepGitCommits(args) {
167386
168746
  }
167387
168747
 
167388
168748
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
167389
- import { createHash as createHash8 } from "node:crypto";
168749
+ import { createHash as createHash10 } from "node:crypto";
167390
168750
  init_logger();
167391
168751
  var EMBEDDING_AFFECTING_KEYS = new Set([
167392
168752
  "embedding.api_key",
@@ -167408,7 +168768,7 @@ var EMBEDDING_WARNING_TERMS = [
167408
168768
  ];
167409
168769
  var loggedFailureSignatures = new Map;
167410
168770
  function sha256Prefix2(value, length = 16) {
167411
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
168771
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
167412
168772
  }
167413
168773
  function warningLooksEmbeddingRelated(message) {
167414
168774
  const lower = message.toLowerCase();
@@ -168450,6 +169810,88 @@ function registerCtxDreamCommand(pi, deps) {
168450
169810
  });
168451
169811
  }
168452
169812
 
169813
+ // src/commands/ctx-embed-history.ts
169814
+ function registerCtxEmbedHistoryCommand(pi, deps) {
169815
+ pi.registerCommand("ctx-embed-history", {
169816
+ description: "Embed all of this session's history compartments for semantic search, in one pass",
169817
+ handler: async (_args, ctx) => {
169818
+ const sessionId = resolveSessionId(ctx);
169819
+ if (!sessionId) {
169820
+ sendCtxStatusMessage(pi, {
169821
+ title: "/ctx-embed-history",
169822
+ text: `## /ctx-embed-history
169823
+
169824
+ No active Pi session is available.`,
169825
+ level: "error"
169826
+ });
169827
+ return;
169828
+ }
169829
+ if (deps.memoryEnabled === false) {
169830
+ sendCtxStatusMessage(pi, {
169831
+ title: "/ctx-embed-history",
169832
+ text: `## /ctx-embed-history
169833
+
169834
+ Memory is disabled for this project, so there is no semantic embedding to backfill.`,
169835
+ level: "info"
169836
+ });
169837
+ return;
169838
+ }
169839
+ const project = deps.resolveProject?.(ctx) ?? {
169840
+ projectDir: deps.projectDir,
169841
+ projectIdentity: deps.projectIdentity
169842
+ };
169843
+ await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
169844
+ const outcome = await embedSessionCompartmentChunks(deps.db, project.projectIdentity, sessionId);
169845
+ const { text, level } = (() => {
169846
+ switch (outcome.status) {
169847
+ case "nothing":
169848
+ return {
169849
+ text: `## /ctx-embed-history
169850
+
169851
+ All of this session's history is already embedded.`,
169852
+ level: "info"
169853
+ };
169854
+ case "disabled":
169855
+ return {
169856
+ text: `## /ctx-embed-history
169857
+
169858
+ No embedding provider is configured, so there is nothing to embed.`,
169859
+ level: "info"
169860
+ };
169861
+ case "busy":
169862
+ return {
169863
+ text: `## /ctx-embed-history
169864
+
169865
+ Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`,
169866
+ level: "info"
169867
+ };
169868
+ case "stalled":
169869
+ return {
169870
+ text: `## /ctx-embed-history
169871
+
169872
+ Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"}; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed-history again to retry them.`,
169873
+ level: "info"
169874
+ };
169875
+ default:
169876
+ return {
169877
+ text: `## /ctx-embed-history
169878
+
169879
+ Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`,
169880
+ level: "success"
169881
+ };
169882
+ }
169883
+ })();
169884
+ sendCtxStatusMessage(pi, { title: "/ctx-embed-history", text, level }, {
169885
+ sessionId,
169886
+ projectIdentity: project.projectIdentity,
169887
+ status: outcome.status,
169888
+ embedded: outcome.embedded,
169889
+ total: outcome.total
169890
+ });
169891
+ }
169892
+ });
169893
+ }
169894
+
168453
169895
  // ../plugin/src/hooks/magic-context/execute-flush.ts
168454
169896
  init_logger();
168455
169897
  function executeFlush(db, sessionId) {
@@ -170585,53 +172027,6 @@ ${body}
170585
172027
  </system-reminder>`;
170586
172028
  }
170587
172029
 
170588
- // ../plugin/src/features/magic-context/memory/constants.ts
170589
- var V2_MEMORY_CATEGORIES = [
170590
- "PROJECT_RULES",
170591
- "ARCHITECTURE",
170592
- "CONSTRAINTS",
170593
- "CONFIG_VALUES",
170594
- "NAMING"
170595
- ];
170596
- var PROMOTABLE_CATEGORIES = [
170597
- "PROJECT_RULES",
170598
- "ARCHITECTURE",
170599
- "CONSTRAINTS",
170600
- "CONFIG_VALUES",
170601
- "NAMING",
170602
- "ARCHITECTURE_DECISIONS",
170603
- "CONFIG_DEFAULTS",
170604
- "USER_PREFERENCES",
170605
- "USER_DIRECTIVES",
170606
- "ENVIRONMENT",
170607
- "WORKFLOW_RULES",
170608
- "KNOWN_ISSUES"
170609
- ];
170610
- var CATEGORY_PRIORITY = [
170611
- "PROJECT_RULES",
170612
- "ARCHITECTURE",
170613
- "CONSTRAINTS",
170614
- "CONFIG_VALUES",
170615
- "NAMING",
170616
- "USER_DIRECTIVES",
170617
- "USER_PREFERENCES",
170618
- "CONFIG_DEFAULTS",
170619
- "ARCHITECTURE_DECISIONS",
170620
- "ENVIRONMENT",
170621
- "WORKFLOW_RULES",
170622
- "KNOWN_ISSUES"
170623
- ];
170624
- var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
170625
- var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
170626
- acc[category] = index;
170627
- return acc;
170628
- }, {});
170629
- var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
170630
- var CATEGORY_DEFAULT_TTL = {
170631
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
170632
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
170633
- };
170634
-
170635
172030
  // ../plugin/src/shared/bounded-session-map.ts
170636
172031
  class BoundedSessionMap {
170637
172032
  maxEntries;
@@ -171042,26 +172437,40 @@ ${sections.join(`
171042
172437
  var DEFAULT_MEMORY_BUDGET_TOKENS = 8000;
171043
172438
  var MEMORY_BLOCK_WRAPPER_TOKENS = 6;
171044
172439
  var DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000;
172440
+ function memoryCanonicalIdentity(memory, workspace) {
172441
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
172442
+ }
172443
+ function memorySelectionOrder(left, right) {
172444
+ if (left.status === "permanent" && right.status !== "permanent")
172445
+ return -1;
172446
+ if (right.status === "permanent" && left.status !== "permanent")
172447
+ return 1;
172448
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
172449
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
172450
+ const importanceDiff = rightImportance - leftImportance;
172451
+ if (importanceDiff !== 0)
172452
+ return importanceDiff;
172453
+ return left.id - right.id;
172454
+ }
172455
+ function memoryRenderOrder(left, right) {
172456
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
172457
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
172458
+ const categoryDiff = aPriority - bPriority;
172459
+ if (categoryDiff !== 0)
172460
+ return categoryDiff;
172461
+ return left.id - right.id;
172462
+ }
171045
172463
  var maxCompartmentSeqStatements = new WeakMap;
171046
172464
  var maxMemoryIdStatements = new WeakMap;
171047
172465
  var legacyCompartmentCountStatements = new WeakMap;
171048
172466
  var m0CompartmentStatements = new WeakMap;
171049
172467
  var newCompartmentStatements = new WeakMap;
171050
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
171051
- const selectionOrder = [...memories].sort((a, b) => {
171052
- if (a.status === "permanent" && b.status !== "permanent")
171053
- return -1;
171054
- if (b.status === "permanent" && a.status !== "permanent")
171055
- return 1;
171056
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
171057
- if (importanceDiff !== 0)
171058
- return importanceDiff;
171059
- return a.id - b.id;
171060
- });
172468
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
172469
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
171061
172470
  const selected = [];
171062
172471
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
171063
172472
  for (const memory of selectionOrder) {
171064
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
172473
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
171065
172474
  if (usedTokens + memoryTokens > budgetTokens)
171066
172475
  continue;
171067
172476
  selected.push(memory);
@@ -171070,16 +172479,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
171070
172479
  if (selected.length < memories.length) {
171071
172480
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
171072
172481
  }
171073
- const renderOrder = [...selected].sort((a, b) => {
171074
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
171075
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
171076
- const categoryDiff = aPriority - bPriority;
171077
- if (categoryDiff !== 0)
171078
- return categoryDiff;
171079
- return a.id - b.id;
171080
- });
172482
+ const renderOrder = [...selected].sort(memoryRenderOrder);
171081
172483
  return { selected, renderOrder };
171082
172484
  }
172485
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
172486
+ if (!workspace.isWorkspaced) {
172487
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
172488
+ }
172489
+ const selected = [];
172490
+ const selectedIds = new Set;
172491
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
172492
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
172493
+ const trySelect = (memory) => {
172494
+ if (selectedIds.has(memory.id))
172495
+ return false;
172496
+ const tokens = tokenCost(memory);
172497
+ if (usedTokens + tokens > budgetTokens)
172498
+ return false;
172499
+ selected.push(memory);
172500
+ selectedIds.add(memory.id);
172501
+ usedTokens += tokens;
172502
+ return true;
172503
+ };
172504
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
172505
+ trySelect(memory);
172506
+ }
172507
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
172508
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
172509
+ const byIdentity = new Map;
172510
+ for (const memory of memories) {
172511
+ if (memory.status === "permanent")
172512
+ continue;
172513
+ const identity = memoryCanonicalIdentity(memory, workspace);
172514
+ if (!identity)
172515
+ continue;
172516
+ const list = byIdentity.get(identity) ?? [];
172517
+ list.push(memory);
172518
+ byIdentity.set(identity, list);
172519
+ }
172520
+ for (const identity of workspace.identities) {
172521
+ let memberTokens = 0;
172522
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
172523
+ for (const memory of candidates) {
172524
+ if (selectedIds.has(memory.id))
172525
+ continue;
172526
+ const tokens = tokenCost(memory);
172527
+ if (memberTokens + tokens > floorTokens)
172528
+ continue;
172529
+ if (usedTokens + tokens > budgetTokens)
172530
+ continue;
172531
+ selected.push(memory);
172532
+ selectedIds.add(memory.id);
172533
+ usedTokens += tokens;
172534
+ memberTokens += tokens;
172535
+ }
172536
+ }
172537
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
172538
+ for (const memory of remaining) {
172539
+ trySelect(memory);
172540
+ }
172541
+ if (selected.length < memories.length) {
172542
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
172543
+ }
172544
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
172545
+ }
171083
172546
  function trimUserMemoriesToBudget(memories, budgetTokens) {
171084
172547
  const selected = [];
171085
172548
  let usedTokens = 0;
@@ -171092,15 +172555,16 @@ function trimUserMemoriesToBudget(memories, budgetTokens) {
171092
172555
  }
171093
172556
  return selected;
171094
172557
  }
171095
- function renderMemoryLineV2(memory) {
171096
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
172558
+ function renderMemoryLineV2(memory, sourceName) {
172559
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
172560
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
171097
172561
  }
171098
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
172562
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
171099
172563
  if (memories.length === 0)
171100
172564
  return "";
171101
172565
  const lines = [`<${wrapper}>`];
171102
172566
  for (const memory of memories) {
171103
- lines.push(renderMemoryLineV2(memory));
172567
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
171104
172568
  }
171105
172569
  lines.push(`</${wrapper}>`);
171106
172570
  return lines.join(`
@@ -171694,7 +173158,7 @@ async function ensureMemoryEmbeddings(args) {
171694
173158
  continue;
171695
173159
  }
171696
173160
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
171697
- staged.set(memory.id, embedding);
173161
+ staged.set(memory.id, { embedding, modelId: result.modelId });
171698
173162
  }
171699
173163
  })();
171700
173164
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -171785,6 +173249,37 @@ function previewText(text) {
171785
173249
  }
171786
173250
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
171787
173251
  }
173252
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
173253
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
173254
+ const isWorkspaced = resolved.identities.length > 1;
173255
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
173256
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
173257
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
173258
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
173259
+ return {
173260
+ identities: resolved.identities,
173261
+ expandedIdentities,
173262
+ ownIdentities,
173263
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
173264
+ namesByIdentity: resolved.namesByIdentity,
173265
+ canonicalIdentityByStoredPath,
173266
+ isWorkspaced
173267
+ };
173268
+ }
173269
+ function memoryWorkspaceIdentity(memory, workspace) {
173270
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
173271
+ }
173272
+ function sourceNamesForSearchMemories(args) {
173273
+ if (!args.workspace.isWorkspaced)
173274
+ return;
173275
+ const sourceNames = new Map;
173276
+ for (const memory of args.memories) {
173277
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
173278
+ if (source)
173279
+ sourceNames.set(memory.id, source);
173280
+ }
173281
+ return sourceNames.size > 0 ? sourceNames : undefined;
173282
+ }
171788
173283
  function getMessageSearchStatement(db) {
171789
173284
  let stmt = messageSearchStatements.get(db);
171790
173285
  if (!stmt) {
@@ -171793,6 +173288,30 @@ function getMessageSearchStatement(db) {
171793
173288
  }
171794
173289
  return stmt;
171795
173290
  }
173291
+ var ftsRowCountStatements = new WeakMap;
173292
+ var ftsMatchCountStatements = new WeakMap;
173293
+ function getSessionFtsRowCount(db, sessionId) {
173294
+ let stmt = ftsRowCountStatements.get(db);
173295
+ if (!stmt) {
173296
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
173297
+ ftsRowCountStatements.set(db, stmt);
173298
+ }
173299
+ const row = stmt.get(sessionId);
173300
+ return typeof row?.n === "number" ? row.n : 0;
173301
+ }
173302
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
173303
+ let stmt = ftsMatchCountStatements.get(db);
173304
+ if (!stmt) {
173305
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
173306
+ ftsMatchCountStatements.set(db, stmt);
173307
+ }
173308
+ try {
173309
+ const row = stmt.get(sessionId, ftsQuery);
173310
+ return typeof row?.n === "number" ? row.n : 0;
173311
+ } catch {
173312
+ return 0;
173313
+ }
173314
+ }
171796
173315
  function getMessageOrdinal(value) {
171797
173316
  if (typeof value === "number" && Number.isFinite(value)) {
171798
173317
  return value;
@@ -171808,25 +173327,63 @@ async function getSemanticScores(args) {
171808
173327
  if (!args.queryEmbedding || args.memories.length === 0) {
171809
173328
  return semanticScores;
171810
173329
  }
171811
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
171812
- const embeddings = await ensureMemoryEmbeddings({
171813
- db: args.db,
171814
- projectIdentity: args.projectPath,
171815
- memories: args.memories,
171816
- existingEmbeddings: cachedEmbeddings
171817
- });
173330
+ if (!args.workspace?.isWorkspaced) {
173331
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
173332
+ const embeddings = await ensureMemoryEmbeddings({
173333
+ db: args.db,
173334
+ projectIdentity: args.projectPath,
173335
+ memories: args.memories,
173336
+ existingEmbeddings: cachedEmbeddings
173337
+ });
173338
+ for (const memory of args.memories) {
173339
+ const memoryEmbedding = embeddings.get(memory.id);
173340
+ if (!memoryEmbedding) {
173341
+ continue;
173342
+ }
173343
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
173344
+ }
173345
+ return semanticScores;
173346
+ }
173347
+ if (!args.queryModelId || args.queryModelId === "off") {
173348
+ return semanticScores;
173349
+ }
173350
+ const workspace = args.workspace;
173351
+ const memoriesByIdentity = new Map;
171818
173352
  for (const memory of args.memories) {
171819
- const memoryEmbedding = embeddings.get(memory.id);
171820
- if (!memoryEmbedding) {
173353
+ const identity = memoryWorkspaceIdentity(memory, workspace);
173354
+ if (!identity)
171821
173355
  continue;
173356
+ const list = memoriesByIdentity.get(identity) ?? [];
173357
+ list.push(memory);
173358
+ memoriesByIdentity.set(identity, list);
173359
+ }
173360
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
173361
+ if (ownMemories.length > 0) {
173362
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
173363
+ await ensureMemoryEmbeddings({
173364
+ db: args.db,
173365
+ projectIdentity: args.projectPath,
173366
+ memories: ownMemories,
173367
+ existingEmbeddings: ownEmbeddings
173368
+ });
173369
+ }
173370
+ for (const identity of workspace.identities) {
173371
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
173372
+ if (memberMemories.length === 0)
173373
+ continue;
173374
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
173375
+ for (const memory of memberMemories) {
173376
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
173377
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
173378
+ continue;
173379
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
171822
173380
  }
171823
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
171824
173381
  }
171825
173382
  return semanticScores;
171826
173383
  }
171827
173384
  function getFtsMatches(args) {
171828
173385
  try {
171829
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
173386
+ 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);
171830
173387
  } catch (error51) {
171831
173388
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
171832
173389
  return [];
@@ -171840,8 +173397,11 @@ function selectSemanticCandidates(args) {
171840
173397
  return args.memories;
171841
173398
  }
171842
173399
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
171843
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
171844
- if (cachedEmbeddings) {
173400
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
173401
+ for (const projectPath of embeddingProjects) {
173402
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
173403
+ if (!cachedEmbeddings)
173404
+ continue;
171845
173405
  for (const memoryId of cachedEmbeddings.keys()) {
171846
173406
  candidateIds.add(memoryId);
171847
173407
  }
@@ -171883,7 +173443,8 @@ function mergeMemoryResults(args) {
171883
173443
  score,
171884
173444
  memoryId: memory.id,
171885
173445
  category: memory.category,
171886
- matchType
173446
+ matchType,
173447
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
171887
173448
  });
171888
173449
  }
171889
173450
  return results.sort((left, right) => {
@@ -171897,7 +173458,7 @@ async function searchMemories(args) {
171897
173458
  if (!args.memoryEnabled) {
171898
173459
  return [];
171899
173460
  }
171900
- const memories = getMemoriesByProject(args.db, args.projectPath);
173461
+ 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);
171901
173462
  if (memories.length === 0) {
171902
173463
  return [];
171903
173464
  }
@@ -171905,26 +173466,43 @@ async function searchMemories(args) {
171905
173466
  db: args.db,
171906
173467
  projectPath: args.projectPath,
171907
173468
  query: args.query,
171908
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
173469
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
173470
+ workspace: args.workspace
171909
173471
  });
171910
173472
  const ftsScores = getFtsScores(ftsMatches);
171911
173473
  const semanticCandidates = selectSemanticCandidates({
171912
173474
  memories,
171913
173475
  projectPath: args.projectPath,
171914
- ftsMatches
173476
+ ftsMatches,
173477
+ workspace: args.workspace
171915
173478
  });
171916
173479
  const semanticScores = await getSemanticScores({
171917
173480
  db: args.db,
171918
173481
  projectPath: args.projectPath,
171919
173482
  memories: semanticCandidates,
171920
- queryEmbedding: args.queryEmbedding
173483
+ queryEmbedding: args.queryEmbedding,
173484
+ queryModelId: args.queryModelId,
173485
+ workspace: args.workspace
171921
173486
  });
171922
173487
  return mergeMemoryResults({
171923
173488
  memories,
171924
173489
  semanticScores,
171925
173490
  ftsScores,
171926
173491
  limit: args.limit,
171927
- visibleMemoryIds: args.visibleMemoryIds
173492
+ visibleMemoryIds: args.visibleMemoryIds,
173493
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
173494
+ memories,
173495
+ projectPath: args.projectPath,
173496
+ workspace: args.workspace ?? {
173497
+ identities: [args.projectPath],
173498
+ expandedIdentities: [args.projectPath],
173499
+ namesByIdentity: new Map,
173500
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
173501
+ ownIdentities: [args.projectPath],
173502
+ shareCategories: null,
173503
+ isWorkspaced: false
173504
+ }
173505
+ })
171928
173506
  });
171929
173507
  }
171930
173508
  function linearDecayScore(rank, total) {
@@ -171955,7 +173533,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
171955
173533
  return result;
171956
173534
  }
171957
173535
  var RRF_K = 60;
171958
- var VERBATIM_PROBE_BONUS = 0.5;
173536
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
173537
+ var IDF_FALLOFF = 100;
173538
+ function probeDiscriminationWeight(df, corpusSize) {
173539
+ if (corpusSize <= 0 || df <= 0)
173540
+ return 1;
173541
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
173542
+ }
171959
173543
  function searchMessages(args) {
171960
173544
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
171961
173545
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -171972,20 +173556,31 @@ function searchMessages(args) {
171972
173556
  role: row.role
171973
173557
  }));
171974
173558
  }
173559
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
171975
173560
  const queryLists = [];
171976
173561
  if (baseQuery.length > 0) {
171977
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
173562
+ queryLists.push({
173563
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
173564
+ weight: 1
173565
+ });
171978
173566
  }
173567
+ const probeWeights = new Map;
171979
173568
  for (const probe of probes) {
171980
173569
  const probeQuery = sanitizeFtsQuery(probe);
171981
173570
  if (probeQuery.length === 0)
171982
173571
  continue;
171983
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
173572
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
173573
+ const weight = probeDiscriminationWeight(df, corpusSize);
173574
+ probeWeights.set(probe, weight);
173575
+ queryLists.push({
173576
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
173577
+ weight
173578
+ });
171984
173579
  }
171985
173580
  const fused = new Map;
171986
173581
  for (const list of queryLists) {
171987
- list.forEach((row, rank) => {
171988
- const rrf = 1 / (RRF_K + rank);
173582
+ list.rows.forEach((row, rank) => {
173583
+ const rrf = list.weight / (RRF_K + rank);
171989
173584
  const existing = fused.get(row.messageId);
171990
173585
  if (existing) {
171991
173586
  existing.score += rrf;
@@ -171995,26 +173590,107 @@ function searchMessages(args) {
171995
173590
  });
171996
173591
  }
171997
173592
  for (const entry of fused.values()) {
171998
- if (containsProbeVerbatim(entry.row.content, probes)) {
171999
- entry.score += VERBATIM_PROBE_BONUS;
173593
+ let best = 0;
173594
+ for (const probe of probes) {
173595
+ const weight = probeWeights.get(probe) ?? 0;
173596
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
173597
+ best = weight;
173598
+ }
173599
+ }
173600
+ if (best > 0) {
173601
+ entry.score += best * VERBATIM_RANK_BONUS;
172000
173602
  }
172001
173603
  }
172002
173604
  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);
172003
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
172004
- return ranked.map((entry) => ({
173605
+ return ranked.map((entry, rank) => ({
172005
173606
  source: "message",
172006
173607
  content: previewText(entry.row.content),
172007
- score: maxScore > 0 ? entry.score / maxScore : 0,
173608
+ score: linearDecayScore(rank, ranked.length),
172008
173609
  messageOrdinal: entry.row.messageOrdinal,
172009
173610
  messageId: entry.row.messageId,
172010
173611
  role: entry.row.role
172011
173612
  }));
172012
173613
  }
173614
+ function searchCompartmentChunks(args) {
173615
+ if (!args.queryEmbedding || args.limit <= 0)
173616
+ return [];
173617
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
173618
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
173619
+ if (rows.length === 0)
173620
+ return [];
173621
+ const byCompartment = new Map;
173622
+ for (const row of rows) {
173623
+ if (cutoff !== null && row.endOrdinal > cutoff) {
173624
+ continue;
173625
+ }
173626
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
173627
+ if (score <= 0)
173628
+ continue;
173629
+ const existing = byCompartment.get(row.compartmentId);
173630
+ if (!existing || score > existing.score) {
173631
+ byCompartment.set(row.compartmentId, { row, score });
173632
+ }
173633
+ }
173634
+ 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 }) => ({
173635
+ source: "compartment",
173636
+ content: previewText(row.title),
173637
+ score: score * SINGLE_SOURCE_PENALTY,
173638
+ compartmentId: row.compartmentId,
173639
+ sessionId: row.sessionId,
173640
+ title: row.title,
173641
+ startOrdinal: row.startOrdinal,
173642
+ endOrdinal: row.endOrdinal,
173643
+ matchType: "semantic"
173644
+ }));
173645
+ }
173646
+ function mergeMessageAndCompartmentResults(args) {
173647
+ if (args.compartments.length === 0)
173648
+ return args.messages;
173649
+ if (args.messages.length === 0)
173650
+ return args.compartments;
173651
+ const fused = new Map;
173652
+ const add = (key, result, score, tieOrdinal) => {
173653
+ const existing = fused.get(key);
173654
+ if (existing) {
173655
+ existing.score += score;
173656
+ return existing;
173657
+ }
173658
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
173659
+ fused.set(key, entry);
173660
+ return entry;
173661
+ };
173662
+ args.compartments.forEach((compartment, rank) => {
173663
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
173664
+ });
173665
+ for (const [rank, message] of args.messages.entries()) {
173666
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
173667
+ const contribution = 1 / (RRF_K + rank);
173668
+ if (!containing) {
173669
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
173670
+ continue;
173671
+ }
173672
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
173673
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
173674
+ entry.snippetScore = message.score;
173675
+ entry.result = {
173676
+ ...entry.result,
173677
+ matchType: "hybrid",
173678
+ snippet: message.content
173679
+ };
173680
+ }
173681
+ }
173682
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
173683
+ return ranked.map((entry, rank) => ({
173684
+ ...entry.result,
173685
+ score: linearDecayScore(rank, ranked.length)
173686
+ }));
173687
+ }
172013
173688
  function getSourceBoost(result) {
172014
173689
  switch (result.source) {
172015
173690
  case "memory":
172016
173691
  return MEMORY_SOURCE_BOOST;
172017
173692
  case "message":
173693
+ case "compartment":
172018
173694
  return MESSAGE_SOURCE_BOOST;
172019
173695
  case "git_commit":
172020
173696
  return GIT_COMMIT_SOURCE_BOOST;
@@ -172032,6 +173708,9 @@ function compareUnifiedResults(left, right) {
172032
173708
  if (left.source === "message" && right.source === "message") {
172033
173709
  return left.messageOrdinal - right.messageOrdinal;
172034
173710
  }
173711
+ if (left.source === "compartment" && right.source === "compartment") {
173712
+ return left.startOrdinal - right.startOrdinal;
173713
+ }
172035
173714
  if (left.source === "git_commit" && right.source === "git_commit") {
172036
173715
  return right.committedAtMs - left.committedAtMs;
172037
173716
  }
@@ -172082,10 +173761,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172082
173761
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
172083
173762
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
172084
173763
  const activeSources = resolveSources(options.sources);
172085
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
173764
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
173765
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
172086
173766
  const runMessages = activeSources.has("message");
172087
173767
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
172088
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
173768
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
173769
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
172089
173770
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
172090
173771
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
172091
173772
  return null;
@@ -172101,6 +173782,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172101
173782
  probes: messageProbes
172102
173783
  }) : [];
172103
173784
  const queryEmbedding = await queryEmbeddingPromise;
173785
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
173786
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
173787
+ const embeddingModelId = embeddingSnapshot?.modelId;
173788
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
173789
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
173790
+ db,
173791
+ sessionId,
173792
+ projectPath,
173793
+ queryEmbedding,
173794
+ limit: tierLimit,
173795
+ maxOrdinal: options.maxMessageOrdinal,
173796
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
173797
+ }) : [];
173798
+ const messageLikeResults = mergeMessageAndCompartmentResults({
173799
+ messages: messageResults,
173800
+ compartments: compartmentResults,
173801
+ limit: tierLimit
173802
+ });
172104
173803
  const [memoryResults, gitCommitResults] = await Promise.all([
172105
173804
  runMemory ? searchMemories({
172106
173805
  db,
@@ -172109,6 +173808,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172109
173808
  limit: tierLimit,
172110
173809
  memoryEnabled: true,
172111
173810
  queryEmbedding,
173811
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
173812
+ workspace,
172112
173813
  visibleMemoryIds: options.visibleMemoryIds
172113
173814
  }) : Promise.resolve([]),
172114
173815
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -172119,7 +173820,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172119
173820
  queryEmbedding
172120
173821
  })) : Promise.resolve([])
172121
173822
  ]);
172122
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
173823
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
172123
173824
  const countRetrievals = options.countRetrievals ?? true;
172124
173825
  if (countRetrievals) {
172125
173826
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -172179,6 +173880,11 @@ function renderFragment(result, charCap) {
172179
173880
  const compressed = cavemanCompress(result.content, "ultra");
172180
173881
  return truncate(compressed, charCap);
172181
173882
  }
173883
+ case "compartment": {
173884
+ const source = result.snippet ?? result.title;
173885
+ const compressed = cavemanCompress(source, "ultra");
173886
+ return truncate(compressed, charCap);
173887
+ }
172182
173888
  }
172183
173889
  }
172184
173890
  function buildAutoSearchHint(results, options = {}) {
@@ -173602,6 +175308,49 @@ function safeGetActiveUserMemoriesPi(db) {
173602
175308
  function memoryProjectPath(state) {
173603
175309
  return state.memoryEnabled === false ? undefined : state.projectIdentity;
173604
175310
  }
175311
+ function resolveWorkspaceRenderContextPi(state, db) {
175312
+ const memPath = memoryProjectPath(state);
175313
+ if (!memPath) {
175314
+ return {
175315
+ identities: [],
175316
+ expandedIdentities: [],
175317
+ ownIdentities: [],
175318
+ shareCategories: null,
175319
+ namesByIdentity: new Map,
175320
+ canonicalIdentityByStoredPath: new Map,
175321
+ isWorkspaced: false
175322
+ };
175323
+ }
175324
+ const identitySet = resolveWorkspaceIdentitySet(db, memPath);
175325
+ const isWorkspaced = identitySet.identities.length > 1;
175326
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, identitySet.identities);
175327
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
175328
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
175329
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === memPath);
175330
+ if (ownIdentities.length === 0 && expandedIdentities.includes(memPath)) {
175331
+ ownIdentities = [memPath];
175332
+ }
175333
+ return {
175334
+ identities: identitySet.identities,
175335
+ expandedIdentities,
175336
+ ownIdentities,
175337
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, memPath) : null,
175338
+ namesByIdentity: identitySet.namesByIdentity,
175339
+ canonicalIdentityByStoredPath,
175340
+ isWorkspaced
175341
+ };
175342
+ }
175343
+ function sourceNamesForPiMemories(args) {
175344
+ if (!args.projectPath || !args.workspace.isWorkspaced)
175345
+ return;
175346
+ const names = new Map;
175347
+ for (const memory of args.memories) {
175348
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
175349
+ if (source)
175350
+ names.set(memory.id, source);
175351
+ }
175352
+ return names.size > 0 ? names : undefined;
175353
+ }
173605
175354
  var EMPTY_PI_HARD_SIGNALS = {
173606
175355
  systemHash: "",
173607
175356
  modelKey: "",
@@ -173649,6 +175398,7 @@ function getCachedMarkers(db, state, compartmentsForNormalization) {
173649
175398
  maxMutationId: meta3.cachedM0MaxMutationId,
173650
175399
  maxMemoryMutationId: meta3.cachedM0MaxMemoryMutationId,
173651
175400
  projectMemoryEpoch: meta3.cachedM0ProjectMemoryEpoch,
175401
+ workspaceFingerprint: meta3.cachedM0WorkspaceFingerprint,
173652
175402
  projectUserProfileVersion: meta3.cachedM0ProjectUserProfileVersion,
173653
175403
  projectDocsHash: meta3.cachedM0ProjectDocsHash,
173654
175404
  sessionFactsVersion: meta3.cachedM0SessionFactsVersion,
@@ -173668,15 +175418,17 @@ function readCurrentMarkers(db, state, projectDocsHash) {
173668
175418
  }
173669
175419
  function readCurrentMarkersFromCompartments(db, state, compartments, projectDocsHash) {
173670
175420
  const memPath = memoryProjectPath(state);
173671
- const memories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"]) : [];
175421
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
175422
+ const maxMemoryId = memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0;
173672
175423
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
173673
175424
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
173674
175425
  return {
173675
175426
  maxCompartmentSeq: compartments.length > 0 ? compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ) : EMPTY_MAX_COMPARTMENT_SEQ,
173676
- maxMemoryId: memories.length > 0 ? memories.reduce((max, memory) => memory.id > max ? memory.id : max, 0) : 0,
175427
+ maxMemoryId,
173677
175428
  maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
173678
- maxMemoryMutationId: memPath ? getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
175429
+ maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
173679
175430
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
175431
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
173680
175432
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
173681
175433
  projectDocsHash: projectDocsHash ?? readProjectDocsCanonical(state.projectDirectory).canonicalHash,
173682
175434
  sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
@@ -173714,10 +175466,11 @@ function mustMaterializePi(state, db, currentCompartmentsOverride) {
173714
175466
  if (meta3.cachedM0UpgradeState !== current.upgradeState) {
173715
175467
  return { value: true, reason: "renderer_upgrade" };
173716
175468
  }
173717
- if (current.projectDocsHash !== (meta3.cachedM0ProjectDocsHash ?? "")) {
173718
- return { value: true, reason: "project_docs_change" };
173719
- }
173720
- if (current.projectMemoryEpoch !== (meta3.cachedM0ProjectMemoryEpoch ?? 0)) {
175469
+ if (current.workspaceFingerprint !== null || (meta3.cachedM0WorkspaceFingerprint ?? null) !== null) {
175470
+ if (current.workspaceFingerprint !== (meta3.cachedM0WorkspaceFingerprint ?? null)) {
175471
+ return { value: true, reason: "project_memory_change" };
175472
+ }
175473
+ } else if (current.projectMemoryEpoch !== (meta3.cachedM0ProjectMemoryEpoch ?? 0)) {
173721
175474
  return { value: true, reason: "project_memory_change" };
173722
175475
  }
173723
175476
  if (current.maxMutationId !== (meta3.cachedM0MaxMutationId ?? 0)) {
@@ -173734,11 +175487,19 @@ ${memories.map((memory) => `- ${escapeXmlContent(memory.content)}`).join(`
173734
175487
  `)}
173735
175488
  </${wrapper}>`;
173736
175489
  }
173737
- function renderM0Pi(state, db, projectDocs = readProjectDocsCanonical(state.projectDirectory).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride) {
175490
+ function renderM0Pi(state, db, projectDocs = readProjectDocsCanonical(state.projectDirectory).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride, workspaceOverride) {
173738
175491
  const memPath = memoryProjectPath(state);
173739
- const allMemories = memoriesOverride ?? (memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
173740
- const memories = allMemories.length > 0 ? trimMemoriesToBudgetV2(state.sessionId, allMemories, state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder : allMemories;
173741
- const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories) : undefined;
175492
+ const workspace = workspaceOverride ?? resolveWorkspaceRenderContextPi(state, db);
175493
+ const allMemories = memoriesOverride ?? (memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
175494
+ const memoryRenderOptions = {
175495
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175496
+ memories: allMemories,
175497
+ projectPath: memPath,
175498
+ workspace
175499
+ })
175500
+ };
175501
+ const memories = allMemories.length > 0 ? workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(state.sessionId, allMemories, state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS, workspace, memoryRenderOptions).renderOrder : trimMemoriesToBudgetV2(state.sessionId, allMemories, state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder : allMemories;
175502
+ const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories, "project-memory", memoryRenderOptions) : undefined;
173742
175503
  const baseHistoryBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173743
175504
  const decayed = renderDecayedCompartments({
173744
175505
  compartments: compartmentsOverride ?? getCompartments(db, state.sessionId),
@@ -173760,10 +175521,19 @@ ${decayed}
173760
175521
 
173761
175522
  `).trim();
173762
175523
  }
173763
- function renderedMemoryIdsForPi(state, memories) {
175524
+ function renderedMemoryIdsForPi(state, memories, workspace, db) {
173764
175525
  if (memories.length === 0)
173765
175526
  return [];
173766
- return trimMemoriesToBudgetV2(state.sessionId, [...memories], state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder.map((memory) => memory.id);
175527
+ const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state, db) : undefined);
175528
+ const renderOptions = resolvedWorkspace ? {
175529
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175530
+ memories,
175531
+ projectPath: memoryProjectPath(state),
175532
+ workspace: resolvedWorkspace
175533
+ })
175534
+ } : {};
175535
+ const trimmed = resolvedWorkspace?.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(state.sessionId, [...memories], state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS, resolvedWorkspace, renderOptions) : trimMemoriesToBudgetV2(state.sessionId, [...memories], state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS);
175536
+ return trimmed.renderOrder.map((memory) => memory.id);
173767
175537
  }
173768
175538
  function isTransientSqliteLockError(error51) {
173769
175539
  if (!error51 || typeof error51 !== "object")
@@ -173788,17 +175558,19 @@ class PiMaterializeContentionError extends Error {
173788
175558
  function readFrozenM0InputsPi(state, db, docs = readProjectDocsCanonical(state.projectDirectory), memoryCutoff) {
173789
175559
  const memPath = memoryProjectPath(state);
173790
175560
  const read = db.transaction(() => {
175561
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
173791
175562
  const compartments = getCompartments(db, state.sessionId);
173792
- const memories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
175563
+ const memories = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], memoryCutoff, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
173793
175564
  const userProfile = safeGetActiveUserMemoriesPi(db);
173794
175565
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
173795
175566
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
173796
175567
  const markers = {
173797
175568
  maxCompartmentSeq: compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ),
173798
- maxMemoryId: memories.reduce((max, memory) => memory.id > max ? memory.id : max, 0),
175569
+ maxMemoryId: memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0,
173799
175570
  maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
173800
- maxMemoryMutationId: memPath ? getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
175571
+ maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
173801
175572
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
175573
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
173802
175574
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
173803
175575
  projectDocsHash: docs.canonicalHash,
173804
175576
  sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
@@ -173808,7 +175580,7 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsCanonical(state.p
173808
175580
  systemHash: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
173809
175581
  modelKey: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey
173810
175582
  };
173811
- return { docs, markers, compartments, memories, userProfile };
175583
+ return { docs, markers, compartments, memories, userProfile, workspace };
173812
175584
  });
173813
175585
  return read();
173814
175586
  }
@@ -173819,17 +175591,17 @@ function renderFreshM0PiNonPersisted(state, db) {
173819
175591
  frozen.markers.materializedAt = cachedMaterializedAt;
173820
175592
  const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173821
175593
  let dpm = 1;
173822
- let m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile);
175594
+ let m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace);
173823
175595
  let attempts = 0;
173824
175596
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
173825
175597
  dpm *= 1.15;
173826
- m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile);
175598
+ m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace);
173827
175599
  attempts += 1;
173828
175600
  }
173829
175601
  return {
173830
175602
  m0,
173831
175603
  snapshotMarkers: frozen.markers,
173832
- renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories)
175604
+ renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories, frozen.workspace, db)
173833
175605
  };
173834
175606
  }
173835
175607
  function materializeM0Pi(state, db) {
@@ -173839,14 +175611,14 @@ function materializeM0Pi(state, db) {
173839
175611
  const snapshotMemories = frozen.memories;
173840
175612
  const snapshotCompartments = frozen.compartments;
173841
175613
  const snapshotUserProfile = frozen.userProfile;
173842
- const renderedMemoryIds = renderedMemoryIdsForPi(state, snapshotMemories);
175614
+ const renderedMemoryIds = renderedMemoryIdsForPi(state, snapshotMemories, frozen.workspace, db);
173843
175615
  let decayPressureMultiplier = 1;
173844
- let m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile);
175616
+ let m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace);
173845
175617
  const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173846
175618
  let attempts = 0;
173847
175619
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
173848
175620
  decayPressureMultiplier *= 1.15;
173849
- m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile);
175621
+ m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace);
173850
175622
  attempts += 1;
173851
175623
  }
173852
175624
  const m0Bytes = Buffer.from(m0, "utf8");
@@ -173862,7 +175634,8 @@ function materializeM0Pi(state, db) {
173862
175634
  }
173863
175635
  try {
173864
175636
  const current = readCurrentMarkers(db, state, phase3ProjectDocsHash);
173865
- const stale = current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.projectDocsHash !== snapshotMarkers.projectDocsHash || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
175637
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
175638
+ const stale = memoryEpochStale || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
173866
175639
  if (stale) {
173867
175640
  db.exec("ROLLBACK");
173868
175641
  throw new PiMaterializeContentionError("snapshot changed before persist");
@@ -173873,6 +175646,7 @@ function materializeM0Pi(state, db) {
173873
175646
  persistCachedM0(db, state.sessionId, {
173874
175647
  m0Bytes,
173875
175648
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
175649
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
173876
175650
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
173877
175651
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
173878
175652
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -173937,7 +175711,7 @@ function renderMemoryUpdatesBlockPi(args) {
173937
175711
  if (args.renderedMemoryIds.length === 0)
173938
175712
  return { block: "", count: 0 };
173939
175713
  const renderedIds = new Set(args.renderedMemoryIds);
173940
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
175714
+ const mutations = args.workspace.isWorkspaced ? getMemoryMutationsForRenderByProjects(args.db, args.workspace.expandedIdentities, args.afterId, args.renderedMemoryIds) : getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
173941
175715
  if (mutations.length === 0)
173942
175716
  return { block: "", count: 0 };
173943
175717
  const lines = [
@@ -173968,6 +175742,7 @@ ${lines.join(`
173968
175742
  }
173969
175743
  function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRenderedKeyFilesBlock, compartmentsOverride) {
173970
175744
  const sections = [];
175745
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
173971
175746
  const keyFiles = renderedKeyFilesBlockPi(state, db, preRenderedKeyFilesBlock);
173972
175747
  if (keyFiles)
173973
175748
  sections.push(keyFiles);
@@ -173975,6 +175750,7 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRender
173975
175750
  const memoryUpdates = memPath ? renderMemoryUpdatesBlockPi({
173976
175751
  db,
173977
175752
  projectPath: memPath,
175753
+ workspace,
173978
175754
  afterId: markers.maxMemoryMutationId,
173979
175755
  renderedMemoryIds
173980
175756
  }) : { block: undefined, count: 0 };
@@ -173989,11 +175765,18 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRender
173989
175765
  ${body}
173990
175766
  </new-compartments>`);
173991
175767
  }
173992
- const newMemories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"], markers.materializedAt).filter((memory) => memory.id > markers.maxMemoryId) : [];
175768
+ const newMemories = memPath ? workspace.isWorkspaced ? readNewMemoriesForM1Union(db, workspace.expandedIdentities, markers.maxMemoryId, markers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], markers.materializedAt).filter((memory) => memory.id > markers.maxMemoryId) : [];
173993
175769
  if (newMemories.length > 0) {
173994
175770
  const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
173995
- const trimmedNewMemories = trimMemoriesToBudgetV2(state.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25))).renderOrder;
173996
- const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories");
175771
+ const memoryRenderOptions = {
175772
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175773
+ memories: newMemories,
175774
+ projectPath: memPath,
175775
+ workspace
175776
+ })
175777
+ };
175778
+ const trimmedNewMemories = trimMemoriesToBudgetV2(state.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25)), memoryRenderOptions).renderOrder;
175779
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", memoryRenderOptions);
173997
175780
  if (newMemoriesBlock)
173998
175781
  sections.push(newMemoriesBlock);
173999
175782
  }
@@ -174042,6 +175825,7 @@ function parseMemoryBlockIds(raw) {
174042
175825
  function readCachedPiM0M1Row(db, sessionId) {
174043
175826
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
174044
175827
  cached_m0_project_memory_epoch,
175828
+ cached_m0_workspace_fingerprint,
174045
175829
  cached_m0_project_user_profile_version,
174046
175830
  cached_m0_max_compartment_seq,
174047
175831
  cached_m0_max_memory_id,
@@ -174085,6 +175869,7 @@ function markersFromCachedPiRow(row, compartmentsForNormalization) {
174085
175869
  maxMutationId: row.cached_m0_max_mutation_id,
174086
175870
  maxMemoryMutationId: row.cached_m0_max_memory_mutation_id,
174087
175871
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
175872
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
174088
175873
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
174089
175874
  projectDocsHash: row.cached_m0_project_docs_hash ?? "",
174090
175875
  materializedAt: row.cached_m0_materialized_at,
@@ -174099,7 +175884,7 @@ function cachedPiRowMatchesSnapshot(args) {
174099
175884
  const rowMarkers = markersFromCachedPiRow(args.row, args.compartmentsForNormalization);
174100
175885
  if (!rowMarkers)
174101
175886
  return false;
174102
- return bufferEqualsNullable(args.row.cached_m0_bytes, args.m0Bytes) && rowMarkers.projectMemoryEpoch === args.markers.projectMemoryEpoch && rowMarkers.projectUserProfileVersion === args.markers.projectUserProfileVersion && rowMarkers.maxCompartmentSeq === args.markers.maxCompartmentSeq && rowMarkers.maxMemoryId === args.markers.maxMemoryId && rowMarkers.maxMutationId === args.markers.maxMutationId && rowMarkers.maxMemoryMutationId === args.markers.maxMemoryMutationId && (rowMarkers.projectDocsHash ?? "") === (args.markers.projectDocsHash ?? "") && rowMarkers.materializedAt === args.markers.materializedAt && rowMarkers.sessionFactsVersion === args.markers.sessionFactsVersion && (rowMarkers.upgradeState ?? null) === (args.markers.upgradeState ?? null) && (rowMarkers.systemHash ?? "") === (args.markers.systemHash ?? "") && (rowMarkers.modelKey ?? "") === (args.markers.modelKey ?? "");
175887
+ return bufferEqualsNullable(args.row.cached_m0_bytes, args.m0Bytes) && rowMarkers.projectMemoryEpoch === args.markers.projectMemoryEpoch && rowMarkers.projectUserProfileVersion === args.markers.projectUserProfileVersion && rowMarkers.maxCompartmentSeq === args.markers.maxCompartmentSeq && rowMarkers.maxMemoryId === args.markers.maxMemoryId && rowMarkers.maxMutationId === args.markers.maxMutationId && rowMarkers.maxMemoryMutationId === args.markers.maxMemoryMutationId && rowMarkers.materializedAt === args.markers.materializedAt && rowMarkers.sessionFactsVersion === args.markers.sessionFactsVersion && (rowMarkers.upgradeState ?? null) === (args.markers.upgradeState ?? null) && (rowMarkers.systemHash ?? "") === (args.markers.systemHash ?? "") && (rowMarkers.modelKey ?? "") === (args.markers.modelKey ?? "") && (rowMarkers.workspaceFingerprint ?? null) === (args.markers.workspaceFingerprint ?? null);
174103
175888
  }
174104
175889
  function decodeCachedM1(row, sessionId) {
174105
175890
  if (!row.cached_m1_bytes) {
@@ -174310,14 +176095,14 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
174310
176095
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
174311
176096
  prependM0M1Messages(piMessages, m0, m1);
174312
176097
  sessionLog(state.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
176098
+ const memPath = memoryProjectPath(state);
176099
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
176100
+ const memoryCount = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories).length : getMemoriesByProject(db, memPath, ["active", "permanent"]).length : 0;
174313
176101
  return {
174314
176102
  injected: true,
174315
176103
  compartmentCount: getCompartments(db, state.sessionId).length,
174316
176104
  factCount: 0,
174317
- memoryCount: memoryProjectPath(state) ? getMemoriesByProject(db, memoryProjectPath(state), [
174318
- "active",
174319
- "permanent"
174320
- ]).length : 0,
176105
+ memoryCount,
174321
176106
  skippedVisibleMessages,
174322
176107
  m0Materialized: materialized,
174323
176108
  m0Reason: decision.reason,
@@ -174378,21 +176163,48 @@ import * as crypto2 from "node:crypto";
174378
176163
 
174379
176164
  // ../plugin/src/features/magic-context/compartment-embedding.ts
174380
176165
  init_logger();
174381
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
176166
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
174382
176167
  if (compartments.length === 0)
174383
176168
  return;
174384
- const update = db.prepare("UPDATE compartments SET p1_embedding = ?, p1_embedding_model_id = ? WHERE id = ?");
174385
- for (const c of compartments) {
174386
- if (!c.p1 || c.p1.length === 0)
174387
- continue;
176169
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
176170
+ for (const compartment of compartments) {
174388
176171
  try {
174389
- const result = await embedTextForProject(projectPath, c.p1);
174390
- if (result) {
174391
- const blob = Buffer.from(result.vector.buffer, result.vector.byteOffset, result.vector.byteLength);
174392
- update.run(blob, result.modelId, c.id);
176172
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
176173
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
176174
+ if (canonicalText.length === 0)
176175
+ continue;
176176
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
176177
+ if (windows.length === 0)
176178
+ continue;
176179
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
176180
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
176181
+ continue;
176182
+ }
176183
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
176184
+ if (!result)
176185
+ continue;
176186
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
176187
+ continue;
176188
+ }
176189
+ const rows = [];
176190
+ for (const [index, window] of windows.entries()) {
176191
+ const vector = result.vectors[index];
176192
+ if (!vector)
176193
+ continue;
176194
+ rows.push({
176195
+ compartmentId: compartment.id,
176196
+ sessionId,
176197
+ projectPath,
176198
+ window,
176199
+ modelId: currentModelId,
176200
+ vector
176201
+ });
176202
+ }
176203
+ if (rows.length === windows.length) {
176204
+ replaceCompartmentChunkEmbeddings(db, rows);
174393
176205
  }
174394
176206
  } catch (error51) {
174395
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
176207
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
174396
176208
  }
174397
176209
  }
174398
176210
  }
@@ -177461,6 +179273,7 @@ async function runPiHistorian(deps) {
177461
179273
  fallbackModels,
177462
179274
  historianChunkTokens,
177463
179275
  boundarySnapshot: providedBoundarySnapshot,
179276
+ refreshBoundarySnapshot,
177464
179277
  currentContextLimit,
177465
179278
  historianTimeoutMs = DEFAULT_HISTORIAN_TIMEOUT_MS2,
177466
179279
  twoPass,
@@ -177514,16 +179327,29 @@ async function runPiHistorian(deps) {
177514
179327
  return;
177515
179328
  }
177516
179329
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
177517
- const boundarySnapshot = providedBoundarySnapshot ?? null;
179330
+ let boundarySnapshot = providedBoundarySnapshot ?? null;
177518
179331
  if (!boundarySnapshot) {
177519
179332
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from Pi trigger decision");
177520
179333
  return;
177521
179334
  }
177522
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
179335
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
177523
179336
  db,
177524
179337
  snapshot: boundarySnapshot,
177525
179338
  currentContextLimit: currentContextLimit ?? boundarySnapshot.contextLimit
177526
179339
  }) : { ok: true };
179340
+ if (!validation.ok && validation.reason === "stale_snapshot" && refreshBoundarySnapshot) {
179341
+ try {
179342
+ const refreshed = refreshBoundarySnapshot();
179343
+ if (hasRunnableCompartmentWindow(refreshed)) {
179344
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
179345
+ boundarySnapshot = refreshed;
179346
+ validation = { ok: true };
179347
+ }
179348
+ } catch (error51) {
179349
+ const desc = describeError(error51);
179350
+ sessionLog(sessionId, `historian: failed to refresh stale protected-tail snapshot at run time (${validation.detail ?? "stale"}): ${desc.brief}`);
179351
+ }
179352
+ }
177527
179353
  if (!validation.ok) {
177528
179354
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
177529
179355
  return;
@@ -177852,8 +179678,13 @@ ${chunkText}`,
177852
179678
  }
177853
179679
  }
177854
179680
  if (embeddingActive) {
177855
- const toEmbed = newCompartments.map((c, i) => ({ id: persistedIds[i], p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
177856
- embedAndStoreCompartments(db, sessionId, projectPath, toEmbed);
179681
+ const chunksToEmbed = newCompartments.map((c, i) => ({
179682
+ id: persistedIds[i],
179683
+ startMessage: c.startMessage,
179684
+ endMessage: c.endMessage,
179685
+ sourceChunkText: chunk.text
179686
+ })).filter((c) => typeof c.id === "number");
179687
+ embedAndStoreCompartmentChunks(db, sessionId, projectPath, chunksToEmbed);
177857
179688
  }
177858
179689
  onPublished?.();
177859
179690
  completedSuccessfully = true;
@@ -178401,7 +180232,7 @@ function stripPiDroppedPlaceholderMessages(args) {
178401
180232
  }
178402
180233
 
178403
180234
  // src/system-prompt.ts
178404
- import { createHash as createHash9 } from "node:crypto";
180235
+ import { createHash as createHash11 } from "node:crypto";
178405
180236
 
178406
180237
  // ../plugin/src/agents/magic-context-prompt.ts
178407
180238
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -178552,7 +180383,7 @@ function processSystemPromptForCache(args) {
178552
180383
  sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
178553
180384
  }
178554
180385
  }
178555
- const currentHash = createHash9("md5").update(frozenPrompt).digest("hex");
180386
+ const currentHash = createHash11("md5").update(frozenPrompt).digest("hex");
178556
180387
  const hashChanged = !isFirstHash && currentHash !== previousHash;
178557
180388
  if (hashChanged) {
178558
180389
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
@@ -179179,6 +181010,19 @@ function extractStableId(msg, index, entryIds) {
179179
181010
  // src/context-handler.ts
179180
181011
  var FORCE_MATERIALIZATION_PERCENTAGE = 85;
179181
181012
  var EMERGENCY_BLOCK_PERCENTAGE = 95;
181013
+ var FORWARD_PRESSURE_LIMIT_FACTOR = 0.85;
181014
+ function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUsageTokens, correctedLimit) {
181015
+ const forwardTokens = typeof piUsageTokens === "number" && piUsageTokens > 0 ? piUsageTokens : 0;
181016
+ if (forwardTokens === 0 || !isSaneLimit(correctedLimit)) {
181017
+ return { percentage: trailingPercentage, inputTokens: trailingInputTokens };
181018
+ }
181019
+ const forwardPressureLimit = correctedLimit * FORWARD_PRESSURE_LIMIT_FACTOR;
181020
+ const forwardPercentage = forwardTokens / forwardPressureLimit * 100;
181021
+ return forwardPercentage > trailingPercentage ? {
181022
+ percentage: forwardPercentage,
181023
+ inputTokens: Math.max(trailingInputTokens, forwardTokens)
181024
+ } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
181025
+ }
179182
181026
  var DEFAULT_CLEAR_REASONING_AGE = 50;
179183
181027
  var PI_STABLE_ID_SCHEME = 1;
179184
181028
  var lastEmergencyNotificationAtMs = new Map;
@@ -179263,7 +181107,7 @@ function trackSessionForProject(projectIdentity, sessionId) {
179263
181107
  function isContextHandlerSessionActive(sessionId) {
179264
181108
  return activeContextHandlerSessions.has(sessionId);
179265
181109
  }
179266
- function updateSessionProjectTracking(sessionId, projectIdentity) {
181110
+ function updateSessionProjectTracking(sessionId, projectIdentity, db) {
179267
181111
  const prev = lastSeenProjectIdentityBySession.get(sessionId);
179268
181112
  if (prev && prev !== projectIdentity) {
179269
181113
  const prevSessions = sessionsByProject.get(prev);
@@ -179272,6 +181116,11 @@ function updateSessionProjectTracking(sessionId, projectIdentity) {
179272
181116
  sessionsByProject.delete(prev);
179273
181117
  clearPiSystemPromptSession(sessionId);
179274
181118
  }
181119
+ if (db && prev !== projectIdentity) {
181120
+ try {
181121
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
181122
+ } catch {}
181123
+ }
179275
181124
  trackSessionForProject(projectIdentity, sessionId);
179276
181125
  lastSeenProjectIdentityBySession.set(sessionId, projectIdentity);
179277
181126
  }
@@ -179552,7 +181401,7 @@ function registerPiContextHandler(pi, baseOptions) {
179552
181401
  const schedulerConfig = options.scheduler ?? DEFAULT_SCHEDULER_CONFIG;
179553
181402
  const scheduler2 = schedulerFor(options);
179554
181403
  const projectIdentity = resolveProjectIdentity(projectDirectory);
179555
- updateSessionProjectTracking(sessionId, projectIdentity);
181404
+ updateSessionProjectTracking(sessionId, projectIdentity, options.db);
179556
181405
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
179557
181406
  const branchEntries = readPiBranchEntriesForContext(ctx, sessionId);
179558
181407
  const rawMessageProvider = {
@@ -179661,9 +181510,10 @@ function registerPiContextHandler(pi, baseOptions) {
179661
181510
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
179662
181511
  usagePercentage = usageInputTokens / usageContextLimit * 100;
179663
181512
  }
181513
+ ({ percentage: usagePercentage, inputTokens: usageInputTokens } = applyForwardPressureFloor(usagePercentage, usageInputTokens, piUsage?.tokens, usageContextLimit));
179664
181514
  if (needsEmergencyBump) {
179665
181515
  sessionLog(sessionId, `transform: overflow recovery flag set — bumping percentage to 95% (detectedLimit=${usageContextLimit ?? "unknown"})`);
179666
- usagePercentage = 95;
181516
+ usagePercentage = Math.max(usagePercentage, 95);
179667
181517
  }
179668
181518
  let schedulerDecision;
179669
181519
  const tScheduler = performance.now();
@@ -180047,6 +181897,7 @@ function spawnPiHistorianRun(args) {
180047
181897
  provider: provider2,
180048
181898
  unregister,
180049
181899
  boundarySnapshot,
181900
+ refreshBoundarySnapshot,
180050
181901
  currentContextLimit
180051
181902
  } = args;
180052
181903
  const holderId = crypto3.randomUUID();
@@ -180070,6 +181921,7 @@ function spawnPiHistorianRun(args) {
180070
181921
  fallbackModels: historian.fallbackModels,
180071
181922
  historianChunkTokens: historian.historianChunkTokens,
180072
181923
  boundarySnapshot,
181924
+ refreshBoundarySnapshot,
180073
181925
  currentContextLimit,
180074
181926
  historianTimeoutMs: historian.timeoutMs,
180075
181927
  twoPass: historian.twoPass,
@@ -180136,6 +181988,7 @@ function maybeFireHistorian(args) {
180136
181988
  let usageContextLimit;
180137
181989
  try {
180138
181990
  const piUsage = ctx.getContextUsage?.();
181991
+ let usageSource;
180139
181992
  usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
180140
181993
  if (usageContextLimit === undefined && isSaneLimit(ctx.model?.contextWindow)) {
180141
181994
  usageContextLimit = ctx.model.contextWindow;
@@ -180152,7 +182005,7 @@ function maybeFireHistorian(args) {
180152
182005
  percentage: sessionMetaForUsage.lastContextPercentage,
180153
182006
  inputTokens: sessionMetaForUsage.lastInputTokens
180154
182007
  };
180155
- sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [from session_meta], checking trigger...`);
182008
+ usageSource = "session_meta";
180156
182009
  } else {
180157
182010
  if (!piUsage || piUsage.tokens === null || piUsage.percent === null || piUsage.contextWindow === 0) {
180158
182011
  sessionLog(sessionId, `historian trigger eval: no usage info yet (tokens=${piUsage?.tokens ?? "<no piUsage>"}, percent=${piUsage?.percent ?? "<no piUsage>"}, contextWindow=${piUsage?.contextWindow ?? "<no piUsage>"})`);
@@ -180163,8 +182016,10 @@ function maybeFireHistorian(args) {
180163
182016
  percentage: fallbackPercentage,
180164
182017
  inputTokens: piUsage.tokens
180165
182018
  };
180166
- sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [piUsage fallback], checking trigger...`);
182019
+ usageSource = "piUsage fallback";
180167
182020
  }
182021
+ usage = applyForwardPressureFloor(usage.percentage, usage.inputTokens, piUsage?.tokens, usageContextLimit);
182022
+ sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [${usageSource}], checking trigger...`);
180168
182023
  } catch (err) {
180169
182024
  sessionLog(sessionId, `historian trigger eval: getContextUsage threw: ${err instanceof Error ? err.message : String(err)}`);
180170
182025
  return;
@@ -180195,10 +182050,14 @@ function maybeFireHistorian(args) {
180195
182050
  cacheNamespace: `pi:${sessionId}`,
180196
182051
  emergencyTailScale
180197
182052
  }));
180198
- let boundarySnapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
180199
- if (!hasRunnableCompartmentWindow(boundarySnapshot) && usage.percentage >= 80) {
180200
- boundarySnapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
180201
- }
182053
+ const resolveRunnablePiBoundarySnapshot = () => {
182054
+ let snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
182055
+ if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= 80) {
182056
+ snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
182057
+ }
182058
+ return snapshot;
182059
+ };
182060
+ const boundarySnapshot = resolveRunnablePiBoundarySnapshot();
180202
182061
  let triggered = false;
180203
182062
  try {
180204
182063
  if (isFirstContextPassForSession) {
@@ -180223,6 +182082,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
180223
182082
  provider: provider2,
180224
182083
  unregister,
180225
182084
  boundarySnapshot,
182085
+ refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
180226
182086
  currentContextLimit: boundaryContextLimit
180227
182087
  });
180228
182088
  return;
@@ -180246,6 +182106,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
180246
182106
  resolvedBoundarySnapshot: boundarySnapshot,
180247
182107
  triggerBoundarySnapshot: trigger.boundarySnapshot
180248
182108
  }),
182109
+ refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
180249
182110
  currentContextLimit: boundaryContextLimit
180250
182111
  });
180251
182112
  } catch (err) {
@@ -180286,7 +182147,31 @@ async function runPipeline(args) {
180286
182147
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
180287
182148
  const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
180288
182149
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
180289
- const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializeEligible || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
182150
+ const piHardSignals = args.injection ? (() => {
182151
+ const hardMeta = getOrCreateSessionMeta(args.db, args.sessionId);
182152
+ let piTtlMs = 5 * 60 * 1000;
182153
+ try {
182154
+ piTtlMs = parseCacheTtl(hardMeta.cacheTtl);
182155
+ } catch {}
182156
+ return {
182157
+ systemHash: typeof hardMeta.systemPromptHash === "string" ? hardMeta.systemPromptHash : "",
182158
+ modelKey: liveModelBySession.get(args.sessionId) ?? "",
182159
+ cacheExpired: hardMeta.lastResponseTime > 0 && Date.now() - hardMeta.lastResponseTime >= piTtlMs,
182160
+ lastResponseTime: hardMeta.lastResponseTime
182161
+ };
182162
+ })() : undefined;
182163
+ const m0HardFoldThisPass = args.injection && piHardSignals ? mustMaterializePi({
182164
+ sessionId: args.sessionId,
182165
+ projectIdentity: args.projectIdentity,
182166
+ projectDirectory: args.projectDirectory,
182167
+ memoryEnabled: args.injection.memoryEnabled,
182168
+ injectionBudgetTokens: args.injection.injectionBudgetTokens,
182169
+ historyBudgetTokens: args.injection.historyBudgetTokens,
182170
+ keyFilesEnabled: args.injection.keyFilesEnabled,
182171
+ keyFilesTokenBudget: args.injection.keyFilesTokenBudget,
182172
+ hardSignals: piHardSignals
182173
+ }, args.db, getCompartments(args.db, args.sessionId)).value : false;
182174
+ const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializeEligible || m0HardFoldThisPass || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
180290
182175
  const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver);
180291
182176
  adoptPiFallbackTags(args.db, args.sessionId, args.tagger, entryFingerprintByMessageId);
180292
182177
  const tTag = performance.now();
@@ -180313,12 +182198,12 @@ async function runPipeline(args) {
180313
182198
  const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
180314
182199
  const deferredHistoryRefreshWasPending = deferredHistoryWasPendingAtPassStart;
180315
182200
  const pendingOps = getPendingOps(args.db, args.sessionId);
180316
- const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
182201
+ const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal || m0HardFoldThisPass;
180317
182202
  const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
180318
182203
  const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
180319
182204
  const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
180320
182205
  if (shouldApplyPendingOps) {
180321
- const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
182206
+ const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : m0HardFoldThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into known m[0] bust, scheduler=${args.schedulerDecision})` : `scheduler_execute (scheduler=${args.schedulerDecision})`;
180322
182207
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
180323
182208
  try {
180324
182209
  const tApplyPending = performance.now();
@@ -180389,7 +182274,7 @@ async function runPipeline(args) {
180389
182274
  const activeTags = getActiveTagsBySession(args.db, args.sessionId);
180390
182275
  logTransformTiming(args.sessionId, "getActiveTagsBySession", tActiveTags, `count=${activeTags.length}`);
180391
182276
  if (shouldRunHeuristics) {
180392
- const reason = args.forceMaterialization ? "force_materialization" : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
182277
+ const reason = args.forceMaterialization ? "force_materialization" : m0HardFoldThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into known m[0] bust, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
180393
182278
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`);
180394
182279
  } else {
180395
182280
  const reason = args.heuristics === undefined ? "disabled" : "scheduler_defer";
@@ -180477,17 +182362,6 @@ async function runPipeline(args) {
180477
182362
  if (args.injection) {
180478
182363
  try {
180479
182364
  const tInjection = performance.now();
180480
- const hardMeta = getOrCreateSessionMeta(args.db, args.sessionId);
180481
- let piTtlMs = 5 * 60 * 1000;
180482
- try {
180483
- piTtlMs = parseCacheTtl(hardMeta.cacheTtl);
180484
- } catch {}
180485
- const piHardSignals = {
180486
- systemHash: typeof hardMeta.systemPromptHash === "string" ? hardMeta.systemPromptHash : "",
180487
- modelKey: liveModelBySession.get(args.sessionId) ?? "",
180488
- cacheExpired: hardMeta.lastResponseTime > 0 && Date.now() - hardMeta.lastResponseTime >= piTtlMs,
180489
- lastResponseTime: hardMeta.lastResponseTime
180490
- };
180491
182365
  injectionResult = injectM0M1Pi({
180492
182366
  sessionId: args.sessionId,
180493
182367
  projectIdentity: args.projectIdentity,
@@ -181577,8 +183451,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
181577
183451
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181578
183452
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
181579
183453
  const liveCompartments = getCompartments(db, sessionId);
181580
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
181581
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
183454
+ const chunksToEmbed = liveCompartments.map((c) => ({
183455
+ id: c.id,
183456
+ startMessage: c.startMessage,
183457
+ endMessage: c.endMessage
183458
+ }));
183459
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
181582
183460
  }
181583
183461
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
181584
183462
  if (lastCompartmentEnd2 > 0) {
@@ -181791,8 +183669,12 @@ Another process acquired the compartment-state lease before recomp could publish
181791
183669
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181792
183670
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
181793
183671
  const liveCompartments = getCompartments(db, sessionId);
181794
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
181795
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
183672
+ const chunksToEmbed = liveCompartments.map((c) => ({
183673
+ id: c.id,
183674
+ startMessage: c.startMessage,
183675
+ endMessage: c.endMessage
183676
+ }));
183677
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
181796
183678
  }
181797
183679
  if (lastCompartmentEnd > 0) {
181798
183680
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -181949,8 +183831,12 @@ Could not acquire the compartment-state lease for this session.`;
181949
183831
  if (deps.memoryEnabled !== false) {
181950
183832
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181951
183833
  const liveCompartments = getCompartments(db, sessionId);
181952
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
181953
- Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed));
183834
+ const chunksToEmbed = liveCompartments.map((c) => ({
183835
+ id: c.id,
183836
+ startMessage: c.startMessage,
183837
+ endMessage: c.endMessage
183838
+ }));
183839
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
181954
183840
  }
181955
183841
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
181956
183842
  if (lastEnd > 0) {
@@ -182762,7 +184648,7 @@ function applyMemoryMigration(db, projectPath, result) {
182762
184648
  inserted++;
182763
184649
  }
182764
184650
  if (removed > 0 || inserted > 0) {
182765
- bumpProjectMemoryEpoch(db, projectPath);
184651
+ bumpEpochsForWorkspaceMembers(db, projectPath);
182766
184652
  }
182767
184653
  })();
182768
184654
  return { removed, inserted };
@@ -183238,7 +185124,7 @@ function formatThresholdPercent(value) {
183238
185124
  // package.json
183239
185125
  var package_default = {
183240
185126
  name: "@cortexkit/pi-magic-context",
183241
- version: "0.23.1",
185127
+ version: "0.24.0",
183242
185128
  type: "module",
183243
185129
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
183244
185130
  main: "dist/index.js",
@@ -188102,6 +189988,23 @@ function createCtxMemoryTool(deps) {
188102
189988
  }
188103
189989
  const projectIdentity = resolveProjectIdentity(ctx.cwd);
188104
189990
  await deps.ensureProjectRegistered?.(ctx.cwd, deps.db);
189991
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectIdentity);
189992
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
189993
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
189994
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? normalizeStoredProjectPath(rawProjectPath) : normalizeStoredProjectPath(rawProjectPath);
189995
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectIdentity) : null;
189996
+ const memoryVisibleToTool = (memory2) => {
189997
+ if (workspaceIdentitySet.identities.length <= 1) {
189998
+ return storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
189999
+ }
190000
+ if (!storedPathBelongsToWorkspace(memory2.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
190001
+ return false;
190002
+ }
190003
+ const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
190004
+ if (isOwn)
190005
+ return true;
190006
+ return toolShareCategories === null || toolShareCategories.includes(memory2.category);
190007
+ };
188105
190008
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
188106
190009
  if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188107
190010
  return err2("Cross-session memory is disabled for this project.");
@@ -188148,28 +190051,34 @@ function createCtxMemoryTool(deps) {
188148
190051
  return err2("Error: 'content' is required when action is 'update'.");
188149
190052
  }
188150
190053
  const memory2 = getMemoryById(deps.db, updateId);
188151
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
190054
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
188152
190055
  return err2(`Error: Memory with ID ${updateId} was not found.`);
188153
190056
  }
188154
190057
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
188155
190058
  return err2(inactiveMemoryError(updateId, "updating"));
188156
190059
  }
188157
190060
  const normalizedHash = computeNormalizedHash(content);
188158
- const duplicate = getMemoryByHash(deps.db, projectIdentity, memory2.category, normalizedHash);
190061
+ const targetIdentity = targetIdentityForStoredPath(memory2.projectPath);
190062
+ const duplicate = getMemoryByHash(deps.db, targetIdentity, memory2.category, normalizedHash);
188159
190063
  if (duplicate && duplicate.id !== memory2.id) {
188160
190064
  return err2(`Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`);
188161
190065
  }
188162
190066
  deps.db.transaction(() => {
188163
190067
  updateMemoryContent(deps.db, memory2.id, content, normalizedHash);
188164
190068
  queueMemoryMutation(deps.db, {
188165
- projectPath: projectIdentity,
190069
+ projectPath: targetIdentity,
188166
190070
  mutationType: "update",
188167
190071
  targetMemoryId: memory2.id,
188168
190072
  category: memory2.category,
188169
190073
  newContent: content
188170
190074
  });
188171
190075
  })();
188172
- queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
190076
+ queueEmbedding({
190077
+ deps,
190078
+ projectIdentity: targetIdentity,
190079
+ memoryId: memory2.id,
190080
+ content
190081
+ });
188173
190082
  return ok2(`Updated memory [ID: ${memory2.id}] in ${memory2.category}.`);
188174
190083
  }
188175
190084
  if (params.action === "merge") {
@@ -188189,7 +190098,7 @@ function createCtxMemoryTool(deps) {
188189
190098
  return err2("Error: One or more source memories were not found.");
188190
190099
  }
188191
190100
  if (!dreamerAllowed) {
188192
- const foreign = sourceMemories.find((memory2) => !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity));
190101
+ const foreign = sourceMemories.find((memory2) => !memoryVisibleToTool(memory2));
188193
190102
  if (foreign) {
188194
190103
  return err2(`Error: Memory with ID ${foreign.id} was not found.`);
188195
190104
  }
@@ -188278,26 +190187,36 @@ function createCtxMemoryTool(deps) {
188278
190187
  return ok2(`Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`);
188279
190188
  }
188280
190189
  if (params.action === "archive") {
188281
- const archiveIds = params.ids;
188282
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
190190
+ const rawArchiveIds = params.ids;
190191
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
188283
190192
  return err2("Error: 'ids' must contain at least one integer memory ID when action is 'archive'.");
188284
190193
  }
190194
+ const archiveIds = [...new Set(rawArchiveIds)];
188285
190195
  for (const memoryId of archiveIds) {
188286
190196
  const memory2 = getMemoryById(deps.db, memoryId);
188287
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
190197
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
188288
190198
  return err2(`Error: Memory with ID ${memoryId} was not found.`);
188289
190199
  }
188290
190200
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
188291
190201
  return err2(inactiveMemoryError(memoryId, "archiving"));
188292
190202
  }
188293
190203
  }
190204
+ const targets = archiveIds.map((memoryId) => {
190205
+ const memory2 = getMemoryById(deps.db, memoryId);
190206
+ if (!memory2)
190207
+ throw new Error(`validated memory ${memoryId} disappeared`);
190208
+ return {
190209
+ memoryId,
190210
+ projectIdentity: targetIdentityForStoredPath(memory2.projectPath)
190211
+ };
190212
+ });
188294
190213
  deps.db.transaction(() => {
188295
- for (const memoryId of archiveIds) {
188296
- archiveMemory(deps.db, memoryId, params.reason);
190214
+ for (const target2 of targets) {
190215
+ archiveMemory(deps.db, target2.memoryId, params.reason);
188297
190216
  queueMemoryMutation(deps.db, {
188298
- projectPath: projectIdentity,
190217
+ projectPath: target2.projectIdentity,
188299
190218
  mutationType: "archive",
188300
- targetMemoryId: memoryId
190219
+ targetMemoryId: target2.memoryId
188301
190220
  });
188302
190221
  }
188303
190222
  })();
@@ -188805,8 +190724,9 @@ function formatAge2(committedAtMs) {
188805
190724
  }
188806
190725
  function formatResult(result, index) {
188807
190726
  if (result.source === "memory") {
190727
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
188808
190728
  return [
188809
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
190729
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
188810
190730
  result.content
188811
190731
  ].join(`
188812
190732
  `);
@@ -188816,6 +190736,13 @@ function formatResult(result, index) {
188816
190736
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
188817
190737
  result.content
188818
190738
  ].join(`
190739
+ `);
190740
+ }
190741
+ if (result.source === "compartment") {
190742
+ return [
190743
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
190744
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
190745
+ ].join(`
188819
190746
  `);
188820
190747
  }
188821
190748
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -188831,7 +190758,7 @@ function formatSearchResults(query, results) {
188831
190758
  return `No results found for "${query}" across memories, git commits, or message history.`;
188832
190759
  }
188833
190760
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
188834
- if (results.some((result) => result.source === "message")) {
190761
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
188835
190762
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
188836
190763
  }
188837
190764
  const body = bodyParts.join(`
@@ -189327,6 +191254,14 @@ async function src_default2(pi) {
189327
191254
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
189328
191255
  });
189329
191256
  info("registered /ctx-dream");
191257
+ registerCtxEmbedHistoryCommand(pi, {
191258
+ db,
191259
+ projectDir,
191260
+ projectIdentity,
191261
+ memoryEnabled: config2.memory.enabled,
191262
+ resolveProject: resolveCurrentProject
191263
+ });
191264
+ info("registered /ctx-embed-history");
189330
191265
  const dreamerConfig = resolveDreamerFromConfig(config2);
189331
191266
  if (dreamerConfig) {
189332
191267
  registerPiDreamerProject({