@wolfx/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 nudgesquieter, 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 pathmulti-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 usagePi 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" };
@@ -165653,6 +166300,443 @@ var MagicContextConfigSchema = exports_external.object({
165653
166300
  // ../plugin/src/features/magic-context/memory/embedding.ts
165654
166301
  init_logger();
165655
166302
 
166303
+ // ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
166304
+ import { createHash as createHash8 } from "node:crypto";
166305
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512;
166306
+ var CHUNK_WINDOW_SAFETY_RATIO = 0.9;
166307
+ var loadFtsRowsStatements = new WeakMap;
166308
+ var existingHashStatements = new WeakMap;
166309
+ var existingHashByProjectStatements = new WeakMap;
166310
+ var deleteByCompartmentStatements = new WeakMap;
166311
+ var insertEmbeddingStatements = new WeakMap;
166312
+ var distinctModelStatements = new WeakMap;
166313
+ var clearProjectStatements = new WeakMap;
166314
+ var clearProjectModelStatements = new WeakMap;
166315
+ var searchRowsStatements = new WeakMap;
166316
+ var searchRowsByModelStatements = new WeakMap;
166317
+ var backfillCandidateStatements = new WeakMap;
166318
+ function getLoadFtsRowsStatement(db) {
166319
+ let stmt = loadFtsRowsStatements.get(db);
166320
+ if (!stmt) {
166321
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
166322
+ FROM message_history_fts
166323
+ WHERE session_id = ?
166324
+ AND message_ordinal >= ?
166325
+ AND message_ordinal <= ?
166326
+ AND role IN ('user', 'assistant')
166327
+ ORDER BY message_ordinal ASC`);
166328
+ loadFtsRowsStatements.set(db, stmt);
166329
+ }
166330
+ return stmt;
166331
+ }
166332
+ function getExistingHashStatement(db, scopedToProject) {
166333
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
166334
+ let stmt = map2.get(db);
166335
+ if (!stmt) {
166336
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
166337
+ FROM compartment_chunk_embeddings
166338
+ WHERE compartment_id = ?
166339
+ AND model_id = ?
166340
+ ${scopedToProject ? "AND project_path = ?" : ""}
166341
+ ORDER BY window_index ASC`);
166342
+ map2.set(db, stmt);
166343
+ }
166344
+ return stmt;
166345
+ }
166346
+ function getDeleteByCompartmentStatement(db) {
166347
+ let stmt = deleteByCompartmentStatements.get(db);
166348
+ if (!stmt) {
166349
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
166350
+ deleteByCompartmentStatements.set(db, stmt);
166351
+ }
166352
+ return stmt;
166353
+ }
166354
+ function getInsertEmbeddingStatement(db) {
166355
+ let stmt = insertEmbeddingStatements.get(db);
166356
+ if (!stmt) {
166357
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
166358
+ compartment_id, session_id, project_path, harness, window_index,
166359
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
166360
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
166361
+ insertEmbeddingStatements.set(db, stmt);
166362
+ }
166363
+ return stmt;
166364
+ }
166365
+ function getDistinctModelStatement(db) {
166366
+ let stmt = distinctModelStatements.get(db);
166367
+ if (!stmt) {
166368
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
166369
+ FROM compartment_chunk_embeddings
166370
+ WHERE project_path = ?`);
166371
+ distinctModelStatements.set(db, stmt);
166372
+ }
166373
+ return stmt;
166374
+ }
166375
+ function getClearProjectStatement(db) {
166376
+ let stmt = clearProjectStatements.get(db);
166377
+ if (!stmt) {
166378
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
166379
+ clearProjectStatements.set(db, stmt);
166380
+ }
166381
+ return stmt;
166382
+ }
166383
+ function getClearProjectModelStatement(db) {
166384
+ let stmt = clearProjectModelStatements.get(db);
166385
+ if (!stmt) {
166386
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
166387
+ clearProjectModelStatements.set(db, stmt);
166388
+ }
166389
+ return stmt;
166390
+ }
166391
+ function getSearchRowsStatement(db, withModel) {
166392
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
166393
+ let stmt = map2.get(db);
166394
+ if (!stmt) {
166395
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
166396
+ e.session_id AS sessionId,
166397
+ c.title AS title,
166398
+ c.start_message AS compartmentStart,
166399
+ c.end_message AS compartmentEnd,
166400
+ e.window_index AS windowIndex,
166401
+ e.start_ordinal AS windowStart,
166402
+ e.end_ordinal AS windowEnd,
166403
+ e.chunk_hash AS chunkHash,
166404
+ e.model_id AS modelId,
166405
+ e.dims AS dims,
166406
+ e.vector AS vector
166407
+ FROM compartment_chunk_embeddings e
166408
+ JOIN compartments c ON c.id = e.compartment_id
166409
+ WHERE e.session_id = ?
166410
+ AND e.project_path = ?
166411
+ ${withModel ? "AND e.model_id = ?" : ""}
166412
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
166413
+ map2.set(db, stmt);
166414
+ }
166415
+ return stmt;
166416
+ }
166417
+ function isFinitePositiveInteger(value) {
166418
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
166419
+ }
166420
+ function normalizeCompartmentChunkMaxInputTokens(value) {
166421
+ if (!isFinitePositiveInteger(value)) {
166422
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
166423
+ }
166424
+ return Math.max(1, Math.floor(value));
166425
+ }
166426
+ function normalizeContent(text) {
166427
+ return text.replace(/\s+/g, " ").trim();
166428
+ }
166429
+ function formatOrdinalRange(start, end) {
166430
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
166431
+ }
166432
+ function rolePrefix(role) {
166433
+ if (role === "user")
166434
+ return "U";
166435
+ if (role === "assistant")
166436
+ return "A";
166437
+ return null;
166438
+ }
166439
+ function parseOrdinal(value) {
166440
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
166441
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
166442
+ }
166443
+ function parseCanonicalLineRange(line) {
166444
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
166445
+ if (!match)
166446
+ return null;
166447
+ const start = Number.parseInt(match[1], 10);
166448
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
166449
+ if (!Number.isFinite(start) || !Number.isFinite(end))
166450
+ return null;
166451
+ return { start, end };
166452
+ }
166453
+ function hashChunkText(text) {
166454
+ return createHash8("sha256").update(text).digest("hex");
166455
+ }
166456
+ function vectorBlob(vector) {
166457
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
166458
+ }
166459
+ function toFloat32Array2(blob) {
166460
+ if (blob instanceof Uint8Array) {
166461
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
166462
+ return new Float32Array(buffer2);
166463
+ }
166464
+ return new Float32Array(blob.slice(0));
166465
+ }
166466
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
166467
+ if (endOrdinal < startOrdinal)
166468
+ return "";
166469
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
166470
+ const lines = [];
166471
+ let current = null;
166472
+ const flush2 = () => {
166473
+ if (!current || current.parts.length === 0)
166474
+ return;
166475
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
166476
+ current = null;
166477
+ };
166478
+ for (const row of rows) {
166479
+ const ordinal = parseOrdinal(row.messageOrdinal);
166480
+ const prefix = rolePrefix(row.role);
166481
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
166482
+ if (ordinal === null || prefix === null || content.length === 0)
166483
+ continue;
166484
+ if (current && current.role === prefix) {
166485
+ current.end = ordinal;
166486
+ current.parts.push(content);
166487
+ continue;
166488
+ }
166489
+ flush2();
166490
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
166491
+ }
166492
+ flush2();
166493
+ return lines.join(`
166494
+ `);
166495
+ }
166496
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
166497
+ const lines = [];
166498
+ for (const rawLine of chunkText.split(/\r?\n/)) {
166499
+ const line = rawLine.trim();
166500
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
166501
+ if (!match)
166502
+ continue;
166503
+ const lineStart = Number.parseInt(match[2], 10);
166504
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
166505
+ if (startOrdinal != null && lineEnd < startOrdinal)
166506
+ continue;
166507
+ if (endOrdinal != null && lineStart > endOrdinal)
166508
+ continue;
166509
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
166510
+ const ordinalSpan = lineEnd - lineStart + 1;
166511
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
166512
+ if (ordinalSpan === rawParts.length) {
166513
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
166514
+ if (part.startsWith("TC:"))
166515
+ return false;
166516
+ if (startOrdinal != null && ordinal < startOrdinal)
166517
+ return false;
166518
+ if (endOrdinal != null && ordinal > endOrdinal)
166519
+ return false;
166520
+ return true;
166521
+ });
166522
+ if (retained.length === 0)
166523
+ continue;
166524
+ const retainedStart = retained[0].ordinal;
166525
+ const retainedEnd = retained[retained.length - 1].ordinal;
166526
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
166527
+ continue;
166528
+ }
166529
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
166530
+ if (parts.length === 0)
166531
+ continue;
166532
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
166533
+ }
166534
+ return lines.join(`
166535
+ `);
166536
+ }
166537
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
166538
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
166539
+ if (lines.length === 0 || endOrdinal < startOrdinal)
166540
+ return [];
166541
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
166542
+ const effectiveMax = Math.max(1, Math.floor(normalizedMax * CHUNK_WINDOW_SAFETY_RATIO));
166543
+ const fullText = lines.join(`
166544
+ `);
166545
+ if (estimateTokens(fullText) <= effectiveMax) {
166546
+ return [
166547
+ {
166548
+ windowIndex: 0,
166549
+ startOrdinal,
166550
+ endOrdinal,
166551
+ text: fullText,
166552
+ chunkHash: hashChunkText(fullText)
166553
+ }
166554
+ ];
166555
+ }
166556
+ const windows = [];
166557
+ let currentLines = [];
166558
+ let currentStart = null;
166559
+ let currentEnd = null;
166560
+ let currentTokens = 0;
166561
+ const flush2 = () => {
166562
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
166563
+ return;
166564
+ const text = currentLines.join(`
166565
+ `);
166566
+ windows.push({
166567
+ windowIndex: windows.length + 1,
166568
+ startOrdinal: currentStart,
166569
+ endOrdinal: currentEnd,
166570
+ text,
166571
+ chunkHash: hashChunkText(text)
166572
+ });
166573
+ currentLines = [];
166574
+ currentStart = null;
166575
+ currentEnd = null;
166576
+ currentTokens = 0;
166577
+ };
166578
+ for (const line of lines) {
166579
+ const range = parseCanonicalLineRange(line);
166580
+ const lineStart = range?.start ?? startOrdinal;
166581
+ const lineEnd = range?.end ?? lineStart;
166582
+ const lineTokens = estimateTokens(line);
166583
+ if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
166584
+ flush2();
166585
+ }
166586
+ if (currentLines.length === 0) {
166587
+ currentStart = lineStart;
166588
+ }
166589
+ currentLines.push(line);
166590
+ currentEnd = lineEnd;
166591
+ currentTokens += lineTokens;
166592
+ }
166593
+ flush2();
166594
+ return windows;
166595
+ }
166596
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
166597
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
166598
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
166599
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
166600
+ }
166601
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
166602
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
166603
+ if (existing.size !== windows.length)
166604
+ return false;
166605
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
166606
+ }
166607
+ function replaceCompartmentChunkEmbeddings(db, rows) {
166608
+ if (rows.length === 0)
166609
+ return;
166610
+ const compartmentId = rows[0].compartmentId;
166611
+ const now = Date.now();
166612
+ db.transaction(() => {
166613
+ getDeleteByCompartmentStatement(db).run(compartmentId);
166614
+ const insert = getInsertEmbeddingStatement(db);
166615
+ for (const row of rows) {
166616
+ 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);
166617
+ }
166618
+ })();
166619
+ }
166620
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
166621
+ const rows = getDistinctModelStatement(db).all(projectPath);
166622
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
166623
+ }
166624
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
166625
+ if (modelId) {
166626
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
166627
+ }
166628
+ return getClearProjectStatement(db).run(projectPath).changes;
166629
+ }
166630
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
166631
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
166632
+ 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) => ({
166633
+ compartmentId: row.compartmentId,
166634
+ sessionId: row.sessionId,
166635
+ title: row.title,
166636
+ startOrdinal: row.compartmentStart,
166637
+ endOrdinal: row.compartmentEnd,
166638
+ windowIndex: row.windowIndex,
166639
+ windowStartOrdinal: row.windowStart,
166640
+ windowEndOrdinal: row.windowEnd,
166641
+ chunkHash: row.chunkHash,
166642
+ modelId: row.modelId,
166643
+ dims: row.dims,
166644
+ vector: toFloat32Array2(row.vector)
166645
+ }));
166646
+ }
166647
+ function mapBackfillCandidateRows(rows) {
166648
+ return rows.filter((row) => {
166649
+ if (row === null || typeof row !== "object")
166650
+ return false;
166651
+ const candidate = row;
166652
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
166653
+ }).map((row) => ({
166654
+ id: row.id,
166655
+ sessionId: row.sessionId,
166656
+ startMessage: row.startMessage,
166657
+ endMessage: row.endMessage,
166658
+ title: row.title
166659
+ }));
166660
+ }
166661
+ var sessionBackfillCandidateStatements = new WeakMap;
166662
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
166663
+ if (excludeIds && excludeIds.length > 0) {
166664
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
166665
+ const stmt2 = db.prepare(`SELECT c.id AS id,
166666
+ c.session_id AS sessionId,
166667
+ c.start_message AS startMessage,
166668
+ c.end_message AS endMessage,
166669
+ c.title AS title
166670
+ FROM compartments c
166671
+ JOIN session_projects sp
166672
+ ON sp.session_id = c.session_id
166673
+ AND sp.harness = c.harness
166674
+ AND sp.project_path = ?
166675
+ WHERE c.session_id = ?
166676
+ AND c.start_message IS NOT NULL
166677
+ AND c.end_message IS NOT NULL
166678
+ AND c.id NOT IN (${placeholders3})
166679
+ AND NOT EXISTS (
166680
+ SELECT 1
166681
+ FROM compartment_chunk_embeddings current
166682
+ WHERE current.compartment_id = c.id
166683
+ AND current.project_path = ?
166684
+ AND current.model_id = ?
166685
+ )
166686
+ ORDER BY c.start_message ASC, c.id ASC
166687
+ LIMIT ?`);
166688
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
166689
+ return mapBackfillCandidateRows(rows2);
166690
+ }
166691
+ let stmt = sessionBackfillCandidateStatements.get(db);
166692
+ if (!stmt) {
166693
+ stmt = db.prepare(`SELECT c.id AS id,
166694
+ c.session_id AS sessionId,
166695
+ c.start_message AS startMessage,
166696
+ c.end_message AS endMessage,
166697
+ c.title AS title
166698
+ FROM compartments c
166699
+ JOIN session_projects sp
166700
+ ON sp.session_id = c.session_id
166701
+ AND sp.harness = c.harness
166702
+ AND sp.project_path = ?
166703
+ WHERE c.session_id = ?
166704
+ AND c.start_message IS NOT NULL
166705
+ AND c.end_message IS NOT NULL
166706
+ AND NOT EXISTS (
166707
+ SELECT 1
166708
+ FROM compartment_chunk_embeddings current
166709
+ WHERE current.compartment_id = c.id
166710
+ AND current.project_path = ?
166711
+ AND current.model_id = ?
166712
+ )
166713
+ ORDER BY c.start_message ASC, c.id ASC
166714
+ LIMIT ?`);
166715
+ sessionBackfillCandidateStatements.set(db, stmt);
166716
+ }
166717
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
166718
+ return mapBackfillCandidateRows(rows);
166719
+ }
166720
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
166721
+ const row = db.prepare(`SELECT COUNT(*) AS n
166722
+ FROM compartments c
166723
+ JOIN session_projects sp
166724
+ ON sp.session_id = c.session_id
166725
+ AND sp.harness = c.harness
166726
+ AND sp.project_path = ?
166727
+ WHERE c.session_id = ?
166728
+ AND c.start_message IS NOT NULL
166729
+ AND c.end_message IS NOT NULL
166730
+ AND NOT EXISTS (
166731
+ SELECT 1
166732
+ FROM compartment_chunk_embeddings current
166733
+ WHERE current.compartment_id = c.id
166734
+ AND current.project_path = ?
166735
+ AND current.model_id = ?
166736
+ )`).get(projectPath, sessionId, projectPath, modelId);
166737
+ return typeof row?.n === "number" ? row.n : 0;
166738
+ }
166739
+
165656
166740
  // ../plugin/src/features/magic-context/memory/cosine-similarity.ts
165657
166741
  function cosineSimilarity(a, b) {
165658
166742
  if (a.length !== b.length) {
@@ -165811,19 +166895,19 @@ function isArrayLikeNumber(value) {
165811
166895
  }
165812
166896
  return arr.length === 0 || typeof arr[0] === "number";
165813
166897
  }
165814
- function toFloat32Array2(values) {
166898
+ function toFloat32Array3(values) {
165815
166899
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
165816
166900
  }
165817
166901
  function extractBatchEmbeddings(result, expectedCount) {
165818
166902
  const { data } = result;
165819
166903
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
165820
- return data.map((entry) => toFloat32Array2(entry));
166904
+ return data.map((entry) => toFloat32Array3(entry));
165821
166905
  }
165822
166906
  if (!isArrayLikeNumber(data)) {
165823
166907
  log("[magic-context] embedding batch returned unexpected data shape");
165824
166908
  return Array.from({ length: expectedCount }, () => null);
165825
166909
  }
165826
- const flatData = toFloat32Array2(data);
166910
+ const flatData = toFloat32Array3(data);
165827
166911
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
165828
166912
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
165829
166913
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -165838,6 +166922,7 @@ function extractBatchEmbeddings(result, expectedCount) {
165838
166922
 
165839
166923
  class LocalEmbeddingProvider {
165840
166924
  modelId;
166925
+ maxInputTokens;
165841
166926
  model;
165842
166927
  pipeline = null;
165843
166928
  initPromise = null;
@@ -165845,8 +166930,9 @@ class LocalEmbeddingProvider {
165845
166930
  disposing = false;
165846
166931
  disposePromise = null;
165847
166932
  inFlightWaiters = [];
165848
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
166933
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
165849
166934
  this.model = model;
166935
+ this.maxInputTokens = maxInputTokens;
165850
166936
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
165851
166937
  }
165852
166938
  async initialize() {
@@ -166094,6 +167180,13 @@ function blockedEmbeddingEndpointReason(endpoint) {
166094
167180
  function normalizeEndpoint2(endpoint) {
166095
167181
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
166096
167182
  }
167183
+ function embeddingModelsMatch(served, requested) {
167184
+ const a = served.trim().toLowerCase();
167185
+ const b = requested.trim().toLowerCase();
167186
+ if (a.length === 0 || b.length === 0)
167187
+ return true;
167188
+ return a === b || a.includes(b) || b.includes(a);
167189
+ }
166097
167190
  var FAILURE_THRESHOLD = 3;
166098
167191
  var FAILURE_WINDOW_MS = 60000;
166099
167192
  var OPEN_DURATION_MS = 5 * 60000;
@@ -166101,6 +167194,7 @@ var FETCH_TIMEOUT_MS = 30000;
166101
167194
 
166102
167195
  class OpenAICompatibleEmbeddingProvider {
166103
167196
  modelId;
167197
+ maxInputTokens;
166104
167198
  endpoint;
166105
167199
  model;
166106
167200
  apiKey;
@@ -166110,6 +167204,7 @@ class OpenAICompatibleEmbeddingProvider {
166110
167204
  failureTimes = [];
166111
167205
  circuitOpenUntil = 0;
166112
167206
  openLogged = false;
167207
+ modelMismatchLogged = false;
166113
167208
  halfOpenProbeInFlight = false;
166114
167209
  constructor(options) {
166115
167210
  this.endpoint = normalizeEndpoint2(options.endpoint);
@@ -166117,11 +167212,13 @@ class OpenAICompatibleEmbeddingProvider {
166117
167212
  this.apiKey = options.apiKey?.trim() ?? "";
166118
167213
  this.inputType = options.inputType?.trim() ?? "";
166119
167214
  this.truncate = options.truncate?.trim() ?? "";
167215
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
166120
167216
  this.modelId = getEmbeddingProviderIdentity({
166121
167217
  provider: "openai-compatible",
166122
167218
  endpoint: this.endpoint,
166123
167219
  model: this.model,
166124
- ...this.apiKey ? { api_key: this.apiKey } : {}
167220
+ ...this.apiKey ? { api_key: this.apiKey } : {},
167221
+ ...this.inputType ? { input_type: this.inputType } : {}
166125
167222
  });
166126
167223
  }
166127
167224
  async initialize() {
@@ -166206,6 +167303,15 @@ class OpenAICompatibleEmbeddingProvider {
166206
167303
  this.recordFailure(isProbe);
166207
167304
  return Array.from({ length: texts.length }, () => null);
166208
167305
  }
167306
+ const servedModel = typeof body.model === "string" ? body.model : "";
167307
+ if (this.model && servedModel && !embeddingModelsMatch(servedModel, this.model)) {
167308
+ if (!this.modelMismatchLogged) {
167309
+ 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.`);
167310
+ this.modelMismatchLogged = true;
167311
+ }
167312
+ this.recordFailure(isProbe);
167313
+ return Array.from({ length: texts.length }, () => null);
167314
+ }
166209
167315
  const items = Array.isArray(body.data) ? body.data : [];
