@cortexkit/pi-magic-context 0.23.1 → 0.24.1

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);
@@ -146125,15 +146668,22 @@ function ownerMessageIdForTagRow(row) {
146125
146668
  }
146126
146669
  return row.message_id.replace(CONTENT_ID_SUFFIX, "");
146127
146670
  }
146128
- function getActiveTagTokenAggregate(db, sessionId) {
146129
- const row = db.prepare(`SELECT
146671
+ function getActiveTagTokenAggregate(db, sessionId, protectedTags = 0) {
146672
+ const toolOutputExpr = protectedTags > 0 ? `COALESCE(SUM(CASE WHEN type = 'tool' AND tag_number < (
146673
+ SELECT tag_number FROM tags
146674
+ WHERE session_id = ? AND status = 'active'
146675
+ ORDER BY tag_number DESC LIMIT 1 OFFSET ?
146676
+ ) THEN COALESCE(token_count, 0) ELSE 0 END), 0)` : `COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)`;
146677
+ const sql = `SELECT
146130
146678
  COALESCE(SUM(CASE WHEN type != 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)
146131
146679
  + COALESCE(SUM(COALESCE(reasoning_token_count, 0)), 0) AS conversation,
146132
146680
  COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) + COALESCE(input_token_count, 0) ELSE 0 END), 0) AS tool_call,
146133
- COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0) AS tool_output,
146681
+ ${toolOutputExpr} AS tool_output,
146134
146682
  COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
146135
146683
  FROM tags
146136
- WHERE session_id = ? AND status = 'active'`).get(sessionId);
146684
+ WHERE session_id = ? AND status = 'active'`;
146685
+ const params = protectedTags > 0 ? [sessionId, protectedTags - 1, sessionId] : [sessionId];
146686
+ const row = db.prepare(sql).get(...params);
146137
146687
  return {
146138
146688
  conversation: row?.conversation ?? 0,
146139
146689
  toolCall: row?.tool_call ?? 0,
@@ -146324,8 +146874,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
146324
146874
  }
146325
146875
  return all;
146326
146876
  }
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);
146877
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
146878
+ 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
146879
  return rows.map(toTagEntry);
146330
146880
  }
146331
146881
  var getToolTagNumberByOwnerStatements = new WeakMap;
@@ -146395,7 +146945,7 @@ var ERROR_CLASSES = new Set([
146395
146945
  ]);
146396
146946
  // ../plugin/src/features/magic-context/v22-deferred-backfill.ts
146397
146947
  init_logger();
146398
- import { createHash as createHash4 } from "node:crypto";
146948
+ import { createHash as createHash5 } from "node:crypto";
146399
146949
  import { realpathSync as realpathSync2 } from "node:fs";
146400
146950
  import path5 from "node:path";
146401
146951
  var BATCH_SIZE = 25;
@@ -146449,7 +146999,7 @@ function computeLegacyRustDirIdentity(rawProjectPath) {
146449
146999
  } catch {
146450
147000
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
146451
147001
  }
146452
- return `dir:${createHash4("sha256").update(canonical, "utf8").digest("hex")}`;
147002
+ return `dir:${createHash5("sha256").update(canonical, "utf8").digest("hex")}`;
146453
147003
  }
146454
147004
  function upsertRekeyMap(db, oldProjectPath, newProjectPath, rekeyedAt) {
146455
147005
  db.prepare(`INSERT INTO v22_identity_rekey_map (old_project_path, new_project_path, rekeyed_at)
@@ -147007,7 +147557,7 @@ function clearNoteNudgeTriggerOnly(db, sessionId) {
147007
147557
  }
147008
147558
 
147009
147559
  // ../plugin/src/hooks/magic-context/todo-view.ts
147010
- import { createHash as createHash5 } from "node:crypto";
147560
+ import { createHash as createHash6 } from "node:crypto";
147011
147561
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
147012
147562
  var TITLE_DONE_STATUSES = new Set(["completed"]);
147013
147563
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -147052,7 +147602,7 @@ function buildSyntheticTodoPart(stateJson) {
147052
147602
  };
147053
147603
  }
147054
147604
  function computeSyntheticCallId(stateJson) {
147055
- const hash = createHash5("sha256").update(stateJson).digest("hex").slice(0, 16);
147605
+ const hash = createHash6("sha256").update(stateJson).digest("hex").slice(0, 16);
147056
147606
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash}`;
147057
147607
  }
147058
147608
  function parseTodoState(stateJson) {
@@ -147193,13 +147743,13 @@ async function maybeSendUpgradeReminder(deps, sessionId) {
147193
147743
  init_data_path();
147194
147744
  import * as fs2 from "node:fs";
147195
147745
  import * as path6 from "node:path";
147196
- var ANNOUNCEMENT_VERSION = "0.23.0";
147746
+ var ANNOUNCEMENT_VERSION = "0.24.0";
147197
147747
  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."
147748
+ "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).",
147749
+ "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.",
147750
+ "Pi: fixed sessions overflowing the model context while still showing moderate usage — Pi now sheds context before a tool-heavy turn overflows.",
147751
+ "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
147752
+ "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
147753
  ];
147204
147754
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
147205
147755
  var STATE_FILENAME = "last_announced_version";
@@ -149082,7 +149632,7 @@ function isEmbeddingRow(row) {
149082
149632
  if (row === null || typeof row !== "object")
149083
149633
  return false;
149084
149634
  const candidate = row;
149085
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
149635
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
149086
149636
  }
149087
149637
  function toFloat32Array(blob) {
149088
149638
  if (blob instanceof Uint8Array) {
@@ -149102,7 +149652,7 @@ function getSaveEmbeddingStatement(db) {
149102
149652
  function getLoadAllEmbeddingsStatement(db) {
149103
149653
  let stmt = loadAllEmbeddingsStatements.get(db);
149104
149654
  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");
149655
+ 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
149656
  loadAllEmbeddingsStatements.set(db, stmt);
149107
149657
  }
149108
149658
  return stmt;
@@ -149131,7 +149681,10 @@ function loadAllEmbeddings(db, projectPath) {
149131
149681
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
149132
149682
  const embeddings = new Map;
149133
149683
  for (const row of rows) {
149134
- embeddings.set(row.memoryId, toFloat32Array(row.embedding));
149684
+ embeddings.set(row.memoryId, {
149685
+ embedding: toFloat32Array(row.embedding),
149686
+ modelId: row.modelId
149687
+ });
149135
149688
  }
149136
149689
  return embeddings;
149137
149690
  }
@@ -149182,13 +149735,13 @@ function invalidateMemory(projectPath, memoryId) {
149182
149735
  }
149183
149736
 
149184
149737
  // ../plugin/src/features/magic-context/memory/normalize-hash.ts
149185
- import { createHash as createHash6 } from "node:crypto";
149738
+ import { createHash as createHash7 } from "node:crypto";
149186
149739
  function normalizeMemoryContent(content) {
149187
149740
  return content.toLowerCase().replace(/\s+/g, " ").trim();
149188
149741
  }
149189
149742
  function computeNormalizedHash(content) {
149190
149743
  const normalized = normalizeMemoryContent(content);
149191
- return createHash6("md5").update(normalized).digest("hex");
149744
+ return createHash7("md5").update(normalized).digest("hex");
149192
149745
  }
149193
149746
 
149194
149747
  // ../plugin/src/features/magic-context/memory/storage-memory.ts
@@ -149376,8 +149929,8 @@ function getMemoriesByProjectStatement(db, statuses) {
149376
149929
  }
149377
149930
  let stmt = statements.get(db);
149378
149931
  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`);
149932
+ const placeholders3 = statuses.map(() => "?").join(", ");
149933
+ 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
149934
  statements.set(db, stmt);
149382
149935
  }
149383
149936
  return stmt;
@@ -149522,6 +150075,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
149522
150075
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
149523
150076
  return rows.map(toMemory);
149524
150077
  }
150078
+ function sqlPlaceholders(values) {
150079
+ return values.map(() => "?").join(", ");
150080
+ }
150081
+ function uniqueValues(values) {
150082
+ return [...new Set(values.filter((value) => value.length > 0))];
150083
+ }
150084
+ function buildWorkspaceMemorySqlFilter(args) {
150085
+ if (args.shareCategories === null || args.shareCategories === undefined) {
150086
+ return { clause: "", params: [], active: false };
150087
+ }
150088
+ const identities = uniqueValues(args.identities);
150089
+ const identitySet = new Set(identities);
150090
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
150091
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
150092
+ if (foreignIdentities.length === 0) {
150093
+ return { clause: "", params: [], active: false };
150094
+ }
150095
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
150096
+ const shareCategories = uniqueValues([...args.shareCategories]);
150097
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
150098
+ const predicates = [];
150099
+ const params = [];
150100
+ if (ownIdentities.length > 0) {
150101
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
150102
+ params.push(...ownIdentities);
150103
+ }
150104
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
150105
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
150106
+ params.push(...foreignIdentities, ...shareCategories);
150107
+ }
150108
+ if (predicates.length === 0) {
150109
+ return { clause: " AND 0 = 1", params: [], active: true };
150110
+ }
150111
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
150112
+ }
150113
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
150114
+ const identities = uniqueValues(projectPaths);
150115
+ if (identities.length === 0 || statuses.length === 0)
150116
+ return [];
150117
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
150118
+ identities,
150119
+ ownIdentities,
150120
+ shareCategories
150121
+ });
150122
+ if (identities.length === 1 && !sharingFilter.active) {
150123
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
150124
+ }
150125
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
150126
+ FROM memories
150127
+ WHERE project_path IN (${sqlPlaceholders(identities)})
150128
+ AND status IN (${sqlPlaceholders(statuses)})
150129
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
150130
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
150131
+ return rows.map(toMemory);
150132
+ }
150133
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
150134
+ const identities = uniqueValues(projectPaths);
150135
+ if (identities.length === 0)
150136
+ return 0;
150137
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
150138
+ identities,
150139
+ ownIdentities,
150140
+ shareCategories
150141
+ });
150142
+ if (identities.length === 1 && !sharingFilter.active) {
150143
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
150144
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
150145
+ }
150146
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
150147
+ FROM memories
150148
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
150149
+ return typeof row?.max_id === "number" ? row.max_id : 0;
150150
+ }
150151
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
150152
+ const identities = uniqueValues(projectPaths);
150153
+ if (identities.length === 0)
150154
+ return [];
150155
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
150156
+ identities,
150157
+ ownIdentities,
150158
+ shareCategories
150159
+ });
150160
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
150161
+ FROM memories
150162
+ WHERE project_path IN (${sqlPlaceholders(identities)})
150163
+ AND id > ?
150164
+ AND status IN ('active', 'permanent')
150165
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
150166
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
150167
+ return rows.map(toMemory);
150168
+ }
149525
150169
  function getAllActiveMemoriesForMigration(db, projectPath) {
149526
150170
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
149527
150171
  return rows.map(toMemory);
@@ -150043,8 +150687,8 @@ function getUserMemoryCandidates(db) {
150043
150687
  function deleteUserMemoryCandidates(db, ids) {
150044
150688
  if (ids.length === 0)
150045
150689
  return;
150046
- const placeholders = ids.map(() => "?").join(",");
150047
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
150690
+ const placeholders3 = ids.map(() => "?").join(",");
150691
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
150048
150692
  }
150049
150693
  function insertUserMemory(db, content, sourceCandidateIds) {
150050
150694
  const now = Date.now();
@@ -165534,7 +166178,8 @@ var BaseEmbeddingConfigSchema = exports_external.object({
165534
166178
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
165535
166179
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
165536
166180
  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.")
166181
+ 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."),
166182
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
165538
166183
  }).superRefine((data, ctx) => {
165539
166184
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
165540
166185
  ctx.addIssue({
@@ -165555,7 +166200,8 @@ var EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data) => {
165555
166200
  if (data.provider === "local") {
165556
166201
  return {
165557
166202
  provider: "local",
165558
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
166203
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
166204
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
165559
166205
  };
165560
166206
  }
165561
166207
  if (data.provider === "openai-compatible") {
@@ -165568,7 +166214,8 @@ var EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data) => {
165568
166214
  endpoint: data.endpoint?.trim() ?? "",
165569
166215
  ...apiKey ? { api_key: apiKey } : {},
165570
166216
  ...inputType ? { input_type: inputType } : {},
165571
- ...truncate ? { truncate } : {}
166217
+ ...truncate ? { truncate } : {},
166218
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
165572
166219
  };
165573
166220
  }
165574
166221
  return { provider: "off" };
@@ -165650,6 +166297,443 @@ var MagicContextConfigSchema = exports_external.object({
165650
166297
  // ../plugin/src/features/magic-context/memory/embedding.ts
165651
166298
  init_logger();
165652
166299
 
166300
+ // ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
166301
+ import { createHash as createHash8 } from "node:crypto";
166302
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512;
166303
+ var CHUNK_WINDOW_SAFETY_RATIO = 0.9;
166304
+ var loadFtsRowsStatements = new WeakMap;
166305
+ var existingHashStatements = new WeakMap;
166306
+ var existingHashByProjectStatements = new WeakMap;
166307
+ var deleteByCompartmentStatements = new WeakMap;
166308
+ var insertEmbeddingStatements = new WeakMap;
166309
+ var distinctModelStatements = new WeakMap;
166310
+ var clearProjectStatements = new WeakMap;
166311
+ var clearProjectModelStatements = new WeakMap;
166312
+ var searchRowsStatements = new WeakMap;
166313
+ var searchRowsByModelStatements = new WeakMap;
166314
+ var backfillCandidateStatements = new WeakMap;
166315
+ function getLoadFtsRowsStatement(db) {
166316
+ let stmt = loadFtsRowsStatements.get(db);
166317
+ if (!stmt) {
166318
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
166319
+ FROM message_history_fts
166320
+ WHERE session_id = ?
166321
+ AND message_ordinal >= ?
166322
+ AND message_ordinal <= ?
166323
+ AND role IN ('user', 'assistant')
166324
+ ORDER BY message_ordinal ASC`);
166325
+ loadFtsRowsStatements.set(db, stmt);
166326
+ }
166327
+ return stmt;
166328
+ }
166329
+ function getExistingHashStatement(db, scopedToProject) {
166330
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
166331
+ let stmt = map2.get(db);
166332
+ if (!stmt) {
166333
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
166334
+ FROM compartment_chunk_embeddings
166335
+ WHERE compartment_id = ?
166336
+ AND model_id = ?
166337
+ ${scopedToProject ? "AND project_path = ?" : ""}
166338
+ ORDER BY window_index ASC`);
166339
+ map2.set(db, stmt);
166340
+ }
166341
+ return stmt;
166342
+ }
166343
+ function getDeleteByCompartmentStatement(db) {
166344
+ let stmt = deleteByCompartmentStatements.get(db);
166345
+ if (!stmt) {
166346
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
166347
+ deleteByCompartmentStatements.set(db, stmt);
166348
+ }
166349
+ return stmt;
166350
+ }
166351
+ function getInsertEmbeddingStatement(db) {
166352
+ let stmt = insertEmbeddingStatements.get(db);
166353
+ if (!stmt) {
166354
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
166355
+ compartment_id, session_id, project_path, harness, window_index,
166356
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
166357
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
166358
+ insertEmbeddingStatements.set(db, stmt);
166359
+ }
166360
+ return stmt;
166361
+ }
166362
+ function getDistinctModelStatement(db) {
166363
+ let stmt = distinctModelStatements.get(db);
166364
+ if (!stmt) {
166365
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
166366
+ FROM compartment_chunk_embeddings
166367
+ WHERE project_path = ?`);
166368
+ distinctModelStatements.set(db, stmt);
166369
+ }
166370
+ return stmt;
166371
+ }
166372
+ function getClearProjectStatement(db) {
166373
+ let stmt = clearProjectStatements.get(db);
166374
+ if (!stmt) {
166375
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
166376
+ clearProjectStatements.set(db, stmt);
166377
+ }
166378
+ return stmt;
166379
+ }
166380
+ function getClearProjectModelStatement(db) {
166381
+ let stmt = clearProjectModelStatements.get(db);
166382
+ if (!stmt) {
166383
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
166384
+ clearProjectModelStatements.set(db, stmt);
166385
+ }
166386
+ return stmt;
166387
+ }
166388
+ function getSearchRowsStatement(db, withModel) {
166389
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
166390
+ let stmt = map2.get(db);
166391
+ if (!stmt) {
166392
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
166393
+ e.session_id AS sessionId,
166394
+ c.title AS title,
166395
+ c.start_message AS compartmentStart,
166396
+ c.end_message AS compartmentEnd,
166397
+ e.window_index AS windowIndex,
166398
+ e.start_ordinal AS windowStart,
166399
+ e.end_ordinal AS windowEnd,
166400
+ e.chunk_hash AS chunkHash,
166401
+ e.model_id AS modelId,
166402
+ e.dims AS dims,
166403
+ e.vector AS vector
166404
+ FROM compartment_chunk_embeddings e
166405
+ JOIN compartments c ON c.id = e.compartment_id
166406
+ WHERE e.session_id = ?
166407
+ AND e.project_path = ?
166408
+ ${withModel ? "AND e.model_id = ?" : ""}
166409
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
166410
+ map2.set(db, stmt);
166411
+ }
166412
+ return stmt;
166413
+ }
166414
+ function isFinitePositiveInteger(value) {
166415
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
166416
+ }
166417
+ function normalizeCompartmentChunkMaxInputTokens(value) {
166418
+ if (!isFinitePositiveInteger(value)) {
166419
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
166420
+ }
166421
+ return Math.max(1, Math.floor(value));
166422
+ }
166423
+ function normalizeContent(text) {
166424
+ return text.replace(/\s+/g, " ").trim();
166425
+ }
166426
+ function formatOrdinalRange(start, end) {
166427
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
166428
+ }
166429
+ function rolePrefix(role) {
166430
+ if (role === "user")
166431
+ return "U";
166432
+ if (role === "assistant")
166433
+ return "A";
166434
+ return null;
166435
+ }
166436
+ function parseOrdinal(value) {
166437
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
166438
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
166439
+ }
166440
+ function parseCanonicalLineRange(line) {
166441
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
166442
+ if (!match)
166443
+ return null;
166444
+ const start = Number.parseInt(match[1], 10);
166445
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
166446
+ if (!Number.isFinite(start) || !Number.isFinite(end))
166447
+ return null;
166448
+ return { start, end };
166449
+ }
166450
+ function hashChunkText(text) {
166451
+ return createHash8("sha256").update(text).digest("hex");
166452
+ }
166453
+ function vectorBlob(vector) {
166454
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
166455
+ }
166456
+ function toFloat32Array2(blob) {
166457
+ if (blob instanceof Uint8Array) {
166458
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
166459
+ return new Float32Array(buffer2);
166460
+ }
166461
+ return new Float32Array(blob.slice(0));
166462
+ }
166463
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
166464
+ if (endOrdinal < startOrdinal)
166465
+ return "";
166466
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
166467
+ const lines = [];
166468
+ let current = null;
166469
+ const flush2 = () => {
166470
+ if (!current || current.parts.length === 0)
166471
+ return;
166472
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
166473
+ current = null;
166474
+ };
166475
+ for (const row of rows) {
166476
+ const ordinal = parseOrdinal(row.messageOrdinal);
166477
+ const prefix = rolePrefix(row.role);
166478
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
166479
+ if (ordinal === null || prefix === null || content.length === 0)
166480
+ continue;
166481
+ if (current && current.role === prefix) {
166482
+ current.end = ordinal;
166483
+ current.parts.push(content);
166484
+ continue;
166485
+ }
166486
+ flush2();
166487
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
166488
+ }
166489
+ flush2();
166490
+ return lines.join(`
166491
+ `);
166492
+ }
166493
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
166494
+ const lines = [];
166495
+ for (const rawLine of chunkText.split(/\r?\n/)) {
166496
+ const line = rawLine.trim();
166497
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
166498
+ if (!match)
166499
+ continue;
166500
+ const lineStart = Number.parseInt(match[2], 10);
166501
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
166502
+ if (startOrdinal != null && lineEnd < startOrdinal)
166503
+ continue;
166504
+ if (endOrdinal != null && lineStart > endOrdinal)
166505
+ continue;
166506
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
166507
+ const ordinalSpan = lineEnd - lineStart + 1;
166508
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
166509
+ if (ordinalSpan === rawParts.length) {
166510
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
166511
+ if (part.startsWith("TC:"))
166512
+ return false;
166513
+ if (startOrdinal != null && ordinal < startOrdinal)
166514
+ return false;
166515
+ if (endOrdinal != null && ordinal > endOrdinal)
166516
+ return false;
166517
+ return true;
166518
+ });
166519
+ if (retained.length === 0)
166520
+ continue;
166521
+ const retainedStart = retained[0].ordinal;
166522
+ const retainedEnd = retained[retained.length - 1].ordinal;
166523
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
166524
+ continue;
166525
+ }
166526
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
166527
+ if (parts.length === 0)
166528
+ continue;
166529
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
166530
+ }
166531
+ return lines.join(`
166532
+ `);
166533
+ }
166534
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
166535
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
166536
+ if (lines.length === 0 || endOrdinal < startOrdinal)
166537
+ return [];
166538
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
166539
+ const effectiveMax = Math.max(1, Math.floor(normalizedMax * CHUNK_WINDOW_SAFETY_RATIO));
166540
+ const fullText = lines.join(`
166541
+ `);
166542
+ if (estimateTokens(fullText) <= effectiveMax) {
166543
+ return [
166544
+ {
166545
+ windowIndex: 0,
166546
+ startOrdinal,
166547
+ endOrdinal,
166548
+ text: fullText,
166549
+ chunkHash: hashChunkText(fullText)
166550
+ }
166551
+ ];
166552
+ }
166553
+ const windows = [];
166554
+ let currentLines = [];
166555
+ let currentStart = null;
166556
+ let currentEnd = null;
166557
+ let currentTokens = 0;
166558
+ const flush2 = () => {
166559
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
166560
+ return;
166561
+ const text = currentLines.join(`
166562
+ `);
166563
+ windows.push({
166564
+ windowIndex: windows.length + 1,
166565
+ startOrdinal: currentStart,
166566
+ endOrdinal: currentEnd,
166567
+ text,
166568
+ chunkHash: hashChunkText(text)
166569
+ });
166570
+ currentLines = [];
166571
+ currentStart = null;
166572
+ currentEnd = null;
166573
+ currentTokens = 0;
166574
+ };
166575
+ for (const line of lines) {
166576
+ const range = parseCanonicalLineRange(line);
166577
+ const lineStart = range?.start ?? startOrdinal;
166578
+ const lineEnd = range?.end ?? lineStart;
166579
+ const lineTokens = estimateTokens(line);
166580
+ if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
166581
+ flush2();
166582
+ }
166583
+ if (currentLines.length === 0) {
166584
+ currentStart = lineStart;
166585
+ }
166586
+ currentLines.push(line);
166587
+ currentEnd = lineEnd;
166588
+ currentTokens += lineTokens;
166589
+ }
166590
+ flush2();
166591
+ return windows;
166592
+ }
166593
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
166594
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
166595
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
166596
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
166597
+ }
166598
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
166599
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
166600
+ if (existing.size !== windows.length)
166601
+ return false;
166602
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
166603
+ }
166604
+ function replaceCompartmentChunkEmbeddings(db, rows) {
166605
+ if (rows.length === 0)
166606
+ return;
166607
+ const compartmentId = rows[0].compartmentId;
166608
+ const now = Date.now();
166609
+ db.transaction(() => {
166610
+ getDeleteByCompartmentStatement(db).run(compartmentId);
166611
+ const insert = getInsertEmbeddingStatement(db);
166612
+ for (const row of rows) {
166613
+ 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);
166614
+ }
166615
+ })();
166616
+ }
166617
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
166618
+ const rows = getDistinctModelStatement(db).all(projectPath);
166619
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
166620
+ }
166621
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
166622
+ if (modelId) {
166623
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
166624
+ }
166625
+ return getClearProjectStatement(db).run(projectPath).changes;
166626
+ }
166627
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
166628
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
166629
+ 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) => ({
166630
+ compartmentId: row.compartmentId,
166631
+ sessionId: row.sessionId,
166632
+ title: row.title,
166633
+ startOrdinal: row.compartmentStart,
166634
+ endOrdinal: row.compartmentEnd,
166635
+ windowIndex: row.windowIndex,
166636
+ windowStartOrdinal: row.windowStart,
166637
+ windowEndOrdinal: row.windowEnd,
166638
+ chunkHash: row.chunkHash,
166639
+ modelId: row.modelId,
166640
+ dims: row.dims,
166641
+ vector: toFloat32Array2(row.vector)
166642
+ }));
166643
+ }
166644
+ function mapBackfillCandidateRows(rows) {
166645
+ return rows.filter((row) => {
166646
+ if (row === null || typeof row !== "object")
166647
+ return false;
166648
+ const candidate = row;
166649
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
166650
+ }).map((row) => ({
166651
+ id: row.id,
166652
+ sessionId: row.sessionId,
166653
+ startMessage: row.startMessage,
166654
+ endMessage: row.endMessage,
166655
+ title: row.title
166656
+ }));
166657
+ }
166658
+ var sessionBackfillCandidateStatements = new WeakMap;
166659
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
166660
+ if (excludeIds && excludeIds.length > 0) {
166661
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
166662
+ const stmt2 = db.prepare(`SELECT c.id AS id,
166663
+ c.session_id AS sessionId,
166664
+ c.start_message AS startMessage,
166665
+ c.end_message AS endMessage,
166666
+ c.title AS title
166667
+ FROM compartments c
166668
+ JOIN session_projects sp
166669
+ ON sp.session_id = c.session_id
166670
+ AND sp.harness = c.harness
166671
+ AND sp.project_path = ?
166672
+ WHERE c.session_id = ?
166673
+ AND c.start_message IS NOT NULL
166674
+ AND c.end_message IS NOT NULL
166675
+ AND c.id NOT IN (${placeholders3})
166676
+ AND NOT EXISTS (
166677
+ SELECT 1
166678
+ FROM compartment_chunk_embeddings current
166679
+ WHERE current.compartment_id = c.id
166680
+ AND current.project_path = ?
166681
+ AND current.model_id = ?
166682
+ )
166683
+ ORDER BY c.start_message ASC, c.id ASC
166684
+ LIMIT ?`);
166685
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
166686
+ return mapBackfillCandidateRows(rows2);
166687
+ }
166688
+ let stmt = sessionBackfillCandidateStatements.get(db);
166689
+ if (!stmt) {
166690
+ stmt = db.prepare(`SELECT c.id AS id,
166691
+ c.session_id AS sessionId,
166692
+ c.start_message AS startMessage,
166693
+ c.end_message AS endMessage,
166694
+ c.title AS title
166695
+ FROM compartments c
166696
+ JOIN session_projects sp
166697
+ ON sp.session_id = c.session_id
166698
+ AND sp.harness = c.harness
166699
+ AND sp.project_path = ?
166700
+ WHERE c.session_id = ?
166701
+ AND c.start_message IS NOT NULL
166702
+ AND c.end_message IS NOT NULL
166703
+ AND NOT EXISTS (
166704
+ SELECT 1
166705
+ FROM compartment_chunk_embeddings current
166706
+ WHERE current.compartment_id = c.id
166707
+ AND current.project_path = ?
166708
+ AND current.model_id = ?
166709
+ )
166710
+ ORDER BY c.start_message ASC, c.id ASC
166711
+ LIMIT ?`);
166712
+ sessionBackfillCandidateStatements.set(db, stmt);
166713
+ }
166714
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
166715
+ return mapBackfillCandidateRows(rows);
166716
+ }
166717
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
166718
+ const row = db.prepare(`SELECT COUNT(*) AS n
166719
+ FROM compartments c
166720
+ JOIN session_projects sp
166721
+ ON sp.session_id = c.session_id
166722
+ AND sp.harness = c.harness
166723
+ AND sp.project_path = ?
166724
+ WHERE c.session_id = ?
166725
+ AND c.start_message IS NOT NULL
166726
+ AND c.end_message IS NOT NULL
166727
+ AND NOT EXISTS (
166728
+ SELECT 1
166729
+ FROM compartment_chunk_embeddings current
166730
+ WHERE current.compartment_id = c.id
166731
+ AND current.project_path = ?
166732
+ AND current.model_id = ?
166733
+ )`).get(projectPath, sessionId, projectPath, modelId);
166734
+ return typeof row?.n === "number" ? row.n : 0;
166735
+ }
166736
+
165653
166737
  // ../plugin/src/features/magic-context/memory/cosine-similarity.ts
