@wolfx/opencode-magic-context 0.23.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/config/schema/magic-context.d.ts +10 -3
  2. package/dist/config/schema/magic-context.d.ts.map +1 -1
  3. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  4. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +70 -0
  5. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -0
  6. package/dist/features/magic-context/compartment-embedding.d.ts +22 -26
  7. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  8. package/dist/features/magic-context/memory/embedding-backfill.d.ts +3 -2
  9. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  10. package/dist/features/magic-context/memory/embedding-cache.d.ts +3 -2
  11. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -1
  12. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -1
  13. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding-openai.d.ts +3 -0
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  16. package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -0
  17. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  18. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  19. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +5 -1
  21. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  22. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -7
  23. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  24. package/dist/features/magic-context/memory/storage-memory.d.ts +18 -12
  25. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  26. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  27. package/dist/features/magic-context/project-embedding-registry.d.ts +53 -0
  28. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  29. package/dist/features/magic-context/search.d.ts +14 -1
  30. package/dist/features/magic-context/search.d.ts.map +1 -1
  31. package/dist/features/magic-context/session-project-storage.d.ts +17 -0
  32. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -0
  33. package/dist/features/magic-context/storage-db.d.ts +1 -1
  34. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-memory-mutation-log.d.ts +2 -0
  36. package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -1
  37. package/dist/features/magic-context/storage-meta-persisted.d.ts +16 -0
  38. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  39. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-meta-shared.d.ts +3 -1
  41. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  42. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  43. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  44. package/dist/features/magic-context/storage.d.ts +3 -2
  45. package/dist/features/magic-context/storage.d.ts.map +1 -1
  46. package/dist/features/magic-context/types.d.ts +1 -0
  47. package/dist/features/magic-context/types.d.ts.map +1 -1
  48. package/dist/features/magic-context/workspaces.d.ts +20 -0
  49. package/dist/features/magic-context/workspaces.d.ts.map +1 -0
  50. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/command-handler.d.ts +5 -0
  52. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/compartment-runner-types.d.ts +11 -1
  57. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/inject-compartments.d.ts +23 -3
  61. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -1
  63. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/sentinel.d.ts +33 -28
  66. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/strip-content.d.ts +34 -17
  68. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/strip-structural-noise.d.ts +5 -7
  70. package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +4 -5
  72. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  73. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  74. package/dist/index.js +2304 -302
  75. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  76. package/dist/shared/announcement.d.ts +1 -1
  77. package/dist/shared/rpc-types.d.ts +1 -1
  78. package/dist/shared/rpc-types.d.ts.map +1 -1
  79. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  80. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  81. package/package.json +1 -1
  82. package/src/shared/announcement.ts +6 -6
  83. package/src/shared/rpc-types.ts +1 -1
  84. package/src/tui/index.tsx +5 -3
  85. package/src/tui/slots/sidebar-content.tsx +24 -5