166210
167316
  const results = Array.from({ length: texts.length }, (_, index) => {
166211
167317
  const embedding = items[index]?.embedding;
@@ -166312,7 +167418,7 @@ class OpenAICompatibleEmbeddingProvider {
166312
167418
  }
166313
167419
 
166314
167420
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
166315
- import { createHash as createHash7, randomUUID } from "node:crypto";
167421
+ import { createHash as createHash9, randomUUID } from "node:crypto";
166316
167422
  init_logger();
166317
167423
 
166318
167424
  // ../plugin/src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
@@ -166320,7 +167426,7 @@ var saveStatements = new WeakMap;
166320
167426
  var loadProjectStatements = new WeakMap;
166321
167427
  var loadUnembeddedStatements = new WeakMap;
166322
167428
  var countEmbeddedStatements = new WeakMap;
166323
- var clearProjectStatements = new WeakMap;
167429
+ var clearProjectStatements2 = new WeakMap;
166324
167430
  var distinctModelIdStatements = new WeakMap;
166325
167431
  function getSaveStatement(db) {
166326
167432
  let stmt = saveStatements.get(db);
@@ -166368,12 +167474,12 @@ function getCountEmbeddedStatement(db) {
166368
167474
  }
166369
167475
  return stmt;
166370
167476
  }
166371
- function getClearProjectStatement(db) {
166372
- let stmt = clearProjectStatements.get(db);
167477
+ function getClearProjectStatement2(db) {
167478
+ let stmt = clearProjectStatements2.get(db);
166373
167479
  if (!stmt) {
166374
167480
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
166375
167481
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
166376
- clearProjectStatements.set(db, stmt);
167482
+ clearProjectStatements2.set(db, stmt);
166377
167483
  }
166378
167484
  return stmt;
166379
167485
  }
@@ -166409,7 +167515,7 @@ function countEmbeddedCommits(db, projectPath) {
166409
167515
  return row?.count ?? 0;
166410
167516
  }
166411
167517
  function clearProjectCommitEmbeddings(db, projectPath) {
166412
- return getClearProjectStatement(db).run(projectPath).changes;
167518
+ return getClearProjectStatement2(db).run(projectPath).changes;
166413
167519
  }
166414
167520
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
166415
167521
  const rows = getDistinctModelIdStatement(db).all(projectPath);
@@ -166535,9 +167641,83 @@ function releaseGitSweepLease(db, projectPath, holderId) {
166535
167641
  });
166536
167642
  }
166537
167643
 
167644
+ // ../plugin/src/features/magic-context/session-project-storage.ts
167645
+ var upsertSessionProjectStatements = new WeakMap;
167646
+ var repairSessionChunkProjectStatements = new WeakMap;
167647
+ var repairProjectChunkProjectStatements = new WeakMap;
167648
+ function getUpsertSessionProjectStatement(db) {
167649
+ let stmt = upsertSessionProjectStatements.get(db);
167650
+ if (!stmt) {
167651
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
167652
+ VALUES (?, ?, ?, ?)
167653
+ ON CONFLICT(session_id, harness) DO UPDATE SET
167654
+ project_path = excluded.project_path,
167655
+ updated_at = excluded.updated_at
167656
+ WHERE session_projects.project_path <> excluded.project_path`);
167657
+ upsertSessionProjectStatements.set(db, stmt);
167658
+ }
167659
+ return stmt;
167660
+ }
167661
+ function getRepairSessionChunkProjectStatement(db) {
167662
+ let stmt = repairSessionChunkProjectStatements.get(db);
167663
+ if (!stmt) {
167664
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
167665
+ SET project_path = ?
167666
+ WHERE session_id = ?
167667
+ AND harness = ?
167668
+ AND project_path <> ?`);
167669
+ repairSessionChunkProjectStatements.set(db, stmt);
167670
+ }
167671
+ return stmt;
167672
+ }
167673
+ function getRepairProjectChunkProjectStatement(db) {
167674
+ let stmt = repairProjectChunkProjectStatements.get(db);
167675
+ if (!stmt) {
167676
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
167677
+ SET project_path = (
167678
+ SELECT sp.project_path
167679
+ FROM session_projects sp
167680
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
167681
+ AND sp.harness = compartment_chunk_embeddings.harness
167682
+ LIMIT 1
167683
+ )
167684
+ WHERE EXISTS (
167685
+ SELECT 1
167686
+ FROM session_projects sp
167687
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
167688
+ AND sp.harness = compartment_chunk_embeddings.harness
167689
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
167690
+ AND (
167691
+ sp.project_path = ?
167692
+ OR compartment_chunk_embeddings.project_path = ?
167693
+ )
167694
+ )`);
167695
+ repairProjectChunkProjectStatements.set(db, stmt);
167696
+ }
167697
+ return stmt;
167698
+ }
167699
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
167700
+ if (!sessionId || !projectPath)
167701
+ return;
167702
+ const harness = getHarness();
167703
+ const now = Date.now();
167704
+ db.transaction(() => {
167705
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
167706
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
167707
+ })();
167708
+ }
167709
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
167710
+ if (!projectPath)
167711
+ return 0;
167712
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
167713
+ }
167714
+
166538
167715
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
166539
167716
  var OFF_PROVIDER_IDENTITY = "embedding-provider:off";
166540
167717
  var SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167718
+ var CHUNK_DRAIN_BATCH_SIZE = 8;
167719
+ var MAX_WINDOWS_PER_EMBED_CALL = 16;
167720
+ var SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
166541
167721
  var projectRegistrations = new Map;
166542
167722
  var loadUnembeddedMemoriesStatements = new WeakMap;
166543
167723
  var globalRegistrationGeneration = 0;
@@ -166546,7 +167726,10 @@ function resolveEmbeddingConfig(config2) {
166546
167726
  if (!config2 || config2.provider === "local") {
166547
167727
  return {
166548
167728
  provider: "local",
166549
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
167729
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
167730
+ ...config2?.max_input_tokens ? {
167731
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
167732
+ } : {}
166550
167733
  };
166551
167734
  }
166552
167735
  if (config2.provider === "openai-compatible") {
@@ -166559,7 +167742,10 @@ function resolveEmbeddingConfig(config2) {
166559
167742
  endpoint: config2.endpoint.trim(),
166560
167743
  ...apiKey ? { api_key: apiKey } : {},
166561
167744
  ...inputType ? { input_type: inputType } : {},
166562
- ...truncate ? { truncate } : {}
167745
+ ...truncate ? { truncate } : {},
167746
+ ...config2.max_input_tokens ? {
167747
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
167748
+ } : {}
166563
167749
  };
166564
167750
  }
166565
167751
  return { provider: "off" };
@@ -166577,10 +167763,11 @@ function createProvider(config2) {
166577
167763
  model: config2.model,
166578
167764
  apiKey: config2.api_key,
166579
167765
  inputType: config2.input_type,
166580
- truncate: config2.truncate
167766
+ truncate: config2.truncate,
167767
+ maxInputTokens: config2.max_input_tokens
166581
167768
  });
166582
167769
  }
166583
- return new LocalEmbeddingProvider(config2.model);
167770
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
166584
167771
  }
166585
167772
  function stableStringify2(value) {
166586
167773
  if (Array.isArray(value)) {
@@ -166593,7 +167780,7 @@ function stableStringify2(value) {
166593
167780
  return JSON.stringify(value);
166594
167781
  }
166595
167782
  function sha256Prefix(value, length = 16) {
166596
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
167783
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
166597
167784
  }
166598
167785
  function getRuntimeFingerprint(config2) {
166599
167786
  if (config2.provider === "off") {
@@ -166601,6 +167788,18 @@ function getRuntimeFingerprint(config2) {
166601
167788
  }
166602
167789
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
166603
167790
  }
167791
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
167792
+ if (config2.provider === "off") {
167793
+ return OFF_PROVIDER_IDENTITY;
167794
+ }
167795
+ const chunkIdentity = {
167796
+ providerIdentity,
167797
+ chunkerVersion: 2,
167798
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
167799
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
167800
+ };
167801
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
167802
+ }
166604
167803
  function sameFeatures(a, b) {
166605
167804
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
166606
167805
  }
@@ -166617,7 +167816,8 @@ function snapshotFor(registration) {
166617
167816
  features: { ...registration.features },
166618
167817
  enabled,
166619
167818
  gitCommitEnabled,
166620
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
167819
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
167820
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
166621
167821
  };
166622
167822
  }
166623
167823
  function disposeProvider(provider) {
@@ -166637,7 +167837,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
166637
167837
  }
166638
167838
  return false;
166639
167839
  }
166640
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
167840
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
166641
167841
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
166642
167842
  return false;
166643
167843
  }
@@ -166658,6 +167858,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
166658
167858
  wiped = true;
166659
167859
  }
166660
167860
  }
167861
+ if (features.memoryEnabled) {
167862
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
167863
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
167864
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
167865
+ clearChunkEmbeddingsForProject(db, projectIdentity);
167866
+ wiped = true;
167867
+ }
167868
+ }
166661
167869
  })();
166662
167870
  return wiped;
166663
167871
  }
@@ -166665,10 +167873,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
166665
167873
  const resolvedConfig = resolveEmbeddingConfig(config2);
166666
167874
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
166667
167875
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
167876
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
166668
167877
  const prior = projectRegistrations.get(projectIdentity);
166669
167878
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
166670
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
166671
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
167879
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
167880
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
166672
167881
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
166673
167882
  const registration = {
166674
167883
  projectIdentity,
@@ -166680,6 +167889,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
166680
167889
  generation,
166681
167890
  features: { ...features },
166682
167891
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
167892
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
166683
167893
  observationMode: false
166684
167894
  };
166685
167895
  projectRegistrations.set(projectIdentity, registration);
@@ -166702,6 +167912,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
166702
167912
  generation,
166703
167913
  features: { memoryEnabled: false, gitCommitEnabled: false },
166704
167914
  modelId: "off",
167915
+ chunkModelId: "off",
166705
167916
  observationMode: true
166706
167917
  };
166707
167918
  projectRegistrations.set(projectIdentity, registration);
@@ -166712,6 +167923,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
166712
167923
  const registration = projectRegistrations.get(projectIdentity);
166713
167924
  return registration ? snapshotFor(registration) : null;
166714
167925
  }
167926
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
167927
+ const registration = projectRegistrations.get(projectIdentity);
167928
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
167929
+ }
167930
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
167931
+ const registration = projectRegistrations.get(projectIdentity);
167932
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
167933
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
167934
+ }
166715
167935
  function getOrCreateProjectProvider(registration) {
166716
167936
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
166717
167937
  return null;
@@ -166806,6 +168026,131 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
166806
168026
  return 0;
166807
168027
  }
166808
168028
  }
168029
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
168030
+ const noWork = [];
168031
+ if (candidates.length === 0)
168032
+ return { embedded: 0, noWork };
168033
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
168034
+ const prepared = [];
168035
+ for (const candidate of candidates) {
168036
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
168037
+ if (canonicalText.length === 0) {
168038
+ noWork.push(candidate.id);
168039
+ continue;
168040
+ }
168041
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
168042
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
168043
+ noWork.push(candidate.id);
168044
+ continue;
168045
+ }
168046
+ prepared.push({ candidate, windows });
168047
+ }
168048
+ if (prepared.length === 0)
168049
+ return { embedded: 0, noWork };
168050
+ let embedded = 0;
168051
+ let i = 0;
168052
+ while (i < prepared.length) {
168053
+ if (signal?.aborted)
168054
+ break;
168055
+ const slice = [];
168056
+ let windowCount = 0;
168057
+ do {
168058
+ const item = prepared[i];
168059
+ slice.push(item);
168060
+ windowCount += item.windows.length;
168061
+ i += 1;
168062
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
168063
+ const texts = [];
168064
+ for (const item of slice)
168065
+ texts.push(...item.windows.map((w) => w.text));
168066
+ try {
168067
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
168068
+ if (!result)
168069
+ continue;
168070
+ if (signal?.aborted)
168071
+ break;
168072
+ let offset = 0;
168073
+ for (const item of slice) {
168074
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
168075
+ offset += item.windows.length;
168076
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
168077
+ continue;
168078
+ }
168079
+ const rows = item.windows.map((window, index) => ({
168080
+ compartmentId: item.candidate.id,
168081
+ sessionId: item.candidate.sessionId,
168082
+ projectPath: projectIdentity,
168083
+ window,
168084
+ modelId,
168085
+ vector: vectors[index]
168086
+ }));
168087
+ replaceCompartmentChunkEmbeddings(db, rows);
168088
+ embedded += 1;
168089
+ }
168090
+ } catch (error51) {
168091
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
168092
+ }
168093
+ }
168094
+ return { embedded, noWork };
168095
+ }
168096
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
168097
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
168098
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
168099
+ return { status: "disabled", embedded: 0, total: 0 };
168100
+ }
168101
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
168102
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
168103
+ if (total === 0)
168104
+ return { status: "nothing", embedded: 0, total: 0 };
168105
+ const holderId = `session-embed-${randomUUID()}`;
168106
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
168107
+ if (!lease2.acquired)
168108
+ return { status: "busy", embedded: 0, total };
168109
+ const renewal = setInterval(() => {
168110
+ try {
168111
+ renewGitSweepLease(db, projectIdentity, holderId);
168112
+ } catch {}
168113
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
168114
+ renewal.unref?.();
168115
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
168116
+ const skipIds = [];
168117
+ let embedded = 0;
168118
+ let aborted2 = false;
168119
+ let providerStalled = false;
168120
+ try {
168121
+ options?.onProgress?.({ embedded, total });
168122
+ for (;; ) {
168123
+ if (options?.signal?.aborted) {
168124
+ aborted2 = true;
168125
+ break;
168126
+ }
168127
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
168128
+ if (candidates.length === 0)
168129
+ break;
168130
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
168131
+ for (const id of noWork)
168132
+ skipIds.push(id);
168133
+ if (n === 0 && noWork.length === 0) {
168134
+ providerStalled = true;
168135
+ break;
168136
+ }
168137
+ embedded += n;
168138
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
168139
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
168140
+ }
168141
+ } finally {
168142
+ clearInterval(renewal);
168143
+ releaseGitSweepLease(db, projectIdentity, holderId);
168144
+ }
168145
+ if (aborted2)
168146
+ return { status: "aborted", embedded, total };
168147
+ if (providerStalled) {
168148
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
168149
+ if (remaining > 0)
168150
+ return { status: "stalled", embedded, total, remaining };
168151
+ }
168152
+ return { status: "done", embedded, total };
168153
+ }
166809
168154
 
166810
168155
  // ../plugin/src/features/magic-context/memory/embedding.ts
166811
168156
  var DEFAULT_EMBEDDING_CONFIG = {
@@ -166825,10 +168170,11 @@ function createProvider2(config2) {
166825
168170
  model: config2.model,
166826
168171
  apiKey: config2.api_key,
166827
168172
  inputType: config2.input_type,
166828
- truncate: config2.truncate
168173
+ truncate: config2.truncate,
168174
+ maxInputTokens: config2.max_input_tokens
166829
168175
  });
166830
168176
  }
166831
- return new LocalEmbeddingProvider(config2.model);
168177
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
166832
168178
  }
166833
168179
  function getOrCreateProvider() {
166834
168180
  if (provider) {
@@ -167071,6 +168417,7 @@ init_logger();
167071
168417
  // ../plugin/src/features/magic-context/memory/storage-memory-fts.ts
167072
168418
  var DEFAULT_SEARCH_LIMIT = 10;
167073
168419
  var searchStatements = new WeakMap;
168420
+ var unionSearchStatements = new Map;
167074
168421
  function getSearchStatement(db) {
167075
168422
  let stmt = searchStatements.get(db);
167076
168423
  if (!stmt) {
@@ -167079,6 +168426,23 @@ function getSearchStatement(db) {
167079
168426
  }
167080
168427
  return stmt;
167081
168428
  }
168429
+ function getUnionSearchStatement(db, arity) {
168430
+ let statements = unionSearchStatements.get(arity);
168431
+ if (!statements) {
168432
+ statements = new WeakMap;
168433
+ unionSearchStatements.set(arity, statements);
168434
+ }
168435
+ let stmt = statements.get(db);
168436
+ if (!stmt) {
168437
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
168438
+ 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 ?`);
168439
+ statements.set(db, stmt);
168440
+ }
168441
+ return stmt;
168442
+ }
168443
+ function uniqueProjectPaths2(projectPaths) {
168444
+ return [...new Set(projectPaths.filter((path7) => path7.length > 0))];
168445
+ }
167082
168446
  function sanitizeFtsQuery(query) {
167083
168447
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
167084
168448
  if (tokens.length === 0)
@@ -167097,6 +168461,28 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
167097
168461
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
167098
168462
  return rows.map(toMemory);
167099
168463
  }