165654
166738
  function cosineSimilarity(a, b) {
165655
166739
  if (a.length !== b.length) {
@@ -165808,19 +166892,19 @@ function isArrayLikeNumber(value) {
165808
166892
  }
165809
166893
  return arr.length === 0 || typeof arr[0] === "number";
165810
166894
  }
165811
- function toFloat32Array2(values) {
166895
+ function toFloat32Array3(values) {
165812
166896
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
165813
166897
  }
165814
166898
  function extractBatchEmbeddings(result, expectedCount) {
165815
166899
  const { data } = result;
165816
166900
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
165817
- return data.map((entry) => toFloat32Array2(entry));
166901
+ return data.map((entry) => toFloat32Array3(entry));
165818
166902
  }
165819
166903
  if (!isArrayLikeNumber(data)) {
165820
166904
  log("[magic-context] embedding batch returned unexpected data shape");
165821
166905
  return Array.from({ length: expectedCount }, () => null);
165822
166906
  }
165823
- const flatData = toFloat32Array2(data);
166907
+ const flatData = toFloat32Array3(data);
165824
166908
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
165825
166909
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
165826
166910
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -165835,6 +166919,7 @@ function extractBatchEmbeddings(result, expectedCount) {
165835
166919
 
165836
166920
  class LocalEmbeddingProvider {
165837
166921
  modelId;
166922
+ maxInputTokens;
165838
166923
  model;
165839
166924
  pipeline = null;
165840
166925
  initPromise = null;
@@ -165842,8 +166927,9 @@ class LocalEmbeddingProvider {
165842
166927
  disposing = false;
165843
166928
  disposePromise = null;
165844
166929
  inFlightWaiters = [];
165845
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
166930
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
165846
166931
  this.model = model;
166932
+ this.maxInputTokens = maxInputTokens;
165847
166933
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
165848
166934
  }
165849
166935
  async initialize() {
@@ -166091,6 +167177,13 @@ function blockedEmbeddingEndpointReason(endpoint) {
166091
167177
  function normalizeEndpoint2(endpoint) {
166092
167178
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
166093
167179
  }
167180
+ function embeddingModelsMatch(served, requested) {
167181
+ const a = served.trim().toLowerCase();
167182
+ const b = requested.trim().toLowerCase();
167183
+ if (a.length === 0 || b.length === 0)
167184
+ return true;
167185
+ return a === b || a.includes(b) || b.includes(a);
167186
+ }
166094
167187
  var FAILURE_THRESHOLD = 3;
166095
167188
  var FAILURE_WINDOW_MS = 60000;
166096
167189
  var OPEN_DURATION_MS = 5 * 60000;
@@ -166098,6 +167191,7 @@ var FETCH_TIMEOUT_MS = 30000;
166098
167191
 
166099
167192
  class OpenAICompatibleEmbeddingProvider {
166100
167193
  modelId;
167194
+ maxInputTokens;
166101
167195
  endpoint;
166102
167196
  model;
166103
167197
  apiKey;
@@ -166107,6 +167201,7 @@ class OpenAICompatibleEmbeddingProvider {
166107
167201
  failureTimes = [];
166108
167202
  circuitOpenUntil = 0;
166109
167203
  openLogged = false;
167204
+ modelMismatchLogged = false;
166110
167205
  halfOpenProbeInFlight = false;
166111
167206
  constructor(options) {
166112
167207
  this.endpoint = normalizeEndpoint2(options.endpoint);
@@ -166114,11 +167209,13 @@ class OpenAICompatibleEmbeddingProvider {
166114
167209
  this.apiKey = options.apiKey?.trim() ?? "";
166115
167210
  this.inputType = options.inputType?.trim() ?? "";
166116
167211
  this.truncate = options.truncate?.trim() ?? "";
167212
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
166117
167213
  this.modelId = getEmbeddingProviderIdentity({
166118
167214
  provider: "openai-compatible",
166119
167215
  endpoint: this.endpoint,
166120
167216
  model: this.model,
166121
- ...this.apiKey ? { api_key: this.apiKey } : {}
167217
+ ...this.apiKey ? { api_key: this.apiKey } : {},
167218
+ ...this.inputType ? { input_type: this.inputType } : {}
166122
167219
  });
166123
167220
  }
166124
167221
  async initialize() {
@@ -166203,6 +167300,15 @@ class OpenAICompatibleEmbeddingProvider {
166203
167300
  this.recordFailure(isProbe);
166204
167301
  return Array.from({ length: texts.length }, () => null);
166205
167302
  }
167303
+ const servedModel = typeof body.model === "string" ? body.model : "";
167304
+ if (this.model && servedModel && !embeddingModelsMatch(servedModel, this.model)) {
167305
+ if (!this.modelMismatchLogged) {
167306
+ log(`[magic-context] embedding endpoint served a DIFFERENT model than requested — refusing the substituted vectors (they have the wrong dimensions/space). requested="${this.model}" served="${servedModel}". The endpoint likely substituted a loaded model; load/select "${this.model}" on the endpoint, or set embedding.model to the served model.`);
167307
+ this.modelMismatchLogged = true;
167308
+ }
167309
+ this.recordFailure(isProbe);
167310
+ return Array.from({ length: texts.length }, () => null);
167311
+ }
166206
167312
  const items = Array.isArray(body.data) ? body.data : [];
166207
167313
  const results = Array.from({ length: texts.length }, (_, index) => {
166208
167314
  const embedding = items[index]?.embedding;
@@ -166309,7 +167415,7 @@ class OpenAICompatibleEmbeddingProvider {
166309
167415
  }
166310
167416
 
166311
167417
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
166312
- import { createHash as createHash7, randomUUID } from "node:crypto";
167418
+ import { createHash as createHash9, randomUUID } from "node:crypto";
166313
167419
  init_logger();
166314
167420
 
166315
167421
  // ../plugin/src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
@@ -166317,7 +167423,7 @@ var saveStatements = new WeakMap;
166317
167423
  var loadProjectStatements = new WeakMap;
166318
167424
  var loadUnembeddedStatements = new WeakMap;
166319
167425
  var countEmbeddedStatements = new WeakMap;
166320
- var clearProjectStatements = new WeakMap;
167426
+ var clearProjectStatements2 = new WeakMap;
166321
167427
  var distinctModelIdStatements = new WeakMap;
166322
167428
  function getSaveStatement(db) {
166323
167429
  let stmt = saveStatements.get(db);
@@ -166365,12 +167471,12 @@ function getCountEmbeddedStatement(db) {
166365
167471
  }
166366
167472
  return stmt;
166367
167473
  }
166368
- function getClearProjectStatement(db) {
166369
- let stmt = clearProjectStatements.get(db);
167474
+ function getClearProjectStatement2(db) {
167475
+ let stmt = clearProjectStatements2.get(db);
166370
167476
  if (!stmt) {
166371
167477
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
166372
167478
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
166373
- clearProjectStatements.set(db, stmt);
167479
+ clearProjectStatements2.set(db, stmt);
166374
167480
  }
166375
167481
  return stmt;
166376
167482
  }
@@ -166406,7 +167512,7 @@ function countEmbeddedCommits(db, projectPath) {
166406
167512
  return row?.count ?? 0;
166407
167513
  }
166408
167514
  function clearProjectCommitEmbeddings(db, projectPath) {
166409
- return getClearProjectStatement(db).run(projectPath).changes;
167515
+ return getClearProjectStatement2(db).run(projectPath).changes;
166410
167516
  }
166411
167517
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
166412
167518
  const rows = getDistinctModelIdStatement(db).all(projectPath);
@@ -166532,9 +167638,83 @@ function releaseGitSweepLease(db, projectPath, holderId) {
166532
167638
  });
166533
167639
  }
166534
167640
 
167641
+ // ../plugin/src/features/magic-context/session-project-storage.ts
167642
+ var upsertSessionProjectStatements = new WeakMap;
167643
+ var repairSessionChunkProjectStatements = new WeakMap;
167644
+ var repairProjectChunkProjectStatements = new WeakMap;
167645
+ function getUpsertSessionProjectStatement(db) {
167646
+ let stmt = upsertSessionProjectStatements.get(db);
167647
+ if (!stmt) {
167648
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
167649
+ VALUES (?, ?, ?, ?)
167650
+ ON CONFLICT(session_id, harness) DO UPDATE SET
167651
+ project_path = excluded.project_path,
167652
+ updated_at = excluded.updated_at
167653
+ WHERE session_projects.project_path <> excluded.project_path`);
167654
+ upsertSessionProjectStatements.set(db, stmt);
167655
+ }
167656
+ return stmt;
167657
+ }
167658
+ function getRepairSessionChunkProjectStatement(db) {
167659
+ let stmt = repairSessionChunkProjectStatements.get(db);
167660
+ if (!stmt) {
167661
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
167662
+ SET project_path = ?
167663
+ WHERE session_id = ?
167664
+ AND harness = ?
167665
+ AND project_path <> ?`);
167666
+ repairSessionChunkProjectStatements.set(db, stmt);
167667
+ }
167668
+ return stmt;
167669
+ }
167670
+ function getRepairProjectChunkProjectStatement(db) {
167671
+ let stmt = repairProjectChunkProjectStatements.get(db);
167672
+ if (!stmt) {
167673
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
167674
+ SET project_path = (
167675
+ SELECT sp.project_path
167676
+ FROM session_projects sp
167677
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
167678
+ AND sp.harness = compartment_chunk_embeddings.harness
167679
+ LIMIT 1
167680
+ )
167681
+ WHERE EXISTS (
167682
+ SELECT 1
167683
+ FROM session_projects sp
167684
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
167685
+ AND sp.harness = compartment_chunk_embeddings.harness
167686
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
167687
+ AND (
167688
+ sp.project_path = ?
167689
+ OR compartment_chunk_embeddings.project_path = ?
167690
+ )
167691
+ )`);
167692
+ repairProjectChunkProjectStatements.set(db, stmt);
167693
+ }
167694
+ return stmt;
167695
+ }
167696
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
167697
+ if (!sessionId || !projectPath)
167698
+ return;
167699
+ const harness = getHarness();
167700
+ const now = Date.now();
167701
+ db.transaction(() => {
167702
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
167703
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
167704
+ })();
167705
+ }
167706
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
167707
+ if (!projectPath)
167708
+ return 0;
167709
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
167710
+ }
167711
+
166535
167712
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
166536
167713
  var OFF_PROVIDER_IDENTITY = "embedding-provider:off";
166537
167714
  var SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167715
+ var CHUNK_DRAIN_BATCH_SIZE = 8;
167716
+ var MAX_WINDOWS_PER_EMBED_CALL = 16;
167717
+ var SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
166538
167718
  var projectRegistrations = new Map;
166539
167719
  var loadUnembeddedMemoriesStatements = new WeakMap;
166540
167720
  var globalRegistrationGeneration = 0;
@@ -166543,7 +167723,10 @@ function resolveEmbeddingConfig(config2) {
166543
167723
  if (!config2 || config2.provider === "local") {
166544
167724
  return {
166545
167725
  provider: "local",
166546
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
167726
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
167727
+ ...config2?.max_input_tokens ? {
167728
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
167729
+ } : {}
166547
167730
  };
166548
167731
  }
166549
167732
  if (config2.provider === "openai-compatible") {
@@ -166556,7 +167739,10 @@ function resolveEmbeddingConfig(config2) {
166556
167739
  endpoint: config2.endpoint.trim(),
166557
167740
  ...apiKey ? { api_key: apiKey } : {},
166558
167741
  ...inputType ? { input_type: inputType } : {},
166559
- ...truncate ? { truncate } : {}
167742
+ ...truncate ? { truncate } : {},
167743
+ ...config2.max_input_tokens ? {
167744
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
167745
+ } : {}
166560
167746
  };
166561
167747
  }
166562
167748
  return { provider: "off" };
@@ -166574,10 +167760,11 @@ function createProvider(config2) {
166574
167760
  model: config2.model,
166575
167761
  apiKey: config2.api_key,
166576
167762
  inputType: config2.input_type,
166577
- truncate: config2.truncate
167763
+ truncate: config2.truncate,
167764
+ maxInputTokens: config2.max_input_tokens
166578
167765
  });
166579
167766
  }
166580
- return new LocalEmbeddingProvider(config2.model);
167767
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
166581
167768
  }
166582
167769
  function stableStringify2(value) {
166583
167770
  if (Array.isArray(value)) {
@@ -166590,7 +167777,7 @@ function stableStringify2(value) {
166590
167777
  return JSON.stringify(value);
166591
167778
  }
166592
167779
  function sha256Prefix(value, length = 16) {
166593
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
167780
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
166594
167781
  }
166595
167782
  function getRuntimeFingerprint(config2) {
166596
167783
  if (config2.provider === "off") {
@@ -166598,6 +167785,18 @@ function getRuntimeFingerprint(config2) {
166598
167785
  }
166599
167786
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
166600
167787
  }
167788
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
167789
+ if (config2.provider === "off") {
167790
+ return OFF_PROVIDER_IDENTITY;
167791
+ }
167792
+ const chunkIdentity = {
167793
+ providerIdentity,
167794
+ chunkerVersion: 2,
167795
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
167796
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
167797
+ };
167798
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
167799
+ }
166601
167800
  function sameFeatures(a, b) {
166602
167801
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
166603
167802
  }
@@ -166614,7 +167813,8 @@ function snapshotFor(registration) {
166614
167813
  features: { ...registration.features },
166615
167814
  enabled,
166616
167815
  gitCommitEnabled,
166617
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
167816
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
167817
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
166618
167818
  };
166619
167819
  }
166620
167820
  function disposeProvider(provider) {
@@ -166634,7 +167834,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
166634
167834
  }
166635
167835
  return false;
166636
167836
  }
166637
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
167837
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
166638
167838
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
166639
167839
  return false;
166640
167840
  }
@@ -166655,6 +167855,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
166655
167855
  wiped = true;
166656
167856
  }
166657
167857
  }
167858
+ if (features.memoryEnabled) {
167859
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
167860
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
167861
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
167862
+ clearChunkEmbeddingsForProject(db, projectIdentity);
167863
+ wiped = true;
167864
+ }
167865
+ }
166658
167866
  })();
166659
167867
  return wiped;
166660
167868
  }
@@ -166662,10 +167870,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
166662
167870
  const resolvedConfig = resolveEmbeddingConfig(config2);
166663
167871
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
166664
167872
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
167873
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
166665
167874
  const prior = projectRegistrations.get(projectIdentity);
166666
167875
  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;
167876
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
167877
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
166669
167878
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
166670
167879
  const registration = {
166671
167880
  projectIdentity,
@@ -166677,6 +167886,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
166677
167886
  generation,
166678
167887
  features: { ...features },
166679
167888
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
167889
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
166680
167890
  observationMode: false
166681
167891
  };
166682
167892
  projectRegistrations.set(projectIdentity, registration);
@@ -166699,6 +167909,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
166699
167909
  generation,
166700
167910
  features: { memoryEnabled: false, gitCommitEnabled: false },
166701
167911
  modelId: "off",
167912
+ chunkModelId: "off",
166702
167913
  observationMode: true
166703
167914
  };
166704
167915
  projectRegistrations.set(projectIdentity, registration);
@@ -166709,6 +167920,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
166709
167920
  const registration = projectRegistrations.get(projectIdentity);
166710
167921
  return registration ? snapshotFor(registration) : null;
166711
167922
  }
167923
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
167924
+ const registration = projectRegistrations.get(projectIdentity);
167925
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
167926
+ }
167927
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
167928
+ const registration = projectRegistrations.get(projectIdentity);
167929
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
167930
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
167931
+ }
166712
167932
  function getOrCreateProjectProvider(registration) {
166713
167933
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
166714
167934
  return null;
@@ -166803,6 +168023,131 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
166803
168023
  return 0;
166804
168024
  }
166805
168025
  }
168026
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
168027
+ const noWork = [];
168028
+ if (candidates.length === 0)
168029
+ return { embedded: 0, noWork };
168030
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
168031
+ const prepared = [];
168032
+ for (const candidate of candidates) {
168033
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
168034
+ if (canonicalText.length === 0) {
168035
+ noWork.push(candidate.id);
168036
+ continue;
168037
+ }
168038
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
168039
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
168040
+ noWork.push(candidate.id);
168041
+ continue;
168042
+ }
168043
+ prepared.push({ candidate, windows });
168044
+ }
168045
+ if (prepared.length === 0)
168046
+ return { embedded: 0, noWork };
168047
+ let embedded = 0;
168048
+ let i = 0;
168049
+ while (i < prepared.length) {
168050
+ if (signal?.aborted)
168051
+ break;
168052
+ const slice = [];
168053
+ let windowCount = 0;
168054
+ do {
168055
+ const item = prepared[i];
168056
+ slice.push(item);
168057
+ windowCount += item.windows.length;
168058
+ i += 1;
168059
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
168060
+ const texts = [];
168061
+ for (const item of slice)
168062
+ texts.push(...item.windows.map((w) => w.text));
168063
+ try {
168064
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
168065
+ if (!result)
168066
+ continue;
168067
+ if (signal?.aborted)
168068
+ break;
168069
+ let offset = 0;
168070
+ for (const item of slice) {
168071
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
168072
+ offset += item.windows.length;
168073
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
168074
+ continue;
168075
+ }
168076
+ const rows = item.windows.map((window, index) => ({
168077
+ compartmentId: item.candidate.id,
168078
+ sessionId: item.candidate.sessionId,
168079
+ projectPath: projectIdentity,
168080
+ window,
168081
+ modelId,
168082
+ vector: vectors[index]
168083
+ }));
168084
+ replaceCompartmentChunkEmbeddings(db, rows);
168085
+ embedded += 1;
168086
+ }
168087
+ } catch (error51) {
168088
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
168089
+ }
168090
+ }
168091
+ return { embedded, noWork };
168092
+ }
168093
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
168094
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
168095
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
168096
+ return { status: "disabled", embedded: 0, total: 0 };
168097
+ }
168098
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
168099
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
168100
+ if (total === 0)
168101
+ return { status: "nothing", embedded: 0, total: 0 };
168102
+ const holderId = `session-embed-${randomUUID()}`;
168103
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
168104
+ if (!lease2.acquired)
168105
+ return { status: "busy", embedded: 0, total };
168106
+ const renewal = setInterval(() => {
168107
+ try {
168108
+ renewGitSweepLease(db, projectIdentity, holderId);
168109
+ } catch {}
168110
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
168111
+ renewal.unref?.();
168112
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
168113
+ const skipIds = [];
168114
+ let embedded = 0;
168115
+ let aborted2 = false;
168116
+ let providerStalled = false;
168117
+ try {
168118
+ options?.onProgress?.({ embedded, total });
168119
+ for (;; ) {
168120
+ if (options?.signal?.aborted) {
168121
+ aborted2 = true;
168122
+ break;
168123
+ }
168124
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
168125
+ if (candidates.length === 0)
168126
+ break;
168127
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
168128
+ for (const id of noWork)
168129
+ skipIds.push(id);
168130
+ if (n === 0 && noWork.length === 0) {
168131
+ providerStalled = true;
168132
+ break;
168133
+ }
168134
+ embedded += n;
168135
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
168136
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
168137
+ }
168138
+ } finally {
168139
+ clearInterval(renewal);
168140
+ releaseGitSweepLease(db, projectIdentity, holderId);
168141
+ }
168142
+ if (aborted2)
168143
+ return { status: "aborted", embedded, total };
168144
+ if (providerStalled) {
168145
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
168146
+ if (remaining > 0)
168147
+ return { status: "stalled", embedded, total, remaining };
168148
+ }
168149
+ return { status: "done", embedded, total };
168150
+ }
166806
168151
 
166807
168152
  // ../plugin/src/features/magic-context/memory/embedding.ts
166808
168153
  var DEFAULT_EMBEDDING_CONFIG = {
@@ -166822,10 +168167,11 @@ function createProvider2(config2) {
166822
168167
  model: config2.model,
166823
168168
  apiKey: config2.api_key,
166824
168169
  inputType: config2.input_type,
166825
- truncate: config2.truncate
168170
+ truncate: config2.truncate,
168171
+ maxInputTokens: config2.max_input_tokens
166826
168172
  });
166827
168173
  }
166828
- return new LocalEmbeddingProvider(config2.model);
168174
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
166829
168175
  }
166830
168176
  function getOrCreateProvider() {
166831
168177
  if (provider) {
@@ -167068,6 +168414,7 @@ init_logger();
167068
168414
  // ../plugin/src/features/magic-context/memory/storage-memory-fts.ts
167069
168415
  var DEFAULT_SEARCH_LIMIT = 10;
167070
168416
  var searchStatements = new WeakMap;
168417
+ var unionSearchStatements = new Map;
167071
168418
  function getSearchStatement(db) {
167072
168419
  let stmt = searchStatements.get(db);
167073
168420
  if (!stmt) {
@@ -167076,6 +168423,23 @@ function getSearchStatement(db) {
167076
168423
  }
167077
168424
  return stmt;
167078
168425
  }
168426
+ function getUnionSearchStatement(db, arity) {
168427
+ let statements = unionSearchStatements.get(arity);
168428
+ if (!statements) {
168429
+ statements = new WeakMap;
168430
+ unionSearchStatements.set(arity, statements);
168431
+ }
168432
+ let stmt = statements.get(db);
168433
+ if (!stmt) {
168434
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
168435
+ 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 ?`);
168436
+ statements.set(db, stmt);
168437
+ }
168438
+ return stmt;
168439
+ }
168440
+ function uniqueProjectPaths2(projectPaths) {
168441
+ return [...new Set(projectPaths.filter((path7) => path7.length > 0))];
168442
+ }
167079
168443
  function sanitizeFtsQuery(query) {
167080
168444
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
167081
168445
  if (tokens.length === 0)
@@ -167094,6 +168458,28 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
167094
168458
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
167095
168459
  return rows.map(toMemory);
167096
168460
  }
168461
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
168462
+ const identities = uniqueProjectPaths2(projectPaths);
168463
+ if (identities.length === 0)
168464
+ return [];
168465
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
168466
+ identities,
168467
+ ownIdentities,
168468
+ shareCategories,
168469
+ tableName: "memories"
168470
+ });
168471
+ if (identities.length === 1 && !sharingFilter.active) {
168472
+ return searchMemoriesFTS(db, identities[0], query, limit);
168473
+ }
168474
+ const trimmedQuery = query.trim();
168475
+ if (trimmedQuery.length === 0 || limit <= 0)
168476
+ return [];
168477
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
168478
+ if (sanitized.length === 0)
168479
+ return [];
168480
+ 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);
168481
+ return rows.map(toMemory);
168482
+ }
167097
168483
 
167098
168484
  // ../plugin/src/features/magic-context/git-commits/search-git-commits.ts
167099
168485
  var ftsStatements = new WeakMap;
@@ -167386,7 +168772,7 @@ async function sweepGitCommits(args) {
167386
168772
  }
167387
168773
 
167388
168774
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
167389
- import { createHash as createHash8 } from "node:crypto";
168775
+ import { createHash as createHash10 } from "node:crypto";
167390
168776
  init_logger();
167391
168777
  var EMBEDDING_AFFECTING_KEYS = new Set([
167392
168778
  "embedding.api_key",
@@ -167408,7 +168794,7 @@ var EMBEDDING_WARNING_TERMS = [
167408
168794
  ];
167409
168795
  var loggedFailureSignatures = new Map;
167410
168796
  function sha256Prefix2(value, length = 16) {
167411
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
168797
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
167412
168798
  }
167413
168799
  function warningLooksEmbeddingRelated(message) {
167414
168800
  const lower = message.toLowerCase();
@@ -168450,6 +169836,88 @@ function registerCtxDreamCommand(pi, deps) {
168450
169836
  });
168451
169837
  }
168452
169838
 
169839
+ // src/commands/ctx-embed-history.ts
169840
+ function registerCtxEmbedHistoryCommand(pi, deps) {
169841
+ pi.registerCommand("ctx-embed-history", {
169842
+ description: "Embed all of this session's history compartments for semantic search, in one pass",
169843
+ handler: async (_args, ctx) => {
169844
+ const sessionId = resolveSessionId(ctx);
169845
+ if (!sessionId) {
169846
+ sendCtxStatusMessage(pi, {
169847
+ title: "/ctx-embed-history",
169848
+ text: `## /ctx-embed-history
169849
+
169850
+ No active Pi session is available.`,
169851
+ level: "error"
169852
+ });
169853
+ return;
169854
+ }
169855
+ if (deps.memoryEnabled === false) {
169856
+ sendCtxStatusMessage(pi, {
169857
+ title: "/ctx-embed-history",
169858
+ text: `## /ctx-embed-history
169859
+
169860
+ Memory is disabled for this project, so there is no semantic embedding to backfill.`,
169861
+ level: "info"
169862
+ });
169863
+ return;
169864
+ }
169865
+ const project = deps.resolveProject?.(ctx) ?? {
169866
+ projectDir: deps.projectDir,
169867
+ projectIdentity: deps.projectIdentity
169868
+ };
169869
+ await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
169870
+ const outcome = await embedSessionCompartmentChunks(deps.db, project.projectIdentity, sessionId);
169871
+ const { text, level } = (() => {
169872
+ switch (outcome.status) {
169873
+ case "nothing":
169874
+ return {
169875
+ text: `## /ctx-embed-history
169876
+
169877
+ All of this session's history is already embedded.`,
169878
+ level: "info"
169879
+ };
169880
+ case "disabled":
169881
+ return {
169882
+ text: `## /ctx-embed-history
169883
+
169884
+ No embedding provider is configured, so there is nothing to embed.`,
169885
+ level: "info"
169886
+ };
169887
+ case "busy":
169888
+ return {
169889
+ text: `## /ctx-embed-history
169890
+
169891
+ Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`,
169892
+ level: "info"
169893
+ };
169894
+ case "stalled":
169895
+ return {
169896
+ text: `## /ctx-embed-history
169897
+
169898
+ 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.`,
169899
+ level: "info"
169900
+ };
169901
+ default:
169902
+ return {
169903
+ text: `## /ctx-embed-history
169904
+
169905
+ Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`,
169906
+ level: "success"
169907
+ };
169908
+ }
169909
+ })();
169910
+ sendCtxStatusMessage(pi, { title: "/ctx-embed-history", text, level }, {
169911
+ sessionId,
169912
+ projectIdentity: project.projectIdentity,
169913
+ status: outcome.status,
169914
+ embedded: outcome.embedded,
169915
+ total: outcome.total
169916
+ });
169917
+ }
169918
+ });
169919
+ }
169920
+
168453
169921
  // ../plugin/src/hooks/magic-context/execute-flush.ts
168454
169922
  init_logger();
168455
169923
  function executeFlush(db, sessionId) {
@@ -168863,6 +170331,51 @@ function computePiWorkMetrics(sessionEntries) {
168863
170331
  return { newWorkTokens, totalInputTokens };
168864
170332
  }
168865
170333
 
170334
+ // ../plugin/src/hooks/magic-context/system-injection-stripper.ts
170335
+ var SYSTEM_INJECTION_MARKERS = [
170336
+ "<!-- OMO_INTERNAL_INITIATOR -->",
170337
+ "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
170338
+ "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
170339
+ "[Category+Skill Reminder]",
170340
+ "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
170341
+ "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
170342
+ "[EMERGENCY CONTEXT WINDOW WARNING]",
170343
+ "Unstable background agent appears idle",
170344
+ "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
170345
+ ];
170346
+ var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
170347
+ var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
170348
+ function stripSystemInjection(text) {
170349
+ let hasInjection = false;
170350
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
170351
+ if (text.includes(marker)) {
170352
+ hasInjection = true;
170353
+ break;
170354
+ }
170355
+ }
170356
+ if (SYSTEM_REMINDER_REGEX.test(text))
170357
+ hasInjection = true;
170358
+ SYSTEM_REMINDER_REGEX.lastIndex = 0;
170359
+ if (!hasInjection)
170360
+ return null;
170361
+ let cleaned = text;
170362
+ cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
170363
+ cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
170364
+ cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
170365
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
170366
+ if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
170367
+ continue;
170368
+ const idx = cleaned.indexOf(marker);
170369
+ if (idx === -1)
170370
+ continue;
170371
+ const blockEnd = cleaned.indexOf(`
170372
+
170373
+ `, idx + marker.length);
170374
+ cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
170375
+ }
170376
+ return cleaned.trim();
170377
+ }
170378
+
168866
170379
  // ../plugin/src/hooks/magic-context/apply-operations.ts
168867
170380
  var USER_DROP_PREVIEW_CHARS = 250;
168868
170381
  var RECENT_TOOL_SKELETON_WINDOW = 20;
@@ -168872,6 +170385,10 @@ function buildReplacementContent(tagId, target) {
168872
170385
  return `[dropped §${tagId}§]`;
168873
170386
  }
168874
170387
  const currentContent = target.getContent?.() ?? "";
170388
+ const strippedInjection = stripSystemInjection(currentContent);
170389
+ if (strippedInjection !== null && stripTagPrefix(strippedInjection).trim().length === 0) {
170390
+ return `[dropped §${tagId}§]`;
170391
+ }
168875
170392
  const originalText = stripTagPrefix(currentContent);
168876
170393
  if (originalText.length <= USER_DROP_PREVIEW_CHARS) {
168877
170394
  return `[truncated §${tagId}§]
@@ -170480,8 +171997,8 @@ var CHANNEL1_SENTINEL = "<system-reminder>";
170480
171997
  var TOKENS_PER_BYTE = 0.25;
170481
171998
  var CHANNEL1_FLOOR_TOKENS = 1e4;
170482
171999
  var CHANNEL1_REFIRE_FLOOR_TOKENS = 1e4;
170483
- function channel1RefireTokens(historyBudgetTokens) {
170484
- const scaled = Math.round(0.05 * Math.max(0, historyBudgetTokens));
172000
+ function channel1RefireTokens(workingWindowTokens) {
172001
+ const scaled = Math.round(0.05 * Math.max(0, workingWindowTokens));
170485
172002
  return Math.max(CHANNEL1_REFIRE_FLOOR_TOKENS, scaled);
170486
172003
  }
170487
172004
  var S_GENTLE = 0.2;
@@ -170497,7 +172014,7 @@ function toolOutputTokens(output) {
170497
172014
  return Math.round(byteSize(output) * TOKENS_PER_BYTE);
170498
172015
  }
170499
172016
  function decideChannel1(input) {
170500
- const { undroppedTokens, pressure, historyBudgetTokens, hasRecentReduce } = input;
172017
+ const { undroppedTokens, pressure, workingWindowTokens, hasRecentReduce } = input;
170501
172018
  const resetCycle = hasRecentReduce || undroppedTokens < input.lastNudgeUndropped;
170502
172019
  const lastNudge = resetCycle ? 0 : input.lastNudgeUndropped;
170503
172020
  const lastLevel = resetCycle ? "" : input.lastNudgeLevel;
@@ -170512,7 +172029,7 @@ function decideChannel1(input) {
170512
172029
  return quiet();
170513
172030
  if (undroppedTokens < CHANNEL1_FLOOR_TOKENS)
170514
172031
  return quiet();
170515
- const budget = historyBudgetTokens > 0 ? historyBudgetTokens : undroppedTokens || 1;
172032
+ const budget = workingWindowTokens > 0 ? workingWindowTokens : undroppedTokens || 1;
170516
172033
  const severity = undroppedTokens / budget * pressure;
170517
172034
  if (severity < S_GENTLE)
170518
172035
  return quiet();
@@ -170524,7 +172041,7 @@ function decideChannel1(input) {
170524
172041
  else
170525
172042
  level = "gentle";
170526
172043
  if (lastLevel === "") {
170527
- if (undroppedTokens < lastNudge + channel1RefireTokens(historyBudgetTokens)) {
172044
+ if (undroppedTokens < lastNudge + channel1RefireTokens(workingWindowTokens)) {
170528
172045
  return quiet();
170529
172046
  }
170530
172047
  } else if (LEVEL_RANK[level] <= LEVEL_RANK[lastLevel]) {
@@ -170569,13 +172086,13 @@ function buildChannel1Reminder(level, undroppedTokens) {
170569
172086
  let body;
170570
172087
  switch (level) {
170571
172088
  case "gentle":
170572
- body = `You have ~${amount} tokens of tool output you have not reduced. ` + `Once you are done with earlier outputs, drop them with ctx_reduce to keep context lean.`;
172089
+ body = `You have ~${amount} tokens of tool output you have not reduced. ` + `When you are done with earlier outputs, dropping them with ctx_reduce keeps context lean.`;
170573
172090
  break;
170574
172091
  case "firm":
170575
- body = `~${amount} tokens of unreduced tool output is accumulating. ` + `Drop what you have already processed with ctx_reduce before continuing.`;
172092
+ body = `~${amount} tokens of unreduced tool output has built up. ` + `At your next natural stopping point, consider dropping what you have already processed with ctx_reduce.`;
170576
172093
  break;
170577
172094
  case "urgent":
170578
- body = `~${amount} tokens of unreduced tool output remain. ` + `A large span of this session will be comparted soon; drop spent outputs with ctx_reduce first so the archived span is the part that matters.`;
172095
+ body = `~${amount} tokens of unreduced tool output remain, and a large span of this session will be comparted before long. ` + `Consider dropping spent outputs with ctx_reduce so the archived span is the part that matters.`;
170579
172096
  break;
170580
172097
  }
170581
172098
  return `
@@ -170585,53 +172102,6 @@ ${body}
170585
172102
  </system-reminder>`;
170586
172103
  }
170587
172104
 
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
172105
  // ../plugin/src/shared/bounded-session-map.ts
170636
172106
  class BoundedSessionMap {
170637
172107
  maxEntries;
@@ -171042,26 +172512,40 @@ ${sections.join(`
171042
172512
  var DEFAULT_MEMORY_BUDGET_TOKENS = 8000;
171043
172513
  var MEMORY_BLOCK_WRAPPER_TOKENS = 6;
171044
172514
  var DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000;
172515
+ function memoryCanonicalIdentity(memory, workspace) {
172516
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
172517
+ }
172518
+ function memorySelectionOrder(left, right) {
172519
+ if (left.status === "permanent" && right.status !== "permanent")
172520
+ return -1;
172521
+ if (right.status === "permanent" && left.status !== "permanent")
172522
+ return 1;
172523
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
172524
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
172525
+ const importanceDiff = rightImportance - leftImportance;
172526
+ if (importanceDiff !== 0)
172527
+ return importanceDiff;
172528
+ return left.id - right.id;
172529
+ }
172530
+ function memoryRenderOrder(left, right) {
172531
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
172532
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
172533
+ const categoryDiff = aPriority - bPriority;
172534
+ if (categoryDiff !== 0)
172535
+ return categoryDiff;
172536
+ return left.id - right.id;
172537
+ }
171045
172538
  var maxCompartmentSeqStatements = new WeakMap;
171046
172539
  var maxMemoryIdStatements = new WeakMap;
171047
172540
  var legacyCompartmentCountStatements = new WeakMap;
171048
172541
  var m0CompartmentStatements = new WeakMap;
171049
172542
  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
- });
172543
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
172544
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
171061
172545
  const selected = [];
171062
172546
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
171063
172547
  for (const memory of selectionOrder) {
171064
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
172548
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
171065
172549
  if (usedTokens + memoryTokens > budgetTokens)
171066
172550
  continue;
171067
172551
  selected.push(memory);
@@ -171070,16 +172554,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
171070
172554
  if (selected.length < memories.length) {
171071
172555
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
171072
172556
  }
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
- });
172557
+ const renderOrder = [...selected].sort(memoryRenderOrder);
171081
172558
  return { selected, renderOrder };
171082
172559
  }
172560
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
172561
+ if (!workspace.isWorkspaced) {
172562
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
172563
+ }
172564
+ const selected = [];
172565
+ const selectedIds = new Set;
172566
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
172567
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
172568
+ const trySelect = (memory) => {
172569
+ if (selectedIds.has(memory.id))
172570
+ return false;
172571
+ const tokens = tokenCost(memory);
172572
+ if (usedTokens + tokens > budgetTokens)
172573
+ return false;
172574
+ selected.push(memory);
172575
+ selectedIds.add(memory.id);
172576
+ usedTokens += tokens;
172577
+ return true;
172578
+ };
172579
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
172580
+ trySelect(memory);
172581
+ }
172582
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
172583
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
172584
+ const byIdentity = new Map;
172585
+ for (const memory of memories) {
172586
+ if (memory.status === "permanent")
172587
+ continue;
172588
+ const identity = memoryCanonicalIdentity(memory, workspace);
172589
+ if (!identity)
172590
+ continue;
172591
+ const list = byIdentity.get(identity) ?? [];
172592
+ list.push(memory);
172593
+ byIdentity.set(identity, list);
172594
+ }
172595
+ for (const identity of workspace.identities) {
172596
+ let memberTokens = 0;
172597
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
172598
+ for (const memory of candidates) {
172599
+ if (selectedIds.has(memory.id))
172600
+ continue;
172601
+ const tokens = tokenCost(memory);
172602
+ if (memberTokens + tokens > floorTokens)
172603
+ continue;
172604
+ if (usedTokens + tokens > budgetTokens)
172605
+ continue;
172606
+ selected.push(memory);
172607
+ selectedIds.add(memory.id);
172608
+ usedTokens += tokens;
172609
+ memberTokens += tokens;
172610
+ }
172611
+ }
172612
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
172613
+ for (const memory of remaining) {
172614
+ trySelect(memory);
172615
+ }
172616
+ if (selected.length < memories.length) {
172617
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
172618
+ }
172619
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
172620
+ }
171083
172621
  function trimUserMemoriesToBudget(memories, budgetTokens) {
171084
172622
  const selected = [];
171085
172623
  let usedTokens = 0;
@@ -171092,15 +172630,16 @@ function trimUserMemoriesToBudget(memories, budgetTokens) {
171092
172630
  }
171093
172631
  return selected;
171094
172632
  }