package/dist/index.js CHANGED
@@ -14887,7 +14887,8 @@ var init_magic_context = __esm(() => {
14887
14887
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
14888
14888
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
14889
14889
  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."),
14890
- 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.")
14890
+ 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."),
14891
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
14891
14892
  }).superRefine((data, ctx) => {
14892
14893
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
14893
14894
  ctx.addIssue({
@@ -14908,7 +14909,8 @@ var init_magic_context = __esm(() => {
14908
14909
  if (data.provider === "local") {
14909
14910
  return {
14910
14911
  provider: "local",
14911
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
14912
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
14913
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14912
14914
  };
14913
14915
  }
14914
14916
  if (data.provider === "openai-compatible") {
@@ -14921,7 +14923,8 @@ var init_magic_context = __esm(() => {
14921
14923
  endpoint: data.endpoint?.trim() ?? "",
14922
14924
  ...apiKey ? { api_key: apiKey } : {},
14923
14925
  ...inputType ? { input_type: inputType } : {},
14924
- ...truncate ? { truncate } : {}
14926
+ ...truncate ? { truncate } : {},
14927
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14925
14928
  };
14926
14929
  }
14927
14930
  return { provider: "off" };
@@ -15844,7 +15847,7 @@ function isSessionMetaRow(row) {
15844
15847
  if (row === null || typeof row !== "object")
15845
15848
  return false;
15846
15849
  const r = row;
15847
- 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);
15850
+ 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);
15848
15851
  }
15849
15852
  function getDefaultSessionMeta(sessionId) {
15850
15853
  return {
@@ -15871,6 +15874,7 @@ function getDefaultSessionMeta(sessionId) {
15871
15874
  cachedM0Bytes: null,
15872
15875
  cachedM1Bytes: null,
15873
15876
  cachedM0ProjectMemoryEpoch: null,
15877
+ cachedM0WorkspaceFingerprint: null,
15874
15878
  cachedM0ProjectUserProfileVersion: null,
15875
15879
  cachedM0MaxCompartmentSeq: null,
15876
15880
  cachedM0MaxMemoryId: null,
@@ -15933,6 +15937,7 @@ function toSessionMeta(row) {
15933
15937
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
15934
15938
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
15935
15939
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
15940
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
15936
15941
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
15937
15942
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
15938
15943
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -15963,6 +15968,7 @@ function persistCachedM0(db, sessionId, payload) {
15963
15968
  db.prepare(`UPDATE session_meta SET
15964
15969
  cached_m0_bytes = ?,
15965
15970
  cached_m0_project_memory_epoch = ?,
15971
+ cached_m0_workspace_fingerprint = ?,
15966
15972
  cached_m0_project_user_profile_version = ?,
15967
15973
  cached_m0_max_compartment_seq = ?,
15968
15974
  cached_m0_max_memory_id = ?,
@@ -15975,7 +15981,7 @@ function persistCachedM0(db, sessionId, payload) {
15975
15981
  cached_m0_upgrade_state = ?,
15976
15982
  cached_m0_system_hash = ?,
15977
15983
  cached_m0_model_key = ?
15978
- 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);
15984
+ 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);
15979
15985
  }
15980
15986
  function clearCachedM0M1(db, sessionId) {
15981
15987
  ensureSessionMetaRow(db, sessionId);
@@ -15984,6 +15990,7 @@ function clearCachedM0M1(db, sessionId) {
15984
15990
  ["cached_m0_bytes", null],
15985
15991
  ["cached_m1_bytes", null],
15986
15992
  ["cached_m0_project_memory_epoch", null],
15993
+ ["cached_m0_workspace_fingerprint", null],
15987
15994
  ["cached_m0_project_user_profile_version", null],
15988
15995
  ["cached_m0_max_compartment_seq", null],
15989
15996
  ["cached_m0_max_memory_id", null],
@@ -16039,6 +16046,7 @@ var init_storage_meta_shared = __esm(() => {
16039
16046
  "cached_m0_bytes",
16040
16047
  "cached_m1_bytes",
16041
16048
  "cached_m0_project_memory_epoch",
16049
+ "cached_m0_workspace_fingerprint",
16042
16050
  "cached_m0_project_user_profile_version",
16043
16051
  "cached_m0_max_compartment_seq",
16044
16052
  "cached_m0_max_memory_id",
@@ -16086,6 +16094,7 @@ var init_storage_meta_shared = __esm(() => {
16086
16094
  cachedM0Bytes: "cached_m0_bytes",
16087
16095
  cachedM1Bytes: "cached_m1_bytes",
16088
16096
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
16097
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
16089
16098
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
16090
16099
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
16091
16100
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -16115,6 +16124,7 @@ var init_storage_meta_shared = __esm(() => {
16115
16124
  "cachedM0Bytes",
16116
16125
  "cachedM1Bytes",
16117
16126
  "cachedM0ProjectMemoryEpoch",
16127
+ "cachedM0WorkspaceFingerprint",
16118
16128
  "cachedM0ProjectUserProfileVersion",
16119
16129
  "cachedM0MaxCompartmentSeq",
16120
16130
  "cachedM0MaxMemoryId",
@@ -150826,6 +150836,35 @@ function initializeDatabase(db) {
150826
150836
  );
150827
150837
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
150828
150838
 
150839
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
150840
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
150841
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
150842
+ session_id TEXT NOT NULL,
150843
+ project_path TEXT NOT NULL,
150844
+ harness TEXT NOT NULL DEFAULT 'opencode',
150845
+ window_index INTEGER NOT NULL DEFAULT 0,
150846
+ start_ordinal INTEGER NOT NULL,
150847
+ end_ordinal INTEGER NOT NULL,
150848
+ chunk_hash TEXT NOT NULL,
150849
+ model_id TEXT NOT NULL,
150850
+ dims INTEGER NOT NULL,
150851
+ vector BLOB NOT NULL,
150852
+ created_at INTEGER NOT NULL,
150853
+ UNIQUE(compartment_id, window_index)
150854
+ );
150855
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
150856
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
150857
+
150858
+ CREATE TABLE IF NOT EXISTS session_projects (
150859
+ session_id TEXT NOT NULL,
150860
+ harness TEXT NOT NULL DEFAULT 'opencode',
150861
+ project_path TEXT NOT NULL,
150862
+ updated_at INTEGER NOT NULL,
150863
+ PRIMARY KEY(session_id, harness)
150864
+ );
150865
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
150866
+ ON session_projects(project_path);
150867
+
150829
150868
  CREATE TABLE IF NOT EXISTS compartment_events (
150830
150869
  id INTEGER PRIMARY KEY AUTOINCREMENT,
150831
150870
  session_id TEXT NOT NULL,
@@ -151011,6 +151050,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151011
151050
  rekeyed_at INTEGER NOT NULL
151012
151051
  );
151013
151052
 
151053
+ CREATE TABLE IF NOT EXISTS workspaces (
151054
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151055
+ name TEXT NOT NULL UNIQUE,
151056
+ created_at INTEGER NOT NULL,
151057
+ updated_at INTEGER NOT NULL,
151058
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151059
+ );
151060
+
151061
+ CREATE TABLE IF NOT EXISTS workspace_members (
151062
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151063
+ project_path TEXT NOT NULL,
151064
+ display_name TEXT NOT NULL,
151065
+ display_path TEXT NOT NULL,
151066
+ added_at INTEGER NOT NULL,
151067
+ PRIMARY KEY (workspace_id, project_path)
151068
+ );
151069
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151070
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151071
+
151014
151072
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151015
151073
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151016
151074
  table_name TEXT NOT NULL,
@@ -151122,6 +151180,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151122
151180
  deferred_execute_state TEXT,
151123
151181
  cached_m0_bytes BLOB,
151124
151182
  cached_m0_project_memory_epoch INTEGER,
151183
+ cached_m0_workspace_fingerprint TEXT,
151125
151184
  cached_m0_project_user_profile_version INTEGER,
151126
151185
  cached_m0_max_compartment_seq INTEGER,
151127
151186
  cached_m0_max_memory_id INTEGER,
@@ -151280,6 +151339,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151280
151339
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151281
151340
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151282
151341
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151342
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
151283
151343
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
151284
151344
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
151285
151345
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -151333,6 +151393,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151333
151393
  ensureColumn(db, "memories", "importance", "INTEGER");
151334
151394
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
151335
151395
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
151396
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
151336
151397
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
151337
151398
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
151338
151399
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -151364,6 +151425,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151364
151425
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
151365
151426
  updated_at INTEGER NOT NULL DEFAULT 0
151366
151427
  );
151428
+ CREATE TABLE IF NOT EXISTS session_projects (
151429
+ session_id TEXT NOT NULL,
151430
+ harness TEXT NOT NULL DEFAULT 'opencode',
151431
+ project_path TEXT NOT NULL,
151432
+ updated_at INTEGER NOT NULL,
151433
+ PRIMARY KEY(session_id, harness)
151434
+ );
151435
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
151436
+ ON session_projects(project_path);
151367
151437
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
151368
151438
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151369
151439
  session_id TEXT NOT NULL,
@@ -151391,6 +151461,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151391
151461
  new_project_path TEXT NOT NULL,
151392
151462
  rekeyed_at INTEGER NOT NULL
151393
151463
  );
151464
+ CREATE TABLE IF NOT EXISTS workspaces (
151465
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151466
+ name TEXT NOT NULL UNIQUE,
151467
+ created_at INTEGER NOT NULL,
151468
+ updated_at INTEGER NOT NULL,
151469
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151470
+ );
151471
+ CREATE TABLE IF NOT EXISTS workspace_members (
151472
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151473
+ project_path TEXT NOT NULL,
151474
+ display_name TEXT NOT NULL,
151475
+ display_path TEXT NOT NULL,
151476
+ added_at INTEGER NOT NULL,
151477
+ PRIMARY KEY (workspace_id, project_path)
151478
+ );
151479
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151480
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151394
151481
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151395
151482
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151396
151483
  table_name TEXT NOT NULL,
@@ -151412,6 +151499,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151412
151499
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151413
151500
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151414
151501
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151502
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
151415
151503
  }
151416
151504
  function healAllNullColumns(db) {
151417
151505
  healNullTextColumns(db);
@@ -151449,6 +151537,7 @@ function healNullTextColumns(db) {
151449
151537
  ["system_prompt_hash", ""],
151450
151538
  ["stripped_placeholder_ids", ""],
151451
151539
  ["stale_reduce_stripped_ids", ""],
151540
+ ["processed_image_stripped_ids", ""],
151452
151541
  ["memory_block_cache", ""],
151453
151542
  ["memory_block_ids", ""],
151454
151543
  ["compaction_marker_state", ""],
@@ -151493,7 +151582,7 @@ function healNullIntegerColumns(db) {
151493
151582
  }
151494
151583
  }
151495
151584
  function ensureColumn(db, table, column, definition) {
151496
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
151585
+ if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
151497
151586
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
151498
151587
  }
151499
151588
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -151579,7 +151668,7 @@ function getDatabasePersistenceError(db) {
151579
151668
  return null;
151580
151669
  return persistenceErrorByDatabase.get(db) ?? null;
151581
151670
  }
151582
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 32, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151671
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 36, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151583
151672
  var init_storage_db = __esm(async () => {
151584
151673
  init_data_path();
151585
151674
  init_logger();
@@ -151599,10 +151688,302 @@ var init_storage_db = __esm(async () => {
151599
151688
  };
151600
151689
  });
151601
151690
 
151691
+ // src/features/magic-context/memory/constants.ts
151692
+ var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
151693
+ var init_constants = __esm(() => {
151694
+ V2_MEMORY_CATEGORIES = [
151695
+ "PROJECT_RULES",
151696
+ "ARCHITECTURE",
151697
+ "CONSTRAINTS",
151698
+ "CONFIG_VALUES",
151699
+ "NAMING"
151700
+ ];
151701
+ PROMOTABLE_CATEGORIES = [
151702
+ "PROJECT_RULES",
151703
+ "ARCHITECTURE",
151704
+ "CONSTRAINTS",
151705
+ "CONFIG_VALUES",
151706
+ "NAMING",
151707
+ "ARCHITECTURE_DECISIONS",
151708
+ "CONFIG_DEFAULTS",
151709
+ "USER_PREFERENCES",
151710
+ "USER_DIRECTIVES",
151711
+ "ENVIRONMENT",
151712
+ "WORKFLOW_RULES",
151713
+ "KNOWN_ISSUES"
151714
+ ];
151715
+ CATEGORY_PRIORITY = [
151716
+ "PROJECT_RULES",
151717
+ "ARCHITECTURE",
151718
+ "CONSTRAINTS",
151719
+ "CONFIG_VALUES",
151720
+ "NAMING",
151721
+ "USER_DIRECTIVES",
151722
+ "USER_PREFERENCES",
151723
+ "CONFIG_DEFAULTS",
151724
+ "ARCHITECTURE_DECISIONS",
151725
+ "ENVIRONMENT",
151726
+ "WORKFLOW_RULES",
151727
+ "KNOWN_ISSUES"
151728
+ ];
151729
+ MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
151730
+ acc[category] = index;
151731
+ return acc;
151732
+ }, {});
151733
+ MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
151734
+ CATEGORY_DEFAULT_TTL = {
151735
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
151736
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
151737
+ };
151738
+ });
151739
+
151740
+ // src/features/magic-context/project-identity.ts
151741
+ var init_project_identity2 = __esm(() => {
151742
+ init_project_identity();
151743
+ });
151744
+
151745
+ // src/features/magic-context/workspaces.ts
151746
+ import { createHash as createHash4 } from "node:crypto";
151747
+ function tableExists(db, tableName) {
151748
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
151749
+ return Boolean(row);
151750
+ }
151751
+ function columnExists(db, tableName, columnName) {
151752
+ const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
151753
+ return rows.some((row) => row.name === columnName);
151754
+ }
151755
+ function uniqueSorted(values) {
151756
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
151757
+ }
151758
+ function placeholders(values) {
151759
+ return values.map(() => "?").join(", ");
151760
+ }
151761
+ function normalizeShareCategories(raw) {
151762
+ if (raw === null || raw === undefined)
151763
+ return null;
151764
+ if (typeof raw !== "string")
151765
+ return null;
151766
+ let parsed;
151767
+ try {
151768
+ parsed = JSON.parse(raw);
151769
+ } catch {
151770
+ return null;
151771
+ }
151772
+ if (!Array.isArray(parsed))
151773
+ return null;
151774
+ const categories = [];
151775
+ for (const value of parsed) {
151776
+ if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
151777
+ return null;
151778
+ }
151779
+ if (!categories.includes(value))
151780
+ categories.push(value);
151781
+ }
151782
+ return categories.sort((left, right) => left.localeCompare(right));
151783
+ }
151784
+ function selectWorkspaceShareCategories(db, identities) {
151785
+ const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
151786
+ if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
151787
+ return null;
151788
+ }
151789
+ const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
151790
+ FROM workspace_members AS member
151791
+ JOIN workspaces AS workspace ON workspace.id = member.workspace_id
151792
+ WHERE member.project_path IN (${placeholders(candidates)})
151793
+ ORDER BY workspace.id ASC
151794
+ LIMIT 1`).get(...candidates);
151795
+ return normalizeShareCategories(row?.shareCategories ?? null);
151796
+ }
151797
+ function resolveWorkspaceShareCategories(db, projectIdentity) {
151798
+ return selectWorkspaceShareCategories(db, [projectIdentity]);
151799
+ }
151800
+ function resolveWorkspaceIdentitySet(db, projectIdentity) {
151801
+ if (!tableExists(db, "workspace_members")) {
151802
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151803
+ }
151804
+ const rows = db.prepare(`SELECT member.project_path AS identity, member.display_name AS displayName
151805
+ FROM workspace_members AS anchor
151806
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151807
+ WHERE anchor.project_path = ?
151808
+ ORDER BY member.display_name ASC, member.project_path ASC`).all(projectIdentity);
151809
+ if (rows.length === 0) {
151810
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151811
+ }
151812
+ const namesByIdentity = new Map;
151813
+ const identities = [];
151814
+ for (const row of rows) {
151815
+ if (typeof row.identity !== "string" || row.identity.length === 0)
151816
+ continue;
151817
+ if (identities.includes(row.identity))
151818
+ continue;
151819
+ identities.push(row.identity);
151820
+ if (typeof row.displayName === "string" && row.displayName.length > 0) {
151821
+ namesByIdentity.set(row.identity, row.displayName);
151822
+ }
151823
+ }
151824
+ return identities.length > 0 ? { identities, namesByIdentity } : { identities: [projectIdentity], namesByIdentity: new Map };
151825
+ }
151826
+ function expandWorkspaceIdentitySetWithAliases(db, identities) {
151827
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151828
+ const expanded = new Set(canonical);
151829
+ const canonicalIdentityByStoredPath = new Map;
151830
+ for (const identity of canonical) {
151831
+ canonicalIdentityByStoredPath.set(identity, identity);
151832
+ }
151833
+ if (canonical.length === 0 || !tableExists(db, "v22_identity_rekey_map")) {
151834
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151835
+ }
151836
+ const rows = db.prepare(`SELECT old_project_path AS oldProjectPath, new_project_path AS newProjectPath
151837
+ FROM v22_identity_rekey_map
151838
+ WHERE new_project_path IN (${placeholders(canonical)})
151839
+ ORDER BY old_project_path ASC`).all(...canonical);
151840
+ for (const row of rows) {
151841
+ if (typeof row.oldProjectPath !== "string" || typeof row.newProjectPath !== "string") {
151842
+ continue;
151843
+ }
151844
+ if (!canonicalIdentityByStoredPath.has(row.newProjectPath))
151845
+ continue;
151846
+ expanded.add(row.oldProjectPath);
151847
+ canonicalIdentityByStoredPath.set(row.oldProjectPath, row.newProjectPath);
151848
+ }
151849
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151850
+ }
151851
+ function resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) {
151852
+ const direct = canonicalIdentityByStoredPath.get(storedProjectPath);
151853
+ if (direct)
151854
+ return direct;
151855
+ const normalized = normalizeStoredProjectPath(storedProjectPath);
151856
+ const normalizedDirect = canonicalIdentityByStoredPath.get(normalized);
151857
+ if (normalizedDirect)
151858
+ return normalizedDirect;
151859
+ if (memberIdentities.includes(normalized))
151860
+ return normalized;
151861
+ for (const identity of memberIdentities) {
151862
+ if (storedPathBelongsToIdentity(storedProjectPath, identity)) {
151863
+ return identity;
151864
+ }
151865
+ }
151866
+ return null;
151867
+ }
151868
+ function storedPathBelongsToWorkspace(storedProjectPath, memberIdentities, expandedIdentities, canonicalIdentityByStoredPath) {
151869
+ if (expandedIdentities.includes(storedProjectPath))
151870
+ return true;
151871
+ return resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) !== null;
151872
+ }
151873
+ function sourceNameForMemory(storedProjectPath, ownIdentity, memberIdentities, namesByIdentity, canonicalIdentityByStoredPath) {
151874
+ const canonicalIdentity = resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath);
151875
+ if (!canonicalIdentity || canonicalIdentity === ownIdentity)
151876
+ return;
151877
+ return namesByIdentity.get(canonicalIdentity);
151878
+ }
151879
+ function getEpochMap(db, identities) {
151880
+ if (identities.length === 0)
151881
+ return new Map;
151882
+ const rows = db.prepare(`SELECT project_path AS projectPath, project_memory_epoch AS epoch
151883
+ FROM project_state
151884
+ WHERE project_path IN (${placeholders(identities)})`).all(...identities);
151885
+ const epochs = new Map;
151886
+ for (const row of rows) {
151887
+ if (typeof row.projectPath !== "string" || typeof row.epoch !== "number")
151888
+ continue;
151889
+ epochs.set(row.projectPath, row.epoch);
151890
+ }
151891
+ return epochs;
151892
+ }
151893
+ function computeWorkspaceEpochFingerprint(db, identities) {
151894
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151895
+ const epochs = getEpochMap(db, canonical);
151896
+ const shareCategories = selectWorkspaceShareCategories(db, canonical);
151897
+ const hash2 = createHash4("sha256");
151898
+ hash2.update("share_categories", "utf8");
151899
+ hash2.update("\x00");
151900
+ hash2.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
151901
+ hash2.update(`
151902
+ `);
151903
+ for (const identity of canonical) {
151904
+ hash2.update(identity, "utf8");
151905
+ hash2.update("\x00");
151906
+ hash2.update(String(epochs.get(identity) ?? 0), "utf8");
151907
+ hash2.update(`
151908
+ `);
151909
+ }
151910
+ return hash2.digest("hex");
151911
+ }
151912
+ function isInTransaction(db) {
151913
+ const candidate = db;
151914
+ return candidate.inTransaction === true || candidate.isTransaction === true;
151915
+ }
151916
+ function workspaceMembersForIdentity(db, identity) {
151917
+ if (!tableExists(db, "workspace_members"))
151918
+ return [identity];
151919
+ const rows = db.prepare(`SELECT member.project_path AS identity
151920
+ FROM workspace_members AS anchor
151921
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151922
+ WHERE anchor.project_path = ?
151923
+ ORDER BY member.project_path ASC`).all(identity);
151924
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((value) => value.length > 0);
151925
+ return identities.length > 0 ? uniqueSorted(identities) : [identity];
151926
+ }
151927
+ function bumpEpochRows(db, identities, now) {
151928
+ const stmt = db.prepare(`INSERT INTO project_state
151929
+ (project_path, project_memory_epoch, project_user_profile_version, updated_at)
151930
+ VALUES (?, 1, 0, ?)
151931
+ ON CONFLICT(project_path) DO UPDATE SET
151932
+ project_memory_epoch = project_memory_epoch + 1,
151933
+ updated_at = excluded.updated_at`);
151934
+ for (const identity of uniqueSorted(identities)) {
151935
+ stmt.run(identity, now);
151936
+ }
151937
+ }
151938
+ function bumpEpochsForWorkspaceMembers(db, identity, now = Date.now()) {
151939
+ const run = () => bumpEpochRows(db, workspaceMembersForIdentity(db, identity), now);
151940
+ if (isInTransaction(db)) {
151941
+ run();
151942
+ return;
151943
+ }
151944
+ db.exec("BEGIN IMMEDIATE");
151945
+ try {
151946
+ run();
151947
+ db.exec("COMMIT");
151948
+ } catch (error51) {
151949
+ try {
151950
+ db.exec("ROLLBACK");
151951
+ } catch {}
151952
+ throw error51;
151953
+ }
151954
+ }
151955
+ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
151956
+ const run = () => bumpEpochRows(db, identities, now);
151957
+ if (isInTransaction(db)) {
151958
+ run();
151959
+ return;
151960
+ }
151961
+ db.exec("BEGIN IMMEDIATE");
151962
+ try {
151963
+ run();
151964
+ db.exec("COMMIT");
151965
+ } catch (error51) {
151966
+ try {
151967
+ db.exec("ROLLBACK");
151968
+ } catch {}
151969
+ throw error51;
151970
+ }
151971
+ }
151972
+ var VALID_SHARE_CATEGORIES;
151973
+ var init_workspaces = __esm(() => {
151974
+ init_constants();
151975
+ init_project_identity2();
151976
+ VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
151977
+ });
151978
+
151602
151979
  // src/features/magic-context/migrations.ts
151603
- function tableExists(db, name2) {
151980
+ function tableExists2(db, name2) {
151604
151981
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
151605
151982
  }
151983
+ function columnExists2(db, table, column) {
151984
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
151985
+ return rows.some((row) => row.name === column);
151986
+ }
151606
151987
  function ensureMigrationsTable(db) {
151607
151988
  db.exec(`
151608
151989
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -151671,6 +152052,7 @@ function runMigrations(db) {
151671
152052
  var MIGRATIONS, LATEST_MIGRATION_VERSION;
151672
152053
  var init_migrations = __esm(async () => {
151673
152054
  init_logger();
152055
+ init_workspaces();
151674
152056
  await init_storage_db();
151675
152057
  MIGRATIONS = [
151676
152058
  {
@@ -152130,9 +152512,9 @@ var init_migrations = __esm(async () => {
152130
152512
  version: 22,
152131
152513
  description: "v2.0 cache architecture schema foundation",
152132
152514
  up: (db) => {
152133
- const hasSessionMetaTable = tableExists(db, "session_meta");
152134
- const hasCompartmentsTable = tableExists(db, "compartments");
152135
- const hasMemoriesTable = tableExists(db, "memories");
152515
+ const hasSessionMetaTable = tableExists2(db, "session_meta");
152516
+ const hasCompartmentsTable = tableExists2(db, "compartments");
152517
+ const hasMemoriesTable = tableExists2(db, "memories");
152136
152518
  if (hasSessionMetaTable) {
152137
152519
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
152138
152520
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
@@ -152157,7 +152539,7 @@ var init_migrations = __esm(async () => {
152157
152539
  ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
152158
152540
  ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
152159
152541
  }
152160
- const hasRecompCompartmentsTable = tableExists(db, "recomp_compartments");
152542
+ const hasRecompCompartmentsTable = tableExists2(db, "recomp_compartments");
152161
152543
  if (hasRecompCompartmentsTable) {
152162
152544
  ensureColumn(db, "recomp_compartments", "p1", "TEXT");
152163
152545
  ensureColumn(db, "recomp_compartments", "p2", "TEXT");
@@ -152468,13 +152850,163 @@ var init_migrations = __esm(async () => {
152468
152850
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
152469
152851
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
152470
152852
  }
152853
+ },
152854
+ {
152855
+ version: 33,
152856
+ description: "Compartment chunk embeddings for semantic message-history search",
152857
+ up: (db) => {
152858
+ db.exec(`
152859
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
152860
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152861
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
152862
+ session_id TEXT NOT NULL,
152863
+ project_path TEXT NOT NULL,
152864
+ harness TEXT NOT NULL DEFAULT 'opencode',
152865
+ window_index INTEGER NOT NULL DEFAULT 0,
152866
+ start_ordinal INTEGER NOT NULL,
152867
+ end_ordinal INTEGER NOT NULL,
152868
+ chunk_hash TEXT NOT NULL,
152869
+ model_id TEXT NOT NULL,
152870
+ dims INTEGER NOT NULL,
152871
+ vector BLOB NOT NULL,
152872
+ created_at INTEGER NOT NULL,
152873
+ UNIQUE(compartment_id, window_index)
152874
+ );
152875
+ CREATE INDEX IF NOT EXISTS idx_cce_session
152876
+ ON compartment_chunk_embeddings(session_id);
152877
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model
152878
+ ON compartment_chunk_embeddings(project_path, model_id);
152879
+ `);
152880
+ }
152881
+ },
152882
+ {
152883
+ version: 34,
152884
+ description: "workspace tables and m[0] workspace fingerprint cache reset",
152885
+ up: (db) => {
152886
+ db.exec(`
152887
+ CREATE TABLE IF NOT EXISTS workspaces (
152888
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152889
+ name TEXT NOT NULL UNIQUE,
152890
+ created_at INTEGER NOT NULL,
152891
+ updated_at INTEGER NOT NULL
152892
+ );
152893
+ CREATE TABLE IF NOT EXISTS workspace_members (
152894
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
152895
+ project_path TEXT NOT NULL,
152896
+ display_name TEXT NOT NULL,
152897
+ display_path TEXT NOT NULL,
152898
+ added_at INTEGER NOT NULL,
152899
+ PRIMARY KEY (workspace_id, project_path)
152900
+ );
152901
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique
152902
+ ON workspace_members(project_path);
152903
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name
152904
+ ON workspace_members(workspace_id, display_name);
152905
+ `);
152906
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
152907
+ if (!hasSessionMeta)
152908
+ return;
152909
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
152910
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
152911
+ const clears = [
152912
+ ["cached_m0_bytes", null],
152913
+ ["cached_m1_bytes", null],
152914
+ ["cached_m0_project_memory_epoch", null],
152915
+ ["cached_m0_workspace_fingerprint", null],
152916
+ ["cached_m0_project_user_profile_version", null],
152917
+ ["cached_m0_max_compartment_seq", null],
152918
+ ["cached_m0_max_memory_id", null],
152919
+ ["cached_m0_max_mutation_id", null],
152920
+ ["cached_m0_max_memory_mutation_id", null],
152921
+ ["cached_m0_project_docs_hash", null],
152922
+ ["cached_m0_materialized_at", null],
152923
+ ["cached_m0_session_facts_version", null],
152924
+ ["cached_m0_upgrade_state", null],
152925
+ ["cached_m0_system_hash", null],
152926
+ ["cached_m0_tool_set_hash", null],
152927
+ ["cached_m0_model_key", null],
152928
+ ["cached_m0_last_baseline_end_message_id", null],
152929
+ ["memory_block_cache", ""],
152930
+ ["memory_block_ids", ""],
152931
+ ["memory_block_count", 0]
152932
+ ];
152933
+ const setClauses = [];
152934
+ const values = [];
152935
+ for (const [column, value] of clears) {
152936
+ if (!columns.has(column))
152937
+ continue;
152938
+ setClauses.push(`${column} = ?`);
152939
+ values.push(value);
152940
+ }
152941
+ if (setClauses.length > 0) {
152942
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")}`).run(...values);
152943
+ }
152944
+ }
152945
+ },
152946
+ {
152947
+ version: 35,
152948
+ description: "workspace per-category share defaults and epoch refresh",
152949
+ up: (db) => {
152950
+ db.exec(`
152951
+ CREATE TABLE IF NOT EXISTS workspaces (
152952
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152953
+ name TEXT NOT NULL UNIQUE,
152954
+ created_at INTEGER NOT NULL,
152955
+ updated_at INTEGER NOT NULL,
152956
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
152957
+ );
152958
+ `);
152959
+ if (!columnExists2(db, "workspaces", "share_categories")) {
152960
+ db.exec(`ALTER TABLE workspaces ADD COLUMN share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
152961
+ }
152962
+ db.prepare(`UPDATE workspaces
152963
+ SET share_categories = '["CONSTRAINTS"]'
152964
+ WHERE share_categories IS NULL OR share_categories = ''`).run();
152965
+ if (!tableExists2(db, "workspace_members"))
152966
+ return;
152967
+ const rows = db.prepare(`SELECT DISTINCT project_path AS identity
152968
+ FROM workspace_members
152969
+ WHERE project_path IS NOT NULL AND project_path <> ''
152970
+ ORDER BY project_path ASC`).all();
152971
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((identity) => identity.length > 0);
152972
+ if (identities.length > 0) {
152973
+ bumpEpochsForWorkspaceMemberSet(db, identities, Date.now());
152974
+ }
152975
+ }
152976
+ },
152977
+ {
152978
+ version: 36,
152979
+ description: "session project ownership map for compartment chunk backfill scoping",
152980
+ up: (db) => {
152981
+ db.exec(`
152982
+ CREATE TABLE IF NOT EXISTS session_projects (
152983
+ session_id TEXT NOT NULL,
152984
+ harness TEXT NOT NULL DEFAULT 'opencode',
152985
+ project_path TEXT NOT NULL,
152986
+ updated_at INTEGER NOT NULL,
152987
+ PRIMARY KEY(session_id, harness)
152988
+ );
152989
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
152990
+ ON session_projects(project_path);
152991
+ `);
152992
+ const hasChunkTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartment_chunk_embeddings'").get();
152993
+ if (hasChunkTable) {
152994
+ db.exec(`
152995
+ INSERT OR IGNORE INTO session_projects (session_id, harness, project_path, updated_at)
152996
+ SELECT session_id, harness, MIN(project_path), 0
152997
+ FROM compartment_chunk_embeddings
152998
+ GROUP BY session_id, harness
152999
+ HAVING COUNT(DISTINCT project_path) = 1;
153000
+ `);
153001
+ }
153002
+ }
152471
153003
  }
152472
153004
  ];
152473
153005
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
152474
153006
  });
152475
153007
 
152476
153008
  // src/features/magic-context/project-docs-hash.ts
152477
- import { createHash as createHash4 } from "node:crypto";
153009
+ import { createHash as createHash5 } from "node:crypto";
152478
153010
  import { lstatSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152479
153011
  import path4 from "node:path";
152480
153012
  function canonicalizeDocContent(raw) {
@@ -152564,7 +153096,7 @@ function hashCanonicalPieces(hashPieces) {
152564
153096
  if (hashPieces.length === 0) {
152565
153097
  return "";
152566
153098
  }
152567
- return createHash4("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
153099
+ return createHash5("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
152568
153100
  }
152569
153101
  function readProjectDocsCanonical(projectDirectory) {
152570
153102
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -152602,11 +153134,6 @@ var init_project_docs_hash = __esm(() => {
152602
153134
  MAX_PROJECT_DOC_BYTES = 256 * 1024;
152603
153135
  docsCache = new Map;
152604
153136
  });
152605
-
152606
- // src/features/magic-context/project-identity.ts
152607
- var init_project_identity2 = __esm(() => {
152608
- init_project_identity();
152609
- });
152610
153137
  // src/features/magic-context/storage-m0-mutation-log.ts
152611
153138
  function assertMutationType(mutationType) {
152612
153139
  if (!M0_MUTATION_TYPES.has(mutationType)) {
@@ -152691,18 +153218,13 @@ function getMemoryMutation(db, id) {
152691
153218
  WHERE id = ?`).get(id);
152692
153219
  return row ? toMemoryMutation(row) : null;
152693
153220
  }
152694
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
152695
- if (renderedMemoryIds.length === 0)
152696
- return [];
152697
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
152698
- const placeholders = uniqueIds.map(() => "?").join(", ");
152699
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
152700
- superseded_by_id, category, new_content, queued_at
152701
- FROM memory_mutation_log
152702
- WHERE project_path = ?
152703
- AND id > ?
152704
- AND target_memory_id IN (${placeholders})
152705
- ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153221
+ function uniqueProjectPaths(projectPaths) {
153222
+ return [...new Set(projectPaths.filter((path5) => path5.length > 0))];
153223
+ }
153224
+ function placeholders2(values) {
153225
+ return values.map(() => "?").join(", ");
153226
+ }
153227
+ function coalesceMutations(rows) {
152706
153228
  const chosenByTarget = new Map;
152707
153229
  for (const dbRow of rows) {
152708
153230
  const candidate = toMemoryMutation(dbRow);
@@ -152723,10 +153245,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
152723
153245
  }
152724
153246
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
152725
153247
  }
153248
+ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
153249
+ if (renderedMemoryIds.length === 0)
153250
+ return [];
153251
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153252
+ const placeholders3 = uniqueIds.map(() => "?").join(", ");
153253
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153254
+ superseded_by_id, category, new_content, queued_at
153255
+ FROM memory_mutation_log
153256
+ WHERE project_path = ?
153257
+ AND id > ?
153258
+ AND target_memory_id IN (${placeholders3})
153259
+ ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153260
+ return coalesceMutations(rows);
153261
+ }
153262
+ function getMemoryMutationsForRenderByProjects(db, projectPaths, afterId, renderedMemoryIds) {
153263
+ if (renderedMemoryIds.length === 0)
153264
+ return [];
153265
+ const identities = uniqueProjectPaths(projectPaths);
153266
+ if (identities.length === 0)
153267
+ return [];
153268
+ if (identities.length === 1) {
153269
+ return getMemoryMutationsForRender(db, identities[0], afterId, renderedMemoryIds);
153270
+ }
153271
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153272
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153273
+ superseded_by_id, category, new_content, queued_at
153274
+ FROM memory_mutation_log
153275
+ WHERE project_path IN (${placeholders2(identities)})
153276
+ AND id > ?
153277
+ AND target_memory_id IN (${placeholders2(uniqueIds)})
153278
+ ORDER BY id ASC`).all(...identities, afterId ?? 0, ...uniqueIds);
153279
+ return coalesceMutations(rows);
153280
+ }
152726
153281
  function getMaxMemoryMutationId(db, projectPath) {
152727
153282
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
152728
153283
  return row?.max_id ?? null;
152729
153284
  }
153285
+ function getMaxMemoryMutationIdForProjects(db, projectPaths) {
153286
+ const identities = uniqueProjectPaths(projectPaths);
153287
+ if (identities.length === 0)
153288
+ return null;
153289
+ if (identities.length === 1)
153290
+ return getMaxMemoryMutationId(db, identities[0]);
153291
+ const row = db.prepare(`SELECT MAX(id) AS max_id
153292
+ FROM memory_mutation_log
153293
+ WHERE project_path IN (${placeholders2(identities)})`).get(...identities);
153294
+ return row?.max_id ?? null;
153295
+ }
152730
153296
  var MEMORY_MUTATION_TYPES, TERMINAL_MUTATION_TYPES;
152731
153297
  var init_storage_memory_mutation_log = __esm(() => {
152732
153298
  MEMORY_MUTATION_TYPES = new Set(["archive", "delete", "update", "superseded"]);
@@ -153419,6 +153985,36 @@ function addStaleReduceStrippedIds(db, sessionId, ids) {
153419
153985
  sessionLog(sessionId, `stale_reduce_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
153420
153986
  return false;
153421
153987
  }
153988
+ function getProcessedImageStrippedIds(db, sessionId) {
153989
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153990
+ return new Set(parseStrippedBlob(row?.processed_image_stripped_ids));
153991
+ }
153992
+ function addProcessedImageStrippedIds(db, sessionId, ids) {
153993
+ const add = [...ids];
153994
+ if (add.length === 0)
153995
+ return true;
153996
+ ensureSessionMetaRow(db, sessionId);
153997
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
153998
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153999
+ const rawStored = row ? row.processed_image_stripped_ids ?? null : null;
154000
+ const current = new Set(parseStrippedBlob(rawStored));
154001
+ let changed = false;
154002
+ for (const id of add) {
154003
+ if (!current.has(id)) {
154004
+ current.add(id);
154005
+ changed = true;
154006
+ }
154007
+ }
154008
+ if (!changed)
154009
+ return true;
154010
+ const nextBlob = JSON.stringify([...current]);
154011
+ const result = db.prepare("UPDATE session_meta SET processed_image_stripped_ids = ? WHERE session_id = ? AND processed_image_stripped_ids IS ?").run(nextBlob, sessionId, rawStored);
154012
+ if (result.changes > 0)
154013
+ return true;
154014
+ }
154015
+ sessionLog(sessionId, `processed_image_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
154016
+ return false;
154017
+ }
153422
154018
  function isPendingCompactionMarker(value) {
153423
154019
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
153424
154020
  }
@@ -153597,6 +154193,8 @@ function clearSession(db, sessionId) {
153597
154193
  db.prepare("DELETE FROM source_contents WHERE session_id = ?").run(sessionId);
153598
154194
  db.prepare("DELETE FROM tags WHERE session_id = ?").run(sessionId);
153599
154195
  db.prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
154196
+ db.prepare("DELETE FROM session_projects WHERE session_id = ?").run(sessionId);
154197
+ db.prepare("DELETE FROM compartment_chunk_embeddings WHERE session_id = ?").run(sessionId);
153600
154198
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
153601
154199
  clearCompressionDepth(db, sessionId);
153602
154200
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
@@ -153703,9 +154301,9 @@ function buildStatusClause(status) {
153703
154301
  if (statuses.length === 0) {
153704
154302
  return null;
153705
154303
  }
153706
- const placeholders = statuses.map(() => "?").join(", ");
154304
+ const placeholders3 = statuses.map(() => "?").join(", ");
153707
154305
  return {
153708
- sql: `status IN (${placeholders})`,
154306
+ sql: `status IN (${placeholders3})`,
153709
154307
  params: statuses
153710
154308
  };
153711
154309
  }
@@ -153911,19 +154509,6 @@ function getProjectState(db, projectPath) {
153911
154509
  WHERE project_path = ?`).get(projectPath);
153912
154510
  return row ? toProjectState(row) : null;
153913
154511
  }
153914
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
153915
- db.prepare(`INSERT INTO project_state
153916
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
153917
- VALUES (?, 1, 0, ?)
153918
- ON CONFLICT(project_path) DO UPDATE SET
153919
- project_memory_epoch = project_memory_epoch + 1,
153920
- updated_at = excluded.updated_at`).run(projectPath, now);
153921
- const state = getProjectState(db, projectPath);
153922
- if (!state) {
153923
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
153924
- }
153925
- return state;
153926
- }
153927
154512
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
153928
154513
  db.prepare(`INSERT INTO project_state
153929
154514
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -153959,8 +154544,8 @@ function getSourceContents(db, sessionId, tagIds) {
153959
154544
  if (tagIds.length === 0) {
153960
154545
  return new Map;
153961
154546
  }
153962
- const placeholders = tagIds.map(() => "?").join(", ");
153963
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
154547
+ const placeholders3 = tagIds.map(() => "?").join(", ");
154548
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders3})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
153964
154549
  const sources = new Map;
153965
154550
  for (const row of rows) {
153966
154551
  sources.set(row.tag_id, row.content);
@@ -154270,8 +154855,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
154270
154855
  }
154271
154856
  return all;
154272
154857
  }
154273
- const placeholders = tagNumbers.map(() => "?").join(",");
154274
- 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);
154858
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
154859
+ 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);
154275
154860
  return rows.map(toTagEntry);
154276
154861
  }
154277
154862
  function getMaxDroppedTagNumber(db, sessionId) {
@@ -154424,6 +155009,7 @@ var init_storage = __esm(async () => {
154424
155009
  init_storage_source();
154425
155010
  init_storage_tags();
154426
155011
  init_storage_v22_backfill_failures();
155012
+ init_workspaces();
154427
155013
  await __promiseAll([
154428
155014
  init_message_index(),
154429
155015
  init_migrations(),
@@ -163875,7 +164461,7 @@ function isEmbeddingRow(row) {
163875
164461
  if (row === null || typeof row !== "object")
163876
164462
  return false;
163877
164463
  const candidate = row;
163878
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
164464
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
163879
164465
  }
163880
164466
  function toFloat32Array(blob) {
163881
164467
  if (blob instanceof Uint8Array) {
@@ -163895,7 +164481,7 @@ function getSaveEmbeddingStatement(db) {
163895
164481
  function getLoadAllEmbeddingsStatement(db) {
163896
164482
  let stmt = loadAllEmbeddingsStatements.get(db);
163897
164483
  if (!stmt) {
163898
- 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");
164484
+ 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");
163899
164485
  loadAllEmbeddingsStatements.set(db, stmt);
163900
164486
  }
163901
164487
  return stmt;
@@ -163924,7 +164510,10 @@ function loadAllEmbeddings(db, projectPath) {
163924
164510
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
163925
164511
  const embeddings = new Map;
163926
164512
  for (const row of rows) {
163927
- embeddings.set(row.memoryId, toFloat32Array(row.embedding));
164513
+ embeddings.set(row.memoryId, {
164514
+ embedding: toFloat32Array(row.embedding),
164515
+ modelId: row.modelId
164516
+ });
163928
164517
  }
163929
164518
  return embeddings;
163930
164519
  }
@@ -163987,13 +164576,13 @@ var init_embedding_cache = __esm(() => {
163987
164576
  });
163988
164577
 
163989
164578
  // src/features/magic-context/memory/normalize-hash.ts
163990
- import { createHash as createHash6 } from "node:crypto";
164579
+ import { createHash as createHash7 } from "node:crypto";
163991
164580
  function normalizeMemoryContent(content) {
163992
164581
  return content.toLowerCase().replace(/\s+/g, " ").trim();
163993
164582
  }
163994
164583
  function computeNormalizedHash(content) {
163995
164584
  const normalized = normalizeMemoryContent(content);
163996
- return createHash6("md5").update(normalized).digest("hex");
164585
+ return createHash7("md5").update(normalized).digest("hex");
163997
164586
  }
163998
164587
  var init_normalize_hash = () => {};
163999
164588
 
@@ -164107,8 +164696,8 @@ function getMemoriesByProjectStatement(db, statuses) {
164107
164696
  }
164108
164697
  let stmt = statements.get(db);
164109
164698
  if (!stmt) {
164110
- const placeholders = statuses.map(() => "?").join(", ");
164111
- 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`);
164699
+ const placeholders3 = statuses.map(() => "?").join(", ");
164700
+ 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`);
164112
164701
  statements.set(db, stmt);
164113
164702
  }
164114
164703
  return stmt;
@@ -164229,6 +164818,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
164229
164818
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
164230
164819
  return rows.map(toMemory);
164231
164820
  }
164821
+ function sqlPlaceholders(values) {
164822
+ return values.map(() => "?").join(", ");
164823
+ }
164824
+ function uniqueValues(values) {
164825
+ return [...new Set(values.filter((value) => value.length > 0))];
164826
+ }
164827
+ function buildWorkspaceMemorySqlFilter(args) {
164828
+ if (args.shareCategories === null || args.shareCategories === undefined) {
164829
+ return { clause: "", params: [], active: false };
164830
+ }
164831
+ const identities = uniqueValues(args.identities);
164832
+ const identitySet = new Set(identities);
164833
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
164834
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
164835
+ if (foreignIdentities.length === 0) {
164836
+ return { clause: "", params: [], active: false };
164837
+ }
164838
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
164839
+ const shareCategories = uniqueValues([...args.shareCategories]);
164840
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
164841
+ const predicates = [];
164842
+ const params = [];
164843
+ if (ownIdentities.length > 0) {
164844
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
164845
+ params.push(...ownIdentities);
164846
+ }
164847
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
164848
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
164849
+ params.push(...foreignIdentities, ...shareCategories);
164850
+ }
164851
+ if (predicates.length === 0) {
164852
+ return { clause: " AND 0 = 1", params: [], active: true };
164853
+ }
164854
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
164855
+ }
164856
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
164857
+ const identities = uniqueValues(projectPaths);
164858
+ if (identities.length === 0 || statuses.length === 0)
164859
+ return [];
164860
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164861
+ identities,
164862
+ ownIdentities,
164863
+ shareCategories
164864
+ });
164865
+ if (identities.length === 1 && !sharingFilter.active) {
164866
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
164867
+ }
164868
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164869
+ FROM memories
164870
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164871
+ AND status IN (${sqlPlaceholders(statuses)})
164872
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164873
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164874
+ return rows.map(toMemory);
164875
+ }
164876
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
164877
+ const identities = uniqueValues(projectPaths);
164878
+ if (identities.length === 0)
164879
+ return 0;
164880
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164881
+ identities,
164882
+ ownIdentities,
164883
+ shareCategories
164884
+ });
164885
+ if (identities.length === 1 && !sharingFilter.active) {
164886
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
164887
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
164888
+ }
164889
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
164890
+ FROM memories
164891
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
164892
+ return typeof row?.max_id === "number" ? row.max_id : 0;
164893
+ }
164894
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
164895
+ const identities = uniqueValues(projectPaths);
164896
+ if (identities.length === 0)
164897
+ return [];
164898
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164899
+ identities,
164900
+ ownIdentities,
164901
+ shareCategories
164902
+ });
164903
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164904
+ FROM memories
164905
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164906
+ AND id > ?
164907
+ AND status IN ('active', 'permanent')
164908
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164909
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164910
+ return rows.map(toMemory);
164911
+ }
164232
164912
  function getAllActiveMemoriesForMigration(db, projectPath) {
164233
164913
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
164234
164914
  return rows.map(toMemory);
@@ -164326,6 +165006,7 @@ function getMemoryCountsByStatus(db, projectPath) {
164326
165006
  }
164327
165007
  var COLUMN_MAP, MEMORY_CATEGORY_LOOKUP, MEMORY_STATUS_LOOKUP, MEMORY_SOURCE_TYPE_LOOKUP, VERIFICATION_STATUS_LOOKUP, insertMemoryStatements, getMemoryByHashStatements, getMemoryByIdStatements, activeMemoriesNoExpiryStatements, updateMemorySeenCountStatements, updateMemoryRetrievalCountStatements, updateMemoryStatusStatements, updateArchivedMemoryStatements, updateMemoryVerificationStatements, updateMemoryContentStatements, supersededMemoryStatements, mergeMemoryStatsStatements, deleteMemoryStatements, deleteMemoryEmbeddingStatements, deleteEmbeddingOnContentUpdateStatements, getMemoryCountStatements, getMemoryCountByProjectStatements, getMemoryCountsByStatusStatements, memoriesByProjectStatements, memoryImportanceColumnCache;
164328
165008
  var init_storage_memory = __esm(() => {
165009
+ init_constants();
164329
165010
  init_embedding_cache();
164330
165011
  init_normalize_hash();
164331
165012
  COLUMN_MAP = {
@@ -164431,8 +165112,8 @@ function getUserMemoryCandidates(db) {
164431
165112
  function deleteUserMemoryCandidates(db, ids) {
164432
165113
  if (ids.length === 0)
164433
165114
  return;
164434
- const placeholders = ids.map(() => "?").join(",");
164435
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
165115
+ const placeholders3 = ids.map(() => "?").join(",");
165116
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
164436
165117
  }
164437
165118
  function insertUserMemory(db, content, sourceCandidateIds) {
164438
165119
  const now = Date.now();
@@ -164465,6 +165146,444 @@ function parseUserMemoryRow(row) {
164465
165146
  };
164466
165147
  }
164467
165148
 
165149
+ // src/features/magic-context/compartment-chunk-embedding.ts
165150
+ import { createHash as createHash8 } from "node:crypto";
165151
+ function getLoadFtsRowsStatement(db) {
165152
+ let stmt = loadFtsRowsStatements.get(db);
165153
+ if (!stmt) {
165154
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
165155
+ FROM message_history_fts
165156
+ WHERE session_id = ?
165157
+ AND message_ordinal >= ?
165158
+ AND message_ordinal <= ?
165159
+ AND role IN ('user', 'assistant')
165160
+ ORDER BY message_ordinal ASC`);
165161
+ loadFtsRowsStatements.set(db, stmt);
165162
+ }
165163
+ return stmt;
165164
+ }
165165
+ function getExistingHashStatement(db, scopedToProject) {
165166
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
165167
+ let stmt = map2.get(db);
165168
+ if (!stmt) {
165169
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
165170
+ FROM compartment_chunk_embeddings
165171
+ WHERE compartment_id = ?
165172
+ AND model_id = ?
165173
+ ${scopedToProject ? "AND project_path = ?" : ""}
165174
+ ORDER BY window_index ASC`);
165175
+ map2.set(db, stmt);
165176
+ }
165177
+ return stmt;
165178
+ }
165179
+ function getDeleteByCompartmentStatement(db) {
165180
+ let stmt = deleteByCompartmentStatements.get(db);
165181
+ if (!stmt) {
165182
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
165183
+ deleteByCompartmentStatements.set(db, stmt);
165184
+ }
165185
+ return stmt;
165186
+ }
165187
+ function getInsertEmbeddingStatement(db) {
165188
+ let stmt = insertEmbeddingStatements.get(db);
165189
+ if (!stmt) {
165190
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
165191
+ compartment_id, session_id, project_path, harness, window_index,
165192
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
165193
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
165194
+ insertEmbeddingStatements.set(db, stmt);
165195
+ }
165196
+ return stmt;
165197
+ }
165198
+ function getDistinctModelStatement(db) {
165199
+ let stmt = distinctModelStatements.get(db);
165200
+ if (!stmt) {
165201
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
165202
+ FROM compartment_chunk_embeddings
165203
+ WHERE project_path = ?`);
165204
+ distinctModelStatements.set(db, stmt);
165205
+ }
165206
+ return stmt;
165207
+ }
165208
+ function getClearProjectStatement(db) {
165209
+ let stmt = clearProjectStatements.get(db);
165210
+ if (!stmt) {
165211
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
165212
+ clearProjectStatements.set(db, stmt);
165213
+ }
165214
+ return stmt;
165215
+ }
165216
+ function getClearProjectModelStatement(db) {
165217
+ let stmt = clearProjectModelStatements.get(db);
165218
+ if (!stmt) {
165219
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
165220
+ clearProjectModelStatements.set(db, stmt);
165221
+ }
165222
+ return stmt;
165223
+ }
165224
+ function getSearchRowsStatement(db, withModel) {
165225
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
165226
+ let stmt = map2.get(db);
165227
+ if (!stmt) {
165228
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
165229
+ e.session_id AS sessionId,
165230
+ c.title AS title,
165231
+ c.start_message AS compartmentStart,
165232
+ c.end_message AS compartmentEnd,
165233
+ e.window_index AS windowIndex,
165234
+ e.start_ordinal AS windowStart,
165235
+ e.end_ordinal AS windowEnd,
165236
+ e.chunk_hash AS chunkHash,
165237
+ e.model_id AS modelId,
165238
+ e.dims AS dims,
165239
+ e.vector AS vector
165240
+ FROM compartment_chunk_embeddings e
165241
+ JOIN compartments c ON c.id = e.compartment_id
165242
+ WHERE e.session_id = ?
165243
+ AND e.project_path = ?
165244
+ ${withModel ? "AND e.model_id = ?" : ""}
165245
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
165246
+ map2.set(db, stmt);
165247
+ }
165248
+ return stmt;
165249
+ }
165250
+ function isFinitePositiveInteger(value) {
165251
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
165252
+ }
165253
+ function normalizeCompartmentChunkMaxInputTokens(value) {
165254
+ if (!isFinitePositiveInteger(value)) {
165255
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
165256
+ }
165257
+ return Math.max(1, Math.floor(value));
165258
+ }
165259
+ function normalizeContent(text) {
165260
+ return text.replace(/\s+/g, " ").trim();
165261
+ }
165262
+ function formatOrdinalRange(start, end) {
165263
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
165264
+ }
165265
+ function rolePrefix(role) {
165266
+ if (role === "user")
165267
+ return "U";
165268
+ if (role === "assistant")
165269
+ return "A";
165270
+ return null;
165271
+ }
165272
+ function parseOrdinal(value) {
165273
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
165274
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
165275
+ }
165276
+ function parseCanonicalLineRange(line) {
165277
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
165278
+ if (!match)
165279
+ return null;
165280
+ const start = Number.parseInt(match[1], 10);
165281
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
165282
+ if (!Number.isFinite(start) || !Number.isFinite(end))
165283
+ return null;
165284
+ return { start, end };
165285
+ }
165286
+ function hashChunkText(text) {
165287
+ return createHash8("sha256").update(text).digest("hex");
165288
+ }
165289
+ function vectorBlob(vector) {
165290
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
165291
+ }
165292
+ function toFloat32Array2(blob) {
165293
+ if (blob instanceof Uint8Array) {
165294
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
165295
+ return new Float32Array(buffer2);
165296
+ }
165297
+ return new Float32Array(blob.slice(0));
165298
+ }
165299
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
165300
+ if (endOrdinal < startOrdinal)
165301
+ return "";
165302
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
165303
+ const lines = [];
165304
+ let current = null;
165305
+ const flush2 = () => {
165306
+ if (!current || current.parts.length === 0)
165307
+ return;
165308
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
165309
+ current = null;
165310
+ };
165311
+ for (const row of rows) {
165312
+ const ordinal = parseOrdinal(row.messageOrdinal);
165313
+ const prefix = rolePrefix(row.role);
165314
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
165315
+ if (ordinal === null || prefix === null || content.length === 0)
165316
+ continue;
165317
+ if (current && current.role === prefix) {
165318
+ current.end = ordinal;
165319
+ current.parts.push(content);
165320
+ continue;
165321
+ }
165322
+ flush2();
165323
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
165324
+ }
165325
+ flush2();
165326
+ return lines.join(`
165327
+ `);
165328
+ }
165329
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
165330
+ const lines = [];
165331
+ for (const rawLine of chunkText.split(/\r?\n/)) {
165332
+ const line = rawLine.trim();
165333
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
165334
+ if (!match)
165335
+ continue;
165336
+ const lineStart = Number.parseInt(match[2], 10);
165337
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
165338
+ if (startOrdinal != null && lineEnd < startOrdinal)
165339
+ continue;
165340
+ if (endOrdinal != null && lineStart > endOrdinal)
165341
+ continue;
165342
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
165343
+ const ordinalSpan = lineEnd - lineStart + 1;
165344
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
165345
+ if (ordinalSpan === rawParts.length) {
165346
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
165347
+ if (part.startsWith("TC:"))
165348
+ return false;
165349
+ if (startOrdinal != null && ordinal < startOrdinal)
165350
+ return false;
165351
+ if (endOrdinal != null && ordinal > endOrdinal)
165352
+ return false;
165353
+ return true;
165354
+ });
165355
+ if (retained.length === 0)
165356
+ continue;
165357
+ const retainedStart = retained[0].ordinal;
165358
+ const retainedEnd = retained[retained.length - 1].ordinal;
165359
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
165360
+ continue;
165361
+ }
165362
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
165363
+ if (parts.length === 0)
165364
+ continue;
165365
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
165366
+ }
165367
+ return lines.join(`
165368
+ `);
165369
+ }
165370
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
165371
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
165372
+ if (lines.length === 0 || endOrdinal < startOrdinal)
165373
+ return [];
165374
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
165375
+ const fullText = lines.join(`
165376
+ `);
165377
+ if (estimateTokens(fullText) <= normalizedMax) {
165378
+ return [
165379
+ {
165380
+ windowIndex: 0,
165381
+ startOrdinal,
165382
+ endOrdinal,
165383
+ text: fullText,
165384
+ chunkHash: hashChunkText(fullText)
165385
+ }
165386
+ ];
165387
+ }
165388
+ const windows = [];
165389
+ let currentLines = [];
165390
+ let currentStart = null;
165391
+ let currentEnd = null;
165392
+ let currentTokens = 0;
165393
+ const flush2 = () => {
165394
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
165395
+ return;
165396
+ const text = currentLines.join(`
165397
+ `);
165398
+ windows.push({
165399
+ windowIndex: windows.length + 1,
165400
+ startOrdinal: currentStart,
165401
+ endOrdinal: currentEnd,
165402
+ text,
165403
+ chunkHash: hashChunkText(text)
165404
+ });
165405
+ currentLines = [];
165406
+ currentStart = null;
165407
+ currentEnd = null;
165408
+ currentTokens = 0;
165409
+ };
165410
+ for (const line of lines) {
165411
+ const range = parseCanonicalLineRange(line);
165412
+ const lineStart = range?.start ?? startOrdinal;
165413
+ const lineEnd = range?.end ?? lineStart;
165414
+ const lineTokens = estimateTokens(line);
165415
+ if (currentLines.length > 0 && currentTokens + lineTokens > normalizedMax) {
165416
+ flush2();
165417
+ }
165418
+ if (currentLines.length === 0) {
165419
+ currentStart = lineStart;
165420
+ }
165421
+ currentLines.push(line);
165422
+ currentEnd = lineEnd;
165423
+ currentTokens += lineTokens;
165424
+ }
165425
+ flush2();
165426
+ return windows;
165427
+ }
165428
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
165429
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
165430
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
165431
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
165432
+ }
165433
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
165434
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
165435
+ if (existing.size !== windows.length)
165436
+ return false;
165437
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
165438
+ }
165439
+ function replaceCompartmentChunkEmbeddings(db, rows) {
165440
+ if (rows.length === 0)
165441
+ return;
165442
+ const compartmentId = rows[0].compartmentId;
165443
+ const now = Date.now();
165444
+ db.transaction(() => {
165445
+ getDeleteByCompartmentStatement(db).run(compartmentId);
165446
+ const insert = getInsertEmbeddingStatement(db);
165447
+ for (const row of rows) {
165448
+ 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);
165449
+ }
165450
+ })();
165451
+ }
165452
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
165453
+ const rows = getDistinctModelStatement(db).all(projectPath);
165454
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165455
+ }
165456
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
165457
+ if (modelId) {
165458
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
165459
+ }
165460
+ return getClearProjectStatement(db).run(projectPath).changes;
165461
+ }
165462
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
165463
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
165464
+ 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) => ({
165465
+ compartmentId: row.compartmentId,
165466
+ sessionId: row.sessionId,
165467
+ title: row.title,
165468
+ startOrdinal: row.compartmentStart,
165469
+ endOrdinal: row.compartmentEnd,
165470
+ windowIndex: row.windowIndex,
165471
+ windowStartOrdinal: row.windowStart,
165472
+ windowEndOrdinal: row.windowEnd,
165473
+ chunkHash: row.chunkHash,
165474
+ modelId: row.modelId,
165475
+ dims: row.dims,
165476
+ vector: toFloat32Array2(row.vector)
165477
+ }));
165478
+ }
165479
+ function mapBackfillCandidateRows(rows) {
165480
+ return rows.filter((row) => {
165481
+ if (row === null || typeof row !== "object")
165482
+ return false;
165483
+ const candidate = row;
165484
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
165485
+ }).map((row) => ({
165486
+ id: row.id,
165487
+ sessionId: row.sessionId,
165488
+ startMessage: row.startMessage,
165489
+ endMessage: row.endMessage,
165490
+ title: row.title
165491
+ }));
165492
+ }
165493
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
165494
+ if (excludeIds && excludeIds.length > 0) {
165495
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
165496
+ const stmt2 = db.prepare(`SELECT c.id AS id,
165497
+ c.session_id AS sessionId,
165498
+ c.start_message AS startMessage,
165499
+ c.end_message AS endMessage,
165500
+ c.title AS title
165501
+ FROM compartments c
165502
+ JOIN session_projects sp
165503
+ ON sp.session_id = c.session_id
165504
+ AND sp.harness = c.harness
165505
+ AND sp.project_path = ?
165506
+ WHERE c.session_id = ?
165507
+ AND c.start_message IS NOT NULL
165508
+ AND c.end_message IS NOT NULL
165509
+ AND c.id NOT IN (${placeholders3})
165510
+ AND NOT EXISTS (
165511
+ SELECT 1
165512
+ FROM compartment_chunk_embeddings current
165513
+ WHERE current.compartment_id = c.id
165514
+ AND current.project_path = ?
165515
+ AND current.model_id = ?
165516
+ )
165517
+ ORDER BY c.start_message ASC, c.id ASC
165518
+ LIMIT ?`);
165519
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
165520
+ return mapBackfillCandidateRows(rows2);
165521
+ }
165522
+ let stmt = sessionBackfillCandidateStatements.get(db);
165523
+ if (!stmt) {
165524
+ stmt = db.prepare(`SELECT c.id AS id,
165525
+ c.session_id AS sessionId,
165526
+ c.start_message AS startMessage,
165527
+ c.end_message AS endMessage,
165528
+ c.title AS title
165529
+ FROM compartments c
165530
+ JOIN session_projects sp
165531
+ ON sp.session_id = c.session_id
165532
+ AND sp.harness = c.harness
165533
+ AND sp.project_path = ?
165534
+ WHERE c.session_id = ?
165535
+ AND c.start_message IS NOT NULL
165536
+ AND c.end_message IS NOT NULL
165537
+ AND NOT EXISTS (
165538
+ SELECT 1
165539
+ FROM compartment_chunk_embeddings current
165540
+ WHERE current.compartment_id = c.id
165541
+ AND current.project_path = ?
165542
+ AND current.model_id = ?
165543
+ )
165544
+ ORDER BY c.start_message ASC, c.id ASC
165545
+ LIMIT ?`);
165546
+ sessionBackfillCandidateStatements.set(db, stmt);
165547
+ }
165548
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
165549
+ return mapBackfillCandidateRows(rows);
165550
+ }
165551
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
165552
+ const row = db.prepare(`SELECT COUNT(*) AS n
165553
+ FROM compartments c
165554
+ JOIN session_projects sp
165555
+ ON sp.session_id = c.session_id
165556
+ AND sp.harness = c.harness
165557
+ AND sp.project_path = ?
165558
+ WHERE c.session_id = ?
165559
+ AND c.start_message IS NOT NULL
165560
+ AND c.end_message IS NOT NULL
165561
+ AND NOT EXISTS (
165562
+ SELECT 1
165563
+ FROM compartment_chunk_embeddings current
165564
+ WHERE current.compartment_id = c.id
165565
+ AND current.project_path = ?
165566
+ AND current.model_id = ?
165567
+ )`).get(projectPath, sessionId, projectPath, modelId);
165568
+ return typeof row?.n === "number" ? row.n : 0;
165569
+ }
165570
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
165571
+ var init_compartment_chunk_embedding = __esm(() => {
165572
+ init_read_session_formatting();
165573
+ loadFtsRowsStatements = new WeakMap;
165574
+ existingHashStatements = new WeakMap;
165575
+ existingHashByProjectStatements = new WeakMap;
165576
+ deleteByCompartmentStatements = new WeakMap;
165577
+ insertEmbeddingStatements = new WeakMap;
165578
+ distinctModelStatements = new WeakMap;
165579
+ clearProjectStatements = new WeakMap;
165580
+ clearProjectModelStatements = new WeakMap;
165581
+ searchRowsStatements = new WeakMap;
165582
+ searchRowsByModelStatements = new WeakMap;
165583
+ backfillCandidateStatements = new WeakMap;
165584
+ sessionBackfillCandidateStatements = new WeakMap;
165585
+ });
165586
+
164468
165587
  // src/features/magic-context/memory/cosine-similarity.ts