168464
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
168465
+ const identities = uniqueProjectPaths2(projectPaths);
168466
+ if (identities.length === 0)
168467
+ return [];
168468
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
168469
+ identities,
168470
+ ownIdentities,
168471
+ shareCategories,
168472
+ tableName: "memories"
168473
+ });
168474
+ if (identities.length === 1 && !sharingFilter.active) {
168475
+ return searchMemoriesFTS(db, identities[0], query, limit);
168476
+ }
168477
+ const trimmedQuery = query.trim();
168478
+ if (trimmedQuery.length === 0 || limit <= 0)
168479
+ return [];
168480
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
168481
+ if (sanitized.length === 0)
168482
+ return [];
168483
+ 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);
168484
+ return rows.map(toMemory);
168485
+ }
167100
168486
 
167101
168487
  // ../plugin/src/features/magic-context/git-commits/search-git-commits.ts
167102
168488
  var ftsStatements = new WeakMap;
@@ -167389,7 +168775,7 @@ async function sweepGitCommits(args) {
167389
168775
  }
167390
168776
 
167391
168777
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
167392
- import { createHash as createHash8 } from "node:crypto";
168778
+ import { createHash as createHash10 } from "node:crypto";
167393
168779
  init_logger();
167394
168780
  var EMBEDDING_AFFECTING_KEYS = new Set([
167395
168781
  "embedding.api_key",
@@ -167411,7 +168797,7 @@ var EMBEDDING_WARNING_TERMS = [
167411
168797
  ];
167412
168798
  var loggedFailureSignatures = new Map;
167413
168799
  function sha256Prefix2(value, length = 16) {
167414
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
168800
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
167415
168801
  }
167416
168802
  function warningLooksEmbeddingRelated(message) {
167417
168803
  const lower = message.toLowerCase();
@@ -168457,6 +169843,88 @@ function registerCtxDreamCommand(pi, deps) {
168457
169843
  });
168458
169844
  }
168459
169845
 
169846
+ // src/commands/ctx-embed-history.ts
169847
+ function registerCtxEmbedHistoryCommand(pi, deps) {
169848
+ pi.registerCommand("ctx-embed-history", {
169849
+ description: "Embed all of this session's history compartments for semantic search, in one pass",
169850
+ handler: async (_args, ctx) => {
169851
+ const sessionId = resolveSessionId(ctx);
169852
+ if (!sessionId) {
169853
+ sendCtxStatusMessage(pi, {
169854
+ title: "/ctx-embed-history",
169855
+ text: `## /ctx-embed-history
169856
+
169857
+ No active Pi session is available.`,
169858
+ level: "error"
169859
+ });
169860
+ return;
169861
+ }
169862
+ if (deps.memoryEnabled === false) {
169863
+ sendCtxStatusMessage(pi, {
169864
+ title: "/ctx-embed-history",
169865
+ text: `## /ctx-embed-history
169866
+
169867
+ Memory is disabled for this project, so there is no semantic embedding to backfill.`,
169868
+ level: "info"
169869
+ });
169870
+ return;
169871
+ }
169872
+ const project = deps.resolveProject?.(ctx) ?? {
169873
+ projectDir: deps.projectDir,
169874
+ projectIdentity: deps.projectIdentity
169875
+ };
169876
+ await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
169877
+ const outcome = await embedSessionCompartmentChunks(deps.db, project.projectIdentity, sessionId);
169878
+ const { text, level } = (() => {
169879
+ switch (outcome.status) {
169880
+ case "nothing":
169881
+ return {
169882
+ text: `## /ctx-embed-history
169883
+
169884
+ All of this session's history is already embedded.`,
169885
+ level: "info"
169886
+ };
169887
+ case "disabled":
169888
+ return {
169889
+ text: `## /ctx-embed-history
169890
+
169891
+ No embedding provider is configured, so there is nothing to embed.`,
169892
+ level: "info"
169893
+ };
169894
+ case "busy":
169895
+ return {
169896
+ text: `## /ctx-embed-history
169897
+
169898
+ Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`,
169899
+ level: "info"
169900
+ };
169901
+ case "stalled":
169902
+ return {
169903
+ text: `## /ctx-embed-history
169904
+
169905
+ 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.`,
169906
+ level: "info"
169907
+ };
169908
+ default:
169909
+ return {
169910
+ text: `## /ctx-embed-history
169911
+
169912
+ Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`,
169913
+ level: "success"
169914
+ };
169915
+ }
169916
+ })();
169917
+ sendCtxStatusMessage(pi, { title: "/ctx-embed-history", text, level }, {
169918
+ sessionId,
169919
+ projectIdentity: project.projectIdentity,
169920
+ status: outcome.status,
169921
+ embedded: outcome.embedded,
169922
+ total: outcome.total
169923
+ });
169924
+ }
169925
+ });
169926
+ }
169927
+
168460
169928
  // ../plugin/src/hooks/magic-context/execute-flush.ts
168461
169929
  init_logger();
168462
169930
  function executeFlush(db, sessionId) {
@@ -168870,6 +170338,51 @@ function computePiWorkMetrics(sessionEntries) {
168870
170338
  return { newWorkTokens, totalInputTokens };
168871
170339
  }
168872
170340
 
170341
+ // ../plugin/src/hooks/magic-context/system-injection-stripper.ts
170342
+ var SYSTEM_INJECTION_MARKERS = [
170343
+ "<!-- OMO_INTERNAL_INITIATOR -->",
170344
+ "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
170345
+ "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
170346
+ "[Category+Skill Reminder]",
170347
+ "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
170348
+ "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
170349
+ "[EMERGENCY CONTEXT WINDOW WARNING]",
170350
+ "Unstable background agent appears idle",
170351
+ "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
170352
+ ];
170353
+ var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
170354
+ var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
170355
+ function stripSystemInjection(text) {
170356
+ let hasInjection = false;
170357
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
170358
+ if (text.includes(marker)) {
170359
+ hasInjection = true;
170360
+ break;
170361
+ }
170362
+ }
170363
+ if (SYSTEM_REMINDER_REGEX.test(text))
170364
+ hasInjection = true;
170365
+ SYSTEM_REMINDER_REGEX.lastIndex = 0;
170366
+ if (!hasInjection)
170367
+ return null;
170368
+ let cleaned = text;
170369
+ cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
170370
+ cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
170371
+ cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
170372
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
170373
+ if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
170374
+ continue;
170375
+ const idx = cleaned.indexOf(marker);
170376
+ if (idx === -1)
170377
+ continue;
170378
+ const blockEnd = cleaned.indexOf(`
170379
+
170380
+ `, idx + marker.length);
170381
+ cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
170382
+ }
170383
+ return cleaned.trim();
170384
+ }
170385
+
168873
170386
  // ../plugin/src/hooks/magic-context/apply-operations.ts
168874
170387
  var USER_DROP_PREVIEW_CHARS = 250;
168875
170388
  var RECENT_TOOL_SKELETON_WINDOW = 20;
@@ -168879,6 +170392,10 @@ function buildReplacementContent(tagId, target) {
168879
170392
  return `[dropped §${tagId}§]`;
168880
170393
  }
168881
170394
  const currentContent = target.getContent?.() ?? "";
170395
+ const strippedInjection = stripSystemInjection(currentContent);
170396
+ if (strippedInjection !== null && stripTagPrefix(strippedInjection).trim().length === 0) {
170397
+ return `[dropped §${tagId}§]`;
170398
+ }
168882
170399
  const originalText = stripTagPrefix(currentContent);