171095
- function renderMemoryLineV2(memory) {
171096
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
172633
+ function renderMemoryLineV2(memory, sourceName) {
172634
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
172635
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
171097
172636
  }
171098
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
172637
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
171099
172638
  if (memories.length === 0)
171100
172639
  return "";
171101
172640
  const lines = [`<${wrapper}>`];
171102
172641
  for (const memory of memories) {
171103
- lines.push(renderMemoryLineV2(memory));
172642
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
171104
172643
  }
171105
172644
  lines.push(`</${wrapper}>`);
171106
172645
  return lines.join(`
@@ -171694,7 +173233,7 @@ async function ensureMemoryEmbeddings(args) {
171694
173233
  continue;
171695
173234
  }
171696
173235
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
171697
- staged.set(memory.id, embedding);
173236
+ staged.set(memory.id, { embedding, modelId: result.modelId });
171698
173237
  }
171699
173238
  })();
171700
173239
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -171785,6 +173324,37 @@ function previewText(text) {
171785
173324
  }
171786
173325
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
171787
173326
  }
173327
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
173328
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
173329
+ const isWorkspaced = resolved.identities.length > 1;
173330
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
173331
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
173332
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
173333
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
173334
+ return {
173335
+ identities: resolved.identities,
173336
+ expandedIdentities,
173337
+ ownIdentities,
173338
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
173339
+ namesByIdentity: resolved.namesByIdentity,
173340
+ canonicalIdentityByStoredPath,
173341
+ isWorkspaced
173342
+ };
173343
+ }
173344
+ function memoryWorkspaceIdentity(memory, workspace) {
173345
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
173346
+ }
173347
+ function sourceNamesForSearchMemories(args) {
173348
+ if (!args.workspace.isWorkspaced)
173349
+ return;
173350
+ const sourceNames = new Map;
173351
+ for (const memory of args.memories) {
173352
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
173353
+ if (source)
173354
+ sourceNames.set(memory.id, source);
173355
+ }
173356
+ return sourceNames.size > 0 ? sourceNames : undefined;
173357
+ }
171788
173358
  function getMessageSearchStatement(db) {
171789
173359
  let stmt = messageSearchStatements.get(db);
171790
173360
  if (!stmt) {
@@ -171793,6 +173363,30 @@ function getMessageSearchStatement(db) {
171793
173363
  }
171794
173364
  return stmt;
171795
173365
  }
173366
+ var ftsRowCountStatements = new WeakMap;
173367
+ var ftsMatchCountStatements = new WeakMap;
173368
+ function getSessionFtsRowCount(db, sessionId) {
173369
+ let stmt = ftsRowCountStatements.get(db);
173370
+ if (!stmt) {
173371
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
173372
+ ftsRowCountStatements.set(db, stmt);
173373
+ }
173374
+ const row = stmt.get(sessionId);
173375
+ return typeof row?.n === "number" ? row.n : 0;
173376
+ }
173377
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
173378
+ let stmt = ftsMatchCountStatements.get(db);
173379
+ if (!stmt) {
173380
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
173381
+ ftsMatchCountStatements.set(db, stmt);
173382
+ }
173383
+ try {
173384
+ const row = stmt.get(sessionId, ftsQuery);
173385
+ return typeof row?.n === "number" ? row.n : 0;
173386
+ } catch {
173387
+ return 0;
173388
+ }
173389
+ }
171796
173390
  function getMessageOrdinal(value) {
171797
173391
  if (typeof value === "number" && Number.isFinite(value)) {
171798
173392
  return value;
@@ -171808,25 +173402,63 @@ async function getSemanticScores(args) {
171808
173402
  if (!args.queryEmbedding || args.memories.length === 0) {
171809
173403
  return semanticScores;
171810
173404
  }
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
- });
173405
+ if (!args.workspace?.isWorkspaced) {
173406
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
173407
+ const embeddings = await ensureMemoryEmbeddings({
173408
+ db: args.db,
173409
+ projectIdentity: args.projectPath,
173410
+ memories: args.memories,
173411
+ existingEmbeddings: cachedEmbeddings
173412
+ });
173413
+ for (const memory of args.memories) {
173414
+ const memoryEmbedding = embeddings.get(memory.id);
173415
+ if (!memoryEmbedding) {
173416
+ continue;
173417
+ }
173418
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
173419
+ }
173420
+ return semanticScores;
173421
+ }
173422
+ if (!args.queryModelId || args.queryModelId === "off") {
173423
+ return semanticScores;
173424
+ }
173425
+ const workspace = args.workspace;
173426
+ const memoriesByIdentity = new Map;
171818
173427
  for (const memory of args.memories) {
171819
- const memoryEmbedding = embeddings.get(memory.id);
171820
- if (!memoryEmbedding) {
173428
+ const identity = memoryWorkspaceIdentity(memory, workspace);
173429
+ if (!identity)
173430
+ continue;
173431
+ const list = memoriesByIdentity.get(identity) ?? [];
173432
+ list.push(memory);
173433
+ memoriesByIdentity.set(identity, list);
173434
+ }
173435
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
173436
+ if (ownMemories.length > 0) {
173437
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
173438
+ await ensureMemoryEmbeddings({
173439
+ db: args.db,
173440
+ projectIdentity: args.projectPath,
173441
+ memories: ownMemories,
173442
+ existingEmbeddings: ownEmbeddings
173443
+ });
173444
+ }
173445
+ for (const identity of workspace.identities) {
173446
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
173447
+ if (memberMemories.length === 0)
171821
173448
  continue;
173449
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
173450
+ for (const memory of memberMemories) {
173451
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
173452
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
173453
+ continue;
173454
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
171822
173455
  }
171823
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
171824
173456
  }
171825
173457
  return semanticScores;
171826
173458
  }
171827
173459
  function getFtsMatches(args) {
171828
173460
  try {
171829
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
173461
+ 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
173462
  } catch (error51) {
171831
173463
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
171832
173464
  return [];
@@ -171840,8 +173472,11 @@ function selectSemanticCandidates(args) {
171840
173472
  return args.memories;
171841
173473
  }
171842
173474
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
171843
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
171844
- if (cachedEmbeddings) {
173475
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
173476
+ for (const projectPath of embeddingProjects) {
173477
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
173478
+ if (!cachedEmbeddings)
173479
+ continue;
171845
173480
  for (const memoryId of cachedEmbeddings.keys()) {
171846
173481
  candidateIds.add(memoryId);
171847
173482
  }
@@ -171883,7 +173518,8 @@ function mergeMemoryResults(args) {
171883
173518
  score,
171884
173519
  memoryId: memory.id,
171885
173520
  category: memory.category,
171886
- matchType
173521
+ matchType,
173522
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
171887
173523
  });
171888
173524
  }
171889
173525
  return results.sort((left, right) => {
@@ -171897,7 +173533,7 @@ async function searchMemories(args) {
171897
173533
  if (!args.memoryEnabled) {
171898
173534
  return [];
171899
173535
  }
171900
- const memories = getMemoriesByProject(args.db, args.projectPath);
173536
+ 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
173537
  if (memories.length === 0) {
171902
173538
  return [];
171903
173539
  }
@@ -171905,26 +173541,43 @@ async function searchMemories(args) {
171905
173541
  db: args.db,
171906
173542
  projectPath: args.projectPath,
171907
173543
  query: args.query,
171908
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
173544
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
173545
+ workspace: args.workspace
171909
173546
  });
171910
173547
  const ftsScores = getFtsScores(ftsMatches);
171911
173548
  const semanticCandidates = selectSemanticCandidates({
171912
173549
  memories,
171913
173550
  projectPath: args.projectPath,
171914
- ftsMatches
173551
+ ftsMatches,
173552
+ workspace: args.workspace
171915
173553
  });
171916
173554
  const semanticScores = await getSemanticScores({
171917
173555
  db: args.db,
171918
173556
  projectPath: args.projectPath,
171919
173557
  memories: semanticCandidates,
171920
- queryEmbedding: args.queryEmbedding
173558
+ queryEmbedding: args.queryEmbedding,
173559
+ queryModelId: args.queryModelId,
173560
+ workspace: args.workspace
171921
173561
  });
171922
173562
  return mergeMemoryResults({
171923
173563
  memories,
171924
173564
  semanticScores,
171925
173565
  ftsScores,
171926
173566
  limit: args.limit,
171927
- visibleMemoryIds: args.visibleMemoryIds
173567
+ visibleMemoryIds: args.visibleMemoryIds,
173568
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
173569
+ memories,
173570
+ projectPath: args.projectPath,
173571
+ workspace: args.workspace ?? {
173572
+ identities: [args.projectPath],
173573
+ expandedIdentities: [args.projectPath],
173574
+ namesByIdentity: new Map,
173575
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
173576
+ ownIdentities: [args.projectPath],
173577
+ shareCategories: null,
173578
+ isWorkspaced: false
173579
+ }
173580
+ })
171928
173581
  });
171929
173582
  }
171930
173583
  function linearDecayScore(rank, total) {
@@ -171955,7 +173608,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
171955
173608
  return result;
171956
173609
  }
171957
173610
  var RRF_K = 60;
171958
- var VERBATIM_PROBE_BONUS = 0.5;
173611
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
173612
+ var IDF_FALLOFF = 100;
173613
+ function probeDiscriminationWeight(df, corpusSize) {
173614
+ if (corpusSize <= 0 || df <= 0)
173615
+ return 1;
173616
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
173617
+ }
171959
173618
  function searchMessages(args) {
171960
173619
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
171961
173620
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -171972,20 +173631,31 @@ function searchMessages(args) {
171972
173631
  role: row.role
171973
173632
  }));
171974
173633
  }
173634
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
171975
173635
  const queryLists = [];
171976
173636
  if (baseQuery.length > 0) {
171977
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
173637
+ queryLists.push({
173638
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
173639
+ weight: 1
173640
+ });
171978
173641
  }
173642
+ const probeWeights = new Map;
171979
173643
  for (const probe of probes) {
171980
173644
  const probeQuery = sanitizeFtsQuery(probe);
171981
173645
  if (probeQuery.length === 0)
171982
173646
  continue;
171983
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
173647
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
173648
+ const weight = probeDiscriminationWeight(df, corpusSize);
173649
+ probeWeights.set(probe, weight);
173650
+ queryLists.push({
173651
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
173652
+ weight
173653
+ });
171984
173654
  }
171985
173655
  const fused = new Map;
171986
173656
  for (const list of queryLists) {
171987
- list.forEach((row, rank) => {
171988
- const rrf = 1 / (RRF_K + rank);
173657
+ list.rows.forEach((row, rank) => {
173658
+ const rrf = list.weight / (RRF_K + rank);
171989
173659
  const existing = fused.get(row.messageId);
171990
173660
  if (existing) {
171991
173661
  existing.score += rrf;
@@ -171995,26 +173665,107 @@ function searchMessages(args) {
171995
173665
  });
171996
173666
  }
171997
173667
  for (const entry of fused.values()) {
171998
- if (containsProbeVerbatim(entry.row.content, probes)) {
171999
- entry.score += VERBATIM_PROBE_BONUS;
173668
+ let best = 0;
173669
+ for (const probe of probes) {
173670
+ const weight = probeWeights.get(probe) ?? 0;
173671
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
173672
+ best = weight;
173673
+ }
173674
+ }
173675
+ if (best > 0) {
173676
+ entry.score += best * VERBATIM_RANK_BONUS;
172000
173677
  }
172001
173678
  }
172002
173679
  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) => ({
173680
+ return ranked.map((entry, rank) => ({
172005
173681
  source: "message",
172006
173682
  content: previewText(entry.row.content),
172007
- score: maxScore > 0 ? entry.score / maxScore : 0,
173683
+ score: linearDecayScore(rank, ranked.length),
172008
173684
  messageOrdinal: entry.row.messageOrdinal,
172009
173685
  messageId: entry.row.messageId,
172010
173686
  role: entry.row.role
172011
173687
  }));
172012
173688
  }
173689
+ function searchCompartmentChunks(args) {
173690
+ if (!args.queryEmbedding || args.limit <= 0)
173691
+ return [];
173692
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
173693
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
173694
+ if (rows.length === 0)
173695
+ return [];
173696
+ const byCompartment = new Map;
173697
+ for (const row of rows) {
173698
+ if (cutoff !== null && row.endOrdinal > cutoff) {
173699
+ continue;
173700
+ }
173701
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
173702
+ if (score <= 0)
173703
+ continue;
173704
+ const existing = byCompartment.get(row.compartmentId);
173705
+ if (!existing || score > existing.score) {
173706
+ byCompartment.set(row.compartmentId, { row, score });
173707
+ }
173708
+ }
173709
+ 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 }) => ({
173710
+ source: "compartment",
173711
+ content: previewText(row.title),
173712
+ score: score * SINGLE_SOURCE_PENALTY,
173713
+ compartmentId: row.compartmentId,
173714
+ sessionId: row.sessionId,
173715
+ title: row.title,
173716
+ startOrdinal: row.startOrdinal,
173717
+ endOrdinal: row.endOrdinal,
173718
+ matchType: "semantic"
173719
+ }));
173720
+ }
173721
+ function mergeMessageAndCompartmentResults(args) {
173722
+ if (args.compartments.length === 0)
173723
+ return args.messages;
173724
+ if (args.messages.length === 0)
173725
+ return args.compartments;
173726
+ const fused = new Map;
173727
+ const add = (key, result, score, tieOrdinal) => {
173728
+ const existing = fused.get(key);
173729
+ if (existing) {
173730
+ existing.score += score;
173731
+ return existing;
173732
+ }
173733
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
173734
+ fused.set(key, entry);
173735
+ return entry;
173736
+ };
173737
+ args.compartments.forEach((compartment, rank) => {
173738
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
173739
+ });
173740
+ for (const [rank, message] of args.messages.entries()) {
173741
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
173742
+ const contribution = 1 / (RRF_K + rank);
173743
+ if (!containing) {
173744
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
173745
+ continue;
173746
+ }
173747
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
173748
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
173749
+ entry.snippetScore = message.score;
173750
+ entry.result = {
173751
+ ...entry.result,
173752
+ matchType: "hybrid",
173753
+ snippet: message.content
173754
+ };
173755
+ }
173756
+ }
173757
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
173758
+ return ranked.map((entry, rank) => ({
173759
+ ...entry.result,
173760
+ score: linearDecayScore(rank, ranked.length)
173761
+ }));
173762
+ }
172013
173763
  function getSourceBoost(result) {
172014
173764
  switch (result.source) {
172015
173765
  case "memory":
172016
173766
  return MEMORY_SOURCE_BOOST;
172017
173767
  case "message":
173768
+ case "compartment":
172018
173769
  return MESSAGE_SOURCE_BOOST;
172019
173770
  case "git_commit":
172020
173771
  return GIT_COMMIT_SOURCE_BOOST;
@@ -172032,6 +173783,9 @@ function compareUnifiedResults(left, right) {
172032
173783
  if (left.source === "message" && right.source === "message") {
172033
173784
  return left.messageOrdinal - right.messageOrdinal;
172034
173785
  }
173786
+ if (left.source === "compartment" && right.source === "compartment") {
173787
+ return left.startOrdinal - right.startOrdinal;
173788
+ }
172035
173789
  if (left.source === "git_commit" && right.source === "git_commit") {
172036
173790
  return right.committedAtMs - left.committedAtMs;
172037
173791
  }
@@ -172082,10 +173836,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172082
173836
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
172083
173837
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
172084
173838
  const activeSources = resolveSources(options.sources);
172085
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
173839
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
173840
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
172086
173841
  const runMessages = activeSources.has("message");
172087
173842
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
172088
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
173843
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
173844
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
172089
173845
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
172090
173846
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
172091
173847
  return null;
@@ -172101,6 +173857,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172101
173857
  probes: messageProbes
172102
173858
  }) : [];
172103
173859
  const queryEmbedding = await queryEmbeddingPromise;
173860
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
173861
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
173862
+ const embeddingModelId = embeddingSnapshot?.modelId;
173863
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
173864
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
173865
+ db,
173866
+ sessionId,
173867
+ projectPath,
173868
+ queryEmbedding,
173869
+ limit: tierLimit,
173870
+ maxOrdinal: options.maxMessageOrdinal,
173871
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
173872
+ }) : [];
173873
+ const messageLikeResults = mergeMessageAndCompartmentResults({
173874
+ messages: messageResults,
173875
+ compartments: compartmentResults,
173876
+ limit: tierLimit
173877
+ });
172104
173878
  const [memoryResults, gitCommitResults] = await Promise.all([
172105
173879
  runMemory ? searchMemories({
172106
173880
  db,
@@ -172109,6 +173883,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172109
173883
  limit: tierLimit,
172110
173884
  memoryEnabled: true,
172111
173885
  queryEmbedding,
173886
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
173887
+ workspace,
172112
173888
  visibleMemoryIds: options.visibleMemoryIds
172113
173889
  }) : Promise.resolve([]),
172114
173890
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -172119,7 +173895,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172119
173895
  queryEmbedding
172120
173896
  })) : Promise.resolve([])
172121
173897
  ]);
172122
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
173898
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
172123
173899
  const countRetrievals = options.countRetrievals ?? true;
172124
173900
  if (countRetrievals) {
172125
173901
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -172179,6 +173955,11 @@ function renderFragment(result, charCap) {
172179
173955
  const compressed = cavemanCompress(result.content, "ultra");
172180
173956
  return truncate(compressed, charCap);
172181
173957
  }
173958
+ case "compartment": {
173959
+ const source = result.snippet ?? result.title;
173960
+ const compressed = cavemanCompress(source, "ultra");
173961
+ return truncate(compressed, charCap);
173962
+ }
172182
173963
  }
172183
173964
  }
172184
173965
  function buildAutoSearchHint(results, options = {}) {
@@ -172630,10 +174411,11 @@ function maybeChannel1ReminderForToolResult(args) {
172630
174411
  contextLimit: state.contextLimit,
172631
174412
  executeThresholdPercentage: state.executeThresholdPercentage
172632
174413
  });
174414
+ const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
172633
174415
  const decision = decideChannel1({
172634
174416
  undroppedTokens,
172635
174417
  pressure,
172636
- historyBudgetTokens: state.historyBudgetTokens,
174418
+ workingWindowTokens,
172637
174419
  lastNudgeUndropped: getLastNudgeUndropped(db, sessionId),
172638
174420
  lastNudgeLevel: getLastNudgeLevel(db, sessionId),
172639
174421
  hasRecentReduce: false
@@ -172868,51 +174650,6 @@ function planEmergencyDrop(input) {
172868
174650
  };
172869
174651
  }
172870
174652
 
172871
- // ../plugin/src/hooks/magic-context/system-injection-stripper.ts
172872
- var SYSTEM_INJECTION_MARKERS = [
172873
- "<!-- OMO_INTERNAL_INITIATOR -->",
172874
- "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
172875
- "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
172876
- "[Category+Skill Reminder]",
172877
- "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
172878
- "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
172879
- "[EMERGENCY CONTEXT WINDOW WARNING]",
172880
- "Unstable background agent appears idle",
172881
- "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
172882
- ];
172883
- var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
172884
- var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
172885
- function stripSystemInjection(text) {
172886
- let hasInjection = false;
172887
- for (const marker of SYSTEM_INJECTION_MARKERS) {
172888
- if (text.includes(marker)) {
172889
- hasInjection = true;
172890
- break;
172891
- }
172892
- }
172893
- if (SYSTEM_REMINDER_REGEX.test(text))
172894
- hasInjection = true;
172895
- SYSTEM_REMINDER_REGEX.lastIndex = 0;
172896
- if (!hasInjection)
172897
- return null;
172898
- let cleaned = text;
172899
- cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
172900
- cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
172901
- cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
172902
- for (const marker of SYSTEM_INJECTION_MARKERS) {
172903
- if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
172904
- continue;
172905
- const idx = cleaned.indexOf(marker);
172906
- if (idx === -1)
172907
- continue;
172908
- const blockEnd = cleaned.indexOf(`
172909
-
172910
- `, idx + marker.length);
172911
- cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
172912
- }
172913
- return cleaned.trim();
172914
- }
172915
-
172916
174653
  // src/heuristic-cleanup-pi.ts
172917
174654
  init_logger();
172918
174655
  var DEDUP_SAFE_TOOLS = new Set([
@@ -173602,6 +175339,49 @@ function safeGetActiveUserMemoriesPi(db) {
173602
175339
  function memoryProjectPath(state) {
173603
175340
  return state.memoryEnabled === false ? undefined : state.projectIdentity;
173604
175341
  }
175342
+ function resolveWorkspaceRenderContextPi(state, db) {
175343
+ const memPath = memoryProjectPath(state);
175344
+ if (!memPath) {
175345
+ return {
175346
+ identities: [],
175347
+ expandedIdentities: [],
175348
+ ownIdentities: [],
175349
+ shareCategories: null,
175350
+ namesByIdentity: new Map,
175351
+ canonicalIdentityByStoredPath: new Map,
175352
+ isWorkspaced: false
175353
+ };
175354
+ }
175355
+ const identitySet = resolveWorkspaceIdentitySet(db, memPath);
175356
+ const isWorkspaced = identitySet.identities.length > 1;
175357
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, identitySet.identities);
175358
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
175359
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
175360
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === memPath);
175361
+ if (ownIdentities.length === 0 && expandedIdentities.includes(memPath)) {
175362
+ ownIdentities = [memPath];
175363
+ }
175364
+ return {
175365
+ identities: identitySet.identities,
175366
+ expandedIdentities,
175367
+ ownIdentities,
175368
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, memPath) : null,
175369
+ namesByIdentity: identitySet.namesByIdentity,
175370
+ canonicalIdentityByStoredPath,
175371
+ isWorkspaced
175372
+ };
175373
+ }
175374
+ function sourceNamesForPiMemories(args) {
175375
+ if (!args.projectPath || !args.workspace.isWorkspaced)
175376
+ return;
175377
+ const names = new Map;
175378
+ for (const memory of args.memories) {
175379
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
175380
+ if (source)
175381
+ names.set(memory.id, source);
175382
+ }
175383
+ return names.size > 0 ? names : undefined;
175384
+ }
173605
175385
  var EMPTY_PI_HARD_SIGNALS = {
173606
175386
  systemHash: "",
173607
175387
  modelKey: "",
@@ -173649,6 +175429,7 @@ function getCachedMarkers(db, state, compartmentsForNormalization) {
173649
175429
  maxMutationId: meta3.cachedM0MaxMutationId,
173650
175430
  maxMemoryMutationId: meta3.cachedM0MaxMemoryMutationId,
173651
175431
  projectMemoryEpoch: meta3.cachedM0ProjectMemoryEpoch,
175432
+ workspaceFingerprint: meta3.cachedM0WorkspaceFingerprint,
173652
175433
  projectUserProfileVersion: meta3.cachedM0ProjectUserProfileVersion,
173653
175434
  projectDocsHash: meta3.cachedM0ProjectDocsHash,
173654
175435
  sessionFactsVersion: meta3.cachedM0SessionFactsVersion,
@@ -173668,15 +175449,17 @@ function readCurrentMarkers(db, state, projectDocsHash) {
173668
175449
  }
173669
175450
  function readCurrentMarkersFromCompartments(db, state, compartments, projectDocsHash) {
173670
175451
  const memPath = memoryProjectPath(state);
173671
- const memories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"]) : [];
175452
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
175453
+ const maxMemoryId = memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0;
173672
175454
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
173673
175455
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
173674
175456
  return {
173675
175457
  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,
175458
+ maxMemoryId,
173677
175459
  maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
173678
- maxMemoryMutationId: memPath ? getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
175460
+ maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
173679
175461
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
175462
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
173680
175463
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
173681
175464
  projectDocsHash: projectDocsHash ?? readProjectDocsCanonical(state.projectDirectory).canonicalHash,
173682
175465
  sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
@@ -173714,10 +175497,11 @@ function mustMaterializePi(state, db, currentCompartmentsOverride) {
173714
175497
  if (meta3.cachedM0UpgradeState !== current.upgradeState) {
173715
175498
  return { value: true, reason: "renderer_upgrade" };
173716
175499
  }
173717
- if (current.projectDocsHash !== (meta3.cachedM0ProjectDocsHash ?? "")) {
173718
- return { value: true, reason: "project_docs_change" };
173719
- }
173720
- if (current.projectMemoryEpoch !== (meta3.cachedM0ProjectMemoryEpoch ?? 0)) {
175500
+ if (current.workspaceFingerprint !== null || (meta3.cachedM0WorkspaceFingerprint ?? null) !== null) {
175501
+ if (current.workspaceFingerprint !== (meta3.cachedM0WorkspaceFingerprint ?? null)) {
175502
+ return { value: true, reason: "project_memory_change" };
175503
+ }
175504
+ } else if (current.projectMemoryEpoch !== (meta3.cachedM0ProjectMemoryEpoch ?? 0)) {
173721
175505
  return { value: true, reason: "project_memory_change" };
173722
175506
  }
173723
175507
  if (current.maxMutationId !== (meta3.cachedM0MaxMutationId ?? 0)) {
@@ -173734,11 +175518,19 @@ ${memories.map((memory) => `- ${escapeXmlContent(memory.content)}`).join(`
173734
175518
  `)}
173735
175519
  </${wrapper}>`;
173736
175520
  }
173737
- function renderM0Pi(state, db, projectDocs = readProjectDocsCanonical(state.projectDirectory).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride) {
175521
+ function renderM0Pi(state, db, projectDocs = readProjectDocsCanonical(state.projectDirectory).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride, workspaceOverride) {
173738
175522
  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;
175523
+ const workspace = workspaceOverride ?? resolveWorkspaceRenderContextPi(state, db);
175524
+ const allMemories = memoriesOverride ?? (memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
175525
+ const memoryRenderOptions = {
175526
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175527
+ memories: allMemories,
175528
+ projectPath: memPath,
175529
+ workspace
175530
+ })
175531
+ };
175532
+ 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;
175533
+ const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories, "project-memory", memoryRenderOptions) : undefined;
173742
175534
  const baseHistoryBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173743
175535
  const decayed = renderDecayedCompartments({
173744
175536
  compartments: compartmentsOverride ?? getCompartments(db, state.sessionId),
@@ -173760,10 +175552,19 @@ ${decayed}
173760
175552
 
173761
175553
  `).trim();
173762
175554
  }
173763
- function renderedMemoryIdsForPi(state, memories) {
175555
+ function renderedMemoryIdsForPi(state, memories, workspace, db) {
173764
175556
  if (memories.length === 0)
173765
175557
  return [];
173766
- return trimMemoriesToBudgetV2(state.sessionId, [...memories], state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder.map((memory) => memory.id);
175558
+ const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state, db) : undefined);
175559
+ const renderOptions = resolvedWorkspace ? {
175560
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175561
+ memories,
175562
+ projectPath: memoryProjectPath(state),
175563
+ workspace: resolvedWorkspace
175564
+ })
175565
+ } : {};
175566
+ 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);
175567
+ return trimmed.renderOrder.map((memory) => memory.id);
173767
175568
  }
173768
175569
  function isTransientSqliteLockError(error51) {
173769
175570
  if (!error51 || typeof error51 !== "object")
@@ -173788,17 +175589,19 @@ class PiMaterializeContentionError extends Error {
173788
175589
  function readFrozenM0InputsPi(state, db, docs = readProjectDocsCanonical(state.projectDirectory), memoryCutoff) {
173789
175590
  const memPath = memoryProjectPath(state);
173790
175591
  const read = db.transaction(() => {
175592
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
173791
175593
  const compartments = getCompartments(db, state.sessionId);
173792
- const memories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
175594
+ const memories = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], memoryCutoff, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
173793
175595
  const userProfile = safeGetActiveUserMemoriesPi(db);
173794
175596
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
173795
175597
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
173796
175598
  const markers = {
173797
175599
  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),
175600
+ maxMemoryId: memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0,
173799
175601
  maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
173800
- maxMemoryMutationId: memPath ? getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
175602
+ maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
173801
175603
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
175604
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
173802
175605
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
173803
175606
  projectDocsHash: docs.canonicalHash,
173804
175607
  sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
@@ -173808,7 +175611,7 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsCanonical(state.p
173808
175611
  systemHash: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
173809
175612
  modelKey: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey
173810
175613
  };
173811
- return { docs, markers, compartments, memories, userProfile };
175614
+ return { docs, markers, compartments, memories, userProfile, workspace };
173812
175615
  });
173813
175616
  return read();
173814
175617
  }
@@ -173819,17 +175622,17 @@ function renderFreshM0PiNonPersisted(state, db) {
173819
175622
  frozen.markers.materializedAt = cachedMaterializedAt;
173820
175623
  const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173821
175624
  let dpm = 1;
173822
- let m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile);
175625
+ let m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace);
173823
175626
  let attempts = 0;
173824
175627
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
173825
175628
  dpm *= 1.15;
173826
- m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile);
175629
+ m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace);
173827
175630
  attempts += 1;
173828
175631
  }
173829
175632
  return {
173830
175633
  m0,
173831
175634
  snapshotMarkers: frozen.markers,
173832
- renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories)
175635
+ renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories, frozen.workspace, db)
173833
175636
  };
173834
175637
  }
173835
175638
  function materializeM0Pi(state, db) {
@@ -173839,14 +175642,14 @@ function materializeM0Pi(state, db) {
173839
175642
  const snapshotMemories = frozen.memories;
173840
175643
  const snapshotCompartments = frozen.compartments;
173841
175644
  const snapshotUserProfile = frozen.userProfile;
173842
- const renderedMemoryIds = renderedMemoryIdsForPi(state, snapshotMemories);
175645
+ const renderedMemoryIds = renderedMemoryIdsForPi(state, snapshotMemories, frozen.workspace, db);
173843
175646
  let decayPressureMultiplier = 1;
173844
- let m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile);
175647
+ let m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace);
173845
175648
  const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173846
175649
  let attempts = 0;
173847
175650
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
173848
175651
  decayPressureMultiplier *= 1.15;
173849
- m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile);
175652
+ m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace);
173850
175653
  attempts += 1;
173851
175654
  }
173852
175655
  const m0Bytes = Buffer.from(m0, "utf8");
@@ -173862,7 +175665,8 @@ function materializeM0Pi(state, db) {
173862
175665
  }
173863
175666
  try {
173864
175667
  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;
175668
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
175669
+ 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
175670
  if (stale) {
173867
175671
  db.exec("ROLLBACK");
173868
175672
  throw new PiMaterializeContentionError("snapshot changed before persist");
@@ -173873,6 +175677,7 @@ function materializeM0Pi(state, db) {
173873
175677
  persistCachedM0(db, state.sessionId, {
173874
175678
  m0Bytes,
173875
175679
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
175680
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
173876
175681
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
173877
175682
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
173878
175683
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -173937,7 +175742,7 @@ function renderMemoryUpdatesBlockPi(args) {
173937
175742
  if (args.renderedMemoryIds.length === 0)
173938
175743
  return { block: "", count: 0 };
173939
175744
  const renderedIds = new Set(args.renderedMemoryIds);
173940
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
175745
+ 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
175746
  if (mutations.length === 0)
173942
175747
  return { block: "", count: 0 };
173943
175748
  const lines = [
@@ -173968,6 +175773,7 @@ ${lines.join(`
173968
175773
  }
173969
175774
  function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRenderedKeyFilesBlock, compartmentsOverride) {
173970
175775
  const sections = [];
175776
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
173971
175777
  const keyFiles = renderedKeyFilesBlockPi(state, db, preRenderedKeyFilesBlock);
173972
175778
  if (keyFiles)
173973
175779
  sections.push(keyFiles);
@@ -173975,6 +175781,7 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRender
173975
175781
  const memoryUpdates = memPath ? renderMemoryUpdatesBlockPi({
173976
175782
  db,
173977
175783
  projectPath: memPath,
175784
+ workspace,
173978
175785
  afterId: markers.maxMemoryMutationId,
173979
175786
  renderedMemoryIds
173980
175787
  }) : { block: undefined, count: 0 };
@@ -173989,11 +175796,18 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRender
173989
175796
  ${body}
173990
175797
  </new-compartments>`);
173991
175798
  }
173992
- const newMemories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"], markers.materializedAt).filter((memory) => memory.id > markers.maxMemoryId) : [];
175799
+ 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
175800
  if (newMemories.length > 0) {
173994
175801
  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");
175802
+ const memoryRenderOptions = {
175803
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175804
+ memories: newMemories,
175805
+ projectPath: memPath,
175806
+ workspace
175807
+ })
175808
+ };
175809
+ const trimmedNewMemories = trimMemoriesToBudgetV2(state.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25)), memoryRenderOptions).renderOrder;
175810
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", memoryRenderOptions);
173997
175811
  if (newMemoriesBlock)
173998
175812
  sections.push(newMemoriesBlock);
173999
175813
  }
@@ -174042,6 +175856,7 @@ function parseMemoryBlockIds(raw) {
174042
175856
  function readCachedPiM0M1Row(db, sessionId) {
174043
175857
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
174044
175858
  cached_m0_project_memory_epoch,
175859
+ cached_m0_workspace_fingerprint,
174045
175860
  cached_m0_project_user_profile_version,
174046
175861
  cached_m0_max_compartment_seq,
174047
175862
  cached_m0_max_memory_id,
@@ -174085,6 +175900,7 @@ function markersFromCachedPiRow(row, compartmentsForNormalization) {
174085
175900
  maxMutationId: row.cached_m0_max_mutation_id,
174086
175901
  maxMemoryMutationId: row.cached_m0_max_memory_mutation_id,
174087
175902
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
175903
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
174088
175904
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
174089
175905
  projectDocsHash: row.cached_m0_project_docs_hash ?? "",
174090
175906
  materializedAt: row.cached_m0_materialized_at,
@@ -174099,7 +175915,7 @@ function cachedPiRowMatchesSnapshot(args) {
174099
175915
  const rowMarkers = markersFromCachedPiRow(args.row, args.compartmentsForNormalization);
174100
175916
  if (!rowMarkers)
174101
175917
  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 ?? "");
175918
+ 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
175919
  }
174104
175920
  function decodeCachedM1(row, sessionId) {
174105
175921
  if (!row.cached_m1_bytes) {
@@ -174310,14 +176126,14 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
174310
176126
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
174311
176127
  prependM0M1Messages(piMessages, m0, m1);
174312
176128
  sessionLog(state.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
176129
+ const memPath = memoryProjectPath(state);
176130
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
176131
+ 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
176132
  return {
174314
176133
  injected: true,
174315
176134
  compartmentCount: getCompartments(db, state.sessionId).length,
174316
176135
  factCount: 0,
174317
- memoryCount: memoryProjectPath(state) ? getMemoriesByProject(db, memoryProjectPath(state), [
174318
- "active",
174319
- "permanent"
174320
- ]).length : 0,
176136
+ memoryCount,
174321
176137
  skippedVisibleMessages,
174322
176138
  m0Materialized: materialized,
174323
176139
  m0Reason: decision.reason,
@@ -174378,21 +176194,48 @@ import * as crypto2 from "node:crypto";
174378
176194
 
174379
176195
  // ../plugin/src/features/magic-context/compartment-embedding.ts
174380
176196
  init_logger();
174381
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
176197
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
174382
176198
  if (compartments.length === 0)
174383
176199
  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;
176200
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
176201
+ for (const compartment of compartments) {
174388
176202
  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);
176203
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
176204
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
176205
+ if (canonicalText.length === 0)
176206
+ continue;
176207
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
176208
+ if (windows.length === 0)
176209
+ continue;
176210
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
176211
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
176212
+ continue;
176213
+ }
176214
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
176215
+ if (!result)
176216
+ continue;
176217
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
176218
+ continue;
176219
+ }
176220
+ const rows = [];
176221
+ for (const [index, window] of windows.entries()) {
176222
+ const vector = result.vectors[index];
176223
+ if (!vector)
176224
+ continue;
176225
+ rows.push({
176226
+ compartmentId: compartment.id,
176227
+ sessionId,
176228
+ projectPath,
176229
+ window,
176230
+ modelId: currentModelId,
176231
+ vector
176232
+ });
176233
+ }
176234
+ if (rows.length === windows.length) {
176235
+ replaceCompartmentChunkEmbeddings(db, rows);
174393
176236
  }
174394
176237
  } catch (error51) {
174395
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
176238
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
174396
176239
  }
174397
176240
  }
174398
176241
  }
@@ -177461,6 +179304,7 @@ async function runPiHistorian(deps) {
177461
179304
  fallbackModels,
177462
179305
  historianChunkTokens,
177463
179306
  boundarySnapshot: providedBoundarySnapshot,
179307
+ refreshBoundarySnapshot,
177464
179308
  currentContextLimit,
177465
179309
  historianTimeoutMs = DEFAULT_HISTORIAN_TIMEOUT_MS2,
177466
179310
  twoPass,
@@ -177514,16 +179358,29 @@ async function runPiHistorian(deps) {
177514
179358
  return;
177515
179359
  }
177516
179360
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
177517
- const boundarySnapshot = providedBoundarySnapshot ?? null;
179361
+ let boundarySnapshot = providedBoundarySnapshot ?? null;
177518
179362
  if (!boundarySnapshot) {
177519
179363
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from Pi trigger decision");
177520
179364
  return;
177521
179365
  }
177522
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
179366
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
177523
179367
  db,
177524
179368
  snapshot: boundarySnapshot,
177525
179369
  currentContextLimit: currentContextLimit ?? boundarySnapshot.contextLimit
177526
179370
  }) : { ok: true };
179371
+ if (!validation.ok && validation.reason === "stale_snapshot" && refreshBoundarySnapshot) {
179372
+ try {
179373
+ const refreshed = refreshBoundarySnapshot();
179374
+ if (hasRunnableCompartmentWindow(refreshed)) {
179375
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
179376
+ boundarySnapshot = refreshed;
179377
+ validation = { ok: true };
179378
+ }
179379
+ } catch (error51) {
179380
+ const desc = describeError(error51);
179381
+ sessionLog(sessionId, `historian: failed to refresh stale protected-tail snapshot at run time (${validation.detail ?? "stale"}): ${desc.brief}`);
179382
+ }
179383
+ }
177527
179384
  if (!validation.ok) {
177528
179385
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
177529
179386
  return;
@@ -177852,8 +179709,13 @@ ${chunkText}`,
177852
179709
  }
177853
179710
  }