164469
165588
  function cosineSimilarity(a, b) {
164470
165589
  if (a.length !== b.length) {
@@ -164622,19 +165741,19 @@ function isArrayLikeNumber(value) {
164622
165741
  }
164623
165742
  return arr.length === 0 || typeof arr[0] === "number";
164624
165743
  }
164625
- function toFloat32Array2(values) {
165744
+ function toFloat32Array3(values) {
164626
165745
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
164627
165746
  }
164628
165747
  function extractBatchEmbeddings(result, expectedCount) {
164629
165748
  const { data } = result;
164630
165749
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
164631
- return data.map((entry) => toFloat32Array2(entry));
165750
+ return data.map((entry) => toFloat32Array3(entry));
164632
165751
  }
164633
165752
  if (!isArrayLikeNumber(data)) {
164634
165753
  log("[magic-context] embedding batch returned unexpected data shape");
164635
165754
  return Array.from({ length: expectedCount }, () => null);
164636
165755
  }
164637
- const flatData = toFloat32Array2(data);
165756
+ const flatData = toFloat32Array3(data);
164638
165757
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
164639
165758
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
164640
165759
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -164649,6 +165768,7 @@ function extractBatchEmbeddings(result, expectedCount) {
164649
165768
 
164650
165769
  class LocalEmbeddingProvider {
164651
165770
  modelId;
165771
+ maxInputTokens;
164652
165772
  model;
164653
165773
  pipeline = null;
164654
165774
  initPromise = null;
@@ -164656,8 +165776,9 @@ class LocalEmbeddingProvider {
164656
165776
  disposing = false;
164657
165777
  disposePromise = null;
164658
165778
  inFlightWaiters = [];
164659
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
165779
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
164660
165780
  this.model = model;
165781
+ this.maxInputTokens = maxInputTokens;
164661
165782
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
164662
165783
  }
164663
165784
  async initialize() {
@@ -164917,6 +166038,7 @@ function normalizeEndpoint3(endpoint) {
164917
166038
 
164918
166039
  class OpenAICompatibleEmbeddingProvider {
164919
166040
  modelId;
166041
+ maxInputTokens;
164920
166042
  endpoint;
164921
166043
  model;
164922
166044
  apiKey;
@@ -164933,11 +166055,13 @@ class OpenAICompatibleEmbeddingProvider {
164933
166055
  this.apiKey = options.apiKey?.trim() ?? "";
164934
166056
  this.inputType = options.inputType?.trim() ?? "";
164935
166057
  this.truncate = options.truncate?.trim() ?? "";
166058
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
164936
166059
  this.modelId = getEmbeddingProviderIdentity({
164937
166060
  provider: "openai-compatible",
164938
166061
  endpoint: this.endpoint,
164939
166062
  model: this.model,
164940
- ...this.apiKey ? { api_key: this.apiKey } : {}
166063
+ ...this.apiKey ? { api_key: this.apiKey } : {},
166064
+ ...this.inputType ? { input_type: this.inputType } : {}
164941
166065
  });
164942
166066
  }
164943
166067
  async initialize() {
@@ -165181,12 +166305,12 @@ function getCountEmbeddedStatement(db) {
165181
166305
  }
165182
166306
  return stmt;
165183
166307
  }
165184
- function getClearProjectStatement(db) {
165185
- let stmt = clearProjectStatements.get(db);
166308
+ function getClearProjectStatement2(db) {
166309
+ let stmt = clearProjectStatements2.get(db);
165186
166310
  if (!stmt) {
165187
166311
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
165188
166312
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
165189
- clearProjectStatements.set(db, stmt);
166313
+ clearProjectStatements2.set(db, stmt);
165190
166314
  }
165191
166315
  return stmt;
165192
166316
  }
@@ -165222,19 +166346,19 @@ function countEmbeddedCommits(db, projectPath) {
165222
166346
  return row?.count ?? 0;
165223
166347
  }
165224
166348
  function clearProjectCommitEmbeddings(db, projectPath) {
165225
- return getClearProjectStatement(db).run(projectPath).changes;
166349
+ return getClearProjectStatement2(db).run(projectPath).changes;
165226
166350
  }
165227
166351
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
165228
166352
  const rows = getDistinctModelIdStatement(db).all(projectPath);
165229
166353
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165230
166354
  }
165231
- var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements, distinctModelIdStatements;
166355
+ var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements2, distinctModelIdStatements;
165232
166356
  var init_storage_git_commit_embeddings = __esm(() => {
165233
166357
  saveStatements = new WeakMap;
165234
166358
  loadProjectStatements = new WeakMap;
165235
166359
  loadUnembeddedStatements = new WeakMap;
165236
166360
  countEmbeddedStatements = new WeakMap;
165237
- clearProjectStatements = new WeakMap;
166361
+ clearProjectStatements2 = new WeakMap;
165238
166362
  distinctModelIdStatements = new WeakMap;
165239
166363
  });
165240
166364
 
@@ -165360,13 +166484,90 @@ var init_sweep_coordinator = __esm(() => {
165360
166484
  GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
165361
166485
  });
165362
166486
 
166487
+ // src/features/magic-context/session-project-storage.ts
166488
+ function getUpsertSessionProjectStatement(db) {
166489
+ let stmt = upsertSessionProjectStatements.get(db);
166490
+ if (!stmt) {
166491
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
166492
+ VALUES (?, ?, ?, ?)
166493
+ ON CONFLICT(session_id, harness) DO UPDATE SET
166494
+ project_path = excluded.project_path,
166495
+ updated_at = excluded.updated_at
166496
+ WHERE session_projects.project_path <> excluded.project_path`);
166497
+ upsertSessionProjectStatements.set(db, stmt);
166498
+ }
166499
+ return stmt;
166500
+ }
166501
+ function getRepairSessionChunkProjectStatement(db) {
166502
+ let stmt = repairSessionChunkProjectStatements.get(db);
166503
+ if (!stmt) {
166504
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166505
+ SET project_path = ?
166506
+ WHERE session_id = ?
166507
+ AND harness = ?
166508
+ AND project_path <> ?`);
166509
+ repairSessionChunkProjectStatements.set(db, stmt);
166510
+ }
166511
+ return stmt;
166512
+ }
166513
+ function getRepairProjectChunkProjectStatement(db) {
166514
+ let stmt = repairProjectChunkProjectStatements.get(db);
166515
+ if (!stmt) {
166516
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166517
+ SET project_path = (
166518
+ SELECT sp.project_path
166519
+ FROM session_projects sp
166520
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166521
+ AND sp.harness = compartment_chunk_embeddings.harness
166522
+ LIMIT 1
166523
+ )
166524
+ WHERE EXISTS (
166525
+ SELECT 1
166526
+ FROM session_projects sp
166527
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166528
+ AND sp.harness = compartment_chunk_embeddings.harness
166529
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
166530
+ AND (
166531
+ sp.project_path = ?
166532
+ OR compartment_chunk_embeddings.project_path = ?
166533
+ )
166534
+ )`);
166535
+ repairProjectChunkProjectStatements.set(db, stmt);
166536
+ }
166537
+ return stmt;
166538
+ }
166539
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
166540
+ if (!sessionId || !projectPath)
166541
+ return;
166542
+ const harness = getHarness();
166543
+ const now = Date.now();
166544
+ db.transaction(() => {
166545
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
166546
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
166547
+ })();
166548
+ }
166549
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
166550
+ if (!projectPath)
166551
+ return 0;
166552
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
166553
+ }
166554
+ var upsertSessionProjectStatements, repairSessionChunkProjectStatements, repairProjectChunkProjectStatements;
166555
+ var init_session_project_storage = __esm(() => {
166556
+ upsertSessionProjectStatements = new WeakMap;
166557
+ repairSessionChunkProjectStatements = new WeakMap;
166558
+ repairProjectChunkProjectStatements = new WeakMap;
166559
+ });
166560
+
165363
166561
  // src/features/magic-context/project-embedding-registry.ts
165364
- import { createHash as createHash7, randomUUID } from "node:crypto";
166562
+ import { createHash as createHash9, randomUUID } from "node:crypto";
165365
166563
  function resolveEmbeddingConfig(config2) {
165366
166564
  if (!config2 || config2.provider === "local") {
165367
166565
  return {
165368
166566
  provider: "local",
165369
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
166567
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
166568
+ ...config2?.max_input_tokens ? {
166569
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166570
+ } : {}
165370
166571
  };
165371
166572
  }
165372
166573
  if (config2.provider === "openai-compatible") {
@@ -165379,7 +166580,10 @@ function resolveEmbeddingConfig(config2) {
165379
166580
  endpoint: config2.endpoint.trim(),
165380
166581
  ...apiKey ? { api_key: apiKey } : {},
165381
166582
  ...inputType ? { input_type: inputType } : {},
165382
- ...truncate ? { truncate } : {}
166583
+ ...truncate ? { truncate } : {},
166584
+ ...config2.max_input_tokens ? {
166585
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166586
+ } : {}
165383
166587
  };
165384
166588
  }
165385
166589
  return { provider: "off" };
@@ -165397,10 +166601,11 @@ function createProvider(config2) {
165397
166601
  model: config2.model,
165398
166602
  apiKey: config2.api_key,
165399
166603
  inputType: config2.input_type,
165400
- truncate: config2.truncate
166604
+ truncate: config2.truncate,
166605
+ maxInputTokens: config2.max_input_tokens
165401
166606
  });
165402
166607
  }
165403
- return new LocalEmbeddingProvider(config2.model);
166608
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165404
166609
  }
165405
166610
  function stableStringify2(value) {
165406
166611
  if (Array.isArray(value)) {
@@ -165413,7 +166618,7 @@ function stableStringify2(value) {
165413
166618
  return JSON.stringify(value);
165414
166619
  }
165415
166620
  function sha256Prefix(value, length = 16) {
165416
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
166621
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
165417
166622
  }
165418
166623
  function getRuntimeFingerprint(config2) {
165419
166624
  if (config2.provider === "off") {
@@ -165421,6 +166626,18 @@ function getRuntimeFingerprint(config2) {
165421
166626
  }
165422
166627
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
165423
166628
  }
166629
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
166630
+ if (config2.provider === "off") {
166631
+ return OFF_PROVIDER_IDENTITY;
166632
+ }
166633
+ const chunkIdentity = {
166634
+ providerIdentity,
166635
+ chunkerVersion: 1,
166636
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
166637
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
166638
+ };
166639
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
166640
+ }
165424
166641
  function sameFeatures(a, b) {
165425
166642
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
165426
166643
  }
@@ -165437,7 +166654,8 @@ function snapshotFor(registration) {
165437
166654
  features: { ...registration.features },
165438
166655
  enabled,
165439
166656
  gitCommitEnabled,
165440
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
166657
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
166658
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
165441
166659
  };
165442
166660
  }
165443
166661
  function disposeProvider(provider) {
@@ -165457,7 +166675,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
165457
166675
  }
165458
166676
  return false;
165459
166677
  }
165460
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
166678
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
165461
166679
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
165462
166680
  return false;
165463
166681
  }
@@ -165478,6 +166696,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
165478
166696
  wiped = true;
165479
166697
  }
165480
166698
  }
166699
+ if (features.memoryEnabled) {
166700
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
166701
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
166702
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
166703
+ clearChunkEmbeddingsForProject(db, projectIdentity);
166704
+ wiped = true;
166705
+ }
166706
+ }
165481
166707
  })();
165482
166708
  return wiped;
165483
166709
  }
@@ -165485,10 +166711,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165485
166711
  const resolvedConfig = resolveEmbeddingConfig(config2);
165486
166712
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
165487
166713
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
166714
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
165488
166715
  const prior = projectRegistrations.get(projectIdentity);
165489
166716
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
165490
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
165491
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
166717
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
166718
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
165492
166719
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
165493
166720
  const registration = {
165494
166721
  projectIdentity,
@@ -165500,6 +166727,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165500
166727
  generation,
165501
166728
  features: { ...features },
165502
166729
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
166730
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
165503
166731
  observationMode: false
165504
166732
  };
165505
166733
  projectRegistrations.set(projectIdentity, registration);
@@ -165522,6 +166750,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
165522
166750
  generation,
165523
166751
  features: { memoryEnabled: false, gitCommitEnabled: false },
165524
166752
  modelId: "off",
166753
+ chunkModelId: "off",
165525
166754
  observationMode: true
165526
166755
  };
165527
166756
  projectRegistrations.set(projectIdentity, registration);
@@ -165532,6 +166761,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
165532
166761
  const registration = projectRegistrations.get(projectIdentity);
165533
166762
  return registration ? snapshotFor(registration) : null;
165534
166763
  }
166764
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
166765
+ const registration = projectRegistrations.get(projectIdentity);
166766
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
166767
+ }
166768
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
166769
+ const registration = projectRegistrations.get(projectIdentity);
166770
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
166771
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
166772
+ }
165535
166773
  function getOrCreateProjectProvider(registration) {
165536
166774
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
165537
166775
  return null;
@@ -165626,10 +166864,136 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
165626
166864
  return 0;
165627
166865
  }
165628
166866
  }
165629
- var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
166867
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
166868
+ const noWork = [];
166869
+ if (candidates.length === 0)
166870
+ return { embedded: 0, noWork };
166871
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
166872
+ const prepared = [];
166873
+ for (const candidate of candidates) {
166874
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
166875
+ if (canonicalText.length === 0) {
166876
+ noWork.push(candidate.id);
166877
+ continue;
166878
+ }
166879
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
166880
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
166881
+ noWork.push(candidate.id);
166882
+ continue;
166883
+ }
166884
+ prepared.push({ candidate, windows });
166885
+ }
166886
+ if (prepared.length === 0)
166887
+ return { embedded: 0, noWork };
166888
+ let embedded = 0;
166889
+ let i = 0;
166890
+ while (i < prepared.length) {
166891
+ if (signal?.aborted)
166892
+ break;
166893
+ const slice = [];
166894
+ let windowCount = 0;
166895
+ do {
166896
+ const item = prepared[i];
166897
+ slice.push(item);
166898
+ windowCount += item.windows.length;
166899
+ i += 1;
166900
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
166901
+ const texts = [];
166902
+ for (const item of slice)
166903
+ texts.push(...item.windows.map((w) => w.text));
166904
+ try {
166905
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
166906
+ if (!result)
166907
+ continue;
166908
+ if (signal?.aborted)
166909
+ break;
166910
+ let offset = 0;
166911
+ for (const item of slice) {
166912
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
166913
+ offset += item.windows.length;
166914
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
166915
+ continue;
166916
+ }
166917
+ const rows = item.windows.map((window, index) => ({
166918
+ compartmentId: item.candidate.id,
166919
+ sessionId: item.candidate.sessionId,
166920
+ projectPath: projectIdentity,
166921
+ window,
166922
+ modelId,
166923
+ vector: vectors[index]
166924
+ }));
166925
+ replaceCompartmentChunkEmbeddings(db, rows);
166926
+ embedded += 1;
166927
+ }
166928
+ } catch (error51) {
166929
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
166930
+ }
166931
+ }
166932
+ return { embedded, noWork };
166933
+ }
166934
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
166935
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
166936
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
166937
+ return { status: "disabled", embedded: 0, total: 0 };
166938
+ }
166939
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
166940
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
166941
+ if (total === 0)
166942
+ return { status: "nothing", embedded: 0, total: 0 };
166943
+ const holderId = `session-embed-${randomUUID()}`;
166944
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
166945
+ if (!lease2.acquired)
166946
+ return { status: "busy", embedded: 0, total };
166947
+ const renewal = setInterval(() => {
166948
+ try {
166949
+ renewGitSweepLease(db, projectIdentity, holderId);
166950
+ } catch {}
166951
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
166952
+ renewal.unref?.();
166953
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
166954
+ const skipIds = [];
166955
+ let embedded = 0;
166956
+ let aborted2 = false;
166957
+ let providerStalled = false;
166958
+ try {
166959
+ options?.onProgress?.({ embedded, total });
166960
+ for (;; ) {
166961
+ if (options?.signal?.aborted) {
166962
+ aborted2 = true;
166963
+ break;
166964
+ }
166965
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
166966
+ if (candidates.length === 0)
166967
+ break;
166968
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
166969
+ for (const id of noWork)
166970
+ skipIds.push(id);
166971
+ if (n === 0 && noWork.length === 0) {
166972
+ providerStalled = true;
166973
+ break;
166974
+ }
166975
+ embedded += n;
166976
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
166977
+ await new Promise((resolve6) => setTimeout(resolve6, 0));
166978
+ }
166979
+ } finally {
166980
+ clearInterval(renewal);
166981
+ releaseGitSweepLease(db, projectIdentity, holderId);
166982
+ }
166983
+ if (aborted2)
166984
+ return { status: "aborted", embedded, total };
166985
+ if (providerStalled) {
166986
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
166987
+ if (remaining > 0)
166988
+ return { status: "stalled", embedded, total, remaining };
166989
+ }
166990
+ return { status: "done", embedded, total };
166991
+ }
166992
+ var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, CHUNK_DRAIN_BATCH_SIZE = 8, MAX_WINDOWS_PER_EMBED_CALL = 16, SESSION_EMBED_LEASE_RENEWAL_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
165630
166993
  var init_project_embedding_registry = __esm(() => {
165631
166994
  init_magic_context();
165632
166995
  init_logger();
166996
+ init_compartment_chunk_embedding();
165633
166997
  init_storage_git_commit_embeddings();
165634
166998
  init_sweep_coordinator();
165635
166999
  init_embedding_cache();
@@ -165637,7 +167001,9 @@ var init_project_embedding_registry = __esm(() => {
165637
167001
  init_embedding_local();
165638
167002
  init_embedding_openai();
165639
167003
  init_storage_memory_embeddings();
167004
+ init_session_project_storage();
165640
167005
  SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167006
+ SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
165641
167007
  projectRegistrations = new Map;
165642
167008
  loadUnembeddedMemoriesStatements = new WeakMap;
165643
167009
  });
@@ -165653,10 +167019,11 @@ function createProvider2(config2) {
165653
167019
  model: config2.model,
165654
167020
  apiKey: config2.api_key,
165655
167021
  inputType: config2.input_type,
165656
- truncate: config2.truncate
167022
+ truncate: config2.truncate,
167023
+ maxInputTokens: config2.max_input_tokens
165657
167024
  });
165658
167025
  }
165659
- return new LocalEmbeddingProvider(config2.model);
167026
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165660
167027
  }
165661
167028
  function getOrCreateProvider() {
165662
167029
  if (provider) {
@@ -165682,6 +167049,7 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
165682
167049
  var init_embedding = __esm(() => {
165683
167050
  init_magic_context();
165684
167051
  init_logger();
167052
+ init_compartment_chunk_embedding();
165685
167053
  init_embedding_identity();
165686
167054
  init_embedding_local();
165687
167055
  init_embedding_openai();
@@ -165705,6 +167073,23 @@ function getSearchStatement(db) {
165705
167073
  }
165706
167074
  return stmt;
165707
167075
  }
167076
+ function getUnionSearchStatement(db, arity) {
167077
+ let statements = unionSearchStatements.get(arity);
167078
+ if (!statements) {
167079
+ statements = new WeakMap;
167080
+ unionSearchStatements.set(arity, statements);
167081
+ }
167082
+ let stmt = statements.get(db);
167083
+ if (!stmt) {
167084
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
167085
+ 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 ?`);
167086
+ statements.set(db, stmt);
167087
+ }
167088
+ return stmt;
167089
+ }
167090
+ function uniqueProjectPaths2(projectPaths) {
167091
+ return [...new Set(projectPaths.filter((path6) => path6.length > 0))];
167092
+ }
165708
167093
  function sanitizeFtsQuery(query) {
165709
167094
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
165710
167095
  if (tokens.length === 0)
@@ -165723,10 +167108,33 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
165723
167108
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
165724
167109
  return rows.map(toMemory);
165725
167110
  }
165726
- var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
167111
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
167112
+ const identities = uniqueProjectPaths2(projectPaths);
167113
+ if (identities.length === 0)
167114
+ return [];
167115
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
167116
+ identities,
167117
+ ownIdentities,
167118
+ shareCategories,
167119
+ tableName: "memories"
167120
+ });
167121
+ if (identities.length === 1 && !sharingFilter.active) {
167122
+ return searchMemoriesFTS(db, identities[0], query, limit);
167123
+ }
167124
+ const trimmedQuery = query.trim();
167125
+ if (trimmedQuery.length === 0 || limit <= 0)
167126
+ return [];
167127
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
167128
+ if (sanitized.length === 0)
167129
+ return [];
167130
+ 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);
167131
+ return rows.map(toMemory);
167132
+ }
167133
+ var DEFAULT_SEARCH_LIMIT = 10, searchStatements, unionSearchStatements;
165727
167134
  var init_storage_memory_fts = __esm(() => {
165728
167135
  init_storage_memory();
165729
167136
  searchStatements = new WeakMap;
167137
+ unionSearchStatements = new Map;
165730
167138
  });
165731
167139
 
165732
167140
  // src/shared/models-dev-cache.ts
@@ -165920,26 +167328,54 @@ var init_rpc_notifications = __esm(() => {
165920
167328
  });
165921
167329
 
165922
167330
  // src/features/magic-context/compartment-embedding.ts
165923
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
167331
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
165924
167332
  if (compartments.length === 0)
165925
167333
  return;
165926
- const update = db.prepare("UPDATE compartments SET p1_embedding = ?, p1_embedding_model_id = ? WHERE id = ?");
165927
- for (const c of compartments) {
165928
- if (!c.p1 || c.p1.length === 0)
165929
- continue;
167334
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
167335
+ for (const compartment of compartments) {
165930
167336
  try {
165931
- const result = await embedTextForProject(projectPath, c.p1);
165932
- if (result) {
165933
- const blob = Buffer.from(result.vector.buffer, result.vector.byteOffset, result.vector.byteLength);
165934
- update.run(blob, result.modelId, c.id);
167337
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
167338
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
167339
+ if (canonicalText.length === 0)
167340
+ continue;
167341
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
167342
+ if (windows.length === 0)
167343
+ continue;
167344
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
167345
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167346
+ continue;
167347
+ }
167348
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
167349
+ if (!result)
167350
+ continue;
167351
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167352
+ continue;
167353
+ }
167354
+ const rows = [];
167355
+ for (const [index, window] of windows.entries()) {
167356
+ const vector = result.vectors[index];
167357
+ if (!vector)
167358
+ continue;
167359
+ rows.push({
167360
+ compartmentId: compartment.id,
167361
+ sessionId,
167362
+ projectPath,
167363
+ window,
167364
+ modelId: currentModelId,
167365
+ vector
167366
+ });
167367
+ }
167368
+ if (rows.length === windows.length) {
167369
+ replaceCompartmentChunkEmbeddings(db, rows);
165935
167370
  }
165936
167371
  } catch (error51) {
165937
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
167372
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
165938
167373
  }
165939
167374
  }
165940
167375
  }