168883
170400
  if (originalText.length <= USER_DROP_PREVIEW_CHARS) {
168884
170401
  return `[truncated §${tagId}§]
@@ -170487,8 +172004,8 @@ var CHANNEL1_SENTINEL = "<system-reminder>";
170487
172004
  var TOKENS_PER_BYTE = 0.25;
170488
172005
  var CHANNEL1_FLOOR_TOKENS = 1e4;
170489
172006
  var CHANNEL1_REFIRE_FLOOR_TOKENS = 1e4;
170490
- function channel1RefireTokens(historyBudgetTokens) {
170491
- const scaled = Math.round(0.05 * Math.max(0, historyBudgetTokens));
172007
+ function channel1RefireTokens(workingWindowTokens) {
172008
+ const scaled = Math.round(0.05 * Math.max(0, workingWindowTokens));
170492
172009
  return Math.max(CHANNEL1_REFIRE_FLOOR_TOKENS, scaled);
170493
172010
  }
170494
172011
  var S_GENTLE = 0.2;
@@ -170504,7 +172021,7 @@ function toolOutputTokens(output) {
170504
172021
  return Math.round(byteSize(output) * TOKENS_PER_BYTE);
170505
172022
  }
170506
172023
  function decideChannel1(input) {
170507
- const { undroppedTokens, pressure, historyBudgetTokens, hasRecentReduce } = input;
172024
+ const { undroppedTokens, pressure, workingWindowTokens, hasRecentReduce } = input;
170508
172025
  const resetCycle = hasRecentReduce || undroppedTokens < input.lastNudgeUndropped;
170509
172026
  const lastNudge = resetCycle ? 0 : input.lastNudgeUndropped;
170510
172027
  const lastLevel = resetCycle ? "" : input.lastNudgeLevel;
@@ -170519,7 +172036,7 @@ function decideChannel1(input) {
170519
172036
  return quiet();
170520
172037
  if (undroppedTokens < CHANNEL1_FLOOR_TOKENS)
170521
172038
  return quiet();
170522
- const budget = historyBudgetTokens > 0 ? historyBudgetTokens : undroppedTokens || 1;
172039
+ const budget = workingWindowTokens > 0 ? workingWindowTokens : undroppedTokens || 1;
170523
172040
  const severity = undroppedTokens / budget * pressure;
170524
172041
  if (severity < S_GENTLE)
170525
172042
  return quiet();
@@ -170531,7 +172048,7 @@ function decideChannel1(input) {
170531
172048
  else
170532
172049
  level = "gentle";
170533
172050
  if (lastLevel === "") {
170534
- if (undroppedTokens < lastNudge + channel1RefireTokens(historyBudgetTokens)) {
172051
+ if (undroppedTokens < lastNudge + channel1RefireTokens(workingWindowTokens)) {
170535
172052
  return quiet();
170536
172053
  }
170537
172054
  } else if (LEVEL_RANK[level] <= LEVEL_RANK[lastLevel]) {
@@ -170576,13 +172093,13 @@ function buildChannel1Reminder(level, undroppedTokens) {
170576
172093
  let body;
170577
172094
  switch (level) {
170578
172095
  case "gentle":
170579
- 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.`;
172096
+ 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.`;
170580
172097
  break;
170581
172098
  case "firm":
170582
- body = `~${amount} tokens of unreduced tool output is accumulating. ` + `Drop what you have already processed with ctx_reduce before continuing.`;
172099
+ 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.`;
170583
172100
  break;
170584
172101
  case "urgent":
170585
- 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.`;
172102
+ 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.`;
170586
172103
  break;
170587
172104
  }
170588
172105
  return `
@@ -170592,53 +172109,6 @@ ${body}
170592
172109
  </system-reminder>`;
170593
172110
  }
170594
172111
 
170595
- // ../plugin/src/features/magic-context/memory/constants.ts
170596
- var V2_MEMORY_CATEGORIES = [
170597
- "PROJECT_RULES",
170598
- "ARCHITECTURE",
170599
- "CONSTRAINTS",
170600
- "CONFIG_VALUES",
170601
- "NAMING"
170602
- ];
170603
- var PROMOTABLE_CATEGORIES = [
170604
- "PROJECT_RULES",
170605
- "ARCHITECTURE",
170606
- "CONSTRAINTS",
170607
- "CONFIG_VALUES",
170608
- "NAMING",
170609
- "ARCHITECTURE_DECISIONS",
170610
- "CONFIG_DEFAULTS",
170611
- "USER_PREFERENCES",
170612
- "USER_DIRECTIVES",
170613
- "ENVIRONMENT",
170614
- "WORKFLOW_RULES",
170615
- "KNOWN_ISSUES"
170616
- ];
170617
- var CATEGORY_PRIORITY = [
170618
- "PROJECT_RULES",
170619
- "ARCHITECTURE",
170620
- "CONSTRAINTS",
170621
- "CONFIG_VALUES",
170622
- "NAMING",
170623
- "USER_DIRECTIVES",
170624
- "USER_PREFERENCES",
170625
- "CONFIG_DEFAULTS",
170626
- "ARCHITECTURE_DECISIONS",
170627
- "ENVIRONMENT",
170628
- "WORKFLOW_RULES",
170629
- "KNOWN_ISSUES"
170630
- ];
170631
- var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
170632
- var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
170633
- acc[category] = index;
170634
- return acc;
170635
- }, {});
170636
- var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
170637
- var CATEGORY_DEFAULT_TTL = {
170638
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
170639
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
170640
- };
170641
-
170642
172112
  // ../plugin/src/shared/bounded-session-map.ts
170643
172113
  class BoundedSessionMap {
170644
172114
  maxEntries;
@@ -171049,26 +172519,40 @@ ${sections.join(`
171049
172519
  var DEFAULT_MEMORY_BUDGET_TOKENS = 8000;
171050
172520
  var MEMORY_BLOCK_WRAPPER_TOKENS = 6;
171051
172521
  var DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000;
172522
+ function memoryCanonicalIdentity(memory, workspace) {
172523
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
172524
+ }
172525
+ function memorySelectionOrder(left, right) {
172526
+ if (left.status === "permanent" && right.status !== "permanent")
172527
+ return -1;
172528
+ if (right.status === "permanent" && left.status !== "permanent")
172529
+ return 1;
172530
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
172531
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
172532
+ const importanceDiff = rightImportance - leftImportance;
172533
+ if (importanceDiff !== 0)
172534
+ return importanceDiff;
172535
+ return left.id - right.id;
172536
+ }
172537
+ function memoryRenderOrder(left, right) {
172538
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
172539
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
172540
+ const categoryDiff = aPriority - bPriority;
172541
+ if (categoryDiff !== 0)
172542
+ return categoryDiff;
172543
+ return left.id - right.id;
172544
+ }
171052
172545
  var maxCompartmentSeqStatements = new WeakMap;
171053
172546
  var maxMemoryIdStatements = new WeakMap;
171054
172547
  var legacyCompartmentCountStatements = new WeakMap;
171055
172548
  var m0CompartmentStatements = new WeakMap;
171056
172549
  var newCompartmentStatements = new WeakMap;
171057
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
171058
- const selectionOrder = [...memories].sort((a, b) => {
171059
- if (a.status === "permanent" && b.status !== "permanent")
171060
- return -1;
171061
- if (b.status === "permanent" && a.status !== "permanent")
171062
- return 1;
171063
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
171064
- if (importanceDiff !== 0)
171065
- return importanceDiff;
171066
- return a.id - b.id;
171067
- });
172550
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
172551
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
171068
172552
  const selected = [];
171069
172553
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
171070
172554
  for (const memory of selectionOrder) {
171071
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
172555
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
171072
172556
  if (usedTokens + memoryTokens > budgetTokens)
171073
172557
  continue;
171074
172558
  selected.push(memory);
@@ -171077,16 +172561,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
171077
172561
  if (selected.length < memories.length) {
171078
172562
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
171079
172563
  }
171080
- const renderOrder = [...selected].sort((a, b) => {
171081
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
171082
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
171083
- const categoryDiff = aPriority - bPriority;
171084
- if (categoryDiff !== 0)
171085
- return categoryDiff;
171086
- return a.id - b.id;
171087
- });
172564
+ const renderOrder = [...selected].sort(memoryRenderOrder);
171088
172565
  return { selected, renderOrder };
171089
172566
  }
172567
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
172568
+ if (!workspace.isWorkspaced) {
172569
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
172570
+ }
172571
+ const selected = [];
172572
+ const selectedIds = new Set;
172573
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
172574
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
172575
+ const trySelect = (memory) => {
172576
+ if (selectedIds.has(memory.id))
172577
+ return false;
172578
+ const tokens = tokenCost(memory);
172579
+ if (usedTokens + tokens > budgetTokens)
172580
+ return false;
172581
+ selected.push(memory);
172582
+ selectedIds.add(memory.id);
172583
+ usedTokens += tokens;
172584
+ return true;
172585
+ };
172586
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
172587
+ trySelect(memory);
172588
+ }
172589
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
172590
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
172591
+ const byIdentity = new Map;
172592
+ for (const memory of memories) {
172593
+ if (memory.status === "permanent")
172594
+ continue;
172595
+ const identity = memoryCanonicalIdentity(memory, workspace);
172596
+ if (!identity)
172597
+ continue;
172598
+ const list = byIdentity.get(identity) ?? [];
172599
+ list.push(memory);
172600
+ byIdentity.set(identity, list);
172601
+ }
172602
+ for (const identity of workspace.identities) {
172603
+ let memberTokens = 0;
172604
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
172605
+ for (const memory of candidates) {
172606
+ if (selectedIds.has(memory.id))
172607
+ continue;
172608
+ const tokens = tokenCost(memory);
172609
+ if (memberTokens + tokens > floorTokens)
172610
+ continue;
172611
+ if (usedTokens + tokens > budgetTokens)
172612
+ continue;
172613
+ selected.push(memory);
172614
+ selectedIds.add(memory.id);
172615
+ usedTokens += tokens;
172616
+ memberTokens += tokens;
172617
+ }
172618
+ }
172619
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
172620
+ for (const memory of remaining) {
172621
+ trySelect(memory);
172622
+ }
172623
+ if (selected.length < memories.length) {
172624
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
172625
+ }
172626
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
172627
+ }
171090
172628
  function trimUserMemoriesToBudget(memories, budgetTokens) {
171091
172629
  const selected = [];
171092
172630
  let usedTokens = 0;
@@ -171099,15 +172637,16 @@ function trimUserMemoriesToBudget(memories, budgetTokens) {
171099
172637
  }
171100
172638
  return selected;
171101
172639
  }
171102
- function renderMemoryLineV2(memory) {
171103
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
172640
+ function renderMemoryLineV2(memory, sourceName) {
172641
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
172642
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
171104
172643
  }
171105
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
172644
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
171106
172645
  if (memories.length === 0)
171107
172646
  return "";
171108
172647
  const lines = [`<${wrapper}>`];
171109
172648
  for (const memory of memories) {
171110
- lines.push(renderMemoryLineV2(memory));
172649
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
171111
172650
  }
171112
172651
  lines.push(`</${wrapper}>`);
171113
172652
  return lines.join(`
@@ -171701,7 +173240,7 @@ async function ensureMemoryEmbeddings(args) {
171701
173240
  continue;
171702
173241
  }
171703
173242
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
171704
- staged.set(memory.id, embedding);
173243
+ staged.set(memory.id, { embedding, modelId: result.modelId });
171705
173244
  }
171706
173245
  })();
171707
173246
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -171792,6 +173331,37 @@ function previewText(text) {
171792
173331
  }
171793
173332
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
171794
173333
  }
173334
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
173335
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
173336
+ const isWorkspaced = resolved.identities.length > 1;
173337
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
173338
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
173339
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
173340
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
173341
+ return {
173342
+ identities: resolved.identities,
173343
+ expandedIdentities,
173344
+ ownIdentities,
173345
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
173346
+ namesByIdentity: resolved.namesByIdentity,
173347
+ canonicalIdentityByStoredPath,
173348
+ isWorkspaced
173349
+ };
173350
+ }
173351
+ function memoryWorkspaceIdentity(memory, workspace) {
173352
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
173353
+ }
173354
+ function sourceNamesForSearchMemories(args) {
173355
+ if (!args.workspace.isWorkspaced)
173356
+ return;
173357
+ const sourceNames = new Map;
173358
+ for (const memory of args.memories) {
173359
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
173360
+ if (source)
173361
+ sourceNames.set(memory.id, source);
173362
+ }
173363
+ return sourceNames.size > 0 ? sourceNames : undefined;
173364
+ }
171795
173365
  function getMessageSearchStatement(db) {
171796
173366
  let stmt = messageSearchStatements.get(db);
171797
173367
  if (!stmt) {
@@ -171800,6 +173370,30 @@ function getMessageSearchStatement(db) {
171800
173370
  }
171801
173371
  return stmt;
171802
173372
  }
173373
+ var ftsRowCountStatements = new WeakMap;
173374
+ var ftsMatchCountStatements = new WeakMap;
173375
+ function getSessionFtsRowCount(db, sessionId) {
173376
+ let stmt = ftsRowCountStatements.get(db);
173377
+ if (!stmt) {
173378
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
173379
+ ftsRowCountStatements.set(db, stmt);
173380
+ }
173381
+ const row = stmt.get(sessionId);
173382
+ return typeof row?.n === "number" ? row.n : 0;
173383
+ }
173384
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
173385
+ let stmt = ftsMatchCountStatements.get(db);
173386
+ if (!stmt) {
173387
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
173388
+ ftsMatchCountStatements.set(db, stmt);
173389
+ }
173390
+ try {
173391
+ const row = stmt.get(sessionId, ftsQuery);
173392
+ return typeof row?.n === "number" ? row.n : 0;
173393
+ } catch {
173394
+ return 0;
173395
+ }
173396
+ }
171803
173397
  function getMessageOrdinal(value) {
171804
173398
  if (typeof value === "number" && Number.isFinite(value)) {
171805
173399
  return value;
@@ -171815,25 +173409,63 @@ async function getSemanticScores(args) {
171815
173409
  if (!args.queryEmbedding || args.memories.length === 0) {
171816
173410
  return semanticScores;
171817
173411
  }
171818
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
171819
- const embeddings = await ensureMemoryEmbeddings({
171820
- db: args.db,
171821
- projectIdentity: args.projectPath,
171822
- memories: args.memories,
171823
- existingEmbeddings: cachedEmbeddings
171824
- });
173412
+ if (!args.workspace?.isWorkspaced) {
173413
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
173414
+ const embeddings = await ensureMemoryEmbeddings({
173415
+ db: args.db,
173416
+ projectIdentity: args.projectPath,
173417
+ memories: args.memories,
173418
+ existingEmbeddings: cachedEmbeddings
173419
+ });
173420
+ for (const memory of args.memories) {
173421
+ const memoryEmbedding = embeddings.get(memory.id);
173422
+ if (!memoryEmbedding) {
173423
+ continue;
173424
+ }
173425
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
173426
+ }
173427
+ return semanticScores;
173428
+ }
173429
+ if (!args.queryModelId || args.queryModelId === "off") {
173430
+ return semanticScores;
173431
+ }
173432
+ const workspace = args.workspace;
173433
+ const memoriesByIdentity = new Map;
171825
173434
  for (const memory of args.memories) {
171826
- const memoryEmbedding = embeddings.get(memory.id);
171827
- if (!memoryEmbedding) {
173435
+ const identity = memoryWorkspaceIdentity(memory, workspace);
173436
+ if (!identity)
173437
+ continue;
173438
+ const list = memoriesByIdentity.get(identity) ?? [];
173439
+ list.push(memory);
173440
+ memoriesByIdentity.set(identity, list);
173441
+ }
173442
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
173443
+ if (ownMemories.length > 0) {
173444
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
173445
+ await ensureMemoryEmbeddings({
173446
+ db: args.db,
173447
+ projectIdentity: args.projectPath,
173448
+ memories: ownMemories,
173449
+ existingEmbeddings: ownEmbeddings
173450
+ });
173451
+ }
173452
+ for (const identity of workspace.identities) {
173453
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
173454
+ if (memberMemories.length === 0)
171828
173455
  continue;
173456
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
173457
+ for (const memory of memberMemories) {
173458
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
173459
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
173460
+ continue;
173461
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
171829
173462
  }
171830
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
171831
173463
  }
171832
173464
  return semanticScores;
171833
173465
  }
171834
173466
  function getFtsMatches(args) {
171835
173467
  try {
171836
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
173468
+ 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);
171837
173469
  } catch (error51) {
171838
173470
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
171839
173471
  return [];
@@ -171847,8 +173479,11 @@ function selectSemanticCandidates(args) {
171847
173479
  return args.memories;
171848
173480
  }
171849
173481
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
171850
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
171851
- if (cachedEmbeddings) {
173482
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
173483
+ for (const projectPath of embeddingProjects) {
173484
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
173485
+ if (!cachedEmbeddings)
173486
+ continue;
171852
173487
  for (const memoryId of cachedEmbeddings.keys()) {
171853
173488
  candidateIds.add(memoryId);
171854
173489
  }
@@ -171890,7 +173525,8 @@ function mergeMemoryResults(args) {
171890
173525
  score,
171891
173526
  memoryId: memory.id,
171892
173527
  category: memory.category,
171893
- matchType
173528
+ matchType,
173529
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
171894
173530
  });
171895
173531
  }
171896
173532
  return results.sort((left, right) => {
@@ -171904,7 +173540,7 @@ async function searchMemories(args) {
171904
173540
  if (!args.memoryEnabled) {
171905
173541
  return [];
171906
173542
  }
171907
- const memories = getMemoriesByProject(args.db, args.projectPath);
173543
+ 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);
171908
173544
  if (memories.length === 0) {
171909
173545
  return [];
171910
173546
  }
@@ -171912,26 +173548,43 @@ async function searchMemories(args) {
171912
173548
  db: args.db,
171913
173549
  projectPath: args.projectPath,
171914
173550
  query: args.query,
171915
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
173551
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
173552
+ workspace: args.workspace
171916
173553
  });
171917
173554
  const ftsScores = getFtsScores(ftsMatches);
171918
173555
  const semanticCandidates = selectSemanticCandidates({
171919
173556
  memories,
171920
173557
  projectPath: args.projectPath,
171921
- ftsMatches
173558
+ ftsMatches,
173559
+ workspace: args.workspace
171922
173560
  });
171923
173561
  const semanticScores = await getSemanticScores({
171924
173562
  db: args.db,
171925
173563
  projectPath: args.projectPath,
171926
173564
  memories: semanticCandidates,
171927
- queryEmbedding: args.queryEmbedding
173565
+ queryEmbedding: args.queryEmbedding,
173566
+ queryModelId: args.queryModelId,
173567
+ workspace: args.workspace
171928
173568
  });
171929
173569
  return mergeMemoryResults({
171930
173570
  memories,
171931
173571
  semanticScores,
171932
173572
  ftsScores,
171933
173573
  limit: args.limit,
171934
- visibleMemoryIds: args.visibleMemoryIds
173574
+ visibleMemoryIds: args.visibleMemoryIds,
173575
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
173576
+ memories,
173577
+ projectPath: args.projectPath,
173578
+ workspace: args.workspace ?? {
173579
+ identities: [args.projectPath],
173580
+ expandedIdentities: [args.projectPath],
173581
+ namesByIdentity: new Map,
173582
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
173583
+ ownIdentities: [args.projectPath],
173584
+ shareCategories: null,
173585
+ isWorkspaced: false
173586
+ }
173587
+ })
171935
173588
  });
171936
173589
  }
171937
173590
  function linearDecayScore(rank, total) {
@@ -171962,7 +173615,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
171962
173615
  return result;
171963
173616
  }
171964
173617
  var RRF_K = 60;
171965
- var VERBATIM_PROBE_BONUS = 0.5;
173618
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
173619
+ var IDF_FALLOFF = 100;
173620
+ function probeDiscriminationWeight(df, corpusSize) {
173621
+ if (corpusSize <= 0 || df <= 0)
173622
+ return 1;
173623
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
173624
+ }
171966
173625
  function searchMessages(args) {
171967
173626
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
171968
173627
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -171979,20 +173638,31 @@ function searchMessages(args) {
171979
173638
  role: row.role
171980
173639
  }));
171981
173640
  }