177854
179711
  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);
179712
+ const chunksToEmbed = newCompartments.map((c, i) => ({
179713
+ id: persistedIds[i],
179714
+ startMessage: c.startMessage,
179715
+ endMessage: c.endMessage,
179716
+ sourceChunkText: chunk.text
179717
+ })).filter((c) => typeof c.id === "number");
179718
+ embedAndStoreCompartmentChunks(db, sessionId, projectPath, chunksToEmbed);
177857
179719
  }
177858
179720
  onPublished?.();
177859
179721
  completedSuccessfully = true;
@@ -178401,7 +180263,7 @@ function stripPiDroppedPlaceholderMessages(args) {
178401
180263
  }
178402
180264
 
178403
180265
  // src/system-prompt.ts
178404
- import { createHash as createHash9 } from "node:crypto";
180266
+ import { createHash as createHash11 } from "node:crypto";
178405
180267
 
178406
180268
  // ../plugin/src/agents/magic-context-prompt.ts
178407
180269
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -178552,7 +180414,7 @@ function processSystemPromptForCache(args) {
178552
180414
  sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
178553
180415
  }
178554
180416
  }
178555
- const currentHash = createHash9("md5").update(frozenPrompt).digest("hex");
180417
+ const currentHash = createHash11("md5").update(frozenPrompt).digest("hex");
178556
180418
  const hashChanged = !isFirstHash && currentHash !== previousHash;