165941
167376
  var init_compartment_embedding = __esm(() => {
165942
167377
  init_logger();
167378
+ init_compartment_chunk_embedding();
165943
167379
  init_project_embedding_registry();
165944
167380
  });
165945
167381
 
@@ -167053,55 +168489,6 @@ var init_historian_state_file = __esm(() => {
167053
168489
  init_data_path();
167054
168490
  });
167055
168491
 
167056
- // src/features/magic-context/memory/constants.ts
167057
- var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
167058
- var init_constants = __esm(() => {
167059
- V2_MEMORY_CATEGORIES = [
167060
- "PROJECT_RULES",
167061
- "ARCHITECTURE",
167062
- "CONSTRAINTS",
167063
- "CONFIG_VALUES",
167064
- "NAMING"
167065
- ];
167066
- PROMOTABLE_CATEGORIES = [
167067
- "PROJECT_RULES",
167068
- "ARCHITECTURE",
167069
- "CONSTRAINTS",
167070
- "CONFIG_VALUES",
167071
- "NAMING",
167072
- "ARCHITECTURE_DECISIONS",
167073
- "CONFIG_DEFAULTS",
167074
- "USER_PREFERENCES",
167075
- "USER_DIRECTIVES",
167076
- "ENVIRONMENT",
167077
- "WORKFLOW_RULES",
167078
- "KNOWN_ISSUES"
167079
- ];
167080
- CATEGORY_PRIORITY = [
167081
- "PROJECT_RULES",
167082
- "ARCHITECTURE",
167083
- "CONSTRAINTS",
167084
- "CONFIG_VALUES",
167085
- "NAMING",
167086
- "USER_DIRECTIVES",
167087
- "USER_PREFERENCES",
167088
- "CONFIG_DEFAULTS",
167089
- "ARCHITECTURE_DECISIONS",
167090
- "ENVIRONMENT",
167091
- "WORKFLOW_RULES",
167092
- "KNOWN_ISSUES"
167093
- ];
167094
- MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
167095
- acc[category] = index;
167096
- return acc;
167097
- }, {});
167098
- MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
167099
- CATEGORY_DEFAULT_TTL = {
167100
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
167101
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
167102
- };
167103
- });
167104
-
167105
168492
  // src/features/magic-context/memory/embedding-backfill.ts