173641
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
171982
173642
  const queryLists = [];
171983
173643
  if (baseQuery.length > 0) {
171984
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
173644
+ queryLists.push({
173645
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
173646
+ weight: 1
173647
+ });
171985
173648
  }
173649
+ const probeWeights = new Map;
171986
173650
  for (const probe of probes) {
171987
173651
  const probeQuery = sanitizeFtsQuery(probe);
171988
173652
  if (probeQuery.length === 0)
171989
173653
  continue;
171990
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
173654
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
173655
+ const weight = probeDiscriminationWeight(df, corpusSize);
173656
+ probeWeights.set(probe, weight);
173657
+ queryLists.push({
173658
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
173659
+ weight
173660
+ });
171991
173661
  }
171992
173662
  const fused = new Map;
171993
173663
  for (const list of queryLists) {
171994
- list.forEach((row, rank) => {
171995
- const rrf = 1 / (RRF_K + rank);
173664
+ list.rows.forEach((row, rank) => {
173665
+ const rrf = list.weight / (RRF_K + rank);
171996
173666
  const existing = fused.get(row.messageId);
171997
173667
  if (existing) {
171998
173668
  existing.score += rrf;
@@ -172002,26 +173672,107 @@ function searchMessages(args) {
172002
173672
  });
172003
173673
  }
172004
173674
  for (const entry of fused.values()) {
172005
- if (containsProbeVerbatim(entry.row.content, probes)) {
172006
- entry.score += VERBATIM_PROBE_BONUS;
173675
+ let best = 0;
173676
+ for (const probe of probes) {
173677
+ const weight = probeWeights.get(probe) ?? 0;
173678
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
173679
+ best = weight;
173680
+ }
173681
+ }
173682
+ if (best > 0) {
173683
+ entry.score += best * VERBATIM_RANK_BONUS;
172007
173684
  }
172008
173685
  }
172009
173686
  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);
172010
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
172011
- return ranked.map((entry) => ({
173687
+ return ranked.map((entry, rank) => ({
172012
173688
  source: "message",
172013
173689
  content: previewText(entry.row.content),
172014
- score: maxScore > 0 ? entry.score / maxScore : 0,
173690
+ score: linearDecayScore(rank, ranked.length),
172015
173691
  messageOrdinal: entry.row.messageOrdinal,
172016
173692
  messageId: entry.row.messageId,
172017
173693
  role: entry.row.role
172018
173694
  }));
172019
173695
  }
173696
+ function searchCompartmentChunks(args) {
173697
+ if (!args.queryEmbedding || args.limit <= 0)
173698
+ return [];
173699
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
173700
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
173701
+ if (rows.length === 0)
173702
+ return [];
173703
+ const byCompartment = new Map;
173704
+ for (const row of rows) {
173705
+ if (cutoff !== null && row.endOrdinal > cutoff) {
173706
+ continue;
173707
+ }
173708
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
173709
+ if (score <= 0)
173710
+ continue;
173711
+ const existing = byCompartment.get(row.compartmentId);
173712
+ if (!existing || score > existing.score) {
173713
+ byCompartment.set(row.compartmentId, { row, score });
173714
+ }
173715
+ }
173716
+ 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 }) => ({
173717
+ source: "compartment",
173718
+ content: previewText(row.title),
173719
+ score: score * SINGLE_SOURCE_PENALTY,
173720
+ compartmentId: row.compartmentId,
173721
+ sessionId: row.sessionId,
173722
+ title: row.title,
173723
+ startOrdinal: row.startOrdinal,
173724
+ endOrdinal: row.endOrdinal,
173725
+ matchType: "semantic"
173726
+ }));
173727
+ }
173728
+ function mergeMessageAndCompartmentResults(args) {
173729
+ if (args.compartments.length === 0)
173730
+ return args.messages;
173731
+ if (args.messages.length === 0)
173732
+ return args.compartments;
173733
+ const fused = new Map;
173734
+ const add = (key, result, score, tieOrdinal) => {
173735
+ const existing = fused.get(key);
173736
+ if (existing) {
173737
+ existing.score += score;
173738
+ return existing;
173739
+ }
173740
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
173741
+ fused.set(key, entry);
173742
+ return entry;
173743
+ };
173744
+ args.compartments.forEach((compartment, rank) => {
173745
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
173746
+ });
173747
+ for (const [rank, message] of args.messages.entries()) {
173748
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
173749
+ const contribution = 1 / (RRF_K + rank);
173750
+ if (!containing) {
173751
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
173752
+ continue;
173753
+ }
173754
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
173755
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
173756
+ entry.snippetScore = message.score;
173757
+ entry.result = {
173758
+ ...entry.result,
173759
+ matchType: "hybrid",
173760
+ snippet: message.content
173761
+ };
173762
+ }
173763
+ }
173764
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
173765
+ return ranked.map((entry, rank) => ({
173766
+ ...entry.result,
173767
+ score: linearDecayScore(rank, ranked.length)
173768
+ }));
173769
+ }
172020
173770
  function getSourceBoost(result) {
172021
173771
  switch (result.source) {
172022
173772
  case "memory":
172023
173773
  return MEMORY_SOURCE_BOOST;
172024
173774
  case "message":
173775
+ case "compartment":
172025
173776
  return MESSAGE_SOURCE_BOOST;
172026
173777
  case "git_commit":
172027
173778
  return GIT_COMMIT_SOURCE_BOOST;
@@ -172039,6 +173790,9 @@ function compareUnifiedResults(left, right) {
172039
173790
  if (left.source === "message" && right.source === "message") {
172040
173791
  return left.messageOrdinal - right.messageOrdinal;
172041
173792
  }
173793
+ if (left.source === "compartment" && right.source === "compartment") {
173794
+ return left.startOrdinal - right.startOrdinal;
173795
+ }
172042
173796
  if (left.source === "git_commit" && right.source === "git_commit") {
172043
173797
  return right.committedAtMs - left.committedAtMs;
172044
173798
  }
@@ -172089,10 +173843,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172089
173843
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
172090
173844
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
172091
173845
  const activeSources = resolveSources(options.sources);
172092
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
173846
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
173847
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
172093
173848
  const runMessages = activeSources.has("message");
172094
173849
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
172095
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
173850
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
173851
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
172096
173852
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
172097
173853
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
172098
173854
  return null;
@@ -172108,6 +173864,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172108
173864
  probes: messageProbes
172109
173865
  }) : [];
172110
173866
  const queryEmbedding = await queryEmbeddingPromise;
173867
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
173868
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
173869
+ const embeddingModelId = embeddingSnapshot?.modelId;
173870
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
173871
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
173872
+ db,
173873
+ sessionId,
173874
+ projectPath,
173875
+ queryEmbedding,
173876
+ limit: tierLimit,
173877
+ maxOrdinal: options.maxMessageOrdinal,
173878
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
173879
+ }) : [];
173880
+ const messageLikeResults = mergeMessageAndCompartmentResults({
173881
+ messages: messageResults,
173882
+ compartments: compartmentResults,
173883
+ limit: tierLimit
173884
+ });
172111
173885
  const [memoryResults, gitCommitResults] = await Promise.all([
172112
173886
  runMemory ? searchMemories({
172113
173887
  db,
@@ -172116,6 +173890,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172116
173890
  limit: tierLimit,
172117
173891
  memoryEnabled: true,
172118
173892
  queryEmbedding,
173893
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
173894
+ workspace,
172119
173895
  visibleMemoryIds: options.visibleMemoryIds
172120
173896
  }) : Promise.resolve([]),
172121
173897
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -172126,7 +173902,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172126
173902
  queryEmbedding
172127
173903
  })) : Promise.resolve([])
172128
173904
  ]);
172129
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
173905
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
172130
173906
  const countRetrievals = options.countRetrievals ?? true;
172131
173907
  if (countRetrievals) {
172132
173908
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -172186,6 +173962,11 @@ function renderFragment(result, charCap) {
172186
173962
  const compressed = cavemanCompress(result.content, "ultra");
172187
173963
  return truncate(compressed, charCap);
172188
173964
  }
173965
+ case "compartment": {
173966
+ const source = result.snippet ?? result.title;
173967
+ const compressed = cavemanCompress(source, "ultra");
173968
+ return truncate(compressed, charCap);
173969
+ }
172189
173970
  }
172190
173971
  }
172191
173972
  function buildAutoSearchHint(results, options = {}) {
@@ -172637,10 +174418,11 @@ function maybeChannel1ReminderForToolResult(args) {
172637
174418
  contextLimit: state.contextLimit,
172638
174419
  executeThresholdPercentage: state.executeThresholdPercentage
172639
174420
  });