178557
180419
  if (hashChanged) {
178558
180420
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
@@ -179179,6 +181041,19 @@ function extractStableId(msg, index, entryIds) {
179179
181041
  // src/context-handler.ts
179180
181042
  var FORCE_MATERIALIZATION_PERCENTAGE = 85;
179181
181043
  var EMERGENCY_BLOCK_PERCENTAGE = 95;
181044
+ var FORWARD_PRESSURE_LIMIT_FACTOR = 0.85;
181045
+ function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUsageTokens, correctedLimit) {
181046
+ const forwardTokens = typeof piUsageTokens === "number" && piUsageTokens > 0 ? piUsageTokens : 0;
181047
+ if (forwardTokens === 0 || !isSaneLimit(correctedLimit)) {
181048
+ return { percentage: trailingPercentage, inputTokens: trailingInputTokens };
181049
+ }
181050
+ const forwardPressureLimit = correctedLimit * FORWARD_PRESSURE_LIMIT_FACTOR;
181051
+ const forwardPercentage = forwardTokens / forwardPressureLimit * 100;
181052
+ return forwardPercentage > trailingPercentage ? {
181053
+ percentage: forwardPercentage,
181054
+ inputTokens: Math.max(trailingInputTokens, forwardTokens)
181055
+ } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
181056
+ }
179182
181057
  var DEFAULT_CLEAR_REASONING_AGE = 50;
179183
181058
  var PI_STABLE_ID_SCHEME = 1;
179184
181059
  var lastEmergencyNotificationAtMs = new Map;
@@ -179263,7 +181138,7 @@ function trackSessionForProject(projectIdentity, sessionId) {
179263
181138
  function isContextHandlerSessionActive(sessionId) {
179264
181139
  return activeContextHandlerSessions.has(sessionId);
179265
181140
  }
179266
- function updateSessionProjectTracking(sessionId, projectIdentity) {
181141
+ function updateSessionProjectTracking(sessionId, projectIdentity, db) {
179267
181142
  const prev = lastSeenProjectIdentityBySession.get(sessionId);
179268
181143
  if (prev && prev !== projectIdentity) {
179269
181144
  const prevSessions = sessionsByProject.get(prev);
@@ -179272,6 +181147,11 @@ function updateSessionProjectTracking(sessionId, projectIdentity) {
179272
181147
  sessionsByProject.delete(prev);
179273
181148
  clearPiSystemPromptSession(sessionId);
179274
181149
  }
181150
+ if (db && prev !== projectIdentity) {
181151
+ try {
181152
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
181153
+ } catch {}
181154
+ }
179275
181155
  trackSessionForProject(projectIdentity, sessionId);
179276
181156
  lastSeenProjectIdentityBySession.set(sessionId, projectIdentity);
179277
181157
  }
@@ -179552,7 +181432,7 @@ function registerPiContextHandler(pi, baseOptions) {
179552
181432
  const schedulerConfig = options.scheduler ?? DEFAULT_SCHEDULER_CONFIG;
179553
181433
  const scheduler2 = schedulerFor(options);
179554
181434
  const projectIdentity = resolveProjectIdentity(projectDirectory);
179555
- updateSessionProjectTracking(sessionId, projectIdentity);
181435
+ updateSessionProjectTracking(sessionId, projectIdentity, options.db);
179556
181436
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
179557
181437
  const branchEntries = readPiBranchEntriesForContext(ctx, sessionId);
179558
181438
  const rawMessageProvider = {
@@ -179661,9 +181541,10 @@ function registerPiContextHandler(pi, baseOptions) {
179661
181541
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
179662
181542
  usagePercentage = usageInputTokens / usageContextLimit * 100;
179663
181543
  }
181544
+ ({ percentage: usagePercentage, inputTokens: usageInputTokens } = applyForwardPressureFloor(usagePercentage, usageInputTokens, piUsage?.tokens, usageContextLimit));
179664
181545
  if (needsEmergencyBump) {
179665
181546
  sessionLog(sessionId, `transform: overflow recovery flag set — bumping percentage to 95% (detectedLimit=${usageContextLimit ?? "unknown"})`);
179666
- usagePercentage = 95;
181547
+ usagePercentage = Math.max(usagePercentage, 95);
179667
181548
  }
179668
181549
  let schedulerDecision;
179669
181550
  const tScheduler = performance.now();
@@ -179888,7 +181769,7 @@ function registerPiContextHandler(pi, baseOptions) {
179888
181769
  let tailToolTokens;
179889
181770
  let liveTailTokens;
179890
181771
  try {
179891
- const agg = getActiveTagTokenAggregate(options.db, sessionId);
181772
+ const agg = getActiveTagTokenAggregate(options.db, sessionId, options.protectedTags ?? 20);
179892
181773
  tailToolTokens = agg.toolOutput;
179893
181774
  liveTailTokens = agg.conversation + agg.toolCall;
179894
181775
  } catch {
@@ -180047,6 +181928,7 @@ function spawnPiHistorianRun(args) {
180047
181928
  provider: provider2,
180048
181929
  unregister,
180049
181930
  boundarySnapshot,
181931
+ refreshBoundarySnapshot,
180050
181932
  currentContextLimit
180051
181933
  } = args;
180052
181934
  const holderId = crypto3.randomUUID();
@@ -180070,6 +181952,7 @@ function spawnPiHistorianRun(args) {
180070
181952
  fallbackModels: historian.fallbackModels,
180071
181953
  historianChunkTokens: historian.historianChunkTokens,
180072
181954
  boundarySnapshot,
181955
+ refreshBoundarySnapshot,
180073
181956
  currentContextLimit,
180074
181957
  historianTimeoutMs: historian.timeoutMs,
180075
181958
  twoPass: historian.twoPass,
@@ -180136,6 +182019,7 @@ function maybeFireHistorian(args) {
180136
182019
  let usageContextLimit;
180137
182020
  try {
180138
182021
  const piUsage = ctx.getContextUsage?.();
182022
+ let usageSource;
180139
182023
  usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
180140
182024
  if (usageContextLimit === undefined && isSaneLimit(ctx.model?.contextWindow)) {
180141
182025
  usageContextLimit = ctx.model.contextWindow;
@@ -180152,7 +182036,7 @@ function maybeFireHistorian(args) {
180152
182036
  percentage: sessionMetaForUsage.lastContextPercentage,
180153
182037
  inputTokens: sessionMetaForUsage.lastInputTokens
180154
182038
  };
180155
- sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [from session_meta], checking trigger...`);
182039
+ usageSource = "session_meta";
180156
182040
  } else {
180157
182041
  if (!piUsage || piUsage.tokens === null || piUsage.percent === null || piUsage.contextWindow === 0) {
180158
182042
  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 +182047,10 @@ function maybeFireHistorian(args) {
180163
182047
  percentage: fallbackPercentage,
180164
182048
  inputTokens: piUsage.tokens
180165
182049
  };
180166
- sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [piUsage fallback], checking trigger...`);
182050
+ usageSource = "piUsage fallback";
180167
182051
  }
182052
+ usage = applyForwardPressureFloor(usage.percentage, usage.inputTokens, piUsage?.tokens, usageContextLimit);
182053
+ sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [${usageSource}], checking trigger...`);
180168
182054
  } catch (err) {
180169
182055
  sessionLog(sessionId, `historian trigger eval: getContextUsage threw: ${err instanceof Error ? err.message : String(err)}`);
180170
182056
  return;
@@ -180195,10 +182081,14 @@ function maybeFireHistorian(args) {
180195
182081
  cacheNamespace: `pi:${sessionId}`,
180196
182082
  emergencyTailScale
180197
182083
  }));
180198
- let boundarySnapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
180199
- if (!hasRunnableCompartmentWindow(boundarySnapshot) && usage.percentage >= 80) {
180200
- boundarySnapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
180201
- }
182084
+ const resolveRunnablePiBoundarySnapshot = () => {
182085
+ let snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
182086
+ if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= 80) {
182087
+ snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
182088
+ }
182089
+ return snapshot;
182090
+ };
182091
+ const boundarySnapshot = resolveRunnablePiBoundarySnapshot();
180202
182092
  let triggered = false;
180203
182093
  try {
180204
182094
  if (isFirstContextPassForSession) {
@@ -180223,6 +182113,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
180223
182113
  provider: provider2,
180224
182114
  unregister,
180225
182115
  boundarySnapshot,
182116
+ refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
180226
182117
  currentContextLimit: boundaryContextLimit
180227
182118
  });
180228
182119
  return;
@@ -180231,6 +182122,15 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
180231
182122
  const trigger = checkCompartmentTrigger(db, sessionId, sessionMeta, usage, 0, triggerInputs.executeThresholdPercentage, triggerInputs.triggerBudget, triggerInputs.clearReasoningAge, triggerInputs.commitClusterTrigger, args.activeTags, boundaryContextLimit);
180232
182123
  if (!trigger.shouldFire) {
180233
182124
  sessionLog(sessionId, `historian trigger eval: shouldFire=false (no trigger condition met)`);
182125
+ try {
182126
+ const overflowState = getOverflowState(db, sessionId);
182127
+ if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && !hasRunnableCompartmentWindow(boundarySnapshot)) {
182128
+ clearEmergencyRecovery(db, sessionId);
182129
+ sessionLog(sessionId, `historian: disarming stale emergency recovery — real pressure ${usage.percentage.toFixed(1)}% with no runnable compartment window (would otherwise bump to 95% every pass)`);
182130
+ }
182131
+ } catch (err) {
182132
+ sessionLog(sessionId, `historian: emergency-recovery disarm check failed: ${err instanceof Error ? err.message : String(err)}`);
182133
+ }
180234
182134
  return;
180235
182135
  }
180236
182136
  triggered = true;
@@ -180246,6 +182146,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
180246
182146
  resolvedBoundarySnapshot: boundarySnapshot,
180247
182147
  triggerBoundarySnapshot: trigger.boundarySnapshot
180248
182148
  }),
182149
+ refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
180249
182150
  currentContextLimit: boundaryContextLimit
180250
182151
  });
180251
182152
  } catch (err) {
@@ -180286,7 +182187,31 @@ async function runPipeline(args) {
180286
182187
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
180287
182188
  const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
180288
182189
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
180289
- const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializeEligible || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
182190
+ const piHardSignals = args.injection ? (() => {
182191
+ const hardMeta = getOrCreateSessionMeta(args.db, args.sessionId);
182192
+ let piTtlMs = 5 * 60 * 1000;
182193
+ try {
182194
+ piTtlMs = parseCacheTtl(hardMeta.cacheTtl);
182195
+ } catch {}
182196
+ return {
182197
+ systemHash: typeof hardMeta.systemPromptHash === "string" ? hardMeta.systemPromptHash : "",
182198
+ modelKey: liveModelBySession.get(args.sessionId) ?? "",
182199
+ cacheExpired: hardMeta.lastResponseTime > 0 && Date.now() - hardMeta.lastResponseTime >= piTtlMs,
182200
+ lastResponseTime: hardMeta.lastResponseTime
182201
+ };
182202
+ })() : undefined;
182203
+ const m0HardFoldThisPass = args.injection && piHardSignals ? mustMaterializePi({
182204
+ sessionId: args.sessionId,
182205
+ projectIdentity: args.projectIdentity,
182206
+ projectDirectory: args.projectDirectory,
182207
+ memoryEnabled: args.injection.memoryEnabled,
182208
+ injectionBudgetTokens: args.injection.injectionBudgetTokens,
182209
+ historyBudgetTokens: args.injection.historyBudgetTokens,
182210
+ keyFilesEnabled: args.injection.keyFilesEnabled,
182211
+ keyFilesTokenBudget: args.injection.keyFilesTokenBudget,
182212
+ hardSignals: piHardSignals
182213
+ }, args.db, getCompartments(args.db, args.sessionId)).value : false;
182214
+ const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializeEligible || m0HardFoldThisPass || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
180290
182215
  const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver);
180291
182216
  adoptPiFallbackTags(args.db, args.sessionId, args.tagger, entryFingerprintByMessageId);
180292
182217
  const tTag = performance.now();
@@ -180313,12 +182238,12 @@ async function runPipeline(args) {
180313
182238
  const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
180314
182239
  const deferredHistoryRefreshWasPending = deferredHistoryWasPendingAtPassStart;
180315
182240
  const pendingOps = getPendingOps(args.db, args.sessionId);
180316
- const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
182241
+ const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal || m0HardFoldThisPass;
180317
182242
  const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
180318
182243
  const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
180319
182244
  const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
180320
182245
  if (shouldApplyPendingOps) {
180321
- const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
182246
+ 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
182247
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
180323
182248
  try {
180324
182249
  const tApplyPending = performance.now();
@@ -180389,7 +182314,7 @@ async function runPipeline(args) {
180389
182314
  const activeTags = getActiveTagsBySession(args.db, args.sessionId);
180390
182315
  logTransformTiming(args.sessionId, "getActiveTagsBySession", tActiveTags, `count=${activeTags.length}`);
180391
182316
  if (shouldRunHeuristics) {
180392
- const reason = args.forceMaterialization ? "force_materialization" : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
182317
+ 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
182318
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`);
180394
182319
  } else {
180395
182320
  const reason = args.heuristics === undefined ? "disabled" : "scheduler_defer";
@@ -180477,17 +182402,6 @@ async function runPipeline(args) {
180477
182402
  if (args.injection) {
180478
182403
  try {
180479
182404
  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
182405
  injectionResult = injectM0M1Pi({
180492
182406
  sessionId: args.sessionId,
180493
182407
  projectIdentity: args.projectIdentity,
@@ -181577,8 +183491,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
181577
183491
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181578
183492
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
181579
183493
  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);
183494
+ const chunksToEmbed = liveCompartments.map((c) => ({
183495
+ id: c.id,
183496
+ startMessage: c.startMessage,
183497
+ endMessage: c.endMessage
183498
+ }));
183499
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
181582
183500
  }
181583
183501
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
181584
183502
  if (lastCompartmentEnd2 > 0) {
@@ -181791,8 +183709,12 @@ Another process acquired the compartment-state lease before recomp could publish
181791
183709
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181792
183710
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
181793
183711
  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);
183712
+ const chunksToEmbed = liveCompartments.map((c) => ({
183713
+ id: c.id,
183714
+ startMessage: c.startMessage,
183715
+ endMessage: c.endMessage
183716
+ }));
183717
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
181796
183718
  }
181797
183719
  if (lastCompartmentEnd > 0) {
181798
183720
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -181949,8 +183871,12 @@ Could not acquire the compartment-state lease for this session.`;
181949
183871
  if (deps.memoryEnabled !== false) {
181950
183872
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181951
183873
  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));
183874
+ const chunksToEmbed = liveCompartments.map((c) => ({
183875
+ id: c.id,
183876
+ startMessage: c.startMessage,
183877
+ endMessage: c.endMessage
183878
+ }));
183879
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
181954
183880
  }
181955
183881
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
181956
183882
  if (lastEnd > 0) {
@@ -182585,6 +184511,11 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
182585
184511
  fallbackModelId: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined
182586
184512
  }, parsed.kind === "partial" ? { range: parsed.range } : {});
182587
184513
  if (result.published) {
184514
+ try {
184515
+ clearEmergencyRecovery(deps.db, sessionId);
184516
+ } catch (recoveryError) {
184517
+ sessionLog(sessionId, `/ctx-recomp: clearEmergencyRecovery failed (continuing): ${describeError(recoveryError).brief}`);
184518
+ }
182588
184519
  try {
182589
184520
  stagePiRecompMarker({ db: deps.db, sessionId, ctx });
182590
184521
  } catch (markerError) {
@@ -182762,7 +184693,7 @@ function applyMemoryMigration(db, projectPath, result) {
182762
184693
  inserted++;
182763
184694
  }
182764
184695
  if (removed > 0 || inserted > 0) {
182765
- bumpProjectMemoryEpoch(db, projectPath);
184696
+ bumpEpochsForWorkspaceMembers(db, projectPath);
182766
184697
  }
182767
184698
  })();
182768
184699
  return { removed, inserted };
@@ -183238,7 +185169,7 @@ function formatThresholdPercent(value) {
183238
185169
  // package.json
183239
185170
  var package_default = {
183240
185171
  name: "@cortexkit/pi-magic-context",
183241
- version: "0.23.1",
185172
+ version: "0.24.1",
183242
185173
  type: "module",
183243
185174
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
183244
185175
  main: "dist/index.js",
@@ -188102,6 +190033,23 @@ function createCtxMemoryTool(deps) {
188102
190033
  }
188103
190034
  const projectIdentity = resolveProjectIdentity(ctx.cwd);
188104
190035
  await deps.ensureProjectRegistered?.(ctx.cwd, deps.db);
190036
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectIdentity);
190037
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
190038
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
190039
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? normalizeStoredProjectPath(rawProjectPath) : normalizeStoredProjectPath(rawProjectPath);
190040
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectIdentity) : null;
190041
+ const memoryVisibleToTool = (memory2) => {
190042
+ if (workspaceIdentitySet.identities.length <= 1) {
190043
+ return storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
190044
+ }
190045
+ if (!storedPathBelongsToWorkspace(memory2.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
190046
+ return false;
190047
+ }
190048
+ const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
190049
+ if (isOwn)
190050
+ return true;
190051
+ return toolShareCategories === null || toolShareCategories.includes(memory2.category);
190052
+ };
188105
190053
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
188106
190054
  if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188107
190055
  return err2("Cross-session memory is disabled for this project.");
@@ -188148,28 +190096,34 @@ function createCtxMemoryTool(deps) {
188148
190096
  return err2("Error: 'content' is required when action is 'update'.");
188149
190097
  }
188150
190098
  const memory2 = getMemoryById(deps.db, updateId);
188151
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
190099
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
188152
190100
  return err2(`Error: Memory with ID ${updateId} was not found.`);
188153
190101
  }
188154
190102
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
188155
190103
  return err2(inactiveMemoryError(updateId, "updating"));
188156
190104
  }
188157
190105
  const normalizedHash = computeNormalizedHash(content);
188158
- const duplicate = getMemoryByHash(deps.db, projectIdentity, memory2.category, normalizedHash);
190106
+ const targetIdentity = targetIdentityForStoredPath(memory2.projectPath);
190107
+ const duplicate = getMemoryByHash(deps.db, targetIdentity, memory2.category, normalizedHash);
188159
190108
  if (duplicate && duplicate.id !== memory2.id) {
188160
190109
  return err2(`Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`);
188161
190110
  }
188162
190111
  deps.db.transaction(() => {
188163
190112
  updateMemoryContent(deps.db, memory2.id, content, normalizedHash);
188164
190113
  queueMemoryMutation(deps.db, {
188165
- projectPath: projectIdentity,
190114
+ projectPath: targetIdentity,
188166
190115
  mutationType: "update",
188167
190116
  targetMemoryId: memory2.id,
188168
190117
  category: memory2.category,
188169
190118
  newContent: content
188170
190119
  });
188171
190120
  })();
188172
- queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
190121
+ queueEmbedding({
190122
+ deps,
190123
+ projectIdentity: targetIdentity,
190124
+ memoryId: memory2.id,
190125
+ content
190126
+ });
188173
190127
  return ok2(`Updated memory [ID: ${memory2.id}] in ${memory2.category}.`);
188174
190128
  }
188175
190129
  if (params.action === "merge") {
@@ -188189,7 +190143,7 @@ function createCtxMemoryTool(deps) {
188189
190143
  return err2("Error: One or more source memories were not found.");
188190
190144
  }
188191
190145
  if (!dreamerAllowed) {
188192
- const foreign = sourceMemories.find((memory2) => !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity));
190146
+ const foreign = sourceMemories.find((memory2) => !memoryVisibleToTool(memory2));
188193
190147
  if (foreign) {
188194
190148
  return err2(`Error: Memory with ID ${foreign.id} was not found.`);
188195
190149
  }
@@ -188278,26 +190232,36 @@ function createCtxMemoryTool(deps) {
188278
190232
  return ok2(`Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`);
188279
190233
  }
188280
190234
  if (params.action === "archive") {
188281
- const archiveIds = params.ids;
188282
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
190235
+ const rawArchiveIds = params.ids;
190236
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
188283
190237
  return err2("Error: 'ids' must contain at least one integer memory ID when action is 'archive'.");
188284
190238
  }
190239
+ const archiveIds = [...new Set(rawArchiveIds)];
188285
190240
  for (const memoryId of archiveIds) {
188286
190241
  const memory2 = getMemoryById(deps.db, memoryId);
188287
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
190242
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
188288
190243
  return err2(`Error: Memory with ID ${memoryId} was not found.`);
188289
190244
  }
188290
190245
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
188291
190246
  return err2(inactiveMemoryError(memoryId, "archiving"));
188292
190247
  }
188293
190248
  }
190249
+ const targets = archiveIds.map((memoryId) => {
190250
+ const memory2 = getMemoryById(deps.db, memoryId);
190251
+ if (!memory2)
190252
+ throw new Error(`validated memory ${memoryId} disappeared`);
190253
+ return {
190254
+ memoryId,
190255
+ projectIdentity: targetIdentityForStoredPath(memory2.projectPath)
190256
+ };
190257
+ });
188294
190258
  deps.db.transaction(() => {
188295
- for (const memoryId of archiveIds) {
188296
- archiveMemory(deps.db, memoryId, params.reason);
190259
+ for (const target2 of targets) {
190260
+ archiveMemory(deps.db, target2.memoryId, params.reason);
188297
190261
  queueMemoryMutation(deps.db, {
188298
- projectPath: projectIdentity,
190262
+ projectPath: target2.projectIdentity,
188299
190263
  mutationType: "archive",
188300
- targetMemoryId: memoryId
190264
+ targetMemoryId: target2.memoryId
188301
190265
  });
188302
190266
  }
188303
190267
  })();
@@ -188805,8 +190769,9 @@ function formatAge2(committedAtMs) {
188805
190769
  }
188806
190770
  function formatResult(result, index) {
188807
190771
  if (result.source === "memory") {
190772
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
188808
190773
  return [
188809
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
190774
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
188810
190775
  result.content
188811
190776
  ].join(`
188812
190777
  `);
@@ -188816,6 +190781,13 @@ function formatResult(result, index) {
188816
190781
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
188817
190782
  result.content
188818
190783
  ].join(`
190784
+ `);
190785
+ }
190786
+ if (result.source === "compartment") {
190787
+ return [
190788
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
190789
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
190790
+ ].join(`
188819
190791
  `);
188820
190792
  }
188821
190793
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -188831,7 +190803,7 @@ function formatSearchResults(query, results) {
188831
190803
  return `No results found for "${query}" across memories, git commits, or message history.`;
188832
190804
  }
188833
190805
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
188834
- if (results.some((result) => result.source === "message")) {
190806
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
188835
190807
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
188836
190808
  }
188837
190809
  const body = bodyParts.join(`
@@ -189327,6 +191299,14 @@ async function src_default2(pi) {
189327
191299
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
189328
191300
  });
189329
191301
  info("registered /ctx-dream");
191302
+ registerCtxEmbedHistoryCommand(pi, {
191303
+ db,
191304
+ projectDir,
191305
+ projectIdentity,
191306
+ memoryEnabled: config2.memory.enabled,
191307
+ resolveProject: resolveCurrentProject
191308
+ });
191309
+ info("registered /ctx-embed-history");
189330
191310
  const dreamerConfig = resolveDreamerFromConfig(config2);
189331
191311
  if (dreamerConfig) {
189332
191312
  registerPiDreamerProject({