167106
168493
  async function ensureMemoryEmbeddings(args) {
167107
168494
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167125,7 +168512,7 @@ async function ensureMemoryEmbeddings(args) {
167125
168512
  continue;
167126
168513
  }
167127
168514
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
167128
- staged.set(memory.id, embedding);
168515
+ staged.set(memory.id, { embedding, modelId: result.modelId });
167129
168516
  }
167130
168517
  })();
167131
168518
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167922,6 +169309,71 @@ function lastCompartmentBoundaryId(compartments) {
167922
169309
  const last = compartments.at(-1);
167923
169310
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
167924
169311
  }
169312
+ function resolveWorkspaceRenderContext(args) {
169313
+ if (!args.projectPath) {
169314
+ return {
169315
+ identities: [],
169316
+ expandedIdentities: [],
169317
+ ownIdentities: [],
169318
+ shareCategories: null,
169319
+ namesByIdentity: new Map,
169320
+ canonicalIdentityByStoredPath: new Map,
169321
+ isWorkspaced: false
169322
+ };
169323
+ }
169324
+ const identitySet = args.workspaceIdentitySet ?? resolveWorkspaceIdentitySet(args.db, args.projectPath);
169325
+ const isWorkspaced = identitySet.identities.length > 1;
169326
+ const expanded = expandWorkspaceIdentitySetWithAliases(args.db, identitySet.identities);
169327
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
169328
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
169329
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === args.projectPath);
169330
+ if (ownIdentities.length === 0 && expandedIdentities.includes(args.projectPath)) {
169331
+ ownIdentities = [args.projectPath];
169332
+ }
169333
+ return {
169334
+ identities: identitySet.identities,
169335
+ expandedIdentities,
169336
+ ownIdentities,
169337
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(args.db, args.projectPath) : null,
169338
+ namesByIdentity: identitySet.namesByIdentity,
169339
+ canonicalIdentityByStoredPath,
169340
+ isWorkspaced
169341
+ };
169342
+ }
169343
+ function sourceNamesForMemories(args) {
169344
+ if (!args.projectPath || !args.workspace.isWorkspaced)
169345
+ return;
169346
+ const names = new Map;
169347
+ for (const memory of args.memories) {
169348
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
169349
+ if (source)
169350
+ names.set(memory.id, source);
169351
+ }
169352
+ return names.size > 0 ? names : undefined;
169353
+ }
169354
+ function memoryCanonicalIdentity(memory, workspace) {
169355
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
169356
+ }
169357
+ function memorySelectionOrder(left, right) {
169358
+ if (left.status === "permanent" && right.status !== "permanent")
169359
+ return -1;
169360
+ if (right.status === "permanent" && left.status !== "permanent")
169361
+ return 1;
169362
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
169363
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
169364
+ const importanceDiff = rightImportance - leftImportance;
169365
+ if (importanceDiff !== 0)
169366
+ return importanceDiff;
169367
+ return left.id - right.id;
169368
+ }
169369
+ function memoryRenderOrder(left, right) {
169370
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169371
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169372
+ const categoryDiff = aPriority - bPriority;
169373
+ if (categoryDiff !== 0)
169374
+ return categoryDiff;
169375
+ return left.id - right.id;
169376
+ }
167925
169377
  function cachedStatement(cache, db, sql) {
167926
169378
  let stmt = cache.get(db);
167927
169379
  if (!stmt) {
@@ -167964,13 +169416,19 @@ function getGlobalUserProfileVersion(db) {
167964
169416
  function readCurrentM0SnapshotMarkers(args) {
167965
169417
  const projectDirectory = args.projectDirectory ?? args.projectPath ?? "";
167966
169418
  const hard = args.hardSignals ?? EMPTY_HARD_SIGNALS;
169419
+ const workspace = resolveWorkspaceRenderContext({
169420
+ db: args.db,
169421
+ projectPath: args.projectPath,
169422
+ workspaceIdentitySet: args.workspaceIdentitySet
169423
+ });
167967
169424
  return {
167968
169425
  projectMemoryEpoch: getProjectMemoryEpoch(args.db, args.projectPath),
169426
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(args.db, workspace.identities) : null,
167969
169427
  projectUserProfileVersion: getGlobalUserProfileVersion(args.db),
167970
169428
  maxCompartmentSeq: getMaxCompartmentSeq(args.db, args.sessionId),
167971
- maxMemoryId: getMaxMemoryId(args.db, args.projectPath),
169429
+ maxMemoryId: workspace.isWorkspaced ? getMaxMemoryIdForProjects(args.db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryId(args.db, args.projectPath),
167972
169430
  maxMutationId: getMaxM0MutationId(args.db, args.sessionId) ?? 0,
167973
- maxMemoryMutationId: args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
169431
+ maxMemoryMutationId: workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(args.db, workspace.expandedIdentities) ?? 0 : args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
167974
169432
  projectDocsHash: projectDirectory ? computeProjectDocsHash(projectDirectory) : "",
167975
169433
  materializedAt: Date.now(),
167976
169434
  sessionFactsVersion: getSessionFactsVersion(args.db, args.sessionId),
@@ -167998,6 +169456,7 @@ function snapshotMarkersFromCachedM0(state) {
167998
169456
  return null;
167999
169457
  return {
168000
169458
  projectMemoryEpoch: state.cachedM0ProjectMemoryEpoch,
169459
+ workspaceFingerprint: state.cachedM0WorkspaceFingerprint,
168001
169460
  projectUserProfileVersion: state.cachedM0ProjectUserProfileVersion,
168002
169461
  maxCompartmentSeq: state.cachedM0MaxCompartmentSeq,
168003
169462
  maxMemoryId: state.cachedM0MaxMemoryId,
@@ -168027,35 +169486,27 @@ function mustMaterialize(args) {
168027
169486
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (args.state.cachedM0MaterializedAt ?? 0)) {
168028
169487
  return { value: true, reason: "ttl_idle" };
168029
169488
  }
168030
- if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
169489
+ if (current.workspaceFingerprint !== null || (args.state.cachedM0WorkspaceFingerprint ?? null) !== null) {
169490
+ if ((args.state.cachedM0WorkspaceFingerprint ?? null) !== current.workspaceFingerprint) {
169491
+ return { value: true, reason: "project_memory_epoch" };
169492
+ }
169493
+ } else if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
168031
169494
  return { value: true, reason: "project_memory_epoch" };
168032
169495
  }
168033
169496
  if (args.state.cachedM0MaxMutationId !== current.maxMutationId) {
168034
169497
  return { value: true, reason: "max_mutation_id" };
168035
169498
  }
168036
- if ((args.state.cachedM0ProjectDocsHash ?? "") !== current.projectDocsHash) {
168037
- return { value: true, reason: "project_docs_hash" };
168038
- }
168039
169499
  if ((args.state.cachedM0UpgradeState ?? null) !== current.upgradeState) {
168040
169500
  return { value: true, reason: "upgrade_state" };
168041
169501
  }
168042
169502
  return { value: false, reason: null };
168043
169503
  }
168044
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168045
- const selectionOrder = [...memories].sort((a, b) => {
168046
- if (a.status === "permanent" && b.status !== "permanent")
168047
- return -1;
168048
- if (b.status === "permanent" && a.status !== "permanent")
168049
- return 1;
168050
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
168051
- if (importanceDiff !== 0)
168052
- return importanceDiff;
168053
- return a.id - b.id;
168054
- });
169504
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
169505
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
168055
169506
  const selected = [];
168056
169507
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
168057
169508
  for (const memory of selectionOrder) {
168058
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
169509
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168059
169510
  if (usedTokens + memoryTokens > budgetTokens)
168060
169511
  continue;
168061
169512
  selected.push(memory);
@@ -168064,16 +169515,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168064
169515
  if (selected.length < memories.length) {
168065
169516
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
168066
169517
  }
168067
- const renderOrder = [...selected].sort((a, b) => {
168068
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168069
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168070
- const categoryDiff = aPriority - bPriority;
168071
- if (categoryDiff !== 0)
168072
- return categoryDiff;
168073
- return a.id - b.id;
168074
- });
169518
+ const renderOrder = [...selected].sort(memoryRenderOrder);
168075
169519
  return { selected, renderOrder };
168076
169520
  }
169521
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
169522
+ if (!workspace.isWorkspaced) {
169523
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
169524
+ }
169525
+ const selected = [];
169526
+ const selectedIds = new Set;
169527
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
169528
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
169529
+ const trySelect = (memory) => {
169530
+ if (selectedIds.has(memory.id))
169531
+ return false;
169532
+ const tokens = tokenCost(memory);
169533
+ if (usedTokens + tokens > budgetTokens)
169534
+ return false;
169535
+ selected.push(memory);
169536
+ selectedIds.add(memory.id);
169537
+ usedTokens += tokens;
169538
+ return true;
169539
+ };
169540
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
169541
+ trySelect(memory);
169542
+ }
169543
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
169544
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
169545
+ const byIdentity = new Map;
169546
+ for (const memory of memories) {
169547
+ if (memory.status === "permanent")
169548
+ continue;
169549
+ const identity = memoryCanonicalIdentity(memory, workspace);
169550
+ if (!identity)
169551
+ continue;
169552
+ const list = byIdentity.get(identity) ?? [];
169553
+ list.push(memory);
169554
+ byIdentity.set(identity, list);
169555
+ }
169556
+ for (const identity of workspace.identities) {
169557
+ let memberTokens = 0;
169558
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
169559
+ for (const memory of candidates) {
169560
+ if (selectedIds.has(memory.id))
169561
+ continue;
169562
+ const tokens = tokenCost(memory);
169563
+ if (memberTokens + tokens > floorTokens)
169564
+ continue;
169565
+ if (usedTokens + tokens > budgetTokens)
169566
+ continue;
169567
+ selected.push(memory);
169568
+ selectedIds.add(memory.id);
169569
+ usedTokens += tokens;
169570
+ memberTokens += tokens;
169571
+ }
169572
+ }
169573
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
169574
+ for (const memory of remaining) {
169575
+ trySelect(memory);
169576
+ }
169577
+ if (selected.length < memories.length) {
169578
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
169579
+ }
169580
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
169581
+ }
168077
169582
  function safeGetActiveUserMemories(db) {
168078
169583
  try {
168079
169584
  return getActiveUserMemories(db);
@@ -168149,15 +169654,16 @@ function readNewMemoriesForM1(db, projectPath, afterId, expiryCutoff) {
168149
169654
  ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(projectPath, afterId, expiryCutoff).filter(isMemoryRow);
168150
169655
  return rows.map((row) => ({ ...row }));
168151
169656
  }
168152
- function renderMemoryLineV2(memory) {
168153
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
169657
+ function renderMemoryLineV2(memory, sourceName) {
169658
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
169659
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
168154
169660
  }
168155
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
169661
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
168156
169662
  if (memories.length === 0)
168157
169663
  return "";
168158
169664
  const lines = [`<${wrapper}>`];
168159
169665
  for (const memory of memories) {
168160
- lines.push(renderMemoryLineV2(memory));
169666
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168161
169667
  }
168162
169668
  lines.push(`</${wrapper}>`);
168163
169669
  return lines.join(`
@@ -168196,7 +169702,7 @@ function renderM0(args) {
168196
169702
  sections.push(sessionHistory.length > 0 ? `<session-history>
168197
169703
  ${sessionHistory}
168198
169704
  </session-history>` : M0_EMPTY_BODY);
168199
- const memoriesBlock = renderMemoryBlockV2(args.memories);
169705
+ const memoriesBlock = renderMemoryBlockV2(args.memories, "project-memory", args.memoryRenderOptions);
168200
169706
  if (memoriesBlock)
168201
169707
  sections.push(memoriesBlock);
168202
169708
  return sections.join(`
@@ -168208,6 +169714,7 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
168208
169714
  if (m1Bytes)
168209
169715
  state.cachedM1Bytes = m1Bytes;
168210
169716
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
169717
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168211
169718
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168212
169719
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168213
169720
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168233,24 +169740,38 @@ function materializeM0(options) {
168233
169740
  let facts = [];
168234
169741
  let memories = [];
168235
169742
  let userMemories = [];
169743
+ let workspace = resolveWorkspaceRenderContext({
169744
+ db: options.db,
169745
+ projectPath,
169746
+ workspaceIdentitySet: options.workspaceIdentitySet
169747
+ });
168236
169748
  let docs = {
168237
169749
  renderedBlock: "",
168238
169750
  canonicalHash: ""
168239
169751
  };
168240
169752
  options.db.exec("BEGIN");
168241
169753
  try {
169754
+ workspace = resolveWorkspaceRenderContext({
169755
+ db: options.db,
169756
+ projectPath,
169757
+ workspaceIdentitySet: options.workspaceIdentitySet
169758
+ });
168242
169759
  snapshotMarkers = readCurrentM0SnapshotMarkers({
168243
169760
  db: options.db,
168244
169761
  sessionId: options.sessionId,
168245
169762
  projectPath,
168246
169763
  projectDirectory,
168247
- hardSignals: options.hardSignals
169764
+ hardSignals: options.hardSignals,
169765
+ workspaceIdentitySet: {
169766
+ identities: workspace.identities,
169767
+ namesByIdentity: workspace.namesByIdentity
169768
+ }
168248
169769
  });
168249
169770
  docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168250
169771
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168251
169772
  compartments = readM0Compartments(options.db, options.sessionId);
168252
169773
  facts = [];
168253
- memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
169774
+ memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
168254
169775
  userMemories = safeGetActiveUserMemories(options.db);
168255
169776
  options.db.exec("COMMIT");
168256
169777
  } catch (error51) {
@@ -168260,7 +169781,14 @@ function materializeM0(options) {
168260
169781
  throw error51;
168261
169782
  }
168262
169783
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168263
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
169784
+ const memoryRenderOptions = {
169785
+ sourceNameByMemoryId: sourceNamesForMemories({
169786
+ memories,
169787
+ projectPath,
169788
+ workspace
169789
+ })
169790
+ };
169791
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168264
169792
  let decayPressureMultiplier = 1;
168265
169793
  let m0Text = renderM0({
168266
169794
  projectDocs: docs.renderedBlock,
@@ -168268,6 +169796,7 @@ function materializeM0(options) {
168268
169796
  compartments,
168269
169797
  memories: trimmed.renderOrder,
168270
169798
  facts,
169799
+ memoryRenderOptions,
168271
169800
  historyBudgetTokens: options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS,
168272
169801
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168273
169802
  decayPressureMultiplier
@@ -168282,6 +169811,7 @@ function materializeM0(options) {
168282
169811
  compartments,
168283
169812
  memories: trimmed.renderOrder,
168284
169813
  facts,
169814
+ memoryRenderOptions,
168285
169815
  historyBudgetTokens: budget,
168286
169816
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168287
169817
  decayPressureMultiplier
@@ -168300,13 +169830,19 @@ function materializeM0(options) {
168300
169830
  let m1Bytes = Buffer4.from(m1Text, "utf8");
168301
169831
  options.db.exec("BEGIN IMMEDIATE");
168302
169832
  try {
169833
+ const currentWorkspace = resolveWorkspaceRenderContext({
169834
+ db: options.db,
169835
+ projectPath,
169836
+ workspaceIdentitySet: options.workspaceIdentitySet
169837
+ });
168303
169838
  const current = {
168304
169839
  projectMemoryEpoch: getProjectMemoryEpoch(options.db, projectPath),
169840
+ workspaceFingerprint: currentWorkspace.isWorkspaced ? computeWorkspaceEpochFingerprint(options.db, currentWorkspace.identities) : null,
168305
169841
  projectUserProfileVersion: getGlobalUserProfileVersion(options.db),
168306
169842
  maxCompartmentSeq: getMaxCompartmentSeq(options.db, options.sessionId),
168307
- maxMemoryId: getMaxMemoryId(options.db, projectPath),
169843
+ maxMemoryId: currentWorkspace.isWorkspaced ? getMaxMemoryIdForProjects(options.db, currentWorkspace.expandedIdentities, currentWorkspace.ownIdentities, currentWorkspace.shareCategories) : getMaxMemoryId(options.db, projectPath),
168308
169844
  maxMutationId: getMaxM0MutationId(options.db, options.sessionId) ?? 0,
168309
- maxMemoryMutationId: projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
169845
+ maxMemoryMutationId: currentWorkspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(options.db, currentWorkspace.expandedIdentities) ?? 0 : projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
168310
169846
  projectDocsHash: phase3ProjectDocsHash,
168311
169847
  materializedAt: Date.now(),
168312
169848
  sessionFactsVersion: getSessionFactsVersion(options.db, options.sessionId),
@@ -168314,17 +169850,26 @@ function materializeM0(options) {
168314
169850
  systemHash: snapshotMarkers.systemHash,
168315
169851
  modelKey: snapshotMarkers.modelKey
168316
169852
  };
168317
- 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;
169853
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
169854
+ 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;
168318
169855
  if (stale) {
168319
169856
  options.db.exec("ROLLBACK");
168320
169857
  throw new MaterializeContentionError({ reason: "snapshot changed before Phase 3" });
168321
169858
  }
168322
- const m1Render = renderM1WithMetadata({ ...options, preRenderedKeyFilesBlock }, snapshotMarkers, renderedMemoryIds);
169859
+ const m1Render = renderM1WithMetadata({
169860
+ ...options,
169861
+ preRenderedKeyFilesBlock,
169862
+ workspaceIdentitySet: {
169863
+ identities: workspace.identities,
169864
+ namesByIdentity: workspace.namesByIdentity
169865
+ }
169866
+ }, snapshotMarkers, renderedMemoryIds);
168323
169867
  m1Text = m1Render.text;
168324
169868
  m1Bytes = Buffer4.from(m1Text, "utf8");
168325
169869
  persistCachedM0(options.db, options.sessionId, {
168326
169870
  m0Bytes,
168327
169871
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
169872
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
168328
169873
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
168329
169874
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
168330
169875
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -168387,7 +169932,7 @@ function renderMemoryUpdatesBlock(args) {
168387
169932
  return { block: "", count: 0 };
168388
169933
  }
168389
169934
  const renderedIds = new Set(args.renderedMemoryIds);
168390
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
169935
+ const mutations = args.workspace.isWorkspaced ? getMemoryMutationsForRenderByProjects(args.db, args.workspace.expandedIdentities, args.afterId, args.renderedMemoryIds) : getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
168391
169936
  if (mutations.length === 0)
168392
169937
  return { block: "", count: 0 };
168393
169938
  const lines = ["These memories changed since the snapshot below — trust these:"];
@@ -168419,12 +169964,18 @@ function renderM1WithMetadata(options, markers, renderedMemoryIds) {
168419
169964
  throw new RenderM1InvalidMarkersError(options.sessionId);
168420
169965
  }
168421
169966
  const blocks = [];
169967
+ const workspace = resolveWorkspaceRenderContext({
169968
+ db: options.db,
169969
+ projectPath: options.projectPath,
169970
+ workspaceIdentitySet: options.workspaceIdentitySet
169971
+ });
168422
169972
  const keyFiles = renderedKeyFilesBlock(options);
168423
169973
  if (keyFiles)
168424
169974
  blocks.push(keyFiles);
168425
169975
  const memoryUpdates = renderMemoryUpdatesBlock({
168426
169976
  db: options.db,
168427
169977
  projectPath: options.projectPath,
169978
+ workspace,
168428
169979
  afterId: markers.maxMemoryMutationId,
168429
169980
  renderedMemoryIds
168430
169981
  });
@@ -168438,9 +169989,16 @@ ${newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).
168438
169989
  `)}
168439
169990
  </new-compartments>`);
168440
169991
  }
168441
- const newMemories = readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
168442
- const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25))).renderOrder;
168443
- const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories");
169992
+ const newMemories = workspace.isWorkspaced ? readNewMemoriesForM1Union(options.db, workspace.expandedIdentities, markers.maxMemoryId, markers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
169993
+ const newMemoryRenderOptions = {
169994
+ sourceNameByMemoryId: sourceNamesForMemories({
169995
+ memories: newMemories,
169996
+ projectPath: options.projectPath,
169997
+ workspace
169998
+ })
169999
+ };
170000
+ const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25)), newMemoryRenderOptions).renderOrder;
170001
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", newMemoryRenderOptions);
168444
170002
  if (newMemoriesBlock)
168445
170003
  blocks.push(newMemoriesBlock);
168446
170004
  const currentUserProfileVersion = getGlobalUserProfileVersion(options.db);
@@ -168488,6 +170046,7 @@ function parseMemoryBlockIds(raw) {
168488
170046
  function readCachedM0M1Row(db, sessionId) {
168489
170047
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
168490
170048
  cached_m0_project_memory_epoch,
170049
+ cached_m0_workspace_fingerprint,
168491
170050
  cached_m0_project_user_profile_version,
168492
170051
  cached_m0_max_compartment_seq,
168493
170052
  cached_m0_max_memory_id,
@@ -168522,6 +170081,7 @@ function markersFromCachedRow(row) {
168522
170081
  return null;
168523
170082
  return {
168524
170083
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
170084
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
168525
170085
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
168526
170086
  maxCompartmentSeq: row.cached_m0_max_compartment_seq,
168527
170087
  maxMemoryId: row.cached_m0_max_memory_id,
@@ -168536,7 +170096,7 @@ function markersFromCachedRow(row) {
168536
170096
  };
168537
170097
  }
168538
170098
  function cachedRowMatchesState(row, state) {
168539
- return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && (row.cached_m0_project_docs_hash ?? "") === (state.cachedM0ProjectDocsHash ?? "") && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
170099
+ return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && (row.cached_m0_workspace_fingerprint ?? null) === (state.cachedM0WorkspaceFingerprint ?? null) && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
168540
170100
  }
168541
170101
  function applyCachedRowToState(state, row) {
168542
170102
  const markers = markersFromCachedRow(row);
@@ -168546,6 +170106,7 @@ function applyCachedRowToState(state, row) {
168546
170106
  state.cachedM0Bytes = toBuffer(row.cached_m0_bytes);
168547
170107
  state.cachedM1Bytes = toBuffer(row.cached_m1_bytes);
168548
170108
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
170109
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168549
170110
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168550
170111
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168551
170112
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168609,20 +170170,36 @@ function prependM0M1Messages(sessionId, messages, m0Text, m1Text) {
168609
170170
  function renderFreshM0NonPersisted(options) {
168610
170171
  const projectPath = options.projectPath;
168611
170172
  const projectDirectory = options.projectDirectory;
170173
+ const workspace = resolveWorkspaceRenderContext({
170174
+ db: options.db,
170175
+ projectPath,
170176
+ workspaceIdentitySet: options.workspaceIdentitySet
170177
+ });
168612
170178
  const snapshotMarkers = readCurrentM0SnapshotMarkers({
168613
170179
  db: options.db,
168614
170180
  sessionId: options.sessionId,
168615
170181
  projectPath,
168616
- projectDirectory
170182
+ projectDirectory,
170183
+ workspaceIdentitySet: {
170184
+ identities: workspace.identities,
170185
+ namesByIdentity: workspace.namesByIdentity
170186
+ }
168617
170187
  });
168618
170188
  const docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168619
170189
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168620
170190
  snapshotMarkers.materializedAt = options.state.cachedM0MaterializedAt ?? 0;
168621
170191
  const compartments = readM0Compartments(options.db, options.sessionId);
168622
- const memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
170192
+ const memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], snapshotMarkers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
168623
170193
  const userMemories = safeGetActiveUserMemories(options.db);
168624
170194
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168625
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
170195
+ const memoryRenderOptions = {
170196
+ sourceNameByMemoryId: sourceNamesForMemories({
170197
+ memories,
170198
+ projectPath,
170199
+ workspace
170200
+ })
170201
+ };
170202
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168626
170203
  const budget = options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
168627
170204
  let decayPressureMultiplier = 1;
168628
170205
  let m0Text = renderM0({
@@ -168631,6 +170208,7 @@ function renderFreshM0NonPersisted(options) {
168631
170208
  compartments,
168632
170209
  memories: trimmed.renderOrder,
168633
170210
  facts: [],
170211
+ memoryRenderOptions,
168634
170212
  historyBudgetTokens: budget,
168635
170213
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168636
170214
  decayPressureMultiplier
@@ -168644,6 +170222,7 @@ function renderFreshM0NonPersisted(options) {
168644
170222
  compartments,
168645
170223
  memories: trimmed.renderOrder,
168646
170224
  facts: [],
170225
+ memoryRenderOptions,
168647
170226
  historyBudgetTokens: budget,
168648
170227
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168649
170228
  decayPressureMultiplier
@@ -168659,6 +170238,12 @@ function renderFreshM0NonPersisted(options) {
168659
170238
  };
168660
170239
  }
168661
170240
  function injectM0M1(options) {
170241
+ if (!options.workspaceIdentitySet && options.projectPath) {
170242
+ options = {
170243
+ ...options,
170244
+ workspaceIdentitySet: resolveWorkspaceIdentitySet(options.db, options.projectPath)
170245
+ };
170246
+ }
168662
170247
  const skipped = {
168663
170248
  injected: false,
168664
170249
  m0RematerializedThisPass: false,
@@ -168675,7 +170260,8 @@ function injectM0M1(options) {
168675
170260
  state: options.state,
168676
170261
  projectPath: options.projectPath,
168677
170262
  projectDirectory: options.projectDirectory,
168678
- hardSignals: options.hardSignals
170263
+ hardSignals: options.hardSignals,
170264
+ workspaceIdentitySet: options.workspaceIdentitySet
168679
170265
  });
168680
170266
  let rematerialized = false;
168681
170267
  let contentionExhausted = false;
@@ -168769,6 +170355,7 @@ var init_inject_compartments = __esm(async () => {
168769
170355
  init_compartment_storage();
168770
170356
  init_constants();
168771
170357
  init_storage_memory();
170358
+ init_workspaces();
168772
170359
  init_logger();
168773
170360
  init_decay_render();
168774
170361
  init_key_files_block();
@@ -171908,18 +173495,38 @@ async function runCompartmentAgent(deps) {
171908
173495
  return;
171909
173496
  }
171910
173497
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
171911
- const boundarySnapshot = deps.boundarySnapshot ?? null;
173498
+ let boundarySnapshot = deps.boundarySnapshot ?? null;
171912
173499
  if (!boundarySnapshot) {
171913
173500
  telemetry.failureReason = "missing protected-tail boundary snapshot";
171914
173501
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from trigger decision");
171915
173502
  rollbackDrainReservation();
171916
173503
  return;
171917
173504
  }
171918
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
173505
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
171919
173506
  db,
171920
173507
  snapshot: boundarySnapshot,
171921
173508
  currentContextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit
171922
173509
  }) : { ok: true };
173510
+ if (!validation.ok && validation.reason === "stale_snapshot") {
173511
+ const refreshed = resolveOpenCodeProtectedTailBoundary({
173512
+ db,
173513
+ sessionId,
173514
+ mode: "incremental-runner",
173515
+ contextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit,
173516
+ executeThresholdPercentage: boundarySnapshot.executeThresholdPercentage,
173517
+ usage: {
173518
+ percentage: boundarySnapshot.usagePercentage,
173519
+ inputTokens: boundarySnapshot.usageInputTokens
173520
+ },
173521
+ usageSource: boundarySnapshot.usageSource,
173522
+ emergencyTailScale: boundarySnapshot.emergencyTailScale
173523
+ });
173524
+ if (hasRunnableCompartmentWindow(refreshed)) {
173525
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
173526
+ boundarySnapshot = refreshed;
173527
+ validation = { ok: true };
173528
+ }
173529
+ }
171923
173530
  if (!validation.ok) {
171924
173531
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
171925
173532
  telemetry.status = "noop";
@@ -171988,6 +173595,7 @@ async function runCompartmentAgent(deps) {
171988
173595
  rollbackDrainReservation();
171989
173596
  return;
171990
173597
  }
173598
+ deps.onHistorianRunStarted?.();
171991
173599
  const projectPath = resolveProjectIdentity(directory ?? process.cwd());
171992
173600
  const memories = getMemoriesByProject(db, projectPath, ["active", "permanent"]);
171993
173601
  const projectMemory = renderMemoryBlock(memories) ?? "";
@@ -172120,8 +173728,13 @@ ${chunkText}`,
172120
173728
  }
172121
173729
  if (embeddingActive) {
172122
173730
  const projectIdentity = resolveProjectIdentity(promotionDirectory);
172123
- const toEmbed = persistedCompartments.map((c, i) => ({ id: persistedIds[i], p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172124
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173731
+ const chunksToEmbed = persistedCompartments.map((c, i) => ({
173732
+ id: persistedIds[i],
173733
+ startMessage: c.startMessage,
173734
+ endMessage: c.endMessage,
173735
+ sourceChunkText: chunk.text
173736
+ })).filter((c) => typeof c.id === "number");
173737
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172125
173738
  }
172126
173739
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
172127
173740
  deps.onCompartmentStatePublished?.(sessionId);
@@ -172352,8 +173965,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
172352
173965
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172353
173966
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172354
173967
  const liveCompartments = getCompartments(db, sessionId);
172355
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172356
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173968
+ const chunksToEmbed = liveCompartments.map((c) => ({
173969
+ id: c.id,
173970
+ startMessage: c.startMessage,
173971
+ endMessage: c.endMessage
173972
+ }));
173973
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172357
173974
  }
172358
173975
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
172359
173976
  if (lastCompartmentEnd2 > 0) {
@@ -172566,8 +174183,12 @@ Another process acquired the compartment-state lease before recomp could publish
172566
174183
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172567
174184
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172568
174185
  const liveCompartments = getCompartments(db, sessionId);
172569
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172570
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
174186
+ const chunksToEmbed = liveCompartments.map((c) => ({
174187
+ id: c.id,
174188
+ startMessage: c.startMessage,
174189
+ endMessage: c.endMessage
174190
+ }));
174191
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172571
174192
  }
172572
174193
  if (lastCompartmentEnd > 0) {
172573
174194
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -172748,8 +174369,12 @@ Could not acquire the compartment-state lease for this session.`;
172748
174369
  if (deps.memoryEnabled !== false) {
172749
174370
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172750
174371
  const liveCompartments = getCompartments(db, sessionId);
172751
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172752
- Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed));
174372
+ const chunksToEmbed = liveCompartments.map((c) => ({
174373
+ id: c.id,
174374
+ startMessage: c.startMessage,
174375
+ endMessage: c.endMessage
174376
+ }));
174377
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
172753
174378
  }
172754
174379
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
172755
174380
  if (lastEnd > 0) {
@@ -174971,7 +176596,14 @@ function startCompartmentAgent(deps) {
174971
176596
  return;
174972
176597
  }
174973
176598
  const renewal = startLeaseRenewal(deps, holderId);
174974
- const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
176599
+ let realRunStarted = false;
176600
+ const runnerDeps = withPublishedCallback({
176601
+ ...deps,
176602
+ compartmentLeaseHolderId: holderId,
176603
+ onHistorianRunStarted: () => {
176604
+ realRunStarted = true;
176605
+ }
176606
+ });
174975
176607
  const promise2 = runCompartmentAgent(runnerDeps).catch((err) => {
174976
176608
  sessionLog(deps.sessionId, "compartment agent: unhandled rejection:", err);
174977
176609
  try {
@@ -174985,6 +176617,9 @@ function startCompartmentAgent(deps) {
174985
176617
  }
174986
176618
  });
174987
176619
  activeRuns.set(deps.sessionId, { promise: promise2, published: false });
176620
+ if (!realRunStarted && activeRuns.get(deps.sessionId)?.promise === promise2) {
176621
+ activeRuns.delete(deps.sessionId);
176622
+ }
174988
176623
  }
174989
176624
  async function executeContextRecompWithResult(deps, options = {}) {
174990
176625
  const { sessionId } = deps;
@@ -175119,7 +176754,7 @@ function applyMemoryMigration(db, projectPath, result) {
175119
176754
  inserted++;
175120
176755
  }
175121
176756
  if (removed > 0 || inserted > 0) {
175122
- bumpProjectMemoryEpoch(db, projectPath);
176757
+ bumpEpochsForWorkspaceMembers(db, projectPath);
175123
176758
  }
175124
176759
  })();
175125
176760
  return { removed, inserted };
@@ -175577,15 +177212,15 @@ function shouldShowAnnouncement() {
175577
177212
  }
175578
177213
  return state.version !== ANNOUNCEMENT_VERSION;
175579
177214
  }
175580
- var ANNOUNCEMENT_VERSION = "0.23.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
177215
+ var ANNOUNCEMENT_VERSION = "0.24.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
175581
177216
  var init_announcement = __esm(() => {
175582
177217
  init_data_path();
175583
177218
  ANNOUNCEMENT_FEATURES = [
175584
- "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.",
175585
- "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.",
175586
- "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).",
175587
- "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).",
175588
- "Fixed: session titles no longer fail to generate in fresh directories (issue #129), plus 20+ correctness fixes from three audit rounds."
177219
+ "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).",
177220
+ "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.",
177221
+ "Pi: fixed sessions overflowing the model context while still showing moderate usagePi now sheds context before a tool-heavy turn overflows.",
177222
+ "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
177223
+ "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)."
175589
177224
  ];
175590
177225
  });
175591
177226
  // src/agents/permissions.ts
@@ -176313,6 +177948,10 @@ function getMagicContextBuiltinCommands() {
176313
177948
  "ctx-dream": {
176314
177949
  template: "ctx-dream",
176315
177950
  description: "Run the hidden dreamer maintenance pass for this project now"
177951
+ },
177952
+ "ctx-embed-history": {
177953
+ template: "ctx-embed-history",
177954
+ description: "Embed all of this session's history compartments for semantic search, in one pass"
176316
177955
  }
176317
177956
  };
176318
177957
  }
@@ -176806,7 +178445,7 @@ await init_storage_db();
176806
178445
  // src/features/magic-context/v22-deferred-backfill.ts
176807
178446
  init_logger();
176808
178447
  init_project_identity();
176809
- import { createHash as createHash5 } from "node:crypto";
178448
+ import { createHash as createHash6 } from "node:crypto";
176810
178449
  import { realpathSync as realpathSync2 } from "node:fs";
176811
178450
  import path5 from "node:path";
176812
178451
  var BATCH_SIZE = 25;
@@ -176860,7 +178499,7 @@ function computeLegacyRustDirIdentity(rawProjectPath) {
176860
178499
  } catch {
176861
178500
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
176862
178501
  }
176863
- return `dir:${createHash5("sha256").update(canonical, "utf8").digest("hex")}`;
178502
+ return `dir:${createHash6("sha256").update(canonical, "utf8").digest("hex")}`;
176864
178503
  }
176865
178504
  function upsertRekeyMap(db, oldProjectPath, newProjectPath, rekeyedAt) {
176866
178505
  db.prepare(`INSERT INTO v22_identity_rekey_map (old_project_path, new_project_path, rekeyed_at)
@@ -179401,7 +181040,7 @@ init_project_identity();
179401
181040
  // src/plugin/embedding-bootstrap-helpers.ts
179402
181041
  init_embedding();
179403
181042
  init_logger();
179404
- import { createHash as createHash8 } from "node:crypto";
181043
+ import { createHash as createHash10 } from "node:crypto";
179405
181044
  var EMBEDDING_AFFECTING_KEYS = new Set([
179406
181045
  "embedding.api_key",
179407
181046
  "embedding.endpoint",
@@ -179422,7 +181061,7 @@ var EMBEDDING_WARNING_TERMS = [
179422
181061
  ];
179423
181062
  var loggedFailureSignatures = new Map;
179424
181063
  function sha256Prefix2(value, length = 16) {
179425
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
181064
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
179426
181065
  }
179427
181066
  function warningLooksEmbeddingRelated(message) {
179428
181067
  const lower = message.toLowerCase();
@@ -180058,6 +181697,7 @@ function createTagger() {
180058
181697
  // src/hooks/magic-context/hook.ts
180059
181698
  init_magic_context();
180060
181699
  init_project_identity();
181700
+ init_project_embedding_registry();
180061
181701
  await init_storage();
180062
181702
  init_logger();
180063
181703
  init_resolve_fallbacks();
@@ -180651,6 +182291,7 @@ function createMagicContextCommandHandler(deps) {
180651
182291
  const isAugCommand = (command) => command === "ctx-aug";
180652
182292
  const isDreamCommand = (command) => command === "ctx-dream";
180653
182293
  const isSessionUpgradeCommand = (command) => command === "ctx-session-upgrade";
182294
+ const isEmbedHistoryCommand = (command) => command === "ctx-embed-history";
180654
182295
  return {
180655
182296
  "command.execute.before": async (input, _output, _params) => {
180656
182297
  const isStatus = isStatusCommand(input.command);
@@ -180659,7 +182300,8 @@ function createMagicContextCommandHandler(deps) {
180659
182300
  const isAug = isAugCommand(input.command);
180660
182301
  const isDream = isDreamCommand(input.command);
180661
182302
  const isSessionUpgrade = isSessionUpgradeCommand(input.command);
180662
- if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade) {
182303
+ const isEmbedHistory = isEmbedHistoryCommand(input.command);
182304
+ if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbedHistory) {
180663
182305
  return;
180664
182306
  }
180665
182307
  const sessionId = input.sessionID;
@@ -180672,6 +182314,11 @@ function createMagicContextCommandHandler(deps) {
180672
182314
  await executeDreaming(deps, sessionId);
180673
182315
  return;
180674
182316
  }
182317
+ if (isEmbedHistory) {
182318
+ const summary = deps.executeEmbedHistory ? await deps.executeEmbedHistory(sessionId) : "Semantic embedding is not configured for this project, so there is nothing to embed.";
182319
+ await deps.sendNotification(sessionId, summary, {});
182320
+ throwSentinel(input.command);
182321
+ }
180675
182322
  if (isFlush) {
180676
182323
  result = executeFlush(deps.db, sessionId);
180677
182324
  clearCachedM0M1(deps.db, sessionId);
@@ -181412,6 +183059,7 @@ await init_read_session_chunk();
181412
183059
  // src/hooks/magic-context/transform.ts
181413
183060
  init_project_identity();
181414
183061
  import * as crypto2 from "node:crypto";
183062
+ init_session_project_storage();
181415
183063
  init_storage_meta_persisted();
181416
183064
  init_logger();
181417
183065
  await init_storage();
@@ -182012,6 +183660,7 @@ await __promiseAll([
182012
183660
  init_read_session_chunk(),
182013
183661
  init_read_session_db()
182014
183662
  ]);
183663
+
182015
183664
  // src/hooks/magic-context/sentinel.ts
182016
183665
  var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
182017
183666
  function modelAcceptsEmptyContent(providerID) {
@@ -182069,7 +183718,6 @@ function replaySentinelByMessageIds(messages, ids, providerID) {
182069
183718
  missingIds.push(id);
182070
183719
  return { replayed, missingIds };
182071
183720
  }
182072
-
182073
183721
  // src/hooks/magic-context/strip-content.ts
182074
183722
  var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped §\d+§\]$/;
182075
183723
  var TAG_PREFIX_PATTERN = /^§\d+§\s*/;
@@ -182422,8 +184070,10 @@ function stripReasoningFromMergedAssistants(messages, providerID) {
182422
184070
  }
182423
184071
  return stripped;
182424
184072
  }
182425
- function stripProcessedImages(messages, watermark, messageTagNumbers) {
184073
+ function stripProcessedImages(messages, frozenIds, options) {
184074
+ const { detect, watermark, messageTagNumbers } = options;
182426
184075
  let stripped = 0;
184076
+ const newlyStrippedIds = [];
182427
184077
  let hasAssistantResponse = false;
182428
184078
  for (let i = messages.length - 1;i >= 0; i--) {
182429
184079
  const msg = messages[i];
@@ -182431,13 +184081,17 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
182431
184081
  hasAssistantResponse = true;
182432
184082
  continue;
182433
184083
  }
182434
- if (msg.info.role !== "user" || !hasAssistantResponse) {
184084
+ if (msg.info.role !== "user") {
182435
184085
  continue;
182436
184086
  }
184087
+ const id = typeof msg.info.id === "string" ? msg.info.id : undefined;
184088
+ const inFrozen = id !== undefined && frozenIds.has(id);
182437
184089
  const maxTag = messageTagNumbers.get(msg) ?? 0;
182438
- if (maxTag > watermark) {
184090
+ const isNewDetection = !inFrozen && detect && hasAssistantResponse && id !== undefined && maxTag <= watermark;
184091
+ if (!inFrozen && !isNewDetection) {
182439
184092
  continue;
182440
184093
  }
184094
+ let touchedThisMsg = false;
182441
184095
  for (let j = 0;j < msg.parts.length; j++) {
182442
184096
  const part = msg.parts[j];
182443
184097
  if (!isRecord(part) || part.type !== "file") {
@@ -182449,10 +184103,14 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
182449
184103
  if (typeof part.url === "string" && part.url.startsWith("data:") && part.url.length > 200) {
182450
184104
  msg.parts[j] = makeSentinel(part);
182451
184105
  stripped++;
184106
+ touchedThisMsg = true;
182452
184107
  }
182453
184108
  }
184109
+ if (touchedThisMsg && isNewDetection && id !== undefined) {
184110
+ newlyStrippedIds.push(id);
184111
+ }
182454
184112
  }
182455
- return stripped;
184113
+ return { stripped, newlyStrippedIds };
182456
184114
  }
182457
184115
 
182458
184116
  // src/hooks/magic-context/transform.ts
@@ -183395,6 +185053,7 @@ init_embedding();
183395
185053
 
183396
185054
  // src/features/magic-context/search.ts
183397
185055
  init_logger();
185056
+ init_compartment_chunk_embedding();
183398
185057
 
183399
185058
  // src/features/magic-context/literal-probes.ts
183400
185059
  var MAX_PROBES = 5;
@@ -183456,6 +185115,7 @@ function containsProbeVerbatim(text, probes) {
183456
185115
  init_memory();
183457
185116
  init_embedding();
183458
185117
  init_storage_memory_fts();
185118
+ init_workspaces();
183459
185119
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
183460
185120
  var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
183461
185121
  var SEMANTIC_WEIGHT = 0.7;
@@ -183485,6 +185145,37 @@ function previewText(text) {
183485
185145
  }
183486
185146
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
183487
185147
  }
185148
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
185149
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
185150
+ const isWorkspaced = resolved.identities.length > 1;
185151
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
185152
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
185153
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
185154
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
185155
+ return {
185156
+ identities: resolved.identities,
185157
+ expandedIdentities,
185158
+ ownIdentities,
185159
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
185160
+ namesByIdentity: resolved.namesByIdentity,
185161
+ canonicalIdentityByStoredPath,
185162
+ isWorkspaced
185163
+ };
185164
+ }
185165
+ function memoryWorkspaceIdentity(memory, workspace) {
185166
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
185167
+ }
185168
+ function sourceNamesForSearchMemories(args) {
185169
+ if (!args.workspace.isWorkspaced)
185170
+ return;
185171
+ const sourceNames = new Map;
185172
+ for (const memory of args.memories) {
185173
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
185174
+ if (source)
185175
+ sourceNames.set(memory.id, source);
185176
+ }
185177
+ return sourceNames.size > 0 ? sourceNames : undefined;
185178
+ }
183488
185179
  function getMessageSearchStatement(db) {
183489
185180
  let stmt = messageSearchStatements.get(db);
183490
185181
  if (!stmt) {
@@ -183493,6 +185184,30 @@ function getMessageSearchStatement(db) {
183493
185184
  }
183494
185185
  return stmt;
183495
185186
  }
185187
+ var ftsRowCountStatements = new WeakMap;
185188
+ var ftsMatchCountStatements = new WeakMap;
185189
+ function getSessionFtsRowCount(db, sessionId) {
185190
+ let stmt = ftsRowCountStatements.get(db);
185191
+ if (!stmt) {
185192
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
185193
+ ftsRowCountStatements.set(db, stmt);
185194
+ }
185195
+ const row = stmt.get(sessionId);
185196
+ return typeof row?.n === "number" ? row.n : 0;
185197
+ }
185198
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
185199
+ let stmt = ftsMatchCountStatements.get(db);
185200
+ if (!stmt) {
185201
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
185202
+ ftsMatchCountStatements.set(db, stmt);
185203
+ }
185204
+ try {
185205
+ const row = stmt.get(sessionId, ftsQuery);
185206
+ return typeof row?.n === "number" ? row.n : 0;
185207
+ } catch {
185208
+ return 0;
185209
+ }
185210
+ }
183496
185211
  function getMessageOrdinal(value) {
183497
185212
  if (typeof value === "number" && Number.isFinite(value)) {
183498
185213
  return value;
@@ -183508,25 +185223,63 @@ async function getSemanticScores(args) {
183508
185223
  if (!args.queryEmbedding || args.memories.length === 0) {
183509
185224
  return semanticScores;
183510
185225
  }
183511
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
183512
- const embeddings = await ensureMemoryEmbeddings({
183513
- db: args.db,
183514
- projectIdentity: args.projectPath,
183515
- memories: args.memories,
183516
- existingEmbeddings: cachedEmbeddings
183517
- });
185226
+ if (!args.workspace?.isWorkspaced) {
185227
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185228
+ const embeddings = await ensureMemoryEmbeddings({
185229
+ db: args.db,
185230
+ projectIdentity: args.projectPath,
185231
+ memories: args.memories,
185232
+ existingEmbeddings: cachedEmbeddings
185233
+ });
185234
+ for (const memory of args.memories) {
185235
+ const memoryEmbedding = embeddings.get(memory.id);
185236
+ if (!memoryEmbedding) {
185237
+ continue;
185238
+ }
185239
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
185240
+ }
185241
+ return semanticScores;
185242
+ }
185243
+ if (!args.queryModelId || args.queryModelId === "off") {
185244
+ return semanticScores;
185245
+ }
185246
+ const workspace = args.workspace;
185247
+ const memoriesByIdentity = new Map;
183518
185248
  for (const memory of args.memories) {
183519
- const memoryEmbedding = embeddings.get(memory.id);
183520
- if (!memoryEmbedding) {
185249
+ const identity = memoryWorkspaceIdentity(memory, workspace);
185250
+ if (!identity)
183521
185251
  continue;
185252
+ const list = memoriesByIdentity.get(identity) ?? [];
185253
+ list.push(memory);
185254
+ memoriesByIdentity.set(identity, list);
185255
+ }
185256
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
185257
+ if (ownMemories.length > 0) {
185258
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185259
+ await ensureMemoryEmbeddings({
185260
+ db: args.db,
185261
+ projectIdentity: args.projectPath,
185262
+ memories: ownMemories,
185263
+ existingEmbeddings: ownEmbeddings
185264
+ });
185265
+ }
185266
+ for (const identity of workspace.identities) {
185267
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
185268
+ if (memberMemories.length === 0)
185269
+ continue;
185270
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
185271
+ for (const memory of memberMemories) {
185272
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
185273
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
185274
+ continue;
185275
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
183522
185276
  }
183523
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
183524
185277
  }
183525
185278
  return semanticScores;
183526
185279
  }
183527
185280
  function getFtsMatches(args) {
183528
185281
  try {
183529
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
185282
+ 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);
183530
185283
  } catch (error51) {
183531
185284
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
183532
185285
  return [];
@@ -183540,8 +185293,11 @@ function selectSemanticCandidates(args) {
183540
185293
  return args.memories;
183541
185294
  }
183542
185295
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
183543
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
183544
- if (cachedEmbeddings) {
185296
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
185297
+ for (const projectPath of embeddingProjects) {
185298
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
185299
+ if (!cachedEmbeddings)
185300
+ continue;
183545
185301
  for (const memoryId of cachedEmbeddings.keys()) {
183546
185302
  candidateIds.add(memoryId);
183547
185303
  }
@@ -183583,7 +185339,8 @@ function mergeMemoryResults(args) {
183583
185339
  score,
183584
185340
  memoryId: memory.id,
183585
185341
  category: memory.category,
183586
- matchType
185342
+ matchType,
185343
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
183587
185344
  });
183588
185345
  }
183589
185346
  return results.sort((left, right) => {
@@ -183597,7 +185354,7 @@ async function searchMemories(args) {
183597
185354
  if (!args.memoryEnabled) {
183598
185355
  return [];
183599
185356
  }
183600
- const memories = getMemoriesByProject(args.db, args.projectPath);
185357
+ 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);
183601
185358
  if (memories.length === 0) {
183602
185359
  return [];
183603
185360
  }
@@ -183605,26 +185362,43 @@ async function searchMemories(args) {
183605
185362
  db: args.db,
183606
185363
  projectPath: args.projectPath,
183607
185364
  query: args.query,
183608
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
185365
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
185366
+ workspace: args.workspace
183609
185367
  });
183610
185368
  const ftsScores = getFtsScores(ftsMatches);
183611
185369
  const semanticCandidates = selectSemanticCandidates({
183612
185370
  memories,
183613
185371
  projectPath: args.projectPath,
183614
- ftsMatches
185372
+ ftsMatches,
185373
+ workspace: args.workspace
183615
185374
  });
183616
185375
  const semanticScores = await getSemanticScores({
183617
185376
  db: args.db,
183618
185377
  projectPath: args.projectPath,
183619
185378
  memories: semanticCandidates,
183620
- queryEmbedding: args.queryEmbedding
185379
+ queryEmbedding: args.queryEmbedding,
185380
+ queryModelId: args.queryModelId,
185381
+ workspace: args.workspace
183621
185382
  });
183622
185383
  return mergeMemoryResults({
183623
185384
  memories,
183624
185385
  semanticScores,
183625
185386
  ftsScores,
183626
185387
  limit: args.limit,
183627
- visibleMemoryIds: args.visibleMemoryIds
185388
+ visibleMemoryIds: args.visibleMemoryIds,
185389
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
185390
+ memories,
185391
+ projectPath: args.projectPath,
185392
+ workspace: args.workspace ?? {
185393
+ identities: [args.projectPath],
185394
+ expandedIdentities: [args.projectPath],
185395
+ namesByIdentity: new Map,
185396
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
185397
+ ownIdentities: [args.projectPath],
185398
+ shareCategories: null,
185399
+ isWorkspaced: false
185400
+ }
185401
+ })
183628
185402
  });
183629
185403
  }
183630
185404
  function linearDecayScore(rank, total) {
@@ -183655,7 +185429,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
183655
185429
  return result;
183656
185430
  }
183657
185431
  var RRF_K = 60;
183658
- var VERBATIM_PROBE_BONUS = 0.5;
185432
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
185433
+ var IDF_FALLOFF = 100;
185434
+ function probeDiscriminationWeight(df, corpusSize) {
185435
+ if (corpusSize <= 0 || df <= 0)
185436
+ return 1;
185437
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
185438
+ }
183659
185439
  function searchMessages(args) {
183660
185440
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
183661
185441
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -183672,20 +185452,31 @@ function searchMessages(args) {
183672
185452
  role: row.role
183673
185453
  }));
183674
185454
  }
185455
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
183675
185456
  const queryLists = [];
183676
185457
  if (baseQuery.length > 0) {
183677
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
185458
+ queryLists.push({
185459
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
185460
+ weight: 1
185461
+ });
183678
185462
  }
185463
+ const probeWeights = new Map;
183679
185464
  for (const probe of probes) {
183680
185465
  const probeQuery = sanitizeFtsQuery(probe);
183681
185466
  if (probeQuery.length === 0)
183682
185467
  continue;
183683
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
185468
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
185469
+ const weight = probeDiscriminationWeight(df, corpusSize);
185470
+ probeWeights.set(probe, weight);
185471
+ queryLists.push({
185472
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
185473
+ weight
185474
+ });
183684
185475
  }
183685
185476
  const fused = new Map;
183686
185477
  for (const list of queryLists) {
183687
- list.forEach((row, rank) => {
183688
- const rrf = 1 / (RRF_K + rank);
185478
+ list.rows.forEach((row, rank) => {
185479
+ const rrf = list.weight / (RRF_K + rank);
183689
185480
  const existing = fused.get(row.messageId);
183690
185481
  if (existing) {
183691
185482
  existing.score += rrf;
@@ -183695,26 +185486,107 @@ function searchMessages(args) {
183695
185486
  });
183696
185487
  }
183697
185488
  for (const entry of fused.values()) {
183698
- if (containsProbeVerbatim(entry.row.content, probes)) {
183699
- entry.score += VERBATIM_PROBE_BONUS;
185489
+ let best = 0;
185490
+ for (const probe of probes) {
185491
+ const weight = probeWeights.get(probe) ?? 0;
185492
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
185493
+ best = weight;
185494
+ }
185495
+ }
185496
+ if (best > 0) {
185497
+ entry.score += best * VERBATIM_RANK_BONUS;
183700
185498
  }
183701
185499
  }
183702
185500
  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);
183703
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
183704
- return ranked.map((entry) => ({
185501
+ return ranked.map((entry, rank) => ({
183705
185502
  source: "message",
183706
185503
  content: previewText(entry.row.content),
183707
- score: maxScore > 0 ? entry.score / maxScore : 0,
185504
+ score: linearDecayScore(rank, ranked.length),
183708
185505
  messageOrdinal: entry.row.messageOrdinal,
183709
185506
  messageId: entry.row.messageId,
183710
185507
  role: entry.row.role
183711
185508
  }));
183712
185509
  }
185510
+ function searchCompartmentChunks(args) {
185511
+ if (!args.queryEmbedding || args.limit <= 0)
185512
+ return [];
185513
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
185514
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
185515
+ if (rows.length === 0)
185516
+ return [];
185517
+ const byCompartment = new Map;
185518
+ for (const row of rows) {
185519
+ if (cutoff !== null && row.endOrdinal > cutoff) {
185520
+ continue;
185521
+ }
185522
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
185523
+ if (score <= 0)
185524
+ continue;
185525
+ const existing = byCompartment.get(row.compartmentId);
185526
+ if (!existing || score > existing.score) {
185527
+ byCompartment.set(row.compartmentId, { row, score });
185528
+ }
185529
+ }
185530
+ 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 }) => ({
185531
+ source: "compartment",
185532
+ content: previewText(row.title),
185533
+ score: score * SINGLE_SOURCE_PENALTY,
185534
+ compartmentId: row.compartmentId,
185535
+ sessionId: row.sessionId,
185536
+ title: row.title,
185537
+ startOrdinal: row.startOrdinal,
185538
+ endOrdinal: row.endOrdinal,
185539
+ matchType: "semantic"
185540
+ }));
185541
+ }
185542
+ function mergeMessageAndCompartmentResults(args) {
185543
+ if (args.compartments.length === 0)
185544
+ return args.messages;
185545
+ if (args.messages.length === 0)
185546
+ return args.compartments;
185547
+ const fused = new Map;
185548
+ const add = (key, result, score, tieOrdinal) => {
185549
+ const existing = fused.get(key);
185550
+ if (existing) {
185551
+ existing.score += score;
185552
+ return existing;
185553
+ }
185554
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
185555
+ fused.set(key, entry);
185556
+ return entry;
185557
+ };
185558
+ args.compartments.forEach((compartment, rank) => {
185559
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
185560
+ });
185561
+ for (const [rank, message] of args.messages.entries()) {
185562
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
185563
+ const contribution = 1 / (RRF_K + rank);
185564
+ if (!containing) {
185565
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
185566
+ continue;
185567
+ }
185568
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
185569
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
185570
+ entry.snippetScore = message.score;
185571
+ entry.result = {
185572
+ ...entry.result,
185573
+ matchType: "hybrid",
185574
+ snippet: message.content
185575
+ };
185576
+ }
185577
+ }
185578
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
185579
+ return ranked.map((entry, rank) => ({
185580
+ ...entry.result,
185581
+ score: linearDecayScore(rank, ranked.length)
185582
+ }));
185583
+ }
183713
185584
  function getSourceBoost(result) {
183714
185585
  switch (result.source) {
183715
185586
  case "memory":
183716
185587
  return MEMORY_SOURCE_BOOST;
183717
185588
  case "message":
185589
+ case "compartment":
183718
185590
  return MESSAGE_SOURCE_BOOST;
183719
185591
  case "git_commit":
183720
185592
  return GIT_COMMIT_SOURCE_BOOST;
@@ -183732,6 +185604,9 @@ function compareUnifiedResults(left, right) {
183732
185604
  if (left.source === "message" && right.source === "message") {
183733
185605
  return left.messageOrdinal - right.messageOrdinal;
183734
185606
  }
185607
+ if (left.source === "compartment" && right.source === "compartment") {
185608
+ return left.startOrdinal - right.startOrdinal;
185609
+ }
183735
185610
  if (left.source === "git_commit" && right.source === "git_commit") {
183736
185611
  return right.committedAtMs - left.committedAtMs;
183737
185612
  }
@@ -183782,10 +185657,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183782
185657
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
183783
185658
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
183784
185659
  const activeSources = resolveSources(options.sources);
183785
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
185660
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
185661
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
183786
185662
  const runMessages = activeSources.has("message");
183787
185663
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
183788
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
185664
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
185665
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
183789
185666
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
183790
185667
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
183791
185668
  return null;
@@ -183801,6 +185678,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183801
185678
  probes: messageProbes
183802
185679
  }) : [];
183803
185680
  const queryEmbedding = await queryEmbeddingPromise;
185681
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
185682
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
185683
+ const embeddingModelId = embeddingSnapshot?.modelId;
185684
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
185685
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
185686
+ db,
185687
+ sessionId,
185688
+ projectPath,
185689
+ queryEmbedding,
185690
+ limit: tierLimit,
185691
+ maxOrdinal: options.maxMessageOrdinal,
185692
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
185693
+ }) : [];
185694
+ const messageLikeResults = mergeMessageAndCompartmentResults({
185695
+ messages: messageResults,
185696
+ compartments: compartmentResults,
185697
+ limit: tierLimit
185698
+ });
183804
185699
  const [memoryResults, gitCommitResults] = await Promise.all([
183805
185700
  runMemory ? searchMemories({
183806
185701
  db,
@@ -183809,6 +185704,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183809
185704
  limit: tierLimit,
183810
185705
  memoryEnabled: true,
183811
185706
  queryEmbedding,
185707
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
185708
+ workspace,
183812
185709
  visibleMemoryIds: options.visibleMemoryIds
183813
185710
  }) : Promise.resolve([]),
183814
185711
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -183819,7 +185716,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183819
185716
  queryEmbedding
183820
185717
  })) : Promise.resolve([])
183821
185718
  ]);
183822
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
185719
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
183823
185720
  const countRetrievals = options.countRetrievals ?? true;
183824
185721
  if (countRetrievals) {
183825
185722
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -183883,6 +185780,11 @@ function renderFragment(result, charCap) {
183883
185780
  const compressed = cavemanCompress(result.content, "ultra");
183884
185781
  return truncate(compressed, charCap);
183885
185782
  }
185783
+ case "compartment": {
185784
+ const source = result.snippet ?? result.title;
185785
+ const compressed = cavemanCompress(source, "ultra");
185786
+ return truncate(compressed, charCap);
185787
+ }
183886
185788
  }
183887
185789
  }
183888
185790
  function buildAutoSearchHint(results, options = {}) {
@@ -184523,7 +186425,7 @@ function isVisibleNoteReadPart(part) {
184523
186425
  }
184524
186426
 
184525
186427
  // src/hooks/magic-context/todo-view.ts
184526
- import { createHash as createHash9 } from "node:crypto";
186428
+ import { createHash as createHash11 } from "node:crypto";
184527
186429
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
184528
186430
  var TITLE_DONE_STATUSES = new Set(["completed"]);
184529
186431
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -184568,7 +186470,7 @@ function buildSyntheticTodoPart(stateJson) {
184568
186470
  };
184569
186471
  }
184570
186472
  function computeSyntheticCallId(stateJson) {
184571
- const hash2 = createHash9("sha256").update(stateJson).digest("hex").slice(0, 16);
186473
+ const hash2 = createHash11("sha256").update(stateJson).digest("hex").slice(0, 16);
184572
186474
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
184573
186475
  }
184574
186476
  function parseTodoState(stateJson) {
@@ -184619,15 +186521,25 @@ async function runPostTransformPhase(args) {
184619
186521
  const emergencyBypassCompartmentGate = forceMaterialization;
184620
186522
  const deferredMaterialize = args.canConsumeDeferredLate && deferredMaterializationWasPending;
184621
186523
  const materializationRequested = isExplicitFlush || deferredMaterialize;
184622
- const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
186524
+ const m0M1EnabledForFold = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
186525
+ const m0HardFoldThisPass = m0M1EnabledForFold && args.m0M1 ? mustMaterialize({
186526
+ db: args.db,
186527
+ sessionId: args.sessionId,
186528
+ state: args.sessionMeta,
186529
+ projectPath: args.m0M1.projectPath,
186530
+ projectDirectory: args.m0M1.projectDirectory,
186531
+ hardSignals: args.m0M1.hardSignals
186532
+ }).value : false;
186533
+ const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || m0HardFoldThisPass || compartmentRunning;
184623
186534
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
184624
186535
  const hasPendingUserOps = pendingOps.length > 0;
184625
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
184626
- const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
186536
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || m0HardFoldThisPass) && (!compartmentRunning || emergencyBypassCompartmentGate);
186537
+ const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || m0HardFoldThisPass || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
184627
186538
  const isCacheBustingPass = shouldApplyPendingOps || shouldRunHeuristics;
186539
+ const canUseEmptySentinels = modelAcceptsEmptyContent(args.resolvedProviderID);
184628
186540
  if (shouldRunHeuristics) {
184629
186541
  const subagentRerun = !args.fullFeatureMode && alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && !forceMaterialization;
184630
- const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
186542
+ const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : m0HardFoldThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into known m[0] bust, scheduler=${args.schedulerDecision})` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
184631
186543
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
184632
186544
  }
184633
186545
  if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !materializationRequested && args.fullFeatureMode) {
@@ -184670,7 +186582,9 @@ async function runPostTransformPhase(args) {
184670
186582
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags}`);
184671
186583
  const t7 = performance.now();
184672
186584
  const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
184673
- stripClearedReasoning(args.messages);
186585
+ if (canUseEmptySentinels) {
186586
+ stripClearedReasoning(args.messages);
186587
+ }
184674
186588
  const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
184675
186589
  if (clearedReasoning > 0 || strippedInline > 0) {
184676
186590
  let maxTag = 0;
@@ -184716,7 +186630,6 @@ async function runPostTransformPhase(args) {
184716
186630
  if (args.watermark > 0) {
184717
186631
  const tWatermarkCleanup = performance.now();
184718
186632
  truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
184719
- stripProcessedImages(args.messages, args.watermark, args.messageTagNumbers);
184720
186633
  logTransformTiming(args.sessionId, "watermarkCleanup", tWatermarkCleanup);
184721
186634
  }
184722
186635
  if (shouldApplyPendingOps) {
@@ -184726,21 +186639,40 @@ async function runPostTransformPhase(args) {
184726
186639
  sessionLog(args.sessionId, "transform failed applying pending operations:", error51);
184727
186640
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: getErrorMessage(error51) });
184728
186641
  }
184729
- try {
184730
- const t8 = performance.now();
184731
- const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
184732
- const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
184733
- detect: isCacheBustingPass,
184734
- protectedCount: args.protectedTags
184735
- });
184736
- if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
184737
- addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
186642
+ if (canUseEmptySentinels) {
186643
+ try {
186644
+ const t8 = performance.now();
186645
+ const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
186646
+ const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
186647
+ detect: isCacheBustingPass,
186648
+ protectedCount: args.protectedTags
186649
+ });
186650
+ if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
186651
+ addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
186652
+ }
186653
+ logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
186654
+ } catch (error51) {
186655
+ sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
186656
+ }
186657
+ }
186658
+ if (canUseEmptySentinels && args.watermark > 0) {
186659
+ try {
186660
+ const tImg = performance.now();
186661
+ const frozenImageIds = getProcessedImageStrippedIds(args.db, args.sessionId);
186662
+ const imageResult = stripProcessedImages(args.messages, frozenImageIds, {
186663
+ detect: isCacheBustingPass,
186664
+ watermark: args.watermark,
186665
+ messageTagNumbers: args.messageTagNumbers
186666
+ });
186667
+ if (isCacheBustingPass && imageResult.newlyStrippedIds.length > 0) {
186668
+ addProcessedImageStrippedIds(args.db, args.sessionId, imageResult.newlyStrippedIds);
186669
+ }
186670
+ logTransformTiming(args.sessionId, "stripProcessedImages", tImg);
186671
+ } catch (error51) {
186672
+ sessionLog(args.sessionId, "transform failed stripping processed images:", error51);
184738
186673
  }
184739
- logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
184740
- } catch (error51) {
184741
- sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
184742
186674
  }
184743
- const m0M1Enabled = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
186675
+ const m0M1Enabled = m0M1EnabledForFold;
184744
186676
  if (m0M1Enabled && args.m0M1) {
184745
186677
  const tInjectM0M1 = performance.now();
184746
186678
  try {
@@ -184787,7 +186719,7 @@ async function runPostTransformPhase(args) {
184787
186719
  const tPlaceholder = performance.now();
184788
186720
  const persistedIds = getStrippedPlaceholderIds(args.db, args.sessionId);
184789
186721
  if (persistedIds.size > 0) {
184790
- const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.liveProviderID);
186722
+ const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.resolvedProviderID);
184791
186723
  if (replayed > 0) {
184792
186724
  sessionLog(args.sessionId, `sentinel replay: neutralized ${replayed} previously-stripped messages`);
184793
186725
  }
@@ -184798,9 +186730,9 @@ async function runPostTransformPhase(args) {
184798
186730
  }
184799
186731
  }
184800
186732
  if (isCacheBustingPass) {
184801
- const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.liveProviderID);
186733
+ const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.resolvedProviderID);
184802
186734
  const protectedTailStart = Math.max(0, args.messages.length - args.protectedTags * 2);
184803
- const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.liveProviderID);
186735
+ const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.resolvedProviderID);
184804
186736
  const newlyNeutralized = droppedResult.sentineledIds.length + systemInjectedResult.sentineledIds.length;
184805
186737
  if (newlyNeutralized > 0) {
184806
186738
  const addedIds = [
@@ -185000,6 +186932,7 @@ function clearMessageTokensCache(sessionId, messageId) {
185000
186932
  if (cache)
185001
186933
  cache.delete(messageId);
185002
186934
  }
186935
+ var recordedSessionProjectIdentity = new BoundedSessionMap(MESSAGE_TOKENS_CACHE_MAX);
185003
186936
  function findLastAssistantModel2(messages) {
185004
186937
  for (let i = messages.length - 1;i >= 0; i--) {
185005
186938
  const info = messages[i].info;
@@ -185047,9 +186980,11 @@ function createTransform(deps) {
185047
186980
  const fullFeatureMode = !reducedMode;
185048
186981
  const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false && resolveCtxReduceAvailabilityFromMessages(sessionId, messages);
185049
186982
  let sessionDirectory = deps.directory ?? "";
186983
+ let sessionDirectoryResolvedFromHost = false;
185050
186984
  const cachedDirectory = deps.sessionDirectoryBySession?.get(sessionId);
185051
186985
  if (cachedDirectory && cachedDirectory.length > 0) {
185052
186986
  sessionDirectory = cachedDirectory;
186987
+ sessionDirectoryResolvedFromHost = true;
185053
186988
  } else if (deps.client !== undefined) {
185054
186989
  try {
185055
186990
  const sessionResponse = await deps.client.session.get({ path: { id: sessionId } }).catch(() => null);
@@ -185057,6 +186992,7 @@ function createTransform(deps) {
185057
186992
  if (sessionInfo && typeof sessionInfo.directory === "string" && sessionInfo.directory.length > 0) {
185058
186993
  sessionDirectory = sessionInfo.directory;
185059
186994
  deps.sessionDirectoryBySession?.set(sessionId, sessionDirectory);
186995
+ sessionDirectoryResolvedFromHost = true;
185060
186996
  }
185061
186997
  } catch {}
185062
186998
  }
@@ -185151,6 +187087,8 @@ function createTransform(deps) {
185151
187087
  deps.liveModelBySession?.set(sessionId, recovered);
185152
187088
  }
185153
187089
  }
187090
+ const resolvedProviderID = modelForBudget?.providerID;
187091
+ const canUseEmptySentinels = modelAcceptsEmptyContent(resolvedProviderID);
185154
187092
  const resolvedContextLimit = modelForBudget ? resolveTrustedContextLimit(modelForBudget.providerID, modelForBudget.modelID, {
185155
187093
  db,
185156
187094
  sessionID: sessionId
@@ -185315,6 +187253,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185315
187253
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
185316
187254
  const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(compartmentDirectory || process.cwd()) : undefined;
185317
187255
  const sessionProjectIdentity = projectIdentity ?? (sessionDirectory ? resolveProjectIdentity(sessionDirectory) : deps.projectPath);
187256
+ if (sessionProjectIdentity && sessionDirectoryResolvedFromHost && recordedSessionProjectIdentity.get(sessionId) !== sessionProjectIdentity) {
187257
+ recordSessionProjectIdentity(db, sessionId, sessionProjectIdentity);
187258
+ recordedSessionProjectIdentity.set(sessionId, sessionProjectIdentity);
187259
+ }
185318
187260
  let triggerBoundarySnapshot;
185319
187261
  if (fullFeatureMode && historianRunnable && !sessionMeta.compartmentInProgress) {
185320
187262
  const tTrigger = performance.now();
@@ -185409,7 +187351,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185409
187351
  }
185410
187352
  }
185411
187353
  const t3 = performance.now();
185412
- const strippedStructuralNoise = stripStructuralNoise(messages);
187354
+ const strippedStructuralNoise = canUseEmptySentinels ? stripStructuralNoise(messages) : 0;
185413
187355
  logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
185414
187356
  const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
185415
187357
  if (persistedReasoningWatermark > 0) {
@@ -185430,18 +187372,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185430
187372
  logTransformTiming(sessionId, "replayCavemanCompression", tCavemanReplay);
185431
187373
  }
185432
187374
  const t4 = performance.now();
185433
- const strippedClearedReasoning = stripClearedReasoning(messages);
187375
+ const strippedClearedReasoning = canUseEmptySentinels ? stripClearedReasoning(messages) : 0;
185434
187376
  logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
185435
187377
  const tMergeStrip = performance.now();
185436
- let liveProviderID = deps.liveModelBySession?.get(sessionId)?.providerID;
185437
- if (liveProviderID === undefined) {
185438
- const recovered = findLastAssistantModelFromOpenCodeDb(sessionId);
185439
- if (recovered) {
185440
- liveProviderID = recovered.providerID;
185441
- deps.liveModelBySession?.set(sessionId, recovered);
185442
- }
185443
- }
185444
- const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, liveProviderID);
187378
+ const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, resolvedProviderID);
185445
187379
  if (strippedMergedReasoning > 0) {
185446
187380
  sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
185447
187381
  }
@@ -185560,7 +187494,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185560
187494
  sessionDirectory,
185561
187495
  autoSearch: deps.autoSearch,
185562
187496
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
185563
- liveProviderID,
187497
+ resolvedProviderID,
185564
187498
  historyRefreshSessions: deps.historyRefreshSessions,
185565
187499
  m0M1: {
185566
187500
  projectPath: projectIdentity,
@@ -186413,7 +188347,7 @@ function createToolExecuteAfterHook(args) {
186413
188347
  init_send_session_notification();
186414
188348
 
186415
188349
  // src/hooks/magic-context/system-prompt-hash.ts
186416
- import { createHash as createHash10 } from "node:crypto";
188350
+ import { createHash as createHash12 } from "node:crypto";
186417
188351
 
186418
188352
  // src/agents/magic-context-prompt.ts
186419
188353
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -186613,7 +188547,7 @@ function createSystemPromptHashHandler(deps) {
186613
188547
  `);
186614
188548
  if (systemContent.length === 0)
186615
188549
  return;
186616
- const currentHash = createHash10("md5").update(systemContent).digest("hex");
188550
+ const currentHash = createHash12("md5").update(systemContent).digest("hex");
186617
188551
  if (!sessionMetaEarly) {
186618
188552
  return;
186619
188553
  }
@@ -186874,6 +188808,46 @@ function createMagicContextHook(deps) {
186874
188808
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
186875
188809
  getNotificationParams: (sid) => getLiveNotificationParams(sid, liveModelBySession, variantBySession, agentBySession)
186876
188810
  });
188811
+ const executeEmbedHistory = async (sessionId) => {
188812
+ if (deps.config.memory?.enabled === false) {
188813
+ return "Memory is disabled for this project, so there is no semantic embedding to backfill.";
188814
+ }
188815
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
188816
+ await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
188817
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
188818
+ setRecompStarting({ recompProgressBySession }, sessionId, "Embedding history…", "embed");
188819
+ const outcome = await embedSessionCompartmentChunks(db, sessionProjectIdentity, sessionId, {
188820
+ onProgress: ({ embedded, total }) => {
188821
+ const cur = recompProgressBySession.get(sessionId);
188822
+ if (!cur || cur.phase !== "recomp")
188823
+ return;
188824
+ recompProgressBySession.set(sessionId, {
188825
+ ...cur,
188826
+ processedMessages: embedded,
188827
+ totalMessages: total,
188828
+ updatedAt: Date.now()
188829
+ });
188830
+ }
188831
+ });
188832
+ const terminal = (phase, message) => {
188833
+ setRecompTerminal({ recompProgressBySession }, sessionId, phase, message);
188834
+ return message;
188835
+ };
188836
+ switch (outcome.status) {
188837
+ case "nothing":
188838
+ return terminal("done", "All of this session's history is already embedded.");
188839
+ case "disabled":
188840
+ return terminal("skipped", "No embedding provider is configured, so there is nothing to embed.");
188841
+ case "busy":
188842
+ return terminal("skipped", `Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`);
188843
+ case "aborted":
188844
+ return terminal("done", `Embedded ${outcome.embedded} of ${outcome.total} compartments before stopping.`);
188845
+ case "stalled":
188846
+ return terminal("skipped", `Embedded ${outcome.embedded} compartments; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed-history again to retry them.`);
188847
+ default:
188848
+ return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`);
188849
+ }
188850
+ };
186877
188851
  const sidekickRunnable = isSidekickRunnable(deps.config);
186878
188852
  const sidekickConfig = sidekickRunnable ? deps.config.sidekick : undefined;
186879
188853
  const transform2 = createTransform({
@@ -187028,6 +189002,7 @@ function createMagicContextHook(deps) {
187028
189002
  },
187029
189003
  executeRecomp: historianRunnable ? async (sessionId, options) => runManagedRecomp(buildManagedRecompCtx(sessionId), sessionId, options) : undefined,
187030
189004
  runUpgrade: historianRunnable ? async (sessionId) => runManagedUpgrade(buildManagedRecompCtx(sessionId), sessionId) : undefined,
189005
+ executeEmbedHistory,
187031
189006
  sendNotification: async (sessionId, text, params) => {
187032
189007
  await sendIgnoredMessage(deps.client, sessionId, text, {
187033
189008
  ...getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
@@ -188166,6 +190141,7 @@ init_memory();
188166
190141
  init_embedding();
188167
190142
  init_embedding_cache();
188168
190143
  init_normalize_hash();
190144
+ init_workspaces();
188169
190145
  init_logger();
188170
190146
  await init_storage();
188171
190147
  import { tool as tool2 } from "@opencode-ai/plugin";
@@ -188325,6 +190301,23 @@ function createCtxMemoryTool(deps) {
188325
190301
  }
188326
190302
  const projectPath = deps.resolveProjectPath(toolContext.directory);
188327
190303
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
190304
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectPath);
190305
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
190306
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
190307
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? projectIdentityForStoredPath(rawProjectPath) : projectIdentityForStoredPath(rawProjectPath);
190308
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectPath) : null;
190309
+ const memoryVisibleToTool = (memory) => {
190310
+ if (workspaceIdentitySet.identities.length <= 1) {
190311
+ return memoryBelongsToProject(memory, projectPath);
190312
+ }
190313
+ if (!storedPathBelongsToWorkspace(memory.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
190314
+ return false;
190315
+ }
190316
+ const isOwn = targetIdentityForStoredPath(memory.projectPath) === projectPath;
190317
+ if (isOwn)
190318
+ return true;
190319
+ return toolShareCategories === null || toolShareCategories.includes(memory.category);
190320
+ };
188328
190321
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
188329
190322
  if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188330
190323
  return getDisabledMessage();
@@ -188381,18 +190374,18 @@ function createCtxMemoryTool(deps) {
188381
190374
  }
188382
190375
  const rawProjectPath = projectPathForMemoryId(deps.db, updateId);
188383
190376
  const memory = getMemoryById(deps.db, updateId);
188384
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
190377
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
188385
190378
  return `Error: Memory with ID ${updateId} was not found.`;
188386
190379
  }
188387
190380
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
188388
190381
  return inactiveMemoryError(updateId, "updating");
188389
190382
  }
188390
190383
  const normalizedHash = computeNormalizedHash(content);
188391
- const duplicate = getMemoryByHash(deps.db, projectPath, memory.category, normalizedHash);
190384
+ const duplicate = getMemoryByHash(deps.db, targetIdentityForStoredPath(rawProjectPath), memory.category, normalizedHash);
188392
190385
  if (duplicate && duplicate.id !== memory.id) {
188393
190386
  return `Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`;
188394
190387
  }
188395
- const projectIdentity = projectIdentityForStoredPath(rawProjectPath);
190388
+ const projectIdentity = targetIdentityForStoredPath(rawProjectPath);
188396
190389
  deps.db.transaction(() => {
188397
190390
  updateMemoryContentInCurrentTransaction(deps.db, memory, content, normalizedHash);
188398
190391
  queueMemoryMutation(deps.db, {
@@ -188406,7 +190399,7 @@ function createCtxMemoryTool(deps) {
188406
190399
  queueMemoryEmbedding({
188407
190400
  deps,
188408
190401
  sessionId: toolContext.sessionID,
188409
- projectPath,
190402
+ projectPath: projectIdentity,
188410
190403
  memoryId: memory.id,
188411
190404
  content
188412
190405
  });
@@ -188429,7 +190422,7 @@ function createCtxMemoryTool(deps) {
188429
190422
  return "Error: One or more source memories were not found.";
188430
190423
  }
188431
190424
  if (toolContext.agent !== DREAMER_AGENT) {
188432
- const foreign = sourceMemories.find((memory) => !memoryBelongsToProject(memory, projectPath));
190425
+ const foreign = sourceMemories.find((memory) => !memoryVisibleToTool(memory));
188433
190426
  if (foreign) {
188434
190427
  return `Error: Memory with ID ${foreign.id} was not found.`;
188435
190428
  }
@@ -188513,15 +190506,16 @@ function createCtxMemoryTool(deps) {
188513
190506
  return `Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`;
188514
190507
  }
188515
190508
  if (args.action === "archive") {
188516
- const archiveIds = args.ids;
188517
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
190509
+ const rawArchiveIds = args.ids;
190510
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
188518
190511
  return "Error: 'ids' must contain at least one integer memory ID when action is 'archive'.";
188519
190512
  }
190513
+ const archiveIds = [...new Set(rawArchiveIds)];
188520
190514
  const targets = [];
188521
190515
  for (const memoryId of archiveIds) {
188522
190516
  const rawProjectPath = projectPathForMemoryId(deps.db, memoryId);
188523
190517
  const memory = getMemoryById(deps.db, memoryId);
188524
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
190518
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
188525
190519
  return `Error: Memory with ID ${memoryId} was not found.`;
188526
190520
  }
188527
190521
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -188529,7 +190523,7 @@ function createCtxMemoryTool(deps) {
188529
190523
  }
188530
190524
  targets.push({
188531
190525
  memoryId,
188532
- projectIdentity: projectIdentityForStoredPath(rawProjectPath)
190526
+ projectIdentity: targetIdentityForStoredPath(rawProjectPath)
188533
190527
  });
188534
190528
  }
188535
190529
  deps.db.transaction(() => {
@@ -189003,8 +190997,9 @@ function formatAge2(committedAtMs) {
189003
190997
  }
189004
190998
  function formatResult(result, index) {
189005
190999
  if (result.source === "memory") {
191000
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
189006
191001
  return [
189007
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
191002
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
189008
191003
  result.content
189009
191004
  ].join(`
189010
191005
  `);
@@ -189014,6 +191009,13 @@ function formatResult(result, index) {
189014
191009
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
189015
191010
  result.content
189016
191011
  ].join(`
191012
+ `);
191013
+ }
191014
+ if (result.source === "compartment") {
191015
+ return [
191016
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
191017
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
191018
+ ].join(`
189017
191019
  `);
189018
191020
  }
189019
191021
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -189029,7 +191031,7 @@ function formatSearchResults(query, results) {
189029
191031
  return `No results found for "${query}" across memories, git commits, or message history.`;
189030
191032
  }
189031
191033
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
189032
- if (results.some((result) => result.source === "message")) {
191034
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
189033
191035
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
189034
191036
  }
189035
191037
  const body = bodyParts.join(`
@@ -189187,11 +191189,11 @@ import { createServer } from "node:http";
189187
191189
  import { dirname as dirname5 } from "node:path";
189188
191190
 
189189
191191
  // src/shared/rpc-utils.ts
189190
- import { createHash as createHash11 } from "node:crypto";
191192
+ import { createHash as createHash13 } from "node:crypto";
189191
191193
  import { join as join20 } from "node:path";
189192
191194
  function projectHash(directory) {
189193
191195
  const normalized = directory.replace(/\/+$/, "");
189194
- return createHash11("sha256").update(normalized).digest("hex").slice(0, 16);
191196
+ return createHash13("sha256").update(normalized).digest("hex").slice(0, 16);
189195
191197
  }
189196
191198
  function rpcPortDir(storageDir, directory) {
189197
191199
  return join20(storageDir, "rpc", projectHash(directory));