174421
+ const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
172640
174422
  const decision = decideChannel1({
172641
174423
  undroppedTokens,
172642
174424
  pressure,
172643
- historyBudgetTokens: state.historyBudgetTokens,
174425
+ workingWindowTokens,
172644
174426
  lastNudgeUndropped: getLastNudgeUndropped(db, sessionId),
172645
174427
  lastNudgeLevel: getLastNudgeLevel(db, sessionId),
172646
174428
  hasRecentReduce: false
@@ -172875,51 +174657,6 @@ function planEmergencyDrop(input) {
172875
174657
  };
172876
174658
  }
172877
174659
 
172878
- // ../plugin/src/hooks/magic-context/system-injection-stripper.ts
172879
- var SYSTEM_INJECTION_MARKERS = [
172880
- "<!-- OMO_INTERNAL_INITIATOR -->",
172881
- "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
172882
- "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
172883
- "[Category+Skill Reminder]",
172884
- "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
172885
- "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
172886
- "[EMERGENCY CONTEXT WINDOW WARNING]",
172887
- "Unstable background agent appears idle",
172888
- "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
172889
- ];
172890
- var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
172891
- var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
172892
- function stripSystemInjection(text) {
172893
- let hasInjection = false;
172894
- for (const marker of SYSTEM_INJECTION_MARKERS) {
172895
- if (text.includes(marker)) {
172896
- hasInjection = true;
172897
- break;
172898
- }
172899
- }
172900
- if (SYSTEM_REMINDER_REGEX.test(text))
172901
- hasInjection = true;
172902
- SYSTEM_REMINDER_REGEX.lastIndex = 0;
172903
- if (!hasInjection)
172904
- return null;
172905
- let cleaned = text;
172906
- cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
172907
- cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
172908
- cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
172909
- for (const marker of SYSTEM_INJECTION_MARKERS) {
172910
- if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
172911
- continue;
172912
- const idx = cleaned.indexOf(marker);
172913
- if (idx === -1)
172914
- continue;
172915
- const blockEnd = cleaned.indexOf(`
172916
-
172917
- `, idx + marker.length);
172918
- cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
172919
- }
172920
- return cleaned.trim();
172921
- }
172922
-
172923
174660
  // src/heuristic-cleanup-pi.ts
172924
174661
  init_logger();
172925
174662
  var DEDUP_SAFE_TOOLS = new Set([
@@ -173609,6 +175346,49 @@ function safeGetActiveUserMemoriesPi(db) {
173609
175346
  function memoryProjectPath(state) {
173610
175347
  return state.memoryEnabled === false ? undefined : state.projectIdentity;
173611
175348
  }
175349
+ function resolveWorkspaceRenderContextPi(state, db) {
175350
+ const memPath = memoryProjectPath(state);
175351
+ if (!memPath) {
175352
+ return {
175353
+ identities: [],
175354
+ expandedIdentities: [],
175355
+ ownIdentities: [],
175356
+ shareCategories: null,
175357
+ namesByIdentity: new Map,
175358
+ canonicalIdentityByStoredPath: new Map,
175359
+ isWorkspaced: false
175360
+ };
175361
+ }
175362
+ const identitySet = resolveWorkspaceIdentitySet(db, memPath);
175363
+ const isWorkspaced = identitySet.identities.length > 1;
175364
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, identitySet.identities);
175365
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
175366
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
175367
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === memPath);
175368
+ if (ownIdentities.length === 0 && expandedIdentities.includes(memPath)) {
175369
+ ownIdentities = [memPath];
175370
+ }
175371
+ return {
175372
+ identities: identitySet.identities,
175373
+ expandedIdentities,
175374
+ ownIdentities,
175375
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, memPath) : null,
175376
+ namesByIdentity: identitySet.namesByIdentity,
175377
+ canonicalIdentityByStoredPath,
175378
+ isWorkspaced
175379
+ };
175380
+ }
175381
+ function sourceNamesForPiMemories(args) {
175382
+ if (!args.projectPath || !args.workspace.isWorkspaced)
175383
+ return;
175384
+ const names = new Map;
175385
+ for (const memory of args.memories) {
175386
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
175387
+ if (source)
175388
+ names.set(memory.id, source);
175389
+ }
175390
+ return names.size > 0 ? names : undefined;
175391
+ }
173612
175392
  var EMPTY_PI_HARD_SIGNALS = {
173613
175393
  systemHash: "",
173614
175394
  modelKey: "",
@@ -173656,6 +175436,7 @@ function getCachedMarkers(db, state, compartmentsForNormalization) {
173656
175436
  maxMutationId: meta3.cachedM0MaxMutationId,
173657
175437
  maxMemoryMutationId: meta3.cachedM0MaxMemoryMutationId,
173658
175438
  projectMemoryEpoch: meta3.cachedM0ProjectMemoryEpoch,
175439
+ workspaceFingerprint: meta3.cachedM0WorkspaceFingerprint,
173659
175440
  projectUserProfileVersion: meta3.cachedM0ProjectUserProfileVersion,
173660
175441
  projectDocsHash: meta3.cachedM0ProjectDocsHash,
173661
175442
  sessionFactsVersion: meta3.cachedM0SessionFactsVersion,
@@ -173675,15 +175456,17 @@ function readCurrentMarkers(db, state, projectDocsHash) {
173675
175456
  }
173676
175457
  function readCurrentMarkersFromCompartments(db, state, compartments, projectDocsHash) {
173677
175458
  const memPath = memoryProjectPath(state);
173678
- const memories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"]) : [];
175459
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
175460
+ const maxMemoryId = memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0;
173679
175461
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
173680
175462
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
173681
175463
  return {
173682
175464
  maxCompartmentSeq: compartments.length > 0 ? compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ) : EMPTY_MAX_COMPARTMENT_SEQ,
173683
- maxMemoryId: memories.length > 0 ? memories.reduce((max, memory) => memory.id > max ? memory.id : max, 0) : 0,
175465
+ maxMemoryId,
173684
175466
  maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
173685
- maxMemoryMutationId: memPath ? getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
175467
+ maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
173686
175468
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
175469
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
173687
175470
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
173688
175471
  projectDocsHash: projectDocsHash ?? readProjectDocsCanonical(state.projectDirectory).canonicalHash,
173689
175472
  sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
@@ -173721,10 +175504,11 @@ function mustMaterializePi(state, db, currentCompartmentsOverride) {
173721
175504
  if (meta3.cachedM0UpgradeState !== current.upgradeState) {
173722
175505
  return { value: true, reason: "renderer_upgrade" };
173723
175506
  }
173724
- if (current.projectDocsHash !== (meta3.cachedM0ProjectDocsHash ?? "")) {
173725
- return { value: true, reason: "project_docs_change" };
173726
- }
173727
- if (current.projectMemoryEpoch !== (meta3.cachedM0ProjectMemoryEpoch ?? 0)) {
175507
+ if (current.workspaceFingerprint !== null || (meta3.cachedM0WorkspaceFingerprint ?? null) !== null) {
175508
+ if (current.workspaceFingerprint !== (meta3.cachedM0WorkspaceFingerprint ?? null)) {
175509
+ return { value: true, reason: "project_memory_change" };
175510
+ }
175511
+ } else if (current.projectMemoryEpoch !== (meta3.cachedM0ProjectMemoryEpoch ?? 0)) {
173728
175512
  return { value: true, reason: "project_memory_change" };
173729
175513
  }
173730
175514
  if (current.maxMutationId !== (meta3.cachedM0MaxMutationId ?? 0)) {
@@ -173741,11 +175525,19 @@ ${memories.map((memory) => `- ${escapeXmlContent(memory.content)}`).join(`
173741
175525
  `)}
173742
175526
  </${wrapper}>`;
173743
175527
  }
173744
- function renderM0Pi(state, db, projectDocs = readProjectDocsCanonical(state.projectDirectory).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride) {
175528
+ function renderM0Pi(state, db, projectDocs = readProjectDocsCanonical(state.projectDirectory).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride, workspaceOverride) {
173745
175529
  const memPath = memoryProjectPath(state);
173746
- const allMemories = memoriesOverride ?? (memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
173747
- const memories = allMemories.length > 0 ? trimMemoriesToBudgetV2(state.sessionId, allMemories, state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder : allMemories;
173748
- const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories) : undefined;
175530
+ const workspace = workspaceOverride ?? resolveWorkspaceRenderContextPi(state, db);
175531
+ const allMemories = memoriesOverride ?? (memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
175532
+ const memoryRenderOptions = {
175533
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175534
+ memories: allMemories,
175535
+ projectPath: memPath,
175536
+ workspace
175537
+ })
175538
+ };
175539
+ 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;
175540
+ const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories, "project-memory", memoryRenderOptions) : undefined;
173749
175541
  const baseHistoryBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173750
175542
  const decayed = renderDecayedCompartments({
173751
175543
  compartments: compartmentsOverride ?? getCompartments(db, state.sessionId),
@@ -173767,10 +175559,19 @@ ${decayed}
173767
175559
 
173768
175560
  `).trim();
173769
175561
  }
173770
- function renderedMemoryIdsForPi(state, memories) {
175562
+ function renderedMemoryIdsForPi(state, memories, workspace, db) {
173771
175563
  if (memories.length === 0)
173772
175564
  return [];
173773
- return trimMemoriesToBudgetV2(state.sessionId, [...memories], state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder.map((memory) => memory.id);
175565
+ const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state, db) : undefined);
175566
+ const renderOptions = resolvedWorkspace ? {
175567
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175568
+ memories,
175569
+ projectPath: memoryProjectPath(state),
175570
+ workspace: resolvedWorkspace
175571
+ })
175572
+ } : {};
175573
+ 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);
175574
+ return trimmed.renderOrder.map((memory) => memory.id);
173774
175575
  }
173775
175576
  function isTransientSqliteLockError(error51) {
173776
175577
  if (!error51 || typeof error51 !== "object")
@@ -173795,17 +175596,19 @@ class PiMaterializeContentionError extends Error {
173795
175596
  function readFrozenM0InputsPi(state, db, docs = readProjectDocsCanonical(state.projectDirectory), memoryCutoff) {
173796
175597
  const memPath = memoryProjectPath(state);
173797
175598
  const read = db.transaction(() => {
175599
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
173798
175600
  const compartments = getCompartments(db, state.sessionId);
173799
- const memories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
175601
+ const memories = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], memoryCutoff, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
173800
175602
  const userProfile = safeGetActiveUserMemoriesPi(db);
173801
175603
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
173802
175604
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
173803
175605
  const markers = {
173804
175606
  maxCompartmentSeq: compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ),
173805
- maxMemoryId: memories.reduce((max, memory) => memory.id > max ? memory.id : max, 0),
175607
+ maxMemoryId: memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0,
173806
175608
  maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
173807
- maxMemoryMutationId: memPath ? getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
175609
+ maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
173808
175610
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
175611
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
173809
175612
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
173810
175613
  projectDocsHash: docs.canonicalHash,
173811
175614
  sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
@@ -173815,7 +175618,7 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsCanonical(state.p
173815
175618
  systemHash: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
173816
175619
  modelKey: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey
173817
175620
  };
173818
- return { docs, markers, compartments, memories, userProfile };
175621
+ return { docs, markers, compartments, memories, userProfile, workspace };
173819
175622
  });
173820
175623
  return read();
173821
175624
  }
@@ -173826,17 +175629,17 @@ function renderFreshM0PiNonPersisted(state, db) {
173826
175629
  frozen.markers.materializedAt = cachedMaterializedAt;
173827
175630
  const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173828
175631
  let dpm = 1;
173829
- let m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile);
175632
+ let m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace);
173830
175633
  let attempts = 0;
173831
175634
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
173832
175635
  dpm *= 1.15;
173833
- m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile);
175636
+ m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace);
173834
175637
  attempts += 1;
173835
175638
  }
173836
175639
  return {
173837
175640
  m0,
173838
175641
  snapshotMarkers: frozen.markers,
173839
- renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories)
175642
+ renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories, frozen.workspace, db)
173840
175643
  };
173841
175644
  }
173842
175645
  function materializeM0Pi(state, db) {
@@ -173846,14 +175649,14 @@ function materializeM0Pi(state, db) {
173846
175649
  const snapshotMemories = frozen.memories;
173847
175650
  const snapshotCompartments = frozen.compartments;
173848
175651
  const snapshotUserProfile = frozen.userProfile;
173849
- const renderedMemoryIds = renderedMemoryIdsForPi(state, snapshotMemories);
175652
+ const renderedMemoryIds = renderedMemoryIdsForPi(state, snapshotMemories, frozen.workspace, db);
173850
175653
  let decayPressureMultiplier = 1;
173851
- let m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile);
175654
+ let m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace);
173852
175655
  const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
173853
175656
  let attempts = 0;
173854
175657
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
173855
175658
  decayPressureMultiplier *= 1.15;
173856
- m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile);
175659
+ m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace);
173857
175660
  attempts += 1;
173858
175661
  }
173859
175662
  const m0Bytes = Buffer.from(m0, "utf8");
@@ -173869,7 +175672,8 @@ function materializeM0Pi(state, db) {
173869
175672
  }
173870
175673
  try {
173871
175674
  const current = readCurrentMarkers(db, state, phase3ProjectDocsHash);
173872
- 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;
175675
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
175676
+ 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;
173873
175677
  if (stale) {
173874
175678
  db.exec("ROLLBACK");
173875
175679
  throw new PiMaterializeContentionError("snapshot changed before persist");
@@ -173880,6 +175684,7 @@ function materializeM0Pi(state, db) {
173880
175684
  persistCachedM0(db, state.sessionId, {
173881
175685
  m0Bytes,
173882
175686
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
175687
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
173883
175688
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
173884
175689
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
173885
175690
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -173944,7 +175749,7 @@ function renderMemoryUpdatesBlockPi(args) {
173944
175749
  if (args.renderedMemoryIds.length === 0)
173945
175750
  return { block: "", count: 0 };
173946
175751
  const renderedIds = new Set(args.renderedMemoryIds);
173947
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
175752
+ const mutations = args.workspace.isWorkspaced ? getMemoryMutationsForRenderByProjects(args.db, args.workspace.expandedIdentities, args.afterId, args.renderedMemoryIds) : getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
173948
175753
  if (mutations.length === 0)
173949
175754
  return { block: "", count: 0 };
173950
175755
  const lines = [
@@ -173975,6 +175780,7 @@ ${lines.join(`
173975
175780
  }
173976
175781
  function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRenderedKeyFilesBlock, compartmentsOverride) {
173977
175782
  const sections = [];
175783
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
173978
175784
  const keyFiles = renderedKeyFilesBlockPi(state, db, preRenderedKeyFilesBlock);
173979
175785
  if (keyFiles)
173980
175786
  sections.push(keyFiles);
@@ -173982,6 +175788,7 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRender
173982
175788
  const memoryUpdates = memPath ? renderMemoryUpdatesBlockPi({
173983
175789
  db,
173984
175790
  projectPath: memPath,
175791
+ workspace,
173985
175792
  afterId: markers.maxMemoryMutationId,
173986
175793
  renderedMemoryIds
173987
175794
  }) : { block: undefined, count: 0 };
@@ -173996,11 +175803,18 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRender
173996
175803
  ${body}
173997
175804
  </new-compartments>`);
173998
175805
  }
173999
- const newMemories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"], markers.materializedAt).filter((memory) => memory.id > markers.maxMemoryId) : [];
175806
+ 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) : [];
174000
175807
  if (newMemories.length > 0) {
174001
175808
  const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
174002
- const trimmedNewMemories = trimMemoriesToBudgetV2(state.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25))).renderOrder;
174003
- const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories");
175809
+ const memoryRenderOptions = {
175810
+ sourceNameByMemoryId: sourceNamesForPiMemories({
175811
+ memories: newMemories,
175812
+ projectPath: memPath,
175813
+ workspace
175814
+ })
175815
+ };
175816
+ const trimmedNewMemories = trimMemoriesToBudgetV2(state.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25)), memoryRenderOptions).renderOrder;
175817
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", memoryRenderOptions);
174004
175818
  if (newMemoriesBlock)
174005
175819
  sections.push(newMemoriesBlock);
174006
175820
  }
@@ -174049,6 +175863,7 @@ function parseMemoryBlockIds(raw) {
174049
175863
  function readCachedPiM0M1Row(db, sessionId) {
174050
175864
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
174051
175865
  cached_m0_project_memory_epoch,
175866
+ cached_m0_workspace_fingerprint,
174052
175867
  cached_m0_project_user_profile_version,
174053
175868
  cached_m0_max_compartment_seq,
174054
175869
  cached_m0_max_memory_id,
@@ -174092,6 +175907,7 @@ function markersFromCachedPiRow(row, compartmentsForNormalization) {
174092
175907
  maxMutationId: row.cached_m0_max_mutation_id,
174093
175908
  maxMemoryMutationId: row.cached_m0_max_memory_mutation_id,
174094
175909
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
175910
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
174095
175911
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
174096
175912
  projectDocsHash: row.cached_m0_project_docs_hash ?? "",
174097
175913
  materializedAt: row.cached_m0_materialized_at,
@@ -174106,7 +175922,7 @@ function cachedPiRowMatchesSnapshot(args) {
174106
175922
  const rowMarkers = markersFromCachedPiRow(args.row, args.compartmentsForNormalization);
174107
175923
  if (!rowMarkers)
174108
175924
  return false;
174109
- 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 ?? "");
175925
+ 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);
174110
175926
  }
174111
175927
  function decodeCachedM1(row, sessionId) {
174112
175928
  if (!row.cached_m1_bytes) {
@@ -174317,14 +176133,14 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
174317
176133
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
174318
176134
  prependM0M1Messages(piMessages, m0, m1);
174319
176135
  sessionLog(state.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
176136
+ const memPath = memoryProjectPath(state);
176137
+ const workspace = resolveWorkspaceRenderContextPi(state, db);
176138
+ 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;
174320
176139
  return {
174321
176140
  injected: true,
174322
176141
  compartmentCount: getCompartments(db, state.sessionId).length,
174323
176142
  factCount: 0,
174324
- memoryCount: memoryProjectPath(state) ? getMemoriesByProject(db, memoryProjectPath(state), [
174325
- "active",
174326
- "permanent"
174327
- ]).length : 0,
176143
+ memoryCount,
174328
176144
  skippedVisibleMessages,
174329
176145
  m0Materialized: materialized,
174330
176146
  m0Reason: decision.reason,
@@ -174385,21 +176201,48 @@ import * as crypto2 from "node:crypto";
174385
176201
 
174386
176202
  // ../plugin/src/features/magic-context/compartment-embedding.ts
174387
176203
  init_logger();
174388
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
176204
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
174389
176205
  if (compartments.length === 0)
174390
176206
  return;
174391
- const update = db.prepare("UPDATE compartments SET p1_embedding = ?, p1_embedding_model_id = ? WHERE id = ?");
174392
- for (const c of compartments) {
174393
- if (!c.p1 || c.p1.length === 0)
174394
- continue;
176207
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
176208
+ for (const compartment of compartments) {
174395
176209
  try {
174396
- const result = await embedTextForProject(projectPath, c.p1);
174397
- if (result) {
174398
- const blob = Buffer.from(result.vector.buffer, result.vector.byteOffset, result.vector.byteLength);
174399
- update.run(blob, result.modelId, c.id);
176210
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
176211
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
176212
+ if (canonicalText.length === 0)
176213
+ continue;
176214
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
176215
+ if (windows.length === 0)
176216
+ continue;
176217
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
176218
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
176219
+ continue;
176220
+ }
176221
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
176222
+ if (!result)
176223
+ continue;
176224
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
176225
+ continue;
176226
+ }
176227
+ const rows = [];
176228
+ for (const [index, window] of windows.entries()) {
176229
+ const vector = result.vectors[index];
176230
+ if (!vector)
176231
+ continue;
176232
+ rows.push({
176233
+ compartmentId: compartment.id,
176234
+ sessionId,
176235
+ projectPath,
176236
+ window,
176237
+ modelId: currentModelId,
176238
+ vector
176239
+ });
176240
+ }
176241
+ if (rows.length === windows.length) {
176242
+ replaceCompartmentChunkEmbeddings(db, rows);
174400
176243
  }
174401
176244
  } catch (error51) {
174402
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
176245
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
174403
176246
  }
174404
176247
  }
174405
176248
  }
@@ -177468,6 +179311,7 @@ async function runPiHistorian(deps) {
177468
179311
  fallbackModels,
177469
179312
  historianChunkTokens,
177470
179313
  boundarySnapshot: providedBoundarySnapshot,
179314
+ refreshBoundarySnapshot,
177471
179315
  currentContextLimit,
177472
179316
  historianTimeoutMs = DEFAULT_HISTORIAN_TIMEOUT_MS2,
177473
179317
  twoPass,
@@ -177521,16 +179365,29 @@ async function runPiHistorian(deps) {
177521
179365
  return;
177522
179366
  }
177523
179367
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
177524
- const boundarySnapshot = providedBoundarySnapshot ?? null;
179368
+ let boundarySnapshot = providedBoundarySnapshot ?? null;
177525
179369
  if (!boundarySnapshot) {
177526
179370
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from Pi trigger decision");
177527
179371
  return;
177528
179372
  }
177529
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
179373
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
177530
179374
  db,
177531
179375
  snapshot: boundarySnapshot,
177532
179376
  currentContextLimit: currentContextLimit ?? boundarySnapshot.contextLimit
177533
179377
  }) : { ok: true };
179378
+ if (!validation.ok && validation.reason === "stale_snapshot" && refreshBoundarySnapshot) {
179379
+ try {
179380
+ const refreshed = refreshBoundarySnapshot();
179381
+ if (hasRunnableCompartmentWindow(refreshed)) {
179382
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
179383
+ boundarySnapshot = refreshed;
179384
+ validation = { ok: true };
179385
+ }
179386
+ } catch (error51) {
179387
+ const desc = describeError(error51);
179388
+ sessionLog(sessionId, `historian: failed to refresh stale protected-tail snapshot at run time (${validation.detail ?? "stale"}): ${desc.brief}`);
179389
+ }
179390
+ }
177534
179391
  if (!validation.ok) {
177535
179392
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
177536
179393
  return;
@@ -177859,8 +179716,13 @@ ${chunkText}`,
177859
179716
  }
177860
179717
  }
177861
179718
  if (embeddingActive) {
177862
- const toEmbed = newCompartments.map((c, i) => ({ id: persistedIds[i], p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
177863
- embedAndStoreCompartments(db, sessionId, projectPath, toEmbed);
179719
+ const chunksToEmbed = newCompartments.map((c, i) => ({
179720
+ id: persistedIds[i],
179721
+ startMessage: c.startMessage,
179722
+ endMessage: c.endMessage,
179723
+ sourceChunkText: chunk.text
179724
+ })).filter((c) => typeof c.id === "number");
179725
+ embedAndStoreCompartmentChunks(db, sessionId, projectPath, chunksToEmbed);
177864
179726
  }
177865
179727
  onPublished?.();
177866
179728
  completedSuccessfully = true;
@@ -178408,7 +180270,7 @@ function stripPiDroppedPlaceholderMessages(args) {
178408
180270
  }
178409
180271
 
178410
180272
  // src/system-prompt.ts
178411
- import { createHash as createHash9 } from "node:crypto";
180273
+ import { createHash as createHash11 } from "node:crypto";
178412
180274
 
178413
180275
  // ../plugin/src/agents/magic-context-prompt.ts
178414
180276
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -178559,7 +180421,7 @@ function processSystemPromptForCache(args) {
178559
180421
  sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
178560
180422
  }
178561
180423
  }
178562
- const currentHash = createHash9("md5").update(frozenPrompt).digest("hex");
180424
+ const currentHash = createHash11("md5").update(frozenPrompt).digest("hex");
178563
180425
  const hashChanged = !isFirstHash && currentHash !== previousHash;
178564
180426
  if (hashChanged) {
178565
180427
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
@@ -179186,6 +181048,19 @@ function extractStableId(msg, index, entryIds) {
179186
181048
  // src/context-handler.ts
179187
181049
  var FORCE_MATERIALIZATION_PERCENTAGE = 85;
179188
181050
  var EMERGENCY_BLOCK_PERCENTAGE = 95;
181051
+ var FORWARD_PRESSURE_LIMIT_FACTOR = 0.85;
181052
+ function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUsageTokens, correctedLimit) {
181053
+ const forwardTokens = typeof piUsageTokens === "number" && piUsageTokens > 0 ? piUsageTokens : 0;
181054
+ if (forwardTokens === 0 || !isSaneLimit(correctedLimit)) {
181055
+ return { percentage: trailingPercentage, inputTokens: trailingInputTokens };
181056
+ }
181057
+ const forwardPressureLimit = correctedLimit * FORWARD_PRESSURE_LIMIT_FACTOR;
181058
+ const forwardPercentage = forwardTokens / forwardPressureLimit * 100;
181059
+ return forwardPercentage > trailingPercentage ? {
181060
+ percentage: forwardPercentage,
181061
+ inputTokens: Math.max(trailingInputTokens, forwardTokens)
181062
+ } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
181063
+ }
179189
181064
  var DEFAULT_CLEAR_REASONING_AGE = 50;
179190
181065
  var PI_STABLE_ID_SCHEME = 1;
179191
181066
  var lastEmergencyNotificationAtMs = new Map;
@@ -179270,7 +181145,7 @@ function trackSessionForProject(projectIdentity, sessionId) {
179270
181145
  function isContextHandlerSessionActive(sessionId) {
179271
181146
  return activeContextHandlerSessions.has(sessionId);
179272
181147
  }
179273
- function updateSessionProjectTracking(sessionId, projectIdentity) {
181148
+ function updateSessionProjectTracking(sessionId, projectIdentity, db) {
179274
181149
  const prev = lastSeenProjectIdentityBySession.get(sessionId);
179275
181150
  if (prev && prev !== projectIdentity) {
179276
181151
  const prevSessions = sessionsByProject.get(prev);
@@ -179279,6 +181154,11 @@ function updateSessionProjectTracking(sessionId, projectIdentity) {
179279
181154
  sessionsByProject.delete(prev);
179280
181155
  clearPiSystemPromptSession(sessionId);
179281
181156
  }
181157
+ if (db && prev !== projectIdentity) {
181158
+ try {
181159
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
181160
+ } catch {}
181161
+ }
179282
181162
  trackSessionForProject(projectIdentity, sessionId);
179283
181163
  lastSeenProjectIdentityBySession.set(sessionId, projectIdentity);
179284
181164
  }
@@ -179559,7 +181439,7 @@ function registerPiContextHandler(pi, baseOptions) {
179559
181439
  const schedulerConfig = options.scheduler ?? DEFAULT_SCHEDULER_CONFIG;
179560
181440
  const scheduler2 = schedulerFor(options);
179561
181441
  const projectIdentity = resolveProjectIdentity(projectDirectory);
179562
- updateSessionProjectTracking(sessionId, projectIdentity);
181442
+ updateSessionProjectTracking(sessionId, projectIdentity, options.db);
179563
181443
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
179564
181444
  const branchEntries = readPiBranchEntriesForContext(ctx, sessionId);
179565
181445
  const rawMessageProvider = {
@@ -179668,9 +181548,10 @@ function registerPiContextHandler(pi, baseOptions) {
179668
181548
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
179669
181549
  usagePercentage = usageInputTokens / usageContextLimit * 100;
179670
181550
  }
181551
+ ({ percentage: usagePercentage, inputTokens: usageInputTokens } = applyForwardPressureFloor(usagePercentage, usageInputTokens, piUsage?.tokens, usageContextLimit));
179671
181552
  if (needsEmergencyBump) {
179672
181553
  sessionLog(sessionId, `transform: overflow recovery flag set — bumping percentage to 95% (detectedLimit=${usageContextLimit ?? "unknown"})`);
179673
- usagePercentage = 95;
181554
+ usagePercentage = Math.max(usagePercentage, 95);
179674
181555
  }
179675
181556
  let schedulerDecision;
179676
181557
  const tScheduler = performance.now();
@@ -179895,7 +181776,7 @@ function registerPiContextHandler(pi, baseOptions) {
179895
181776
  let tailToolTokens;
179896
181777
  let liveTailTokens;
179897
181778
  try {
179898
- const agg = getActiveTagTokenAggregate(options.db, sessionId);
181779
+ const agg = getActiveTagTokenAggregate(options.db, sessionId, options.protectedTags ?? 20);
179899
181780
  tailToolTokens = agg.toolOutput;
179900
181781
  liveTailTokens = agg.conversation + agg.toolCall;
179901
181782
  } catch {
@@ -180054,6 +181935,7 @@ function spawnPiHistorianRun(args) {
180054
181935
  provider: provider2,
180055
181936
  unregister,
180056
181937
  boundarySnapshot,
181938
+ refreshBoundarySnapshot,
180057
181939
  currentContextLimit
180058
181940
  } = args;
180059
181941
  const holderId = crypto3.randomUUID();
@@ -180077,6 +181959,7 @@ function spawnPiHistorianRun(args) {
180077
181959
  fallbackModels: historian.fallbackModels,
180078
181960
  historianChunkTokens: historian.historianChunkTokens,
180079
181961
  boundarySnapshot,
181962
+ refreshBoundarySnapshot,
180080
181963
  currentContextLimit,
180081
181964
  historianTimeoutMs: historian.timeoutMs,
180082
181965
  twoPass: historian.twoPass,
@@ -180143,6 +182026,7 @@ function maybeFireHistorian(args) {
180143
182026
  let usageContextLimit;
180144
182027
  try {
180145
182028
  const piUsage = ctx.getContextUsage?.();
182029
+ let usageSource;
180146
182030
  usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
180147
182031
  if (usageContextLimit === undefined && isSaneLimit(ctx.model?.contextWindow)) {
180148
182032
  usageContextLimit = ctx.model.contextWindow;
@@ -180159,7 +182043,7 @@ function maybeFireHistorian(args) {
180159
182043
  percentage: sessionMetaForUsage.lastContextPercentage,
180160
182044
  inputTokens: sessionMetaForUsage.lastInputTokens
180161
182045
  };
180162
- sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [from session_meta], checking trigger...`);
182046
+ usageSource = "session_meta";
180163
182047
  } else {
180164
182048
  if (!piUsage || piUsage.tokens === null || piUsage.percent === null || piUsage.contextWindow === 0) {
180165
182049
  sessionLog(sessionId, `historian trigger eval: no usage info yet (tokens=${piUsage?.tokens ?? "<no piUsage>"}, percent=${piUsage?.percent ?? "<no piUsage>"}, contextWindow=${piUsage?.contextWindow ?? "<no piUsage>"})`);
@@ -180170,8 +182054,10 @@ function maybeFireHistorian(args) {
180170
182054
  percentage: fallbackPercentage,
180171
182055
  inputTokens: piUsage.tokens
180172
182056
  };
180173
- sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [piUsage fallback], checking trigger...`);
182057
+ usageSource = "piUsage fallback";
180174
182058
  }
182059
+ usage = applyForwardPressureFloor(usage.percentage, usage.inputTokens, piUsage?.tokens, usageContextLimit);
182060
+ sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [${usageSource}], checking trigger...`);
180175
182061
  } catch (err) {
180176
182062
  sessionLog(sessionId, `historian trigger eval: getContextUsage threw: ${err instanceof Error ? err.message : String(err)}`);
180177
182063
  return;
@@ -180202,10 +182088,14 @@ function maybeFireHistorian(args) {
180202
182088
  cacheNamespace: `pi:${sessionId}`,
180203
182089
  emergencyTailScale
180204
182090
  }));
180205
- let boundarySnapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
180206
- if (!hasRunnableCompartmentWindow(boundarySnapshot) && usage.percentage >= 80) {
180207
- boundarySnapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
180208
- }
182091
+ const resolveRunnablePiBoundarySnapshot = () => {
182092
+ let snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
182093
+ if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= 80) {
182094
+ snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
182095
+ }
182096
+ return snapshot;
182097
+ };
182098
+ const boundarySnapshot = resolveRunnablePiBoundarySnapshot();
180209
182099
  let triggered = false;
180210
182100
  try {
180211
182101
  if (isFirstContextPassForSession) {
@@ -180230,6 +182120,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
180230
182120
  provider: provider2,
180231
182121
  unregister,
180232
182122
  boundarySnapshot,
182123
+ refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
180233
182124
  currentContextLimit: boundaryContextLimit
180234
182125
  });
180235
182126
  return;
@@ -180238,6 +182129,15 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
180238
182129
  const trigger = checkCompartmentTrigger(db, sessionId, sessionMeta, usage, 0, triggerInputs.executeThresholdPercentage, triggerInputs.triggerBudget, triggerInputs.clearReasoningAge, triggerInputs.commitClusterTrigger, args.activeTags, boundaryContextLimit);
180239
182130
  if (!trigger.shouldFire) {
180240
182131
  sessionLog(sessionId, `historian trigger eval: shouldFire=false (no trigger condition met)`);
182132
+ try {
182133
+ const overflowState = getOverflowState(db, sessionId);
182134
+ if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && !hasRunnableCompartmentWindow(boundarySnapshot)) {
182135
+ clearEmergencyRecovery(db, sessionId);
182136
+ 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)`);
182137
+ }
182138
+ } catch (err) {
182139
+ sessionLog(sessionId, `historian: emergency-recovery disarm check failed: ${err instanceof Error ? err.message : String(err)}`);
182140
+ }
180241
182141
  return;
180242
182142
  }
180243
182143
  triggered = true;
@@ -180253,6 +182153,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
180253
182153
  resolvedBoundarySnapshot: boundarySnapshot,
180254
182154
  triggerBoundarySnapshot: trigger.boundarySnapshot
180255
182155
  }),
182156
+ refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
180256
182157
  currentContextLimit: boundaryContextLimit
180257
182158
  });
180258
182159
  } catch (err) {
@@ -180293,7 +182194,31 @@ async function runPipeline(args) {
180293
182194
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
180294
182195
  const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
180295
182196
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
180296
- const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializeEligible || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
182197
+ const piHardSignals = args.injection ? (() => {
182198
+ const hardMeta = getOrCreateSessionMeta(args.db, args.sessionId);
182199
+ let piTtlMs = 5 * 60 * 1000;
182200
+ try {
182201
+ piTtlMs = parseCacheTtl(hardMeta.cacheTtl);
182202
+ } catch {}
182203
+ return {
182204
+ systemHash: typeof hardMeta.systemPromptHash === "string" ? hardMeta.systemPromptHash : "",
182205
+ modelKey: liveModelBySession.get(args.sessionId) ?? "",
182206
+ cacheExpired: hardMeta.lastResponseTime > 0 && Date.now() - hardMeta.lastResponseTime >= piTtlMs,
182207
+ lastResponseTime: hardMeta.lastResponseTime
182208
+ };
182209
+ })() : undefined;
182210
+ const m0HardFoldThisPass = args.injection && piHardSignals ? mustMaterializePi({
182211
+ sessionId: args.sessionId,
182212
+ projectIdentity: args.projectIdentity,
182213
+ projectDirectory: args.projectDirectory,
182214
+ memoryEnabled: args.injection.memoryEnabled,
182215
+ injectionBudgetTokens: args.injection.injectionBudgetTokens,
182216
+ historyBudgetTokens: args.injection.historyBudgetTokens,
182217
+ keyFilesEnabled: args.injection.keyFilesEnabled,
182218
+ keyFilesTokenBudget: args.injection.keyFilesTokenBudget,
182219
+ hardSignals: piHardSignals
182220
+ }, args.db, getCompartments(args.db, args.sessionId)).value : false;
182221
+ const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializeEligible || m0HardFoldThisPass || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
180297
182222
  const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver);
180298
182223
  adoptPiFallbackTags(args.db, args.sessionId, args.tagger, entryFingerprintByMessageId);
180299
182224
  const tTag = performance.now();
@@ -180320,12 +182245,12 @@ async function runPipeline(args) {
180320
182245
  const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
180321
182246
  const deferredHistoryRefreshWasPending = deferredHistoryWasPendingAtPassStart;
180322
182247
  const pendingOps = getPendingOps(args.db, args.sessionId);
180323
- const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
182248
+ const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal || m0HardFoldThisPass;
180324
182249
  const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
180325
182250
  const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
180326
182251
  const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
180327
182252
  if (shouldApplyPendingOps) {
180328
- const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
182253
+ 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})`;
180329
182254
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
180330
182255
  try {
180331
182256
  const tApplyPending = performance.now();
@@ -180396,7 +182321,7 @@ async function runPipeline(args) {
180396
182321
  const activeTags = getActiveTagsBySession(args.db, args.sessionId);
180397
182322
  logTransformTiming(args.sessionId, "getActiveTagsBySession", tActiveTags, `count=${activeTags.length}`);
180398
182323
  if (shouldRunHeuristics) {
180399
- const reason = args.forceMaterialization ? "force_materialization" : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
182324
+ 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})`;
180400
182325
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`);
180401
182326
  } else {
180402
182327
  const reason = args.heuristics === undefined ? "disabled" : "scheduler_defer";
@@ -180484,17 +182409,6 @@ async function runPipeline(args) {
180484
182409
  if (args.injection) {
180485
182410
  try {
180486
182411
  const tInjection = performance.now();
180487
- const hardMeta = getOrCreateSessionMeta(args.db, args.sessionId);
180488
- let piTtlMs = 5 * 60 * 1000;
180489
- try {
180490
- piTtlMs = parseCacheTtl(hardMeta.cacheTtl);
180491
- } catch {}
180492
- const piHardSignals = {
180493
- systemHash: typeof hardMeta.systemPromptHash === "string" ? hardMeta.systemPromptHash : "",
180494
- modelKey: liveModelBySession.get(args.sessionId) ?? "",
180495
- cacheExpired: hardMeta.lastResponseTime > 0 && Date.now() - hardMeta.lastResponseTime >= piTtlMs,
180496
- lastResponseTime: hardMeta.lastResponseTime
180497
- };
180498
182412
  injectionResult = injectM0M1Pi({
180499
182413
  sessionId: args.sessionId,
180500
182414
  projectIdentity: args.projectIdentity,
@@ -181584,8 +183498,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
181584
183498
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181585
183499
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
181586
183500
  const liveCompartments = getCompartments(db, sessionId);
181587
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
181588
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
183501
+ const chunksToEmbed = liveCompartments.map((c) => ({
183502
+ id: c.id,
183503
+ startMessage: c.startMessage,
183504
+ endMessage: c.endMessage
183505
+ }));
183506
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
181589
183507
  }
181590
183508
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
181591
183509
  if (lastCompartmentEnd2 > 0) {
@@ -181798,8 +183716,12 @@ Another process acquired the compartment-state lease before recomp could publish
181798
183716
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181799
183717
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
181800
183718
  const liveCompartments = getCompartments(db, sessionId);
181801
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
181802
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
183719
+ const chunksToEmbed = liveCompartments.map((c) => ({
183720
+ id: c.id,
183721
+ startMessage: c.startMessage,
183722
+ endMessage: c.endMessage
183723
+ }));
183724
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
181803
183725
  }
181804
183726
  if (lastCompartmentEnd > 0) {
181805
183727
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -181956,8 +183878,12 @@ Could not acquire the compartment-state lease for this session.`;
181956
183878
  if (deps.memoryEnabled !== false) {
181957
183879
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
181958
183880
  const liveCompartments = getCompartments(db, sessionId);
181959
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
181960
- Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed));
183881
+ const chunksToEmbed = liveCompartments.map((c) => ({
183882
+ id: c.id,
183883
+ startMessage: c.startMessage,
183884
+ endMessage: c.endMessage
183885
+ }));
183886
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
181961
183887
  }
181962
183888
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
181963
183889
  if (lastEnd > 0) {
@@ -182403,7 +184329,7 @@ function renderStatusText(ctx, db, sessionId) {
182403
184329
  const pct = typeof usage?.percent === "number" ? usage.percent : undefined;
182404
184330
  const meta3 = readSessionMetaStatus(db, sessionId);
182405
184331
  const state = renderHistorianState(meta3, recompSessions.has(sessionId));
182406
- return `mc: ${inputTokens === undefined ? "--" : fmt(inputTokens)} (${pct === undefined ? "--" : `${Math.round(pct)}%`}) · ${state}`;
184332
+ return `MC: ${inputTokens === undefined ? "--" : fmt(inputTokens)} (${pct === undefined ? "--" : `${Math.round(pct)}%`}) · ${state}`;
182407
184333
  }
182408
184334
  function renderHistorianState(meta3, recompActive) {
182409
184335
  const failureCount = meta3?.historian_failure_count ?? 0;
@@ -182592,6 +184518,11 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
182592
184518
  fallbackModelId: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined
182593
184519
  }, parsed.kind === "partial" ? { range: parsed.range } : {});
182594
184520
  if (result.published) {
184521
+ try {
184522
+ clearEmergencyRecovery(deps.db, sessionId);
184523
+ } catch (recoveryError) {
184524
+ sessionLog(sessionId, `/ctx-recomp: clearEmergencyRecovery failed (continuing): ${describeError(recoveryError).brief}`);
184525
+ }
182595
184526
  try {
182596
184527
  stagePiRecompMarker({ db: deps.db, sessionId, ctx });
182597
184528
  } catch (markerError) {
@@ -182769,7 +184700,7 @@ function applyMemoryMigration(db, projectPath, result) {
182769
184700
  inserted++;
182770
184701
  }
182771
184702
  if (removed > 0 || inserted > 0) {
182772
- bumpProjectMemoryEpoch(db, projectPath);
184703
+ bumpEpochsForWorkspaceMembers(db, projectPath);
182773
184704
  }
182774
184705
  })();
182775
184706
  return { removed, inserted };
@@ -183245,7 +185176,7 @@ function formatThresholdPercent(value) {
183245
185176
  // package.json
183246
185177
  var package_default = {
183247
185178
  name: "@wolfx/pi-magic-context",
183248
- version: "0.23.1",
185179
+ version: "0.24.1",
183249
185180
  type: "module",
183250
185181
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
183251
185182
  main: "dist/index.js",
@@ -188136,6 +190067,23 @@ function createCtxMemoryTool(deps) {
188136
190067
  }
188137
190068
  const projectIdentity = resolveProjectIdentity(ctx.cwd);
188138
190069
  await deps.ensureProjectRegistered?.(ctx.cwd, deps.db);
190070
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectIdentity);
190071
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
190072
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
190073
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? normalizeStoredProjectPath(rawProjectPath) : normalizeStoredProjectPath(rawProjectPath);
190074
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectIdentity) : null;
190075
+ const memoryVisibleToTool = (memory2) => {
190076
+ if (workspaceIdentitySet.identities.length <= 1) {
190077
+ return storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
190078
+ }
190079
+ if (!storedPathBelongsToWorkspace(memory2.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
190080
+ return false;
190081
+ }
190082
+ const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
190083
+ if (isOwn)
190084
+ return true;
190085
+ return toolShareCategories === null || toolShareCategories.includes(memory2.category);
190086
+ };
188139
190087
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
188140
190088
  if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188141
190089
  return err2("Cross-session memory is disabled for this project.");
@@ -188182,28 +190130,34 @@ function createCtxMemoryTool(deps) {
188182
190130
  return err2("Error: 'content' is required when action is 'update'.");
188183
190131
  }
188184
190132
  const memory2 = getMemoryById(deps.db, updateId);
188185
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
190133
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
188186
190134
  return err2(`Error: Memory with ID ${updateId} was not found.`);
188187
190135
  }
188188
190136
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
188189
190137
  return err2(inactiveMemoryError(updateId, "updating"));
188190
190138
  }
188191
190139
  const normalizedHash = computeNormalizedHash(content);
188192
- const duplicate = getMemoryByHash(deps.db, projectIdentity, memory2.category, normalizedHash);
190140
+ const targetIdentity = targetIdentityForStoredPath(memory2.projectPath);
190141
+ const duplicate = getMemoryByHash(deps.db, targetIdentity, memory2.category, normalizedHash);
188193
190142
  if (duplicate && duplicate.id !== memory2.id) {
188194
190143
  return err2(`Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`);
188195
190144
  }
188196
190145
  deps.db.transaction(() => {
188197
190146
  updateMemoryContent(deps.db, memory2.id, content, normalizedHash);
188198
190147
  queueMemoryMutation(deps.db, {
188199
- projectPath: projectIdentity,
190148
+ projectPath: targetIdentity,
188200
190149
  mutationType: "update",
188201
190150
  targetMemoryId: memory2.id,
188202
190151
  category: memory2.category,
188203
190152
  newContent: content
188204
190153
  });
188205
190154
  })();
188206
- queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
190155
+ queueEmbedding({
190156
+ deps,
190157
+ projectIdentity: targetIdentity,
190158
+ memoryId: memory2.id,
190159
+ content
190160
+ });
188207
190161
  return ok2(`Updated memory [ID: ${memory2.id}] in ${memory2.category}.`);
188208
190162
  }
188209
190163
  if (params.action === "merge") {
@@ -188223,7 +190177,7 @@ function createCtxMemoryTool(deps) {
188223
190177
  return err2("Error: One or more source memories were not found.");
188224
190178
  }
188225
190179
  if (!dreamerAllowed) {
188226
- const foreign = sourceMemories.find((memory2) => !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity));
190180
+ const foreign = sourceMemories.find((memory2) => !memoryVisibleToTool(memory2));
188227
190181
  if (foreign) {
188228
190182
  return err2(`Error: Memory with ID ${foreign.id} was not found.`);
188229
190183
  }
@@ -188312,26 +190266,36 @@ function createCtxMemoryTool(deps) {
188312
190266
  return ok2(`Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`);
188313
190267
  }
188314
190268
  if (params.action === "archive") {
188315
- const archiveIds = params.ids;
188316
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
190269
+ const rawArchiveIds = params.ids;
190270
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
188317
190271
  return err2("Error: 'ids' must contain at least one integer memory ID when action is 'archive'.");
188318
190272
  }
190273
+ const archiveIds = [...new Set(rawArchiveIds)];
188319
190274
  for (const memoryId of archiveIds) {
188320
190275
  const memory2 = getMemoryById(deps.db, memoryId);
188321
- if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
190276
+ if (!memory2 || !memoryVisibleToTool(memory2)) {
188322
190277
  return err2(`Error: Memory with ID ${memoryId} was not found.`);
188323
190278
  }
188324
190279
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
188325
190280
  return err2(inactiveMemoryError(memoryId, "archiving"));
188326
190281
  }
188327
190282
  }
190283
+ const targets = archiveIds.map((memoryId) => {
190284
+ const memory2 = getMemoryById(deps.db, memoryId);
190285
+ if (!memory2)
190286
+ throw new Error(`validated memory ${memoryId} disappeared`);
190287
+ return {
190288
+ memoryId,
190289
+ projectIdentity: targetIdentityForStoredPath(memory2.projectPath)
190290
+ };
190291
+ });
188328
190292
  deps.db.transaction(() => {
188329
- for (const memoryId of archiveIds) {
188330
- archiveMemory(deps.db, memoryId, params.reason);
190293
+ for (const target2 of targets) {
190294
+ archiveMemory(deps.db, target2.memoryId, params.reason);
188331
190295
  queueMemoryMutation(deps.db, {
188332
- projectPath: projectIdentity,
190296
+ projectPath: target2.projectIdentity,
188333
190297
  mutationType: "archive",
188334
- targetMemoryId: memoryId
190298
+ targetMemoryId: target2.memoryId
188335
190299
  });
188336
190300
  }
188337
190301
  })();
@@ -188839,8 +190803,9 @@ function formatAge2(committedAtMs) {
188839
190803
  }
188840
190804
  function formatResult(result, index) {
188841
190805
  if (result.source === "memory") {
190806
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
188842
190807
  return [
188843
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
190808
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
188844
190809
  result.content
188845
190810
  ].join(`
188846
190811
  `);
@@ -188850,6 +190815,13 @@ function formatResult(result, index) {
188850
190815
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
188851
190816
  result.content
188852
190817
  ].join(`
190818
+ `);
190819
+ }
190820
+ if (result.source === "compartment") {
190821
+ return [
190822
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
190823
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
190824
+ ].join(`
188853
190825
  `);
188854
190826
  }
188855
190827
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -188865,7 +190837,7 @@ function formatSearchResults(query, results) {
188865
190837
  return `No results found for "${query}" across memories, git commits, or message history.`;
188866
190838
  }
188867
190839
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
188868
- if (results.some((result) => result.source === "message")) {
190840
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
188869
190841
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
188870
190842
  }
188871
190843
  const body = bodyParts.join(`
@@ -189361,6 +191333,14 @@ async function src_default2(pi) {
189361
191333
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
189362
191334
  });
189363
191335
  info("registered /ctx-dream");
191336
+ registerCtxEmbedHistoryCommand(pi, {
191337
+ db,
191338
+ projectDir,
191339
+ projectIdentity,
191340
+ memoryEnabled: config2.memory.enabled,
191341
+ resolveProject: resolveCurrentProject
191342
+ });
191343
+ info("registered /ctx-embed-history");
189364
191344
  const dreamerConfig = resolveDreamerFromConfig(config2);
189365
191345
  if (dreamerConfig) {
189366
191346
  registerPiDreamerProject({