@cortexkit/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 (86) hide show
  1. package/README.md +6 -0
  2. package/dist/config/schema/magic-context.d.ts +10 -3
  3. package/dist/config/schema/magic-context.d.ts.map +1 -1
  4. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  5. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +70 -0
  6. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -0
  7. package/dist/features/magic-context/compartment-embedding.d.ts +22 -26
  8. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  9. package/dist/features/magic-context/memory/embedding-backfill.d.ts +3 -2
  10. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  11. package/dist/features/magic-context/memory/embedding-cache.d.ts +3 -2
  12. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -1
  13. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -1
  14. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts +3 -0
  16. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  17. package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -0
  18. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  19. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  20. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  21. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +5 -1
  22. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  23. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -7
  24. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  25. package/dist/features/magic-context/memory/storage-memory.d.ts +18 -12
  26. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  27. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  28. package/dist/features/magic-context/project-embedding-registry.d.ts +53 -0
  29. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  30. package/dist/features/magic-context/search.d.ts +14 -1
  31. package/dist/features/magic-context/search.d.ts.map +1 -1
  32. package/dist/features/magic-context/session-project-storage.d.ts +17 -0
  33. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -0
  34. package/dist/features/magic-context/storage-db.d.ts +1 -1
  35. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  36. package/dist/features/magic-context/storage-memory-mutation-log.d.ts +2 -0
  37. package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage-meta-persisted.d.ts +16 -0
  39. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  41. package/dist/features/magic-context/storage-meta-shared.d.ts +3 -1
  42. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  43. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  44. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  45. package/dist/features/magic-context/storage.d.ts +3 -2
  46. package/dist/features/magic-context/storage.d.ts.map +1 -1
  47. package/dist/features/magic-context/types.d.ts +1 -0
  48. package/dist/features/magic-context/types.d.ts.map +1 -1
  49. package/dist/features/magic-context/workspaces.d.ts +20 -0
  50. package/dist/features/magic-context/workspaces.d.ts.map +1 -0
  51. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/command-handler.d.ts +5 -0
  53. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/compartment-runner-types.d.ts +11 -1
  58. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/inject-compartments.d.ts +23 -3
  62. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -1
  64. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/sentinel.d.ts +33 -28
  67. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/strip-content.d.ts +34 -17
  69. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/strip-structural-noise.d.ts +5 -7
  71. package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
  72. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +4 -5
  73. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  75. package/dist/index.js +2304 -302
  76. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  77. package/dist/shared/announcement.d.ts +1 -1
  78. package/dist/shared/rpc-types.d.ts +1 -1
  79. package/dist/shared/rpc-types.d.ts.map +1 -1
  80. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  81. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/shared/announcement.ts +6 -6
  84. package/src/shared/rpc-types.ts +1 -1
  85. package/src/tui/index.tsx +5 -3
  86. 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" };
@@ -15841,7 +15844,7 @@ function isSessionMetaRow(row) {
15841
15844
  if (row === null || typeof row !== "object")
15842
15845
  return false;
15843
15846
  const r = row;
15844
- 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);
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) && 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);
15845
15848
  }
15846
15849
  function getDefaultSessionMeta(sessionId) {
15847
15850
  return {
@@ -15868,6 +15871,7 @@ function getDefaultSessionMeta(sessionId) {
15868
15871
  cachedM0Bytes: null,
15869
15872
  cachedM1Bytes: null,
15870
15873
  cachedM0ProjectMemoryEpoch: null,
15874
+ cachedM0WorkspaceFingerprint: null,
15871
15875
  cachedM0ProjectUserProfileVersion: null,
15872
15876
  cachedM0MaxCompartmentSeq: null,
15873
15877
  cachedM0MaxMemoryId: null,
@@ -15930,6 +15934,7 @@ function toSessionMeta(row) {
15930
15934
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
15931
15935
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
15932
15936
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
15937
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
15933
15938
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
15934
15939
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
15935
15940
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -15960,6 +15965,7 @@ function persistCachedM0(db, sessionId, payload) {
15960
15965
  db.prepare(`UPDATE session_meta SET
15961
15966
  cached_m0_bytes = ?,
15962
15967
  cached_m0_project_memory_epoch = ?,
15968
+ cached_m0_workspace_fingerprint = ?,
15963
15969
  cached_m0_project_user_profile_version = ?,
15964
15970
  cached_m0_max_compartment_seq = ?,
15965
15971
  cached_m0_max_memory_id = ?,
@@ -15972,7 +15978,7 @@ function persistCachedM0(db, sessionId, payload) {
15972
15978
  cached_m0_upgrade_state = ?,
15973
15979
  cached_m0_system_hash = ?,
15974
15980
  cached_m0_model_key = ?
15975
- 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);
15981
+ 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);
15976
15982
  }
15977
15983
  function clearCachedM0M1(db, sessionId) {
15978
15984
  ensureSessionMetaRow(db, sessionId);
@@ -15981,6 +15987,7 @@ function clearCachedM0M1(db, sessionId) {
15981
15987
  ["cached_m0_bytes", null],
15982
15988
  ["cached_m1_bytes", null],
15983
15989
  ["cached_m0_project_memory_epoch", null],
15990
+ ["cached_m0_workspace_fingerprint", null],
15984
15991
  ["cached_m0_project_user_profile_version", null],
15985
15992
  ["cached_m0_max_compartment_seq", null],
15986
15993
  ["cached_m0_max_memory_id", null],
@@ -16036,6 +16043,7 @@ var init_storage_meta_shared = __esm(() => {
16036
16043
  "cached_m0_bytes",
16037
16044
  "cached_m1_bytes",
16038
16045
  "cached_m0_project_memory_epoch",
16046
+ "cached_m0_workspace_fingerprint",
16039
16047
  "cached_m0_project_user_profile_version",
16040
16048
  "cached_m0_max_compartment_seq",
16041
16049
  "cached_m0_max_memory_id",
@@ -16083,6 +16091,7 @@ var init_storage_meta_shared = __esm(() => {
16083
16091
  cachedM0Bytes: "cached_m0_bytes",
16084
16092
  cachedM1Bytes: "cached_m1_bytes",
16085
16093
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
16094
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
16086
16095
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
16087
16096
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
16088
16097
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -16112,6 +16121,7 @@ var init_storage_meta_shared = __esm(() => {
16112
16121
  "cachedM0Bytes",
16113
16122
  "cachedM1Bytes",
16114
16123
  "cachedM0ProjectMemoryEpoch",
16124
+ "cachedM0WorkspaceFingerprint",
16115
16125
  "cachedM0ProjectUserProfileVersion",
16116
16126
  "cachedM0MaxCompartmentSeq",
16117
16127
  "cachedM0MaxMemoryId",
@@ -150823,6 +150833,35 @@ function initializeDatabase(db) {
150823
150833
  );
150824
150834
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
150825
150835
 
150836
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
150837
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
150838
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
150839
+ session_id TEXT NOT NULL,
150840
+ project_path TEXT NOT NULL,
150841
+ harness TEXT NOT NULL DEFAULT 'opencode',
150842
+ window_index INTEGER NOT NULL DEFAULT 0,
150843
+ start_ordinal INTEGER NOT NULL,
150844
+ end_ordinal INTEGER NOT NULL,
150845
+ chunk_hash TEXT NOT NULL,
150846
+ model_id TEXT NOT NULL,
150847
+ dims INTEGER NOT NULL,
150848
+ vector BLOB NOT NULL,
150849
+ created_at INTEGER NOT NULL,
150850
+ UNIQUE(compartment_id, window_index)
150851
+ );
150852
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
150853
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
150854
+
150855
+ CREATE TABLE IF NOT EXISTS session_projects (
150856
+ session_id TEXT NOT NULL,
150857
+ harness TEXT NOT NULL DEFAULT 'opencode',
150858
+ project_path TEXT NOT NULL,
150859
+ updated_at INTEGER NOT NULL,
150860
+ PRIMARY KEY(session_id, harness)
150861
+ );
150862
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
150863
+ ON session_projects(project_path);
150864
+
150826
150865
  CREATE TABLE IF NOT EXISTS compartment_events (
150827
150866
  id INTEGER PRIMARY KEY AUTOINCREMENT,
150828
150867
  session_id TEXT NOT NULL,
@@ -151008,6 +151047,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151008
151047
  rekeyed_at INTEGER NOT NULL
151009
151048
  );
151010
151049
 
151050
+ CREATE TABLE IF NOT EXISTS workspaces (
151051
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151052
+ name TEXT NOT NULL UNIQUE,
151053
+ created_at INTEGER NOT NULL,
151054
+ updated_at INTEGER NOT NULL,
151055
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151056
+ );
151057
+
151058
+ CREATE TABLE IF NOT EXISTS workspace_members (
151059
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151060
+ project_path TEXT NOT NULL,
151061
+ display_name TEXT NOT NULL,
151062
+ display_path TEXT NOT NULL,
151063
+ added_at INTEGER NOT NULL,
151064
+ PRIMARY KEY (workspace_id, project_path)
151065
+ );
151066
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151067
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151068
+
151011
151069
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151012
151070
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151013
151071
  table_name TEXT NOT NULL,
@@ -151119,6 +151177,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151119
151177
  deferred_execute_state TEXT,
151120
151178
  cached_m0_bytes BLOB,
151121
151179
  cached_m0_project_memory_epoch INTEGER,
151180
+ cached_m0_workspace_fingerprint TEXT,
151122
151181
  cached_m0_project_user_profile_version INTEGER,
151123
151182
  cached_m0_max_compartment_seq INTEGER,
151124
151183
  cached_m0_max_memory_id INTEGER,
@@ -151277,6 +151336,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151277
151336
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151278
151337
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151279
151338
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151339
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
151280
151340
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
151281
151341
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
151282
151342
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -151330,6 +151390,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151330
151390
  ensureColumn(db, "memories", "importance", "INTEGER");
151331
151391
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
151332
151392
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
151393
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
151333
151394
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
151334
151395
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
151335
151396
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -151361,6 +151422,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151361
151422
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
151362
151423
  updated_at INTEGER NOT NULL DEFAULT 0
151363
151424
  );
151425
+ CREATE TABLE IF NOT EXISTS session_projects (
151426
+ session_id TEXT NOT NULL,
151427
+ harness TEXT NOT NULL DEFAULT 'opencode',
151428
+ project_path TEXT NOT NULL,
151429
+ updated_at INTEGER NOT NULL,
151430
+ PRIMARY KEY(session_id, harness)
151431
+ );
151432
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
151433
+ ON session_projects(project_path);
151364
151434
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
151365
151435
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151366
151436
  session_id TEXT NOT NULL,
@@ -151388,6 +151458,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151388
151458
  new_project_path TEXT NOT NULL,
151389
151459
  rekeyed_at INTEGER NOT NULL
151390
151460
  );
151461
+ CREATE TABLE IF NOT EXISTS workspaces (
151462
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151463
+ name TEXT NOT NULL UNIQUE,
151464
+ created_at INTEGER NOT NULL,
151465
+ updated_at INTEGER NOT NULL,
151466
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151467
+ );
151468
+ CREATE TABLE IF NOT EXISTS workspace_members (
151469
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151470
+ project_path TEXT NOT NULL,
151471
+ display_name TEXT NOT NULL,
151472
+ display_path TEXT NOT NULL,
151473
+ added_at INTEGER NOT NULL,
151474
+ PRIMARY KEY (workspace_id, project_path)
151475
+ );
151476
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151477
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151391
151478
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151392
151479
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151393
151480
  table_name TEXT NOT NULL,
@@ -151409,6 +151496,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151409
151496
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151410
151497
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151411
151498
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151499
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
151412
151500
  }
151413
151501
  function healAllNullColumns(db) {
151414
151502
  healNullTextColumns(db);
@@ -151446,6 +151534,7 @@ function healNullTextColumns(db) {
151446
151534
  ["system_prompt_hash", ""],
151447
151535
  ["stripped_placeholder_ids", ""],
151448
151536
  ["stale_reduce_stripped_ids", ""],
151537
+ ["processed_image_stripped_ids", ""],
151449
151538
  ["memory_block_cache", ""],
151450
151539
  ["memory_block_ids", ""],
151451
151540
  ["compaction_marker_state", ""],
@@ -151490,7 +151579,7 @@ function healNullIntegerColumns(db) {
151490
151579
  }
151491
151580
  }
151492
151581
  function ensureColumn(db, table, column, definition) {
151493
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
151582
+ if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
151494
151583
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
151495
151584
  }
151496
151585
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -151576,7 +151665,7 @@ function getDatabasePersistenceError(db) {
151576
151665
  return null;
151577
151666
  return persistenceErrorByDatabase.get(db) ?? null;
151578
151667
  }
151579
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 32, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151668
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 36, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151580
151669
  var init_storage_db = __esm(async () => {
151581
151670
  init_data_path();
151582
151671
  init_logger();
@@ -151596,10 +151685,302 @@ var init_storage_db = __esm(async () => {
151596
151685
  };
151597
151686
  });
151598
151687
 
151688
+ // src/features/magic-context/memory/constants.ts
151689
+ var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
151690
+ var init_constants = __esm(() => {
151691
+ V2_MEMORY_CATEGORIES = [
151692
+ "PROJECT_RULES",
151693
+ "ARCHITECTURE",
151694
+ "CONSTRAINTS",
151695
+ "CONFIG_VALUES",
151696
+ "NAMING"
151697
+ ];
151698
+ PROMOTABLE_CATEGORIES = [
151699
+ "PROJECT_RULES",
151700
+ "ARCHITECTURE",
151701
+ "CONSTRAINTS",
151702
+ "CONFIG_VALUES",
151703
+ "NAMING",
151704
+ "ARCHITECTURE_DECISIONS",
151705
+ "CONFIG_DEFAULTS",
151706
+ "USER_PREFERENCES",
151707
+ "USER_DIRECTIVES",
151708
+ "ENVIRONMENT",
151709
+ "WORKFLOW_RULES",
151710
+ "KNOWN_ISSUES"
151711
+ ];
151712
+ CATEGORY_PRIORITY = [
151713
+ "PROJECT_RULES",
151714
+ "ARCHITECTURE",
151715
+ "CONSTRAINTS",
151716
+ "CONFIG_VALUES",
151717
+ "NAMING",
151718
+ "USER_DIRECTIVES",
151719
+ "USER_PREFERENCES",
151720
+ "CONFIG_DEFAULTS",
151721
+ "ARCHITECTURE_DECISIONS",
151722
+ "ENVIRONMENT",
151723
+ "WORKFLOW_RULES",
151724
+ "KNOWN_ISSUES"
151725
+ ];
151726
+ MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
151727
+ acc[category] = index;
151728
+ return acc;
151729
+ }, {});
151730
+ MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
151731
+ CATEGORY_DEFAULT_TTL = {
151732
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
151733
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
151734
+ };
151735
+ });
151736
+
151737
+ // src/features/magic-context/project-identity.ts
151738
+ var init_project_identity2 = __esm(() => {
151739
+ init_project_identity();
151740
+ });
151741
+
151742
+ // src/features/magic-context/workspaces.ts
151743
+ import { createHash as createHash4 } from "node:crypto";
151744
+ function tableExists(db, tableName) {
151745
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
151746
+ return Boolean(row);
151747
+ }
151748
+ function columnExists(db, tableName, columnName) {
151749
+ const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
151750
+ return rows.some((row) => row.name === columnName);
151751
+ }
151752
+ function uniqueSorted(values) {
151753
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
151754
+ }
151755
+ function placeholders(values) {
151756
+ return values.map(() => "?").join(", ");
151757
+ }
151758
+ function normalizeShareCategories(raw) {
151759
+ if (raw === null || raw === undefined)
151760
+ return null;
151761
+ if (typeof raw !== "string")
151762
+ return null;
151763
+ let parsed;
151764
+ try {
151765
+ parsed = JSON.parse(raw);
151766
+ } catch {
151767
+ return null;
151768
+ }
151769
+ if (!Array.isArray(parsed))
151770
+ return null;
151771
+ const categories = [];
151772
+ for (const value of parsed) {
151773
+ if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
151774
+ return null;
151775
+ }
151776
+ if (!categories.includes(value))
151777
+ categories.push(value);
151778
+ }
151779
+ return categories.sort((left, right) => left.localeCompare(right));
151780
+ }
151781
+ function selectWorkspaceShareCategories(db, identities) {
151782
+ const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
151783
+ if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
151784
+ return null;
151785
+ }
151786
+ const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
151787
+ FROM workspace_members AS member
151788
+ JOIN workspaces AS workspace ON workspace.id = member.workspace_id
151789
+ WHERE member.project_path IN (${placeholders(candidates)})
151790
+ ORDER BY workspace.id ASC
151791
+ LIMIT 1`).get(...candidates);
151792
+ return normalizeShareCategories(row?.shareCategories ?? null);
151793
+ }
151794
+ function resolveWorkspaceShareCategories(db, projectIdentity) {
151795
+ return selectWorkspaceShareCategories(db, [projectIdentity]);
151796
+ }
151797
+ function resolveWorkspaceIdentitySet(db, projectIdentity) {
151798
+ if (!tableExists(db, "workspace_members")) {
151799
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151800
+ }
151801
+ const rows = db.prepare(`SELECT member.project_path AS identity, member.display_name AS displayName
151802
+ FROM workspace_members AS anchor
151803
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151804
+ WHERE anchor.project_path = ?
151805
+ ORDER BY member.display_name ASC, member.project_path ASC`).all(projectIdentity);
151806
+ if (rows.length === 0) {
151807
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151808
+ }
151809
+ const namesByIdentity = new Map;
151810
+ const identities = [];
151811
+ for (const row of rows) {
151812
+ if (typeof row.identity !== "string" || row.identity.length === 0)
151813
+ continue;
151814
+ if (identities.includes(row.identity))
151815
+ continue;
151816
+ identities.push(row.identity);
151817
+ if (typeof row.displayName === "string" && row.displayName.length > 0) {
151818
+ namesByIdentity.set(row.identity, row.displayName);
151819
+ }
151820
+ }
151821
+ return identities.length > 0 ? { identities, namesByIdentity } : { identities: [projectIdentity], namesByIdentity: new Map };
151822
+ }
151823
+ function expandWorkspaceIdentitySetWithAliases(db, identities) {
151824
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151825
+ const expanded = new Set(canonical);
151826
+ const canonicalIdentityByStoredPath = new Map;
151827
+ for (const identity of canonical) {
151828
+ canonicalIdentityByStoredPath.set(identity, identity);
151829
+ }
151830
+ if (canonical.length === 0 || !tableExists(db, "v22_identity_rekey_map")) {
151831
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151832
+ }
151833
+ const rows = db.prepare(`SELECT old_project_path AS oldProjectPath, new_project_path AS newProjectPath
151834
+ FROM v22_identity_rekey_map
151835
+ WHERE new_project_path IN (${placeholders(canonical)})
151836
+ ORDER BY old_project_path ASC`).all(...canonical);
151837
+ for (const row of rows) {
151838
+ if (typeof row.oldProjectPath !== "string" || typeof row.newProjectPath !== "string") {
151839
+ continue;
151840
+ }
151841
+ if (!canonicalIdentityByStoredPath.has(row.newProjectPath))
151842
+ continue;
151843
+ expanded.add(row.oldProjectPath);
151844
+ canonicalIdentityByStoredPath.set(row.oldProjectPath, row.newProjectPath);
151845
+ }
151846
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151847
+ }
151848
+ function resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) {
151849
+ const direct = canonicalIdentityByStoredPath.get(storedProjectPath);
151850
+ if (direct)
151851
+ return direct;
151852
+ const normalized = normalizeStoredProjectPath(storedProjectPath);
151853
+ const normalizedDirect = canonicalIdentityByStoredPath.get(normalized);
151854
+ if (normalizedDirect)
151855
+ return normalizedDirect;
151856
+ if (memberIdentities.includes(normalized))
151857
+ return normalized;
151858
+ for (const identity of memberIdentities) {
151859
+ if (storedPathBelongsToIdentity(storedProjectPath, identity)) {
151860
+ return identity;
151861
+ }
151862
+ }
151863
+ return null;
151864
+ }
151865
+ function storedPathBelongsToWorkspace(storedProjectPath, memberIdentities, expandedIdentities, canonicalIdentityByStoredPath) {
151866
+ if (expandedIdentities.includes(storedProjectPath))
151867
+ return true;
151868
+ return resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) !== null;
151869
+ }
151870
+ function sourceNameForMemory(storedProjectPath, ownIdentity, memberIdentities, namesByIdentity, canonicalIdentityByStoredPath) {
151871
+ const canonicalIdentity = resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath);
151872
+ if (!canonicalIdentity || canonicalIdentity === ownIdentity)
151873
+ return;
151874
+ return namesByIdentity.get(canonicalIdentity);
151875
+ }
151876
+ function getEpochMap(db, identities) {
151877
+ if (identities.length === 0)
151878
+ return new Map;
151879
+ const rows = db.prepare(`SELECT project_path AS projectPath, project_memory_epoch AS epoch
151880
+ FROM project_state
151881
+ WHERE project_path IN (${placeholders(identities)})`).all(...identities);
151882
+ const epochs = new Map;
151883
+ for (const row of rows) {
151884
+ if (typeof row.projectPath !== "string" || typeof row.epoch !== "number")
151885
+ continue;
151886
+ epochs.set(row.projectPath, row.epoch);
151887
+ }
151888
+ return epochs;
151889
+ }
151890
+ function computeWorkspaceEpochFingerprint(db, identities) {
151891
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151892
+ const epochs = getEpochMap(db, canonical);
151893
+ const shareCategories = selectWorkspaceShareCategories(db, canonical);
151894
+ const hash2 = createHash4("sha256");
151895
+ hash2.update("share_categories", "utf8");
151896
+ hash2.update("\x00");
151897
+ hash2.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
151898
+ hash2.update(`
151899
+ `);
151900
+ for (const identity of canonical) {
151901
+ hash2.update(identity, "utf8");
151902
+ hash2.update("\x00");
151903
+ hash2.update(String(epochs.get(identity) ?? 0), "utf8");
151904
+ hash2.update(`
151905
+ `);
151906
+ }
151907
+ return hash2.digest("hex");
151908
+ }
151909
+ function isInTransaction(db) {
151910
+ const candidate = db;
151911
+ return candidate.inTransaction === true || candidate.isTransaction === true;
151912
+ }
151913
+ function workspaceMembersForIdentity(db, identity) {
151914
+ if (!tableExists(db, "workspace_members"))
151915
+ return [identity];
151916
+ const rows = db.prepare(`SELECT member.project_path AS identity
151917
+ FROM workspace_members AS anchor
151918
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151919
+ WHERE anchor.project_path = ?
151920
+ ORDER BY member.project_path ASC`).all(identity);
151921
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((value) => value.length > 0);
151922
+ return identities.length > 0 ? uniqueSorted(identities) : [identity];
151923
+ }
151924
+ function bumpEpochRows(db, identities, now) {
151925
+ const stmt = db.prepare(`INSERT INTO project_state
151926
+ (project_path, project_memory_epoch, project_user_profile_version, updated_at)
151927
+ VALUES (?, 1, 0, ?)
151928
+ ON CONFLICT(project_path) DO UPDATE SET
151929
+ project_memory_epoch = project_memory_epoch + 1,
151930
+ updated_at = excluded.updated_at`);
151931
+ for (const identity of uniqueSorted(identities)) {
151932
+ stmt.run(identity, now);
151933
+ }
151934
+ }
151935
+ function bumpEpochsForWorkspaceMembers(db, identity, now = Date.now()) {
151936
+ const run = () => bumpEpochRows(db, workspaceMembersForIdentity(db, identity), now);
151937
+ if (isInTransaction(db)) {
151938
+ run();
151939
+ return;
151940
+ }
151941
+ db.exec("BEGIN IMMEDIATE");
151942
+ try {
151943
+ run();
151944
+ db.exec("COMMIT");
151945
+ } catch (error51) {
151946
+ try {
151947
+ db.exec("ROLLBACK");
151948
+ } catch {}
151949
+ throw error51;
151950
+ }
151951
+ }
151952
+ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
151953
+ const run = () => bumpEpochRows(db, identities, now);
151954
+ if (isInTransaction(db)) {
151955
+ run();
151956
+ return;
151957
+ }
151958
+ db.exec("BEGIN IMMEDIATE");
151959
+ try {
151960
+ run();
151961
+ db.exec("COMMIT");
151962
+ } catch (error51) {
151963
+ try {
151964
+ db.exec("ROLLBACK");
151965
+ } catch {}
151966
+ throw error51;
151967
+ }
151968
+ }
151969
+ var VALID_SHARE_CATEGORIES;
151970
+ var init_workspaces = __esm(() => {
151971
+ init_constants();
151972
+ init_project_identity2();
151973
+ VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
151974
+ });
151975
+
151599
151976
  // src/features/magic-context/migrations.ts
151600
- function tableExists(db, name2) {
151977
+ function tableExists2(db, name2) {
151601
151978
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
151602
151979
  }
151980
+ function columnExists2(db, table, column) {
151981
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
151982
+ return rows.some((row) => row.name === column);
151983
+ }
151603
151984
  function ensureMigrationsTable(db) {
151604
151985
  db.exec(`
151605
151986
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -151668,6 +152049,7 @@ function runMigrations(db) {
151668
152049
  var MIGRATIONS, LATEST_MIGRATION_VERSION;
151669
152050
  var init_migrations = __esm(async () => {
151670
152051
  init_logger();
152052
+ init_workspaces();
151671
152053
  await init_storage_db();
151672
152054
  MIGRATIONS = [
151673
152055
  {
@@ -152127,9 +152509,9 @@ var init_migrations = __esm(async () => {
152127
152509
  version: 22,
152128
152510
  description: "v2.0 cache architecture schema foundation",
152129
152511
  up: (db) => {
152130
- const hasSessionMetaTable = tableExists(db, "session_meta");
152131
- const hasCompartmentsTable = tableExists(db, "compartments");
152132
- const hasMemoriesTable = tableExists(db, "memories");
152512
+ const hasSessionMetaTable = tableExists2(db, "session_meta");
152513
+ const hasCompartmentsTable = tableExists2(db, "compartments");
152514
+ const hasMemoriesTable = tableExists2(db, "memories");
152133
152515
  if (hasSessionMetaTable) {
152134
152516
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
152135
152517
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
@@ -152154,7 +152536,7 @@ var init_migrations = __esm(async () => {
152154
152536
  ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
152155
152537
  ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
152156
152538
  }
152157
- const hasRecompCompartmentsTable = tableExists(db, "recomp_compartments");
152539
+ const hasRecompCompartmentsTable = tableExists2(db, "recomp_compartments");
152158
152540
  if (hasRecompCompartmentsTable) {
152159
152541
  ensureColumn(db, "recomp_compartments", "p1", "TEXT");
152160
152542
  ensureColumn(db, "recomp_compartments", "p2", "TEXT");
@@ -152465,13 +152847,163 @@ var init_migrations = __esm(async () => {
152465
152847
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
152466
152848
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
152467
152849
  }
152850
+ },
152851
+ {
152852
+ version: 33,
152853
+ description: "Compartment chunk embeddings for semantic message-history search",
152854
+ up: (db) => {
152855
+ db.exec(`
152856
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
152857
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152858
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
152859
+ session_id TEXT NOT NULL,
152860
+ project_path TEXT NOT NULL,
152861
+ harness TEXT NOT NULL DEFAULT 'opencode',
152862
+ window_index INTEGER NOT NULL DEFAULT 0,
152863
+ start_ordinal INTEGER NOT NULL,
152864
+ end_ordinal INTEGER NOT NULL,
152865
+ chunk_hash TEXT NOT NULL,
152866
+ model_id TEXT NOT NULL,
152867
+ dims INTEGER NOT NULL,
152868
+ vector BLOB NOT NULL,
152869
+ created_at INTEGER NOT NULL,
152870
+ UNIQUE(compartment_id, window_index)
152871
+ );
152872
+ CREATE INDEX IF NOT EXISTS idx_cce_session
152873
+ ON compartment_chunk_embeddings(session_id);
152874
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model
152875
+ ON compartment_chunk_embeddings(project_path, model_id);
152876
+ `);
152877
+ }
152878
+ },
152879
+ {
152880
+ version: 34,
152881
+ description: "workspace tables and m[0] workspace fingerprint cache reset",
152882
+ up: (db) => {
152883
+ db.exec(`
152884
+ CREATE TABLE IF NOT EXISTS workspaces (
152885
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152886
+ name TEXT NOT NULL UNIQUE,
152887
+ created_at INTEGER NOT NULL,
152888
+ updated_at INTEGER NOT NULL
152889
+ );
152890
+ CREATE TABLE IF NOT EXISTS workspace_members (
152891
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
152892
+ project_path TEXT NOT NULL,
152893
+ display_name TEXT NOT NULL,
152894
+ display_path TEXT NOT NULL,
152895
+ added_at INTEGER NOT NULL,
152896
+ PRIMARY KEY (workspace_id, project_path)
152897
+ );
152898
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique
152899
+ ON workspace_members(project_path);
152900
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name
152901
+ ON workspace_members(workspace_id, display_name);
152902
+ `);
152903
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
152904
+ if (!hasSessionMeta)
152905
+ return;
152906
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
152907
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
152908
+ const clears = [
152909
+ ["cached_m0_bytes", null],
152910
+ ["cached_m1_bytes", null],
152911
+ ["cached_m0_project_memory_epoch", null],
152912
+ ["cached_m0_workspace_fingerprint", null],
152913
+ ["cached_m0_project_user_profile_version", null],
152914
+ ["cached_m0_max_compartment_seq", null],
152915
+ ["cached_m0_max_memory_id", null],
152916
+ ["cached_m0_max_mutation_id", null],
152917
+ ["cached_m0_max_memory_mutation_id", null],
152918
+ ["cached_m0_project_docs_hash", null],
152919
+ ["cached_m0_materialized_at", null],
152920
+ ["cached_m0_session_facts_version", null],
152921
+ ["cached_m0_upgrade_state", null],
152922
+ ["cached_m0_system_hash", null],
152923
+ ["cached_m0_tool_set_hash", null],
152924
+ ["cached_m0_model_key", null],
152925
+ ["cached_m0_last_baseline_end_message_id", null],
152926
+ ["memory_block_cache", ""],
152927
+ ["memory_block_ids", ""],
152928
+ ["memory_block_count", 0]
152929
+ ];
152930
+ const setClauses = [];
152931
+ const values = [];
152932
+ for (const [column, value] of clears) {
152933
+ if (!columns.has(column))
152934
+ continue;
152935
+ setClauses.push(`${column} = ?`);
152936
+ values.push(value);
152937
+ }
152938
+ if (setClauses.length > 0) {
152939
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")}`).run(...values);
152940
+ }
152941
+ }
152942
+ },
152943
+ {
152944
+ version: 35,
152945
+ description: "workspace per-category share defaults and epoch refresh",
152946
+ up: (db) => {
152947
+ db.exec(`
152948
+ CREATE TABLE IF NOT EXISTS workspaces (
152949
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152950
+ name TEXT NOT NULL UNIQUE,
152951
+ created_at INTEGER NOT NULL,
152952
+ updated_at INTEGER NOT NULL,
152953
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
152954
+ );
152955
+ `);
152956
+ if (!columnExists2(db, "workspaces", "share_categories")) {
152957
+ db.exec(`ALTER TABLE workspaces ADD COLUMN share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
152958
+ }
152959
+ db.prepare(`UPDATE workspaces
152960
+ SET share_categories = '["CONSTRAINTS"]'
152961
+ WHERE share_categories IS NULL OR share_categories = ''`).run();
152962
+ if (!tableExists2(db, "workspace_members"))
152963
+ return;
152964
+ const rows = db.prepare(`SELECT DISTINCT project_path AS identity
152965
+ FROM workspace_members
152966
+ WHERE project_path IS NOT NULL AND project_path <> ''
152967
+ ORDER BY project_path ASC`).all();
152968
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((identity) => identity.length > 0);
152969
+ if (identities.length > 0) {
152970
+ bumpEpochsForWorkspaceMemberSet(db, identities, Date.now());
152971
+ }
152972
+ }
152973
+ },
152974
+ {
152975
+ version: 36,
152976
+ description: "session project ownership map for compartment chunk backfill scoping",
152977
+ up: (db) => {
152978
+ db.exec(`
152979
+ CREATE TABLE IF NOT EXISTS session_projects (
152980
+ session_id TEXT NOT NULL,
152981
+ harness TEXT NOT NULL DEFAULT 'opencode',
152982
+ project_path TEXT NOT NULL,
152983
+ updated_at INTEGER NOT NULL,
152984
+ PRIMARY KEY(session_id, harness)
152985
+ );
152986
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
152987
+ ON session_projects(project_path);
152988
+ `);
152989
+ const hasChunkTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartment_chunk_embeddings'").get();
152990
+ if (hasChunkTable) {
152991
+ db.exec(`
152992
+ INSERT OR IGNORE INTO session_projects (session_id, harness, project_path, updated_at)
152993
+ SELECT session_id, harness, MIN(project_path), 0
152994
+ FROM compartment_chunk_embeddings
152995
+ GROUP BY session_id, harness
152996
+ HAVING COUNT(DISTINCT project_path) = 1;
152997
+ `);
152998
+ }
152999
+ }
152468
153000
  }
152469
153001
  ];
152470
153002
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
152471
153003
  });
152472
153004
 
152473
153005
  // src/features/magic-context/project-docs-hash.ts
152474
- import { createHash as createHash4 } from "node:crypto";
153006
+ import { createHash as createHash5 } from "node:crypto";
152475
153007
  import { lstatSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152476
153008
  import path4 from "node:path";
152477
153009
  function canonicalizeDocContent(raw) {
@@ -152561,7 +153093,7 @@ function hashCanonicalPieces(hashPieces) {
152561
153093
  if (hashPieces.length === 0) {
152562
153094
  return "";
152563
153095
  }
152564
- return createHash4("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
153096
+ return createHash5("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
152565
153097
  }
152566
153098
  function readProjectDocsCanonical(projectDirectory) {
152567
153099
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -152599,11 +153131,6 @@ var init_project_docs_hash = __esm(() => {
152599
153131
  MAX_PROJECT_DOC_BYTES = 256 * 1024;
152600
153132
  docsCache = new Map;
152601
153133
  });
152602
-
152603
- // src/features/magic-context/project-identity.ts
152604
- var init_project_identity2 = __esm(() => {
152605
- init_project_identity();
152606
- });
152607
153134
  // src/features/magic-context/storage-m0-mutation-log.ts
152608
153135
  function assertMutationType(mutationType) {
152609
153136
  if (!M0_MUTATION_TYPES.has(mutationType)) {
@@ -152688,18 +153215,13 @@ function getMemoryMutation(db, id) {
152688
153215
  WHERE id = ?`).get(id);
152689
153216
  return row ? toMemoryMutation(row) : null;
152690
153217
  }
152691
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
152692
- if (renderedMemoryIds.length === 0)
152693
- return [];
152694
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
152695
- const placeholders = uniqueIds.map(() => "?").join(", ");
152696
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
152697
- superseded_by_id, category, new_content, queued_at
152698
- FROM memory_mutation_log
152699
- WHERE project_path = ?
152700
- AND id > ?
152701
- AND target_memory_id IN (${placeholders})
152702
- ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153218
+ function uniqueProjectPaths(projectPaths) {
153219
+ return [...new Set(projectPaths.filter((path5) => path5.length > 0))];
153220
+ }
153221
+ function placeholders2(values) {
153222
+ return values.map(() => "?").join(", ");
153223
+ }
153224
+ function coalesceMutations(rows) {
152703
153225
  const chosenByTarget = new Map;
152704
153226
  for (const dbRow of rows) {
152705
153227
  const candidate = toMemoryMutation(dbRow);
@@ -152720,10 +153242,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
152720
153242
  }
152721
153243
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
152722
153244
  }
153245
+ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
153246
+ if (renderedMemoryIds.length === 0)
153247
+ return [];
153248
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153249
+ const placeholders3 = uniqueIds.map(() => "?").join(", ");
153250
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153251
+ superseded_by_id, category, new_content, queued_at
153252
+ FROM memory_mutation_log
153253
+ WHERE project_path = ?
153254
+ AND id > ?
153255
+ AND target_memory_id IN (${placeholders3})
153256
+ ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153257
+ return coalesceMutations(rows);
153258
+ }
153259
+ function getMemoryMutationsForRenderByProjects(db, projectPaths, afterId, renderedMemoryIds) {
153260
+ if (renderedMemoryIds.length === 0)
153261
+ return [];
153262
+ const identities = uniqueProjectPaths(projectPaths);
153263
+ if (identities.length === 0)
153264
+ return [];
153265
+ if (identities.length === 1) {
153266
+ return getMemoryMutationsForRender(db, identities[0], afterId, renderedMemoryIds);
153267
+ }
153268
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153269
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153270
+ superseded_by_id, category, new_content, queued_at
153271
+ FROM memory_mutation_log
153272
+ WHERE project_path IN (${placeholders2(identities)})
153273
+ AND id > ?
153274
+ AND target_memory_id IN (${placeholders2(uniqueIds)})
153275
+ ORDER BY id ASC`).all(...identities, afterId ?? 0, ...uniqueIds);
153276
+ return coalesceMutations(rows);
153277
+ }
152723
153278
  function getMaxMemoryMutationId(db, projectPath) {
152724
153279
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
152725
153280
  return row?.max_id ?? null;
152726
153281
  }
153282
+ function getMaxMemoryMutationIdForProjects(db, projectPaths) {
153283
+ const identities = uniqueProjectPaths(projectPaths);
153284
+ if (identities.length === 0)
153285
+ return null;
153286
+ if (identities.length === 1)
153287
+ return getMaxMemoryMutationId(db, identities[0]);
153288
+ const row = db.prepare(`SELECT MAX(id) AS max_id
153289
+ FROM memory_mutation_log
153290
+ WHERE project_path IN (${placeholders2(identities)})`).get(...identities);
153291
+ return row?.max_id ?? null;
153292
+ }
152727
153293
  var MEMORY_MUTATION_TYPES, TERMINAL_MUTATION_TYPES;
152728
153294
  var init_storage_memory_mutation_log = __esm(() => {
152729
153295
  MEMORY_MUTATION_TYPES = new Set(["archive", "delete", "update", "superseded"]);
@@ -153416,6 +153982,36 @@ function addStaleReduceStrippedIds(db, sessionId, ids) {
153416
153982
  sessionLog(sessionId, `stale_reduce_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
153417
153983
  return false;
153418
153984
  }
153985
+ function getProcessedImageStrippedIds(db, sessionId) {
153986
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153987
+ return new Set(parseStrippedBlob(row?.processed_image_stripped_ids));
153988
+ }
153989
+ function addProcessedImageStrippedIds(db, sessionId, ids) {
153990
+ const add = [...ids];
153991
+ if (add.length === 0)
153992
+ return true;
153993
+ ensureSessionMetaRow(db, sessionId);
153994
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
153995
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153996
+ const rawStored = row ? row.processed_image_stripped_ids ?? null : null;
153997
+ const current = new Set(parseStrippedBlob(rawStored));
153998
+ let changed = false;
153999
+ for (const id of add) {
154000
+ if (!current.has(id)) {
154001
+ current.add(id);
154002
+ changed = true;
154003
+ }
154004
+ }
154005
+ if (!changed)
154006
+ return true;
154007
+ const nextBlob = JSON.stringify([...current]);
154008
+ 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);
154009
+ if (result.changes > 0)
154010
+ return true;
154011
+ }
154012
+ sessionLog(sessionId, `processed_image_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
154013
+ return false;
154014
+ }
153419
154015
  function isPendingCompactionMarker(value) {
153420
154016
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
153421
154017
  }
@@ -153594,6 +154190,8 @@ function clearSession(db, sessionId) {
153594
154190
  db.prepare("DELETE FROM source_contents WHERE session_id = ?").run(sessionId);
153595
154191
  db.prepare("DELETE FROM tags WHERE session_id = ?").run(sessionId);
153596
154192
  db.prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
154193
+ db.prepare("DELETE FROM session_projects WHERE session_id = ?").run(sessionId);
154194
+ db.prepare("DELETE FROM compartment_chunk_embeddings WHERE session_id = ?").run(sessionId);
153597
154195
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
153598
154196
  clearCompressionDepth(db, sessionId);
153599
154197
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
@@ -153700,9 +154298,9 @@ function buildStatusClause(status) {
153700
154298
  if (statuses.length === 0) {
153701
154299
  return null;
153702
154300
  }
153703
- const placeholders = statuses.map(() => "?").join(", ");
154301
+ const placeholders3 = statuses.map(() => "?").join(", ");
153704
154302
  return {
153705
- sql: `status IN (${placeholders})`,
154303
+ sql: `status IN (${placeholders3})`,
153706
154304
  params: statuses
153707
154305
  };
153708
154306
  }
@@ -153908,19 +154506,6 @@ function getProjectState(db, projectPath) {
153908
154506
  WHERE project_path = ?`).get(projectPath);
153909
154507
  return row ? toProjectState(row) : null;
153910
154508
  }
153911
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
153912
- db.prepare(`INSERT INTO project_state
153913
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
153914
- VALUES (?, 1, 0, ?)
153915
- ON CONFLICT(project_path) DO UPDATE SET
153916
- project_memory_epoch = project_memory_epoch + 1,
153917
- updated_at = excluded.updated_at`).run(projectPath, now);
153918
- const state = getProjectState(db, projectPath);
153919
- if (!state) {
153920
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
153921
- }
153922
- return state;
153923
- }
153924
154509
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
153925
154510
  db.prepare(`INSERT INTO project_state
153926
154511
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -153956,8 +154541,8 @@ function getSourceContents(db, sessionId, tagIds) {
153956
154541
  if (tagIds.length === 0) {
153957
154542
  return new Map;
153958
154543
  }
153959
- const placeholders = tagIds.map(() => "?").join(", ");
153960
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
154544
+ const placeholders3 = tagIds.map(() => "?").join(", ");
154545
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders3})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
153961
154546
  const sources = new Map;
153962
154547
  for (const row of rows) {
153963
154548
  sources.set(row.tag_id, row.content);
@@ -154267,8 +154852,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
154267
154852
  }
154268
154853
  return all;
154269
154854
  }
154270
- const placeholders = tagNumbers.map(() => "?").join(",");
154271
- 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);
154855
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
154856
+ 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);
154272
154857
  return rows.map(toTagEntry);
154273
154858
  }
154274
154859
  function getMaxDroppedTagNumber(db, sessionId) {
@@ -154421,6 +155006,7 @@ var init_storage = __esm(async () => {
154421
155006
  init_storage_source();
154422
155007
  init_storage_tags();
154423
155008
  init_storage_v22_backfill_failures();
155009
+ init_workspaces();
154424
155010
  await __promiseAll([
154425
155011
  init_message_index(),
154426
155012
  init_migrations(),
@@ -163872,7 +164458,7 @@ function isEmbeddingRow(row) {
163872
164458
  if (row === null || typeof row !== "object")
163873
164459
  return false;
163874
164460
  const candidate = row;
163875
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
164461
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
163876
164462
  }
163877
164463
  function toFloat32Array(blob) {
163878
164464
  if (blob instanceof Uint8Array) {
@@ -163892,7 +164478,7 @@ function getSaveEmbeddingStatement(db) {
163892
164478
  function getLoadAllEmbeddingsStatement(db) {
163893
164479
  let stmt = loadAllEmbeddingsStatements.get(db);
163894
164480
  if (!stmt) {
163895
- 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");
164481
+ 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");
163896
164482
  loadAllEmbeddingsStatements.set(db, stmt);
163897
164483
  }
163898
164484
  return stmt;
@@ -163921,7 +164507,10 @@ function loadAllEmbeddings(db, projectPath) {
163921
164507
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
163922
164508
  const embeddings = new Map;
163923
164509
  for (const row of rows) {
163924
- embeddings.set(row.memoryId, toFloat32Array(row.embedding));
164510
+ embeddings.set(row.memoryId, {
164511
+ embedding: toFloat32Array(row.embedding),
164512
+ modelId: row.modelId
164513
+ });
163925
164514
  }
163926
164515
  return embeddings;
163927
164516
  }
@@ -163984,13 +164573,13 @@ var init_embedding_cache = __esm(() => {
163984
164573
  });
163985
164574
 
163986
164575
  // src/features/magic-context/memory/normalize-hash.ts
163987
- import { createHash as createHash6 } from "node:crypto";
164576
+ import { createHash as createHash7 } from "node:crypto";
163988
164577
  function normalizeMemoryContent(content) {
163989
164578
  return content.toLowerCase().replace(/\s+/g, " ").trim();
163990
164579
  }
163991
164580
  function computeNormalizedHash(content) {
163992
164581
  const normalized = normalizeMemoryContent(content);
163993
- return createHash6("md5").update(normalized).digest("hex");
164582
+ return createHash7("md5").update(normalized).digest("hex");
163994
164583
  }
163995
164584
  var init_normalize_hash = () => {};
163996
164585
 
@@ -164104,8 +164693,8 @@ function getMemoriesByProjectStatement(db, statuses) {
164104
164693
  }
164105
164694
  let stmt = statements.get(db);
164106
164695
  if (!stmt) {
164107
- const placeholders = statuses.map(() => "?").join(", ");
164108
- 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`);
164696
+ const placeholders3 = statuses.map(() => "?").join(", ");
164697
+ 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`);
164109
164698
  statements.set(db, stmt);
164110
164699
  }
164111
164700
  return stmt;
@@ -164226,6 +164815,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
164226
164815
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
164227
164816
  return rows.map(toMemory);
164228
164817
  }
164818
+ function sqlPlaceholders(values) {
164819
+ return values.map(() => "?").join(", ");
164820
+ }
164821
+ function uniqueValues(values) {
164822
+ return [...new Set(values.filter((value) => value.length > 0))];
164823
+ }
164824
+ function buildWorkspaceMemorySqlFilter(args) {
164825
+ if (args.shareCategories === null || args.shareCategories === undefined) {
164826
+ return { clause: "", params: [], active: false };
164827
+ }
164828
+ const identities = uniqueValues(args.identities);
164829
+ const identitySet = new Set(identities);
164830
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
164831
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
164832
+ if (foreignIdentities.length === 0) {
164833
+ return { clause: "", params: [], active: false };
164834
+ }
164835
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
164836
+ const shareCategories = uniqueValues([...args.shareCategories]);
164837
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
164838
+ const predicates = [];
164839
+ const params = [];
164840
+ if (ownIdentities.length > 0) {
164841
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
164842
+ params.push(...ownIdentities);
164843
+ }
164844
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
164845
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
164846
+ params.push(...foreignIdentities, ...shareCategories);
164847
+ }
164848
+ if (predicates.length === 0) {
164849
+ return { clause: " AND 0 = 1", params: [], active: true };
164850
+ }
164851
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
164852
+ }
164853
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
164854
+ const identities = uniqueValues(projectPaths);
164855
+ if (identities.length === 0 || statuses.length === 0)
164856
+ return [];
164857
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164858
+ identities,
164859
+ ownIdentities,
164860
+ shareCategories
164861
+ });
164862
+ if (identities.length === 1 && !sharingFilter.active) {
164863
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
164864
+ }
164865
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164866
+ FROM memories
164867
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164868
+ AND status IN (${sqlPlaceholders(statuses)})
164869
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164870
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164871
+ return rows.map(toMemory);
164872
+ }
164873
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
164874
+ const identities = uniqueValues(projectPaths);
164875
+ if (identities.length === 0)
164876
+ return 0;
164877
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164878
+ identities,
164879
+ ownIdentities,
164880
+ shareCategories
164881
+ });
164882
+ if (identities.length === 1 && !sharingFilter.active) {
164883
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
164884
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
164885
+ }
164886
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
164887
+ FROM memories
164888
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
164889
+ return typeof row?.max_id === "number" ? row.max_id : 0;
164890
+ }
164891
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
164892
+ const identities = uniqueValues(projectPaths);
164893
+ if (identities.length === 0)
164894
+ return [];
164895
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164896
+ identities,
164897
+ ownIdentities,
164898
+ shareCategories
164899
+ });
164900
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164901
+ FROM memories
164902
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164903
+ AND id > ?
164904
+ AND status IN ('active', 'permanent')
164905
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164906
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164907
+ return rows.map(toMemory);
164908
+ }
164229
164909
  function getAllActiveMemoriesForMigration(db, projectPath) {
164230
164910
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
164231
164911
  return rows.map(toMemory);
@@ -164323,6 +165003,7 @@ function getMemoryCountsByStatus(db, projectPath) {
164323
165003
  }
164324
165004
  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;
164325
165005
  var init_storage_memory = __esm(() => {
165006
+ init_constants();
164326
165007
  init_embedding_cache();
164327
165008
  init_normalize_hash();
164328
165009
  COLUMN_MAP = {
@@ -164428,8 +165109,8 @@ function getUserMemoryCandidates(db) {
164428
165109
  function deleteUserMemoryCandidates(db, ids) {
164429
165110
  if (ids.length === 0)
164430
165111
  return;
164431
- const placeholders = ids.map(() => "?").join(",");
164432
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
165112
+ const placeholders3 = ids.map(() => "?").join(",");
165113
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
164433
165114
  }
164434
165115
  function insertUserMemory(db, content, sourceCandidateIds) {
164435
165116
  const now = Date.now();
@@ -164462,6 +165143,444 @@ function parseUserMemoryRow(row) {
164462
165143
  };
164463
165144
  }
164464
165145
 
165146
+ // src/features/magic-context/compartment-chunk-embedding.ts
165147
+ import { createHash as createHash8 } from "node:crypto";
165148
+ function getLoadFtsRowsStatement(db) {
165149
+ let stmt = loadFtsRowsStatements.get(db);
165150
+ if (!stmt) {
165151
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
165152
+ FROM message_history_fts
165153
+ WHERE session_id = ?
165154
+ AND message_ordinal >= ?
165155
+ AND message_ordinal <= ?
165156
+ AND role IN ('user', 'assistant')
165157
+ ORDER BY message_ordinal ASC`);
165158
+ loadFtsRowsStatements.set(db, stmt);
165159
+ }
165160
+ return stmt;
165161
+ }
165162
+ function getExistingHashStatement(db, scopedToProject) {
165163
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
165164
+ let stmt = map2.get(db);
165165
+ if (!stmt) {
165166
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
165167
+ FROM compartment_chunk_embeddings
165168
+ WHERE compartment_id = ?
165169
+ AND model_id = ?
165170
+ ${scopedToProject ? "AND project_path = ?" : ""}
165171
+ ORDER BY window_index ASC`);
165172
+ map2.set(db, stmt);
165173
+ }
165174
+ return stmt;
165175
+ }
165176
+ function getDeleteByCompartmentStatement(db) {
165177
+ let stmt = deleteByCompartmentStatements.get(db);
165178
+ if (!stmt) {
165179
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
165180
+ deleteByCompartmentStatements.set(db, stmt);
165181
+ }
165182
+ return stmt;
165183
+ }
165184
+ function getInsertEmbeddingStatement(db) {
165185
+ let stmt = insertEmbeddingStatements.get(db);
165186
+ if (!stmt) {
165187
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
165188
+ compartment_id, session_id, project_path, harness, window_index,
165189
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
165190
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
165191
+ insertEmbeddingStatements.set(db, stmt);
165192
+ }
165193
+ return stmt;
165194
+ }
165195
+ function getDistinctModelStatement(db) {
165196
+ let stmt = distinctModelStatements.get(db);
165197
+ if (!stmt) {
165198
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
165199
+ FROM compartment_chunk_embeddings
165200
+ WHERE project_path = ?`);
165201
+ distinctModelStatements.set(db, stmt);
165202
+ }
165203
+ return stmt;
165204
+ }
165205
+ function getClearProjectStatement(db) {
165206
+ let stmt = clearProjectStatements.get(db);
165207
+ if (!stmt) {
165208
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
165209
+ clearProjectStatements.set(db, stmt);
165210
+ }
165211
+ return stmt;
165212
+ }
165213
+ function getClearProjectModelStatement(db) {
165214
+ let stmt = clearProjectModelStatements.get(db);
165215
+ if (!stmt) {
165216
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
165217
+ clearProjectModelStatements.set(db, stmt);
165218
+ }
165219
+ return stmt;
165220
+ }
165221
+ function getSearchRowsStatement(db, withModel) {
165222
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
165223
+ let stmt = map2.get(db);
165224
+ if (!stmt) {
165225
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
165226
+ e.session_id AS sessionId,
165227
+ c.title AS title,
165228
+ c.start_message AS compartmentStart,
165229
+ c.end_message AS compartmentEnd,
165230
+ e.window_index AS windowIndex,
165231
+ e.start_ordinal AS windowStart,
165232
+ e.end_ordinal AS windowEnd,
165233
+ e.chunk_hash AS chunkHash,
165234
+ e.model_id AS modelId,
165235
+ e.dims AS dims,
165236
+ e.vector AS vector
165237
+ FROM compartment_chunk_embeddings e
165238
+ JOIN compartments c ON c.id = e.compartment_id
165239
+ WHERE e.session_id = ?
165240
+ AND e.project_path = ?
165241
+ ${withModel ? "AND e.model_id = ?" : ""}
165242
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
165243
+ map2.set(db, stmt);
165244
+ }
165245
+ return stmt;
165246
+ }
165247
+ function isFinitePositiveInteger(value) {
165248
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
165249
+ }
165250
+ function normalizeCompartmentChunkMaxInputTokens(value) {
165251
+ if (!isFinitePositiveInteger(value)) {
165252
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
165253
+ }
165254
+ return Math.max(1, Math.floor(value));
165255
+ }
165256
+ function normalizeContent(text) {
165257
+ return text.replace(/\s+/g, " ").trim();
165258
+ }
165259
+ function formatOrdinalRange(start, end) {
165260
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
165261
+ }
165262
+ function rolePrefix(role) {
165263
+ if (role === "user")
165264
+ return "U";
165265
+ if (role === "assistant")
165266
+ return "A";
165267
+ return null;
165268
+ }
165269
+ function parseOrdinal(value) {
165270
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
165271
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
165272
+ }
165273
+ function parseCanonicalLineRange(line) {
165274
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
165275
+ if (!match)
165276
+ return null;
165277
+ const start = Number.parseInt(match[1], 10);
165278
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
165279
+ if (!Number.isFinite(start) || !Number.isFinite(end))
165280
+ return null;
165281
+ return { start, end };
165282
+ }
165283
+ function hashChunkText(text) {
165284
+ return createHash8("sha256").update(text).digest("hex");
165285
+ }
165286
+ function vectorBlob(vector) {
165287
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
165288
+ }
165289
+ function toFloat32Array2(blob) {
165290
+ if (blob instanceof Uint8Array) {
165291
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
165292
+ return new Float32Array(buffer2);
165293
+ }
165294
+ return new Float32Array(blob.slice(0));
165295
+ }
165296
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
165297
+ if (endOrdinal < startOrdinal)
165298
+ return "";
165299
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
165300
+ const lines = [];
165301
+ let current = null;
165302
+ const flush2 = () => {
165303
+ if (!current || current.parts.length === 0)
165304
+ return;
165305
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
165306
+ current = null;
165307
+ };
165308
+ for (const row of rows) {
165309
+ const ordinal = parseOrdinal(row.messageOrdinal);
165310
+ const prefix = rolePrefix(row.role);
165311
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
165312
+ if (ordinal === null || prefix === null || content.length === 0)
165313
+ continue;
165314
+ if (current && current.role === prefix) {
165315
+ current.end = ordinal;
165316
+ current.parts.push(content);
165317
+ continue;
165318
+ }
165319
+ flush2();
165320
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
165321
+ }
165322
+ flush2();
165323
+ return lines.join(`
165324
+ `);
165325
+ }
165326
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
165327
+ const lines = [];
165328
+ for (const rawLine of chunkText.split(/\r?\n/)) {
165329
+ const line = rawLine.trim();
165330
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
165331
+ if (!match)
165332
+ continue;
165333
+ const lineStart = Number.parseInt(match[2], 10);
165334
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
165335
+ if (startOrdinal != null && lineEnd < startOrdinal)
165336
+ continue;
165337
+ if (endOrdinal != null && lineStart > endOrdinal)
165338
+ continue;
165339
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
165340
+ const ordinalSpan = lineEnd - lineStart + 1;
165341
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
165342
+ if (ordinalSpan === rawParts.length) {
165343
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
165344
+ if (part.startsWith("TC:"))
165345
+ return false;
165346
+ if (startOrdinal != null && ordinal < startOrdinal)
165347
+ return false;
165348
+ if (endOrdinal != null && ordinal > endOrdinal)
165349
+ return false;
165350
+ return true;
165351
+ });
165352
+ if (retained.length === 0)
165353
+ continue;
165354
+ const retainedStart = retained[0].ordinal;
165355
+ const retainedEnd = retained[retained.length - 1].ordinal;
165356
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
165357
+ continue;
165358
+ }
165359
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
165360
+ if (parts.length === 0)
165361
+ continue;
165362
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
165363
+ }
165364
+ return lines.join(`
165365
+ `);
165366
+ }
165367
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
165368
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
165369
+ if (lines.length === 0 || endOrdinal < startOrdinal)
165370
+ return [];
165371
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
165372
+ const fullText = lines.join(`
165373
+ `);
165374
+ if (estimateTokens(fullText) <= normalizedMax) {
165375
+ return [
165376
+ {
165377
+ windowIndex: 0,
165378
+ startOrdinal,
165379
+ endOrdinal,
165380
+ text: fullText,
165381
+ chunkHash: hashChunkText(fullText)
165382
+ }
165383
+ ];
165384
+ }
165385
+ const windows = [];
165386
+ let currentLines = [];
165387
+ let currentStart = null;
165388
+ let currentEnd = null;
165389
+ let currentTokens = 0;
165390
+ const flush2 = () => {
165391
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
165392
+ return;
165393
+ const text = currentLines.join(`
165394
+ `);
165395
+ windows.push({
165396
+ windowIndex: windows.length + 1,
165397
+ startOrdinal: currentStart,
165398
+ endOrdinal: currentEnd,
165399
+ text,
165400
+ chunkHash: hashChunkText(text)
165401
+ });
165402
+ currentLines = [];
165403
+ currentStart = null;
165404
+ currentEnd = null;
165405
+ currentTokens = 0;
165406
+ };
165407
+ for (const line of lines) {
165408
+ const range = parseCanonicalLineRange(line);
165409
+ const lineStart = range?.start ?? startOrdinal;
165410
+ const lineEnd = range?.end ?? lineStart;
165411
+ const lineTokens = estimateTokens(line);
165412
+ if (currentLines.length > 0 && currentTokens + lineTokens > normalizedMax) {
165413
+ flush2();
165414
+ }
165415
+ if (currentLines.length === 0) {
165416
+ currentStart = lineStart;
165417
+ }
165418
+ currentLines.push(line);
165419
+ currentEnd = lineEnd;
165420
+ currentTokens += lineTokens;
165421
+ }
165422
+ flush2();
165423
+ return windows;
165424
+ }
165425
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
165426
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
165427
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
165428
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
165429
+ }
165430
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
165431
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
165432
+ if (existing.size !== windows.length)
165433
+ return false;
165434
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
165435
+ }
165436
+ function replaceCompartmentChunkEmbeddings(db, rows) {
165437
+ if (rows.length === 0)
165438
+ return;
165439
+ const compartmentId = rows[0].compartmentId;
165440
+ const now = Date.now();
165441
+ db.transaction(() => {
165442
+ getDeleteByCompartmentStatement(db).run(compartmentId);
165443
+ const insert = getInsertEmbeddingStatement(db);
165444
+ for (const row of rows) {
165445
+ 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);
165446
+ }
165447
+ })();
165448
+ }
165449
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
165450
+ const rows = getDistinctModelStatement(db).all(projectPath);
165451
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165452
+ }
165453
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
165454
+ if (modelId) {
165455
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
165456
+ }
165457
+ return getClearProjectStatement(db).run(projectPath).changes;
165458
+ }
165459
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
165460
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
165461
+ 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) => ({
165462
+ compartmentId: row.compartmentId,
165463
+ sessionId: row.sessionId,
165464
+ title: row.title,
165465
+ startOrdinal: row.compartmentStart,
165466
+ endOrdinal: row.compartmentEnd,
165467
+ windowIndex: row.windowIndex,
165468
+ windowStartOrdinal: row.windowStart,
165469
+ windowEndOrdinal: row.windowEnd,
165470
+ chunkHash: row.chunkHash,
165471
+ modelId: row.modelId,
165472
+ dims: row.dims,
165473
+ vector: toFloat32Array2(row.vector)
165474
+ }));
165475
+ }
165476
+ function mapBackfillCandidateRows(rows) {
165477
+ return rows.filter((row) => {
165478
+ if (row === null || typeof row !== "object")
165479
+ return false;
165480
+ const candidate = row;
165481
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
165482
+ }).map((row) => ({
165483
+ id: row.id,
165484
+ sessionId: row.sessionId,
165485
+ startMessage: row.startMessage,
165486
+ endMessage: row.endMessage,
165487
+ title: row.title
165488
+ }));
165489
+ }
165490
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
165491
+ if (excludeIds && excludeIds.length > 0) {
165492
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
165493
+ const stmt2 = db.prepare(`SELECT c.id AS id,
165494
+ c.session_id AS sessionId,
165495
+ c.start_message AS startMessage,
165496
+ c.end_message AS endMessage,
165497
+ c.title AS title
165498
+ FROM compartments c
165499
+ JOIN session_projects sp
165500
+ ON sp.session_id = c.session_id
165501
+ AND sp.harness = c.harness
165502
+ AND sp.project_path = ?
165503
+ WHERE c.session_id = ?
165504
+ AND c.start_message IS NOT NULL
165505
+ AND c.end_message IS NOT NULL
165506
+ AND c.id NOT IN (${placeholders3})
165507
+ AND NOT EXISTS (
165508
+ SELECT 1
165509
+ FROM compartment_chunk_embeddings current
165510
+ WHERE current.compartment_id = c.id
165511
+ AND current.project_path = ?
165512
+ AND current.model_id = ?
165513
+ )
165514
+ ORDER BY c.start_message ASC, c.id ASC
165515
+ LIMIT ?`);
165516
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
165517
+ return mapBackfillCandidateRows(rows2);
165518
+ }
165519
+ let stmt = sessionBackfillCandidateStatements.get(db);
165520
+ if (!stmt) {
165521
+ stmt = db.prepare(`SELECT c.id AS id,
165522
+ c.session_id AS sessionId,
165523
+ c.start_message AS startMessage,
165524
+ c.end_message AS endMessage,
165525
+ c.title AS title
165526
+ FROM compartments c
165527
+ JOIN session_projects sp
165528
+ ON sp.session_id = c.session_id
165529
+ AND sp.harness = c.harness
165530
+ AND sp.project_path = ?
165531
+ WHERE c.session_id = ?
165532
+ AND c.start_message IS NOT NULL
165533
+ AND c.end_message IS NOT NULL
165534
+ AND NOT EXISTS (
165535
+ SELECT 1
165536
+ FROM compartment_chunk_embeddings current
165537
+ WHERE current.compartment_id = c.id
165538
+ AND current.project_path = ?
165539
+ AND current.model_id = ?
165540
+ )
165541
+ ORDER BY c.start_message ASC, c.id ASC
165542
+ LIMIT ?`);
165543
+ sessionBackfillCandidateStatements.set(db, stmt);
165544
+ }
165545
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
165546
+ return mapBackfillCandidateRows(rows);
165547
+ }
165548
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
165549
+ const row = db.prepare(`SELECT COUNT(*) AS n
165550
+ FROM compartments c
165551
+ JOIN session_projects sp
165552
+ ON sp.session_id = c.session_id
165553
+ AND sp.harness = c.harness
165554
+ AND sp.project_path = ?
165555
+ WHERE c.session_id = ?
165556
+ AND c.start_message IS NOT NULL
165557
+ AND c.end_message IS NOT NULL
165558
+ AND NOT EXISTS (
165559
+ SELECT 1
165560
+ FROM compartment_chunk_embeddings current
165561
+ WHERE current.compartment_id = c.id
165562
+ AND current.project_path = ?
165563
+ AND current.model_id = ?
165564
+ )`).get(projectPath, sessionId, projectPath, modelId);
165565
+ return typeof row?.n === "number" ? row.n : 0;
165566
+ }
165567
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
165568
+ var init_compartment_chunk_embedding = __esm(() => {
165569
+ init_read_session_formatting();
165570
+ loadFtsRowsStatements = new WeakMap;
165571
+ existingHashStatements = new WeakMap;
165572
+ existingHashByProjectStatements = new WeakMap;
165573
+ deleteByCompartmentStatements = new WeakMap;
165574
+ insertEmbeddingStatements = new WeakMap;
165575
+ distinctModelStatements = new WeakMap;
165576
+ clearProjectStatements = new WeakMap;
165577
+ clearProjectModelStatements = new WeakMap;
165578
+ searchRowsStatements = new WeakMap;
165579
+ searchRowsByModelStatements = new WeakMap;
165580
+ backfillCandidateStatements = new WeakMap;
165581
+ sessionBackfillCandidateStatements = new WeakMap;
165582
+ });
165583
+
164465
165584
  // src/features/magic-context/memory/cosine-similarity.ts
164466
165585
  function cosineSimilarity(a, b) {
164467
165586
  if (a.length !== b.length) {
@@ -164619,19 +165738,19 @@ function isArrayLikeNumber(value) {
164619
165738
  }
164620
165739
  return arr.length === 0 || typeof arr[0] === "number";
164621
165740
  }
164622
- function toFloat32Array2(values) {
165741
+ function toFloat32Array3(values) {
164623
165742
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
164624
165743
  }
164625
165744
  function extractBatchEmbeddings(result, expectedCount) {
164626
165745
  const { data } = result;
164627
165746
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
164628
- return data.map((entry) => toFloat32Array2(entry));
165747
+ return data.map((entry) => toFloat32Array3(entry));
164629
165748
  }
164630
165749
  if (!isArrayLikeNumber(data)) {
164631
165750
  log("[magic-context] embedding batch returned unexpected data shape");
164632
165751
  return Array.from({ length: expectedCount }, () => null);
164633
165752
  }
164634
- const flatData = toFloat32Array2(data);
165753
+ const flatData = toFloat32Array3(data);
164635
165754
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
164636
165755
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
164637
165756
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -164646,6 +165765,7 @@ function extractBatchEmbeddings(result, expectedCount) {
164646
165765
 
164647
165766
  class LocalEmbeddingProvider {
164648
165767
  modelId;
165768
+ maxInputTokens;
164649
165769
  model;
164650
165770
  pipeline = null;
164651
165771
  initPromise = null;
@@ -164653,8 +165773,9 @@ class LocalEmbeddingProvider {
164653
165773
  disposing = false;
164654
165774
  disposePromise = null;
164655
165775
  inFlightWaiters = [];
164656
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
165776
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
164657
165777
  this.model = model;
165778
+ this.maxInputTokens = maxInputTokens;
164658
165779
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
164659
165780
  }
164660
165781
  async initialize() {
@@ -164914,6 +166035,7 @@ function normalizeEndpoint3(endpoint) {
164914
166035
 
164915
166036
  class OpenAICompatibleEmbeddingProvider {
164916
166037
  modelId;
166038
+ maxInputTokens;
164917
166039
  endpoint;
164918
166040
  model;
164919
166041
  apiKey;
@@ -164930,11 +166052,13 @@ class OpenAICompatibleEmbeddingProvider {
164930
166052
  this.apiKey = options.apiKey?.trim() ?? "";
164931
166053
  this.inputType = options.inputType?.trim() ?? "";
164932
166054
  this.truncate = options.truncate?.trim() ?? "";
166055
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
164933
166056
  this.modelId = getEmbeddingProviderIdentity({
164934
166057
  provider: "openai-compatible",
164935
166058
  endpoint: this.endpoint,
164936
166059
  model: this.model,
164937
- ...this.apiKey ? { api_key: this.apiKey } : {}
166060
+ ...this.apiKey ? { api_key: this.apiKey } : {},
166061
+ ...this.inputType ? { input_type: this.inputType } : {}
164938
166062
  });
164939
166063
  }
164940
166064
  async initialize() {
@@ -165178,12 +166302,12 @@ function getCountEmbeddedStatement(db) {
165178
166302
  }
165179
166303
  return stmt;
165180
166304
  }
165181
- function getClearProjectStatement(db) {
165182
- let stmt = clearProjectStatements.get(db);
166305
+ function getClearProjectStatement2(db) {
166306
+ let stmt = clearProjectStatements2.get(db);
165183
166307
  if (!stmt) {
165184
166308
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
165185
166309
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
165186
- clearProjectStatements.set(db, stmt);
166310
+ clearProjectStatements2.set(db, stmt);
165187
166311
  }
165188
166312
  return stmt;
165189
166313
  }
@@ -165219,19 +166343,19 @@ function countEmbeddedCommits(db, projectPath) {
165219
166343
  return row?.count ?? 0;
165220
166344
  }
165221
166345
  function clearProjectCommitEmbeddings(db, projectPath) {
165222
- return getClearProjectStatement(db).run(projectPath).changes;
166346
+ return getClearProjectStatement2(db).run(projectPath).changes;
165223
166347
  }
165224
166348
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
165225
166349
  const rows = getDistinctModelIdStatement(db).all(projectPath);
165226
166350
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165227
166351
  }
165228
- var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements, distinctModelIdStatements;
166352
+ var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements2, distinctModelIdStatements;
165229
166353
  var init_storage_git_commit_embeddings = __esm(() => {
165230
166354
  saveStatements = new WeakMap;
165231
166355
  loadProjectStatements = new WeakMap;
165232
166356
  loadUnembeddedStatements = new WeakMap;
165233
166357
  countEmbeddedStatements = new WeakMap;
165234
- clearProjectStatements = new WeakMap;
166358
+ clearProjectStatements2 = new WeakMap;
165235
166359
  distinctModelIdStatements = new WeakMap;
165236
166360
  });
165237
166361
 
@@ -165357,13 +166481,90 @@ var init_sweep_coordinator = __esm(() => {
165357
166481
  GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
165358
166482
  });
165359
166483
 
166484
+ // src/features/magic-context/session-project-storage.ts
166485
+ function getUpsertSessionProjectStatement(db) {
166486
+ let stmt = upsertSessionProjectStatements.get(db);
166487
+ if (!stmt) {
166488
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
166489
+ VALUES (?, ?, ?, ?)
166490
+ ON CONFLICT(session_id, harness) DO UPDATE SET
166491
+ project_path = excluded.project_path,
166492
+ updated_at = excluded.updated_at
166493
+ WHERE session_projects.project_path <> excluded.project_path`);
166494
+ upsertSessionProjectStatements.set(db, stmt);
166495
+ }
166496
+ return stmt;
166497
+ }
166498
+ function getRepairSessionChunkProjectStatement(db) {
166499
+ let stmt = repairSessionChunkProjectStatements.get(db);
166500
+ if (!stmt) {
166501
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166502
+ SET project_path = ?
166503
+ WHERE session_id = ?
166504
+ AND harness = ?
166505
+ AND project_path <> ?`);
166506
+ repairSessionChunkProjectStatements.set(db, stmt);
166507
+ }
166508
+ return stmt;
166509
+ }
166510
+ function getRepairProjectChunkProjectStatement(db) {
166511
+ let stmt = repairProjectChunkProjectStatements.get(db);
166512
+ if (!stmt) {
166513
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166514
+ SET project_path = (
166515
+ SELECT sp.project_path
166516
+ FROM session_projects sp
166517
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166518
+ AND sp.harness = compartment_chunk_embeddings.harness
166519
+ LIMIT 1
166520
+ )
166521
+ WHERE EXISTS (
166522
+ SELECT 1
166523
+ FROM session_projects sp
166524
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166525
+ AND sp.harness = compartment_chunk_embeddings.harness
166526
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
166527
+ AND (
166528
+ sp.project_path = ?
166529
+ OR compartment_chunk_embeddings.project_path = ?
166530
+ )
166531
+ )`);
166532
+ repairProjectChunkProjectStatements.set(db, stmt);
166533
+ }
166534
+ return stmt;
166535
+ }
166536
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
166537
+ if (!sessionId || !projectPath)
166538
+ return;
166539
+ const harness = getHarness();
166540
+ const now = Date.now();
166541
+ db.transaction(() => {
166542
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
166543
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
166544
+ })();
166545
+ }
166546
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
166547
+ if (!projectPath)
166548
+ return 0;
166549
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
166550
+ }
166551
+ var upsertSessionProjectStatements, repairSessionChunkProjectStatements, repairProjectChunkProjectStatements;
166552
+ var init_session_project_storage = __esm(() => {
166553
+ upsertSessionProjectStatements = new WeakMap;
166554
+ repairSessionChunkProjectStatements = new WeakMap;
166555
+ repairProjectChunkProjectStatements = new WeakMap;
166556
+ });
166557
+
165360
166558
  // src/features/magic-context/project-embedding-registry.ts
165361
- import { createHash as createHash7, randomUUID } from "node:crypto";
166559
+ import { createHash as createHash9, randomUUID } from "node:crypto";
165362
166560
  function resolveEmbeddingConfig(config2) {
165363
166561
  if (!config2 || config2.provider === "local") {
165364
166562
  return {
165365
166563
  provider: "local",
165366
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
166564
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
166565
+ ...config2?.max_input_tokens ? {
166566
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166567
+ } : {}
165367
166568
  };
165368
166569
  }
165369
166570
  if (config2.provider === "openai-compatible") {
@@ -165376,7 +166577,10 @@ function resolveEmbeddingConfig(config2) {
165376
166577
  endpoint: config2.endpoint.trim(),
165377
166578
  ...apiKey ? { api_key: apiKey } : {},
165378
166579
  ...inputType ? { input_type: inputType } : {},
165379
- ...truncate ? { truncate } : {}
166580
+ ...truncate ? { truncate } : {},
166581
+ ...config2.max_input_tokens ? {
166582
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166583
+ } : {}
165380
166584
  };
165381
166585
  }
165382
166586
  return { provider: "off" };
@@ -165394,10 +166598,11 @@ function createProvider(config2) {
165394
166598
  model: config2.model,
165395
166599
  apiKey: config2.api_key,
165396
166600
  inputType: config2.input_type,
165397
- truncate: config2.truncate
166601
+ truncate: config2.truncate,
166602
+ maxInputTokens: config2.max_input_tokens
165398
166603
  });
165399
166604
  }
165400
- return new LocalEmbeddingProvider(config2.model);
166605
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165401
166606
  }
165402
166607
  function stableStringify2(value) {
165403
166608
  if (Array.isArray(value)) {
@@ -165410,7 +166615,7 @@ function stableStringify2(value) {
165410
166615
  return JSON.stringify(value);
165411
166616
  }
165412
166617
  function sha256Prefix(value, length = 16) {
165413
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
166618
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
165414
166619
  }
165415
166620
  function getRuntimeFingerprint(config2) {
165416
166621
  if (config2.provider === "off") {
@@ -165418,6 +166623,18 @@ function getRuntimeFingerprint(config2) {
165418
166623
  }
165419
166624
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
165420
166625
  }
166626
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
166627
+ if (config2.provider === "off") {
166628
+ return OFF_PROVIDER_IDENTITY;
166629
+ }
166630
+ const chunkIdentity = {
166631
+ providerIdentity,
166632
+ chunkerVersion: 1,
166633
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
166634
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
166635
+ };
166636
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
166637
+ }
165421
166638
  function sameFeatures(a, b) {
165422
166639
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
165423
166640
  }
@@ -165434,7 +166651,8 @@ function snapshotFor(registration) {
165434
166651
  features: { ...registration.features },
165435
166652
  enabled,
165436
166653
  gitCommitEnabled,
165437
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
166654
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
166655
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
165438
166656
  };
165439
166657
  }
165440
166658
  function disposeProvider(provider) {
@@ -165454,7 +166672,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
165454
166672
  }
165455
166673
  return false;
165456
166674
  }
165457
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
166675
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
165458
166676
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
165459
166677
  return false;
165460
166678
  }
@@ -165475,6 +166693,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
165475
166693
  wiped = true;
165476
166694
  }
165477
166695
  }
166696
+ if (features.memoryEnabled) {
166697
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
166698
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
166699
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
166700
+ clearChunkEmbeddingsForProject(db, projectIdentity);
166701
+ wiped = true;
166702
+ }
166703
+ }
165478
166704
  })();
165479
166705
  return wiped;
165480
166706
  }
@@ -165482,10 +166708,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165482
166708
  const resolvedConfig = resolveEmbeddingConfig(config2);
165483
166709
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
165484
166710
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
166711
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
165485
166712
  const prior = projectRegistrations.get(projectIdentity);
165486
166713
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
165487
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
165488
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
166714
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
166715
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
165489
166716
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
165490
166717
  const registration = {
165491
166718
  projectIdentity,
@@ -165497,6 +166724,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165497
166724
  generation,
165498
166725
  features: { ...features },
165499
166726
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
166727
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
165500
166728
  observationMode: false
165501
166729
  };
165502
166730
  projectRegistrations.set(projectIdentity, registration);
@@ -165519,6 +166747,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
165519
166747
  generation,
165520
166748
  features: { memoryEnabled: false, gitCommitEnabled: false },
165521
166749
  modelId: "off",
166750
+ chunkModelId: "off",
165522
166751
  observationMode: true
165523
166752
  };
165524
166753
  projectRegistrations.set(projectIdentity, registration);
@@ -165529,6 +166758,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
165529
166758
  const registration = projectRegistrations.get(projectIdentity);
165530
166759
  return registration ? snapshotFor(registration) : null;
165531
166760
  }
166761
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
166762
+ const registration = projectRegistrations.get(projectIdentity);
166763
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
166764
+ }
166765
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
166766
+ const registration = projectRegistrations.get(projectIdentity);
166767
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
166768
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
166769
+ }
165532
166770
  function getOrCreateProjectProvider(registration) {
165533
166771
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
165534
166772
  return null;
@@ -165623,10 +166861,136 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
165623
166861
  return 0;
165624
166862
  }
165625
166863
  }
165626
- var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
166864
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
166865
+ const noWork = [];
166866
+ if (candidates.length === 0)
166867
+ return { embedded: 0, noWork };
166868
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
166869
+ const prepared = [];
166870
+ for (const candidate of candidates) {
166871
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
166872
+ if (canonicalText.length === 0) {
166873
+ noWork.push(candidate.id);
166874
+ continue;
166875
+ }
166876
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
166877
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
166878
+ noWork.push(candidate.id);
166879
+ continue;
166880
+ }
166881
+ prepared.push({ candidate, windows });
166882
+ }
166883
+ if (prepared.length === 0)
166884
+ return { embedded: 0, noWork };
166885
+ let embedded = 0;
166886
+ let i = 0;
166887
+ while (i < prepared.length) {
166888
+ if (signal?.aborted)
166889
+ break;
166890
+ const slice = [];
166891
+ let windowCount = 0;
166892
+ do {
166893
+ const item = prepared[i];
166894
+ slice.push(item);
166895
+ windowCount += item.windows.length;
166896
+ i += 1;
166897
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
166898
+ const texts = [];
166899
+ for (const item of slice)
166900
+ texts.push(...item.windows.map((w) => w.text));
166901
+ try {
166902
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
166903
+ if (!result)
166904
+ continue;
166905
+ if (signal?.aborted)
166906
+ break;
166907
+ let offset = 0;
166908
+ for (const item of slice) {
166909
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
166910
+ offset += item.windows.length;
166911
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
166912
+ continue;
166913
+ }
166914
+ const rows = item.windows.map((window, index) => ({
166915
+ compartmentId: item.candidate.id,
166916
+ sessionId: item.candidate.sessionId,
166917
+ projectPath: projectIdentity,
166918
+ window,
166919
+ modelId,
166920
+ vector: vectors[index]
166921
+ }));
166922
+ replaceCompartmentChunkEmbeddings(db, rows);
166923
+ embedded += 1;
166924
+ }
166925
+ } catch (error51) {
166926
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
166927
+ }
166928
+ }
166929
+ return { embedded, noWork };
166930
+ }
166931
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
166932
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
166933
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
166934
+ return { status: "disabled", embedded: 0, total: 0 };
166935
+ }
166936
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
166937
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
166938
+ if (total === 0)
166939
+ return { status: "nothing", embedded: 0, total: 0 };
166940
+ const holderId = `session-embed-${randomUUID()}`;
166941
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
166942
+ if (!lease2.acquired)
166943
+ return { status: "busy", embedded: 0, total };
166944
+ const renewal = setInterval(() => {
166945
+ try {
166946
+ renewGitSweepLease(db, projectIdentity, holderId);
166947
+ } catch {}
166948
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
166949
+ renewal.unref?.();
166950
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
166951
+ const skipIds = [];
166952
+ let embedded = 0;
166953
+ let aborted2 = false;
166954
+ let providerStalled = false;
166955
+ try {
166956
+ options?.onProgress?.({ embedded, total });
166957
+ for (;; ) {
166958
+ if (options?.signal?.aborted) {
166959
+ aborted2 = true;
166960
+ break;
166961
+ }
166962
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
166963
+ if (candidates.length === 0)
166964
+ break;
166965
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
166966
+ for (const id of noWork)
166967
+ skipIds.push(id);
166968
+ if (n === 0 && noWork.length === 0) {
166969
+ providerStalled = true;
166970
+ break;
166971
+ }
166972
+ embedded += n;
166973
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
166974
+ await new Promise((resolve7) => setTimeout(resolve7, 0));
166975
+ }
166976
+ } finally {
166977
+ clearInterval(renewal);
166978
+ releaseGitSweepLease(db, projectIdentity, holderId);
166979
+ }
166980
+ if (aborted2)
166981
+ return { status: "aborted", embedded, total };
166982
+ if (providerStalled) {
166983
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
166984
+ if (remaining > 0)
166985
+ return { status: "stalled", embedded, total, remaining };
166986
+ }
166987
+ return { status: "done", embedded, total };
166988
+ }
166989
+ 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;
165627
166990
  var init_project_embedding_registry = __esm(() => {
165628
166991
  init_magic_context();
165629
166992
  init_logger();
166993
+ init_compartment_chunk_embedding();
165630
166994
  init_storage_git_commit_embeddings();
165631
166995
  init_sweep_coordinator();
165632
166996
  init_embedding_cache();
@@ -165634,7 +166998,9 @@ var init_project_embedding_registry = __esm(() => {
165634
166998
  init_embedding_local();
165635
166999
  init_embedding_openai();
165636
167000
  init_storage_memory_embeddings();
167001
+ init_session_project_storage();
165637
167002
  SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167003
+ SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
165638
167004
  projectRegistrations = new Map;
165639
167005
  loadUnembeddedMemoriesStatements = new WeakMap;
165640
167006
  });
@@ -165650,10 +167016,11 @@ function createProvider2(config2) {
165650
167016
  model: config2.model,
165651
167017
  apiKey: config2.api_key,
165652
167018
  inputType: config2.input_type,
165653
- truncate: config2.truncate
167019
+ truncate: config2.truncate,
167020
+ maxInputTokens: config2.max_input_tokens
165654
167021
  });
165655
167022
  }
165656
- return new LocalEmbeddingProvider(config2.model);
167023
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165657
167024
  }
165658
167025
  function getOrCreateProvider() {
165659
167026
  if (provider) {
@@ -165679,6 +167046,7 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
165679
167046
  var init_embedding = __esm(() => {
165680
167047
  init_magic_context();
165681
167048
  init_logger();
167049
+ init_compartment_chunk_embedding();
165682
167050
  init_embedding_identity();
165683
167051
  init_embedding_local();
165684
167052
  init_embedding_openai();
@@ -165702,6 +167070,23 @@ function getSearchStatement(db) {
165702
167070
  }
165703
167071
  return stmt;
165704
167072
  }
167073
+ function getUnionSearchStatement(db, arity) {
167074
+ let statements = unionSearchStatements.get(arity);
167075
+ if (!statements) {
167076
+ statements = new WeakMap;
167077
+ unionSearchStatements.set(arity, statements);
167078
+ }
167079
+ let stmt = statements.get(db);
167080
+ if (!stmt) {
167081
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
167082
+ 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 ?`);
167083
+ statements.set(db, stmt);
167084
+ }
167085
+ return stmt;
167086
+ }
167087
+ function uniqueProjectPaths2(projectPaths) {
167088
+ return [...new Set(projectPaths.filter((path6) => path6.length > 0))];
167089
+ }
165705
167090
  function sanitizeFtsQuery(query) {
165706
167091
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
165707
167092
  if (tokens.length === 0)
@@ -165720,10 +167105,33 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
165720
167105
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
165721
167106
  return rows.map(toMemory);
165722
167107
  }
165723
- var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
167108
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
167109
+ const identities = uniqueProjectPaths2(projectPaths);
167110
+ if (identities.length === 0)
167111
+ return [];
167112
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
167113
+ identities,
167114
+ ownIdentities,
167115
+ shareCategories,
167116
+ tableName: "memories"
167117
+ });
167118
+ if (identities.length === 1 && !sharingFilter.active) {
167119
+ return searchMemoriesFTS(db, identities[0], query, limit);
167120
+ }
167121
+ const trimmedQuery = query.trim();
167122
+ if (trimmedQuery.length === 0 || limit <= 0)
167123
+ return [];
167124
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
167125
+ if (sanitized.length === 0)
167126
+ return [];
167127
+ 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);
167128
+ return rows.map(toMemory);
167129
+ }
167130
+ var DEFAULT_SEARCH_LIMIT = 10, searchStatements, unionSearchStatements;
165724
167131
  var init_storage_memory_fts = __esm(() => {
165725
167132
  init_storage_memory();
165726
167133
  searchStatements = new WeakMap;
167134
+ unionSearchStatements = new Map;
165727
167135
  });
165728
167136
 
165729
167137
  // src/shared/models-dev-cache.ts
@@ -165917,26 +167325,54 @@ var init_rpc_notifications = __esm(() => {
165917
167325
  });
165918
167326
 
165919
167327
  // src/features/magic-context/compartment-embedding.ts
165920
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
167328
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
165921
167329
  if (compartments.length === 0)
165922
167330
  return;
165923
- const update = db.prepare("UPDATE compartments SET p1_embedding = ?, p1_embedding_model_id = ? WHERE id = ?");
165924
- for (const c of compartments) {
165925
- if (!c.p1 || c.p1.length === 0)
165926
- continue;
167331
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
167332
+ for (const compartment of compartments) {
165927
167333
  try {
165928
- const result = await embedTextForProject(projectPath, c.p1);
165929
- if (result) {
165930
- const blob = Buffer.from(result.vector.buffer, result.vector.byteOffset, result.vector.byteLength);
165931
- update.run(blob, result.modelId, c.id);
167334
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
167335
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
167336
+ if (canonicalText.length === 0)
167337
+ continue;
167338
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
167339
+ if (windows.length === 0)
167340
+ continue;
167341
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
167342
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167343
+ continue;
167344
+ }
167345
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
167346
+ if (!result)
167347
+ continue;
167348
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167349
+ continue;
167350
+ }
167351
+ const rows = [];
167352
+ for (const [index, window] of windows.entries()) {
167353
+ const vector = result.vectors[index];
167354
+ if (!vector)
167355
+ continue;
167356
+ rows.push({
167357
+ compartmentId: compartment.id,
167358
+ sessionId,
167359
+ projectPath,
167360
+ window,
167361
+ modelId: currentModelId,
167362
+ vector
167363
+ });
167364
+ }
167365
+ if (rows.length === windows.length) {
167366
+ replaceCompartmentChunkEmbeddings(db, rows);
165932
167367
  }
165933
167368
  } catch (error51) {
165934
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
167369
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
165935
167370
  }
165936
167371
  }
165937
167372
  }
165938
167373
  var init_compartment_embedding = __esm(() => {
165939
167374
  init_logger();
167375
+ init_compartment_chunk_embedding();
165940
167376
  init_project_embedding_registry();
165941
167377
  });
165942
167378
 
@@ -167050,55 +168486,6 @@ var init_historian_state_file = __esm(() => {
167050
168486
  init_data_path();
167051
168487
  });
167052
168488
 
167053
- // src/features/magic-context/memory/constants.ts
167054
- var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
167055
- var init_constants = __esm(() => {
167056
- V2_MEMORY_CATEGORIES = [
167057
- "PROJECT_RULES",
167058
- "ARCHITECTURE",
167059
- "CONSTRAINTS",
167060
- "CONFIG_VALUES",
167061
- "NAMING"
167062
- ];
167063
- PROMOTABLE_CATEGORIES = [
167064
- "PROJECT_RULES",
167065
- "ARCHITECTURE",
167066
- "CONSTRAINTS",
167067
- "CONFIG_VALUES",
167068
- "NAMING",
167069
- "ARCHITECTURE_DECISIONS",
167070
- "CONFIG_DEFAULTS",
167071
- "USER_PREFERENCES",
167072
- "USER_DIRECTIVES",
167073
- "ENVIRONMENT",
167074
- "WORKFLOW_RULES",
167075
- "KNOWN_ISSUES"
167076
- ];
167077
- CATEGORY_PRIORITY = [
167078
- "PROJECT_RULES",
167079
- "ARCHITECTURE",
167080
- "CONSTRAINTS",
167081
- "CONFIG_VALUES",
167082
- "NAMING",
167083
- "USER_DIRECTIVES",
167084
- "USER_PREFERENCES",
167085
- "CONFIG_DEFAULTS",
167086
- "ARCHITECTURE_DECISIONS",
167087
- "ENVIRONMENT",
167088
- "WORKFLOW_RULES",
167089
- "KNOWN_ISSUES"
167090
- ];
167091
- MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
167092
- acc[category] = index;
167093
- return acc;
167094
- }, {});
167095
- MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
167096
- CATEGORY_DEFAULT_TTL = {
167097
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
167098
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
167099
- };
167100
- });
167101
-
167102
168489
  // src/features/magic-context/memory/embedding-backfill.ts
167103
168490
  async function ensureMemoryEmbeddings(args) {
167104
168491
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167122,7 +168509,7 @@ async function ensureMemoryEmbeddings(args) {
167122
168509
  continue;
167123
168510
  }
167124
168511
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
167125
- staged.set(memory.id, embedding);
168512
+ staged.set(memory.id, { embedding, modelId: result.modelId });
167126
168513
  }
167127
168514
  })();
167128
168515
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167919,6 +169306,71 @@ function lastCompartmentBoundaryId(compartments) {
167919
169306
  const last = compartments.at(-1);
167920
169307
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
167921
169308
  }
169309
+ function resolveWorkspaceRenderContext(args) {
169310
+ if (!args.projectPath) {
169311
+ return {
169312
+ identities: [],
169313
+ expandedIdentities: [],
169314
+ ownIdentities: [],
169315
+ shareCategories: null,
169316
+ namesByIdentity: new Map,
169317
+ canonicalIdentityByStoredPath: new Map,
169318
+ isWorkspaced: false
169319
+ };
169320
+ }
169321
+ const identitySet = args.workspaceIdentitySet ?? resolveWorkspaceIdentitySet(args.db, args.projectPath);
169322
+ const isWorkspaced = identitySet.identities.length > 1;
169323
+ const expanded = expandWorkspaceIdentitySetWithAliases(args.db, identitySet.identities);
169324
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
169325
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
169326
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === args.projectPath);
169327
+ if (ownIdentities.length === 0 && expandedIdentities.includes(args.projectPath)) {
169328
+ ownIdentities = [args.projectPath];
169329
+ }
169330
+ return {
169331
+ identities: identitySet.identities,
169332
+ expandedIdentities,
169333
+ ownIdentities,
169334
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(args.db, args.projectPath) : null,
169335
+ namesByIdentity: identitySet.namesByIdentity,
169336
+ canonicalIdentityByStoredPath,
169337
+ isWorkspaced
169338
+ };
169339
+ }
169340
+ function sourceNamesForMemories(args) {
169341
+ if (!args.projectPath || !args.workspace.isWorkspaced)
169342
+ return;
169343
+ const names = new Map;
169344
+ for (const memory of args.memories) {
169345
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
169346
+ if (source)
169347
+ names.set(memory.id, source);
169348
+ }
169349
+ return names.size > 0 ? names : undefined;
169350
+ }
169351
+ function memoryCanonicalIdentity(memory, workspace) {
169352
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
169353
+ }
169354
+ function memorySelectionOrder(left, right) {
169355
+ if (left.status === "permanent" && right.status !== "permanent")
169356
+ return -1;
169357
+ if (right.status === "permanent" && left.status !== "permanent")
169358
+ return 1;
169359
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
169360
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
169361
+ const importanceDiff = rightImportance - leftImportance;
169362
+ if (importanceDiff !== 0)
169363
+ return importanceDiff;
169364
+ return left.id - right.id;
169365
+ }
169366
+ function memoryRenderOrder(left, right) {
169367
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169368
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169369
+ const categoryDiff = aPriority - bPriority;
169370
+ if (categoryDiff !== 0)
169371
+ return categoryDiff;
169372
+ return left.id - right.id;
169373
+ }
167922
169374
  function cachedStatement(cache, db, sql) {
167923
169375
  let stmt = cache.get(db);
167924
169376
  if (!stmt) {
@@ -167961,13 +169413,19 @@ function getGlobalUserProfileVersion(db) {
167961
169413
  function readCurrentM0SnapshotMarkers(args) {
167962
169414
  const projectDirectory = args.projectDirectory ?? args.projectPath ?? "";
167963
169415
  const hard = args.hardSignals ?? EMPTY_HARD_SIGNALS;
169416
+ const workspace = resolveWorkspaceRenderContext({
169417
+ db: args.db,
169418
+ projectPath: args.projectPath,
169419
+ workspaceIdentitySet: args.workspaceIdentitySet
169420
+ });
167964
169421
  return {
167965
169422
  projectMemoryEpoch: getProjectMemoryEpoch(args.db, args.projectPath),
169423
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(args.db, workspace.identities) : null,
167966
169424
  projectUserProfileVersion: getGlobalUserProfileVersion(args.db),
167967
169425
  maxCompartmentSeq: getMaxCompartmentSeq(args.db, args.sessionId),
167968
- maxMemoryId: getMaxMemoryId(args.db, args.projectPath),
169426
+ maxMemoryId: workspace.isWorkspaced ? getMaxMemoryIdForProjects(args.db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryId(args.db, args.projectPath),
167969
169427
  maxMutationId: getMaxM0MutationId(args.db, args.sessionId) ?? 0,
167970
- maxMemoryMutationId: args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
169428
+ maxMemoryMutationId: workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(args.db, workspace.expandedIdentities) ?? 0 : args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
167971
169429
  projectDocsHash: projectDirectory ? computeProjectDocsHash(projectDirectory) : "",
167972
169430
  materializedAt: Date.now(),
167973
169431
  sessionFactsVersion: getSessionFactsVersion(args.db, args.sessionId),
@@ -167995,6 +169453,7 @@ function snapshotMarkersFromCachedM0(state) {
167995
169453
  return null;
167996
169454
  return {
167997
169455
  projectMemoryEpoch: state.cachedM0ProjectMemoryEpoch,
169456
+ workspaceFingerprint: state.cachedM0WorkspaceFingerprint,
167998
169457
  projectUserProfileVersion: state.cachedM0ProjectUserProfileVersion,
167999
169458
  maxCompartmentSeq: state.cachedM0MaxCompartmentSeq,
168000
169459
  maxMemoryId: state.cachedM0MaxMemoryId,
@@ -168024,35 +169483,27 @@ function mustMaterialize(args) {
168024
169483
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (args.state.cachedM0MaterializedAt ?? 0)) {
168025
169484
  return { value: true, reason: "ttl_idle" };
168026
169485
  }
168027
- if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
169486
+ if (current.workspaceFingerprint !== null || (args.state.cachedM0WorkspaceFingerprint ?? null) !== null) {
169487
+ if ((args.state.cachedM0WorkspaceFingerprint ?? null) !== current.workspaceFingerprint) {
169488
+ return { value: true, reason: "project_memory_epoch" };
169489
+ }
169490
+ } else if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
168028
169491
  return { value: true, reason: "project_memory_epoch" };
168029
169492
  }
168030
169493
  if (args.state.cachedM0MaxMutationId !== current.maxMutationId) {
168031
169494
  return { value: true, reason: "max_mutation_id" };
168032
169495
  }
168033
- if ((args.state.cachedM0ProjectDocsHash ?? "") !== current.projectDocsHash) {
168034
- return { value: true, reason: "project_docs_hash" };
168035
- }
168036
169496
  if ((args.state.cachedM0UpgradeState ?? null) !== current.upgradeState) {
168037
169497
  return { value: true, reason: "upgrade_state" };
168038
169498
  }
168039
169499
  return { value: false, reason: null };
168040
169500
  }
168041
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168042
- const selectionOrder = [...memories].sort((a, b) => {
168043
- if (a.status === "permanent" && b.status !== "permanent")
168044
- return -1;
168045
- if (b.status === "permanent" && a.status !== "permanent")
168046
- return 1;
168047
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
168048
- if (importanceDiff !== 0)
168049
- return importanceDiff;
168050
- return a.id - b.id;
168051
- });
169501
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
169502
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
168052
169503
  const selected = [];
168053
169504
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
168054
169505
  for (const memory of selectionOrder) {
168055
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
169506
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168056
169507
  if (usedTokens + memoryTokens > budgetTokens)
168057
169508
  continue;
168058
169509
  selected.push(memory);
@@ -168061,16 +169512,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168061
169512
  if (selected.length < memories.length) {
168062
169513
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
168063
169514
  }
168064
- const renderOrder = [...selected].sort((a, b) => {
168065
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168066
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168067
- const categoryDiff = aPriority - bPriority;
168068
- if (categoryDiff !== 0)
168069
- return categoryDiff;
168070
- return a.id - b.id;
168071
- });
169515
+ const renderOrder = [...selected].sort(memoryRenderOrder);
168072
169516
  return { selected, renderOrder };
168073
169517
  }
169518
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
169519
+ if (!workspace.isWorkspaced) {
169520
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
169521
+ }
169522
+ const selected = [];
169523
+ const selectedIds = new Set;
169524
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
169525
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
169526
+ const trySelect = (memory) => {
169527
+ if (selectedIds.has(memory.id))
169528
+ return false;
169529
+ const tokens = tokenCost(memory);
169530
+ if (usedTokens + tokens > budgetTokens)
169531
+ return false;
169532
+ selected.push(memory);
169533
+ selectedIds.add(memory.id);
169534
+ usedTokens += tokens;
169535
+ return true;
169536
+ };
169537
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
169538
+ trySelect(memory);
169539
+ }
169540
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
169541
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
169542
+ const byIdentity = new Map;
169543
+ for (const memory of memories) {
169544
+ if (memory.status === "permanent")
169545
+ continue;
169546
+ const identity = memoryCanonicalIdentity(memory, workspace);
169547
+ if (!identity)
169548
+ continue;
169549
+ const list = byIdentity.get(identity) ?? [];
169550
+ list.push(memory);
169551
+ byIdentity.set(identity, list);
169552
+ }
169553
+ for (const identity of workspace.identities) {
169554
+ let memberTokens = 0;
169555
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
169556
+ for (const memory of candidates) {
169557
+ if (selectedIds.has(memory.id))
169558
+ continue;
169559
+ const tokens = tokenCost(memory);
169560
+ if (memberTokens + tokens > floorTokens)
169561
+ continue;
169562
+ if (usedTokens + tokens > budgetTokens)
169563
+ continue;
169564
+ selected.push(memory);
169565
+ selectedIds.add(memory.id);
169566
+ usedTokens += tokens;
169567
+ memberTokens += tokens;
169568
+ }
169569
+ }
169570
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
169571
+ for (const memory of remaining) {
169572
+ trySelect(memory);
169573
+ }
169574
+ if (selected.length < memories.length) {
169575
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
169576
+ }
169577
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
169578
+ }
168074
169579
  function safeGetActiveUserMemories(db) {
168075
169580
  try {
168076
169581
  return getActiveUserMemories(db);
@@ -168146,15 +169651,16 @@ function readNewMemoriesForM1(db, projectPath, afterId, expiryCutoff) {
168146
169651
  ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(projectPath, afterId, expiryCutoff).filter(isMemoryRow);
168147
169652
  return rows.map((row) => ({ ...row }));
168148
169653
  }
168149
- function renderMemoryLineV2(memory) {
168150
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
169654
+ function renderMemoryLineV2(memory, sourceName) {
169655
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
169656
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
168151
169657
  }
168152
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
169658
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
168153
169659
  if (memories.length === 0)
168154
169660
  return "";
168155
169661
  const lines = [`<${wrapper}>`];
168156
169662
  for (const memory of memories) {
168157
- lines.push(renderMemoryLineV2(memory));
169663
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168158
169664
  }
168159
169665
  lines.push(`</${wrapper}>`);
168160
169666
  return lines.join(`
@@ -168193,7 +169699,7 @@ function renderM0(args) {
168193
169699
  sections.push(sessionHistory.length > 0 ? `<session-history>
168194
169700
  ${sessionHistory}
168195
169701
  </session-history>` : M0_EMPTY_BODY);
168196
- const memoriesBlock = renderMemoryBlockV2(args.memories);
169702
+ const memoriesBlock = renderMemoryBlockV2(args.memories, "project-memory", args.memoryRenderOptions);
168197
169703
  if (memoriesBlock)
168198
169704
  sections.push(memoriesBlock);
168199
169705
  return sections.join(`
@@ -168205,6 +169711,7 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
168205
169711
  if (m1Bytes)
168206
169712
  state.cachedM1Bytes = m1Bytes;
168207
169713
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
169714
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168208
169715
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168209
169716
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168210
169717
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168230,24 +169737,38 @@ function materializeM0(options) {
168230
169737
  let facts = [];
168231
169738
  let memories = [];
168232
169739
  let userMemories = [];
169740
+ let workspace = resolveWorkspaceRenderContext({
169741
+ db: options.db,
169742
+ projectPath,
169743
+ workspaceIdentitySet: options.workspaceIdentitySet
169744
+ });
168233
169745
  let docs = {
168234
169746
  renderedBlock: "",
168235
169747
  canonicalHash: ""
168236
169748
  };
168237
169749
  options.db.exec("BEGIN");
168238
169750
  try {
169751
+ workspace = resolveWorkspaceRenderContext({
169752
+ db: options.db,
169753
+ projectPath,
169754
+ workspaceIdentitySet: options.workspaceIdentitySet
169755
+ });
168239
169756
  snapshotMarkers = readCurrentM0SnapshotMarkers({
168240
169757
  db: options.db,
168241
169758
  sessionId: options.sessionId,
168242
169759
  projectPath,
168243
169760
  projectDirectory,
168244
- hardSignals: options.hardSignals
169761
+ hardSignals: options.hardSignals,
169762
+ workspaceIdentitySet: {
169763
+ identities: workspace.identities,
169764
+ namesByIdentity: workspace.namesByIdentity
169765
+ }
168245
169766
  });
168246
169767
  docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168247
169768
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168248
169769
  compartments = readM0Compartments(options.db, options.sessionId);
168249
169770
  facts = [];
168250
- memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
169771
+ memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
168251
169772
  userMemories = safeGetActiveUserMemories(options.db);
168252
169773
  options.db.exec("COMMIT");
168253
169774
  } catch (error51) {
@@ -168257,7 +169778,14 @@ function materializeM0(options) {
168257
169778
  throw error51;
168258
169779
  }
168259
169780
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168260
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
169781
+ const memoryRenderOptions = {
169782
+ sourceNameByMemoryId: sourceNamesForMemories({
169783
+ memories,
169784
+ projectPath,
169785
+ workspace
169786
+ })
169787
+ };
169788
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168261
169789
  let decayPressureMultiplier = 1;
168262
169790
  let m0Text = renderM0({
168263
169791
  projectDocs: docs.renderedBlock,
@@ -168265,6 +169793,7 @@ function materializeM0(options) {
168265
169793
  compartments,
168266
169794
  memories: trimmed.renderOrder,
168267
169795
  facts,
169796
+ memoryRenderOptions,
168268
169797
  historyBudgetTokens: options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS,
168269
169798
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168270
169799
  decayPressureMultiplier
@@ -168279,6 +169808,7 @@ function materializeM0(options) {
168279
169808
  compartments,
168280
169809
  memories: trimmed.renderOrder,
168281
169810
  facts,
169811
+ memoryRenderOptions,
168282
169812
  historyBudgetTokens: budget,
168283
169813
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168284
169814
  decayPressureMultiplier
@@ -168297,13 +169827,19 @@ function materializeM0(options) {
168297
169827
  let m1Bytes = Buffer4.from(m1Text, "utf8");
168298
169828
  options.db.exec("BEGIN IMMEDIATE");
168299
169829
  try {
169830
+ const currentWorkspace = resolveWorkspaceRenderContext({
169831
+ db: options.db,
169832
+ projectPath,
169833
+ workspaceIdentitySet: options.workspaceIdentitySet
169834
+ });
168300
169835
  const current = {
168301
169836
  projectMemoryEpoch: getProjectMemoryEpoch(options.db, projectPath),
169837
+ workspaceFingerprint: currentWorkspace.isWorkspaced ? computeWorkspaceEpochFingerprint(options.db, currentWorkspace.identities) : null,
168302
169838
  projectUserProfileVersion: getGlobalUserProfileVersion(options.db),
168303
169839
  maxCompartmentSeq: getMaxCompartmentSeq(options.db, options.sessionId),
168304
- maxMemoryId: getMaxMemoryId(options.db, projectPath),
169840
+ maxMemoryId: currentWorkspace.isWorkspaced ? getMaxMemoryIdForProjects(options.db, currentWorkspace.expandedIdentities, currentWorkspace.ownIdentities, currentWorkspace.shareCategories) : getMaxMemoryId(options.db, projectPath),
168305
169841
  maxMutationId: getMaxM0MutationId(options.db, options.sessionId) ?? 0,
168306
- maxMemoryMutationId: projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
169842
+ maxMemoryMutationId: currentWorkspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(options.db, currentWorkspace.expandedIdentities) ?? 0 : projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
168307
169843
  projectDocsHash: phase3ProjectDocsHash,
168308
169844
  materializedAt: Date.now(),
168309
169845
  sessionFactsVersion: getSessionFactsVersion(options.db, options.sessionId),
@@ -168311,17 +169847,26 @@ function materializeM0(options) {
168311
169847
  systemHash: snapshotMarkers.systemHash,
168312
169848
  modelKey: snapshotMarkers.modelKey
168313
169849
  };
168314
- 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;
169850
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
169851
+ 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;
168315
169852
  if (stale) {
168316
169853
  options.db.exec("ROLLBACK");
168317
169854
  throw new MaterializeContentionError({ reason: "snapshot changed before Phase 3" });
168318
169855
  }
168319
- const m1Render = renderM1WithMetadata({ ...options, preRenderedKeyFilesBlock }, snapshotMarkers, renderedMemoryIds);
169856
+ const m1Render = renderM1WithMetadata({
169857
+ ...options,
169858
+ preRenderedKeyFilesBlock,
169859
+ workspaceIdentitySet: {
169860
+ identities: workspace.identities,
169861
+ namesByIdentity: workspace.namesByIdentity
169862
+ }
169863
+ }, snapshotMarkers, renderedMemoryIds);
168320
169864
  m1Text = m1Render.text;
168321
169865
  m1Bytes = Buffer4.from(m1Text, "utf8");
168322
169866
  persistCachedM0(options.db, options.sessionId, {
168323
169867
  m0Bytes,
168324
169868
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
169869
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
168325
169870
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
168326
169871
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
168327
169872
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -168384,7 +169929,7 @@ function renderMemoryUpdatesBlock(args) {
168384
169929
  return { block: "", count: 0 };
168385
169930
  }
168386
169931
  const renderedIds = new Set(args.renderedMemoryIds);
168387
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
169932
+ const mutations = args.workspace.isWorkspaced ? getMemoryMutationsForRenderByProjects(args.db, args.workspace.expandedIdentities, args.afterId, args.renderedMemoryIds) : getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
168388
169933
  if (mutations.length === 0)
168389
169934
  return { block: "", count: 0 };
168390
169935
  const lines = ["These memories changed since the snapshot below — trust these:"];
@@ -168416,12 +169961,18 @@ function renderM1WithMetadata(options, markers, renderedMemoryIds) {
168416
169961
  throw new RenderM1InvalidMarkersError(options.sessionId);
168417
169962
  }
168418
169963
  const blocks = [];
169964
+ const workspace = resolveWorkspaceRenderContext({
169965
+ db: options.db,
169966
+ projectPath: options.projectPath,
169967
+ workspaceIdentitySet: options.workspaceIdentitySet
169968
+ });
168419
169969
  const keyFiles = renderedKeyFilesBlock(options);
168420
169970
  if (keyFiles)
168421
169971
  blocks.push(keyFiles);
168422
169972
  const memoryUpdates = renderMemoryUpdatesBlock({
168423
169973
  db: options.db,
168424
169974
  projectPath: options.projectPath,
169975
+ workspace,
168425
169976
  afterId: markers.maxMemoryMutationId,
168426
169977
  renderedMemoryIds
168427
169978
  });
@@ -168435,9 +169986,16 @@ ${newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).
168435
169986
  `)}
168436
169987
  </new-compartments>`);
168437
169988
  }
168438
- const newMemories = readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
168439
- const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25))).renderOrder;
168440
- const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories");
169989
+ 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);
169990
+ const newMemoryRenderOptions = {
169991
+ sourceNameByMemoryId: sourceNamesForMemories({
169992
+ memories: newMemories,
169993
+ projectPath: options.projectPath,
169994
+ workspace
169995
+ })
169996
+ };
169997
+ const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25)), newMemoryRenderOptions).renderOrder;
169998
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", newMemoryRenderOptions);
168441
169999
  if (newMemoriesBlock)
168442
170000
  blocks.push(newMemoriesBlock);
168443
170001
  const currentUserProfileVersion = getGlobalUserProfileVersion(options.db);
@@ -168485,6 +170043,7 @@ function parseMemoryBlockIds(raw) {
168485
170043
  function readCachedM0M1Row(db, sessionId) {
168486
170044
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
168487
170045
  cached_m0_project_memory_epoch,
170046
+ cached_m0_workspace_fingerprint,
168488
170047
  cached_m0_project_user_profile_version,
168489
170048
  cached_m0_max_compartment_seq,
168490
170049
  cached_m0_max_memory_id,
@@ -168519,6 +170078,7 @@ function markersFromCachedRow(row) {
168519
170078
  return null;
168520
170079
  return {
168521
170080
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
170081
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
168522
170082
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
168523
170083
  maxCompartmentSeq: row.cached_m0_max_compartment_seq,
168524
170084
  maxMemoryId: row.cached_m0_max_memory_id,
@@ -168533,7 +170093,7 @@ function markersFromCachedRow(row) {
168533
170093
  };
168534
170094
  }
168535
170095
  function cachedRowMatchesState(row, state) {
168536
- 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 ?? "");
170096
+ 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 ?? "");
168537
170097
  }
168538
170098
  function applyCachedRowToState(state, row) {
168539
170099
  const markers = markersFromCachedRow(row);
@@ -168543,6 +170103,7 @@ function applyCachedRowToState(state, row) {
168543
170103
  state.cachedM0Bytes = toBuffer(row.cached_m0_bytes);
168544
170104
  state.cachedM1Bytes = toBuffer(row.cached_m1_bytes);
168545
170105
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
170106
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168546
170107
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168547
170108
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168548
170109
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168606,20 +170167,36 @@ function prependM0M1Messages(sessionId, messages, m0Text, m1Text) {
168606
170167
  function renderFreshM0NonPersisted(options) {
168607
170168
  const projectPath = options.projectPath;
168608
170169
  const projectDirectory = options.projectDirectory;
170170
+ const workspace = resolveWorkspaceRenderContext({
170171
+ db: options.db,
170172
+ projectPath,
170173
+ workspaceIdentitySet: options.workspaceIdentitySet
170174
+ });
168609
170175
  const snapshotMarkers = readCurrentM0SnapshotMarkers({
168610
170176
  db: options.db,
168611
170177
  sessionId: options.sessionId,
168612
170178
  projectPath,
168613
- projectDirectory
170179
+ projectDirectory,
170180
+ workspaceIdentitySet: {
170181
+ identities: workspace.identities,
170182
+ namesByIdentity: workspace.namesByIdentity
170183
+ }
168614
170184
  });
168615
170185
  const docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168616
170186
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168617
170187
  snapshotMarkers.materializedAt = options.state.cachedM0MaterializedAt ?? 0;
168618
170188
  const compartments = readM0Compartments(options.db, options.sessionId);
168619
- const memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
170189
+ 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) : [];
168620
170190
  const userMemories = safeGetActiveUserMemories(options.db);
168621
170191
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168622
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
170192
+ const memoryRenderOptions = {
170193
+ sourceNameByMemoryId: sourceNamesForMemories({
170194
+ memories,
170195
+ projectPath,
170196
+ workspace
170197
+ })
170198
+ };
170199
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168623
170200
  const budget = options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
168624
170201
  let decayPressureMultiplier = 1;
168625
170202
  let m0Text = renderM0({
@@ -168628,6 +170205,7 @@ function renderFreshM0NonPersisted(options) {
168628
170205
  compartments,
168629
170206
  memories: trimmed.renderOrder,
168630
170207
  facts: [],
170208
+ memoryRenderOptions,
168631
170209
  historyBudgetTokens: budget,
168632
170210
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168633
170211
  decayPressureMultiplier
@@ -168641,6 +170219,7 @@ function renderFreshM0NonPersisted(options) {
168641
170219
  compartments,
168642
170220
  memories: trimmed.renderOrder,
168643
170221
  facts: [],
170222
+ memoryRenderOptions,
168644
170223
  historyBudgetTokens: budget,
168645
170224
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168646
170225
  decayPressureMultiplier
@@ -168656,6 +170235,12 @@ function renderFreshM0NonPersisted(options) {
168656
170235
  };
168657
170236
  }
168658
170237
  function injectM0M1(options) {
170238
+ if (!options.workspaceIdentitySet && options.projectPath) {
170239
+ options = {
170240
+ ...options,
170241
+ workspaceIdentitySet: resolveWorkspaceIdentitySet(options.db, options.projectPath)
170242
+ };
170243
+ }
168659
170244
  const skipped = {
168660
170245
  injected: false,
168661
170246
  m0RematerializedThisPass: false,
@@ -168672,7 +170257,8 @@ function injectM0M1(options) {
168672
170257
  state: options.state,
168673
170258
  projectPath: options.projectPath,
168674
170259
  projectDirectory: options.projectDirectory,
168675
- hardSignals: options.hardSignals
170260
+ hardSignals: options.hardSignals,
170261
+ workspaceIdentitySet: options.workspaceIdentitySet
168676
170262
  });
168677
170263
  let rematerialized = false;
168678
170264
  let contentionExhausted = false;
@@ -168766,6 +170352,7 @@ var init_inject_compartments = __esm(async () => {
168766
170352
  init_compartment_storage();
168767
170353
  init_constants();
168768
170354
  init_storage_memory();
170355
+ init_workspaces();
168769
170356
  init_logger();
168770
170357
  init_decay_render();
168771
170358
  init_key_files_block();
@@ -171905,18 +173492,38 @@ async function runCompartmentAgent(deps) {
171905
173492
  return;
171906
173493
  }
171907
173494
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
171908
- const boundarySnapshot = deps.boundarySnapshot ?? null;
173495
+ let boundarySnapshot = deps.boundarySnapshot ?? null;
171909
173496
  if (!boundarySnapshot) {
171910
173497
  telemetry.failureReason = "missing protected-tail boundary snapshot";
171911
173498
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from trigger decision");
171912
173499
  rollbackDrainReservation();
171913
173500
  return;
171914
173501
  }
171915
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
173502
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
171916
173503
  db,
171917
173504
  snapshot: boundarySnapshot,
171918
173505
  currentContextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit
171919
173506
  }) : { ok: true };
173507
+ if (!validation.ok && validation.reason === "stale_snapshot") {
173508
+ const refreshed = resolveOpenCodeProtectedTailBoundary({
173509
+ db,
173510
+ sessionId,
173511
+ mode: "incremental-runner",
173512
+ contextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit,
173513
+ executeThresholdPercentage: boundarySnapshot.executeThresholdPercentage,
173514
+ usage: {
173515
+ percentage: boundarySnapshot.usagePercentage,
173516
+ inputTokens: boundarySnapshot.usageInputTokens
173517
+ },
173518
+ usageSource: boundarySnapshot.usageSource,
173519
+ emergencyTailScale: boundarySnapshot.emergencyTailScale
173520
+ });
173521
+ if (hasRunnableCompartmentWindow(refreshed)) {
173522
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
173523
+ boundarySnapshot = refreshed;
173524
+ validation = { ok: true };
173525
+ }
173526
+ }
171920
173527
  if (!validation.ok) {
171921
173528
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
171922
173529
  telemetry.status = "noop";
@@ -171985,6 +173592,7 @@ async function runCompartmentAgent(deps) {
171985
173592
  rollbackDrainReservation();
171986
173593
  return;
171987
173594
  }
173595
+ deps.onHistorianRunStarted?.();
171988
173596
  const projectPath = resolveProjectIdentity(directory ?? process.cwd());
171989
173597
  const memories = getMemoriesByProject(db, projectPath, ["active", "permanent"]);
171990
173598
  const projectMemory = renderMemoryBlock(memories) ?? "";
@@ -172117,8 +173725,13 @@ ${chunkText}`,
172117
173725
  }
172118
173726
  if (embeddingActive) {
172119
173727
  const projectIdentity = resolveProjectIdentity(promotionDirectory);
172120
- const toEmbed = persistedCompartments.map((c, i) => ({ id: persistedIds[i], p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172121
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173728
+ const chunksToEmbed = persistedCompartments.map((c, i) => ({
173729
+ id: persistedIds[i],
173730
+ startMessage: c.startMessage,
173731
+ endMessage: c.endMessage,
173732
+ sourceChunkText: chunk.text
173733
+ })).filter((c) => typeof c.id === "number");
173734
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172122
173735
  }
172123
173736
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
172124
173737
  deps.onCompartmentStatePublished?.(sessionId);
@@ -172349,8 +173962,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
172349
173962
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172350
173963
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172351
173964
  const liveCompartments = getCompartments(db, sessionId);
172352
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172353
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173965
+ const chunksToEmbed = liveCompartments.map((c) => ({
173966
+ id: c.id,
173967
+ startMessage: c.startMessage,
173968
+ endMessage: c.endMessage
173969
+ }));
173970
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172354
173971
  }
172355
173972
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
172356
173973
  if (lastCompartmentEnd2 > 0) {
@@ -172563,8 +174180,12 @@ Another process acquired the compartment-state lease before recomp could publish
172563
174180
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172564
174181
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172565
174182
  const liveCompartments = getCompartments(db, sessionId);
172566
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172567
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
174183
+ const chunksToEmbed = liveCompartments.map((c) => ({
174184
+ id: c.id,
174185
+ startMessage: c.startMessage,
174186
+ endMessage: c.endMessage
174187
+ }));
174188
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172568
174189
  }
172569
174190
  if (lastCompartmentEnd > 0) {
172570
174191
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -172745,8 +174366,12 @@ Could not acquire the compartment-state lease for this session.`;
172745
174366
  if (deps.memoryEnabled !== false) {
172746
174367
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172747
174368
  const liveCompartments = getCompartments(db, sessionId);
172748
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172749
- Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed));
174369
+ const chunksToEmbed = liveCompartments.map((c) => ({
174370
+ id: c.id,
174371
+ startMessage: c.startMessage,
174372
+ endMessage: c.endMessage
174373
+ }));
174374
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
172750
174375
  }
172751
174376
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
172752
174377
  if (lastEnd > 0) {
@@ -174996,7 +176621,14 @@ function startCompartmentAgent(deps) {
174996
176621
  return;
174997
176622
  }
174998
176623
  const renewal = startLeaseRenewal(deps, holderId);
174999
- const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
176624
+ let realRunStarted = false;
176625
+ const runnerDeps = withPublishedCallback({
176626
+ ...deps,
176627
+ compartmentLeaseHolderId: holderId,
176628
+ onHistorianRunStarted: () => {
176629
+ realRunStarted = true;
176630
+ }
176631
+ });
175000
176632
  const promise2 = runCompartmentAgent(runnerDeps).catch((err) => {
175001
176633
  sessionLog(deps.sessionId, "compartment agent: unhandled rejection:", err);
175002
176634
  try {
@@ -175010,6 +176642,9 @@ function startCompartmentAgent(deps) {
175010
176642
  }
175011
176643
  });
175012
176644
  activeRuns.set(deps.sessionId, { promise: promise2, published: false });
176645
+ if (!realRunStarted && activeRuns.get(deps.sessionId)?.promise === promise2) {
176646
+ activeRuns.delete(deps.sessionId);
176647
+ }
175013
176648
  }
175014
176649
  async function executeContextRecompWithResult(deps, options = {}) {
175015
176650
  const { sessionId } = deps;
@@ -175144,7 +176779,7 @@ function applyMemoryMigration(db, projectPath, result) {
175144
176779
  inserted++;
175145
176780
  }
175146
176781
  if (removed > 0 || inserted > 0) {
175147
- bumpProjectMemoryEpoch(db, projectPath);
176782
+ bumpEpochsForWorkspaceMembers(db, projectPath);
175148
176783
  }
175149
176784
  })();
175150
176785
  return { removed, inserted };
@@ -175602,15 +177237,15 @@ function shouldShowAnnouncement() {
175602
177237
  }
175603
177238
  return state.version !== ANNOUNCEMENT_VERSION;
175604
177239
  }
175605
- var ANNOUNCEMENT_VERSION = "0.23.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
177240
+ var ANNOUNCEMENT_VERSION = "0.24.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
175606
177241
  var init_announcement = __esm(() => {
175607
177242
  init_data_path();
175608
177243
  ANNOUNCEMENT_FEATURES = [
175609
- "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.",
175610
- "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.",
175611
- "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).",
175612
- "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).",
175613
- "Fixed: session titles no longer fail to generate in fresh directories (issue #129), plus 20+ correctness fixes from three audit rounds."
177244
+ "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).",
177245
+ "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.",
177246
+ "Pi: fixed sessions overflowing the model context while still showing moderate usagePi now sheds context before a tool-heavy turn overflows.",
177247
+ "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
177248
+ "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)."
175614
177249
  ];
175615
177250
  });
175616
177251
 
@@ -176404,6 +178039,10 @@ function getMagicContextBuiltinCommands() {
176404
178039
  "ctx-dream": {
176405
178040
  template: "ctx-dream",
176406
178041
  description: "Run the hidden dreamer maintenance pass for this project now"
178042
+ },
178043
+ "ctx-embed-history": {
178044
+ template: "ctx-embed-history",
178045
+ description: "Embed all of this session's history compartments for semantic search, in one pass"
176407
178046
  }
176408
178047
  };
176409
178048
  }
@@ -176897,7 +178536,7 @@ await init_storage_db();
176897
178536
  // src/features/magic-context/v22-deferred-backfill.ts
176898
178537
  init_logger();
176899
178538
  init_project_identity();
176900
- import { createHash as createHash5 } from "node:crypto";
178539
+ import { createHash as createHash6 } from "node:crypto";
176901
178540
  import { realpathSync as realpathSync2 } from "node:fs";
176902
178541
  import path5 from "node:path";
176903
178542
  var BATCH_SIZE = 25;
@@ -176951,7 +178590,7 @@ function computeLegacyRustDirIdentity(rawProjectPath) {
176951
178590
  } catch {
176952
178591
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
176953
178592
  }
176954
- return `dir:${createHash5("sha256").update(canonical, "utf8").digest("hex")}`;
178593
+ return `dir:${createHash6("sha256").update(canonical, "utf8").digest("hex")}`;
176955
178594
  }
176956
178595
  function upsertRekeyMap(db, oldProjectPath, newProjectPath, rekeyedAt) {
176957
178596
  db.prepare(`INSERT INTO v22_identity_rekey_map (old_project_path, new_project_path, rekeyed_at)
@@ -180089,7 +181728,7 @@ init_project_identity();
180089
181728
  // src/plugin/embedding-bootstrap-helpers.ts
180090
181729
  init_embedding();
180091
181730
  init_logger();
180092
- import { createHash as createHash8 } from "node:crypto";
181731
+ import { createHash as createHash10 } from "node:crypto";
180093
181732
  var EMBEDDING_AFFECTING_KEYS = new Set([
180094
181733
  "embedding.api_key",
180095
181734
  "embedding.endpoint",
@@ -180110,7 +181749,7 @@ var EMBEDDING_WARNING_TERMS = [
180110
181749
  ];
180111
181750
  var loggedFailureSignatures = new Map;
180112
181751
  function sha256Prefix2(value, length = 16) {
180113
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
181752
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
180114
181753
  }
180115
181754
  function warningLooksEmbeddingRelated(message) {
180116
181755
  const lower = message.toLowerCase();
@@ -180747,6 +182386,7 @@ function createTagger() {
180747
182386
  // src/hooks/magic-context/hook.ts
180748
182387
  init_magic_context();
180749
182388
  init_project_identity();
182389
+ init_project_embedding_registry();
180750
182390
  await init_storage();
180751
182391
  init_logger();
180752
182392
  init_resolve_fallbacks();
@@ -181340,6 +182980,7 @@ function createMagicContextCommandHandler(deps) {
181340
182980
  const isAugCommand = (command) => command === "ctx-aug";
181341
182981
  const isDreamCommand = (command) => command === "ctx-dream";
181342
182982
  const isSessionUpgradeCommand = (command) => command === "ctx-session-upgrade";
182983
+ const isEmbedHistoryCommand = (command) => command === "ctx-embed-history";
181343
182984
  return {
181344
182985
  "command.execute.before": async (input, _output, _params) => {
181345
182986
  const isStatus = isStatusCommand(input.command);
@@ -181348,7 +182989,8 @@ function createMagicContextCommandHandler(deps) {
181348
182989
  const isAug = isAugCommand(input.command);
181349
182990
  const isDream = isDreamCommand(input.command);
181350
182991
  const isSessionUpgrade = isSessionUpgradeCommand(input.command);
181351
- if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade) {
182992
+ const isEmbedHistory = isEmbedHistoryCommand(input.command);
182993
+ if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbedHistory) {
181352
182994
  return;
181353
182995
  }
181354
182996
  const sessionId = input.sessionID;
@@ -181361,6 +183003,11 @@ function createMagicContextCommandHandler(deps) {
181361
183003
  await executeDreaming(deps, sessionId);
181362
183004
  return;
181363
183005
  }
183006
+ if (isEmbedHistory) {
183007
+ const summary = deps.executeEmbedHistory ? await deps.executeEmbedHistory(sessionId) : "Semantic embedding is not configured for this project, so there is nothing to embed.";
183008
+ await deps.sendNotification(sessionId, summary, {});
183009
+ throwSentinel(input.command);
183010
+ }
181364
183011
  if (isFlush) {
181365
183012
  result = executeFlush(deps.db, sessionId);
181366
183013
  clearCachedM0M1(deps.db, sessionId);
@@ -182101,6 +183748,7 @@ await init_read_session_chunk();
182101
183748
  // src/hooks/magic-context/transform.ts
182102
183749
  init_project_identity();
182103
183750
  import * as crypto2 from "node:crypto";
183751
+ init_session_project_storage();
182104
183752
  init_storage_meta_persisted();
182105
183753
  init_logger();
182106
183754
  await init_storage();
@@ -182701,6 +184349,7 @@ await __promiseAll([
182701
184349
  init_read_session_chunk(),
182702
184350
  init_read_session_db()
182703
184351
  ]);
184352
+
182704
184353
  // src/hooks/magic-context/sentinel.ts
182705
184354
  var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
182706
184355
  function modelAcceptsEmptyContent(providerID) {
@@ -182758,7 +184407,6 @@ function replaySentinelByMessageIds(messages, ids, providerID) {
182758
184407
  missingIds.push(id);
182759
184408
  return { replayed, missingIds };
182760
184409
  }
182761
-
182762
184410
  // src/hooks/magic-context/strip-content.ts
182763
184411
  var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped §\d+§\]$/;
182764
184412
  var TAG_PREFIX_PATTERN = /^§\d+§\s*/;
@@ -183111,8 +184759,10 @@ function stripReasoningFromMergedAssistants(messages, providerID) {
183111
184759
  }
183112
184760
  return stripped;
183113
184761
  }
183114
- function stripProcessedImages(messages, watermark, messageTagNumbers) {
184762
+ function stripProcessedImages(messages, frozenIds, options) {
184763
+ const { detect, watermark, messageTagNumbers } = options;
183115
184764
  let stripped = 0;
184765
+ const newlyStrippedIds = [];
183116
184766
  let hasAssistantResponse = false;
183117
184767
  for (let i = messages.length - 1;i >= 0; i--) {
183118
184768
  const msg = messages[i];
@@ -183120,13 +184770,17 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
183120
184770
  hasAssistantResponse = true;
183121
184771
  continue;
183122
184772
  }
183123
- if (msg.info.role !== "user" || !hasAssistantResponse) {
184773
+ if (msg.info.role !== "user") {
183124
184774
  continue;
183125
184775
  }
184776
+ const id = typeof msg.info.id === "string" ? msg.info.id : undefined;
184777
+ const inFrozen = id !== undefined && frozenIds.has(id);
183126
184778
  const maxTag = messageTagNumbers.get(msg) ?? 0;
183127
- if (maxTag > watermark) {
184779
+ const isNewDetection = !inFrozen && detect && hasAssistantResponse && id !== undefined && maxTag <= watermark;
184780
+ if (!inFrozen && !isNewDetection) {
183128
184781
  continue;
183129
184782
  }
184783
+ let touchedThisMsg = false;
183130
184784
  for (let j = 0;j < msg.parts.length; j++) {
183131
184785
  const part = msg.parts[j];
183132
184786
  if (!isRecord(part) || part.type !== "file") {
@@ -183138,10 +184792,14 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
183138
184792
  if (typeof part.url === "string" && part.url.startsWith("data:") && part.url.length > 200) {
183139
184793
  msg.parts[j] = makeSentinel(part);
183140
184794
  stripped++;
184795
+ touchedThisMsg = true;
183141
184796
  }
183142
184797
  }
184798
+ if (touchedThisMsg && isNewDetection && id !== undefined) {
184799
+ newlyStrippedIds.push(id);
184800
+ }
183143
184801
  }
183144
- return stripped;
184802
+ return { stripped, newlyStrippedIds };
183145
184803
  }
183146
184804
 
183147
184805
  // src/hooks/magic-context/transform.ts
@@ -184084,6 +185742,7 @@ init_embedding();
184084
185742
 
184085
185743
  // src/features/magic-context/search.ts
184086
185744
  init_logger();
185745
+ init_compartment_chunk_embedding();
184087
185746
 
184088
185747
  // src/features/magic-context/literal-probes.ts
184089
185748
  var MAX_PROBES = 5;
@@ -184145,6 +185804,7 @@ function containsProbeVerbatim(text, probes) {
184145
185804
  init_memory();
184146
185805
  init_embedding();
184147
185806
  init_storage_memory_fts();
185807
+ init_workspaces();
184148
185808
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
184149
185809
  var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
184150
185810
  var SEMANTIC_WEIGHT = 0.7;
@@ -184174,6 +185834,37 @@ function previewText(text) {
184174
185834
  }
184175
185835
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
184176
185836
  }
185837
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
185838
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
185839
+ const isWorkspaced = resolved.identities.length > 1;
185840
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
185841
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
185842
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
185843
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
185844
+ return {
185845
+ identities: resolved.identities,
185846
+ expandedIdentities,
185847
+ ownIdentities,
185848
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
185849
+ namesByIdentity: resolved.namesByIdentity,
185850
+ canonicalIdentityByStoredPath,
185851
+ isWorkspaced
185852
+ };
185853
+ }
185854
+ function memoryWorkspaceIdentity(memory, workspace) {
185855
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
185856
+ }
185857
+ function sourceNamesForSearchMemories(args) {
185858
+ if (!args.workspace.isWorkspaced)
185859
+ return;
185860
+ const sourceNames = new Map;
185861
+ for (const memory of args.memories) {
185862
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
185863
+ if (source)
185864
+ sourceNames.set(memory.id, source);
185865
+ }
185866
+ return sourceNames.size > 0 ? sourceNames : undefined;
185867
+ }
184177
185868
  function getMessageSearchStatement(db) {
184178
185869
  let stmt = messageSearchStatements.get(db);
184179
185870
  if (!stmt) {
@@ -184182,6 +185873,30 @@ function getMessageSearchStatement(db) {
184182
185873
  }
184183
185874
  return stmt;
184184
185875
  }
185876
+ var ftsRowCountStatements = new WeakMap;
185877
+ var ftsMatchCountStatements = new WeakMap;
185878
+ function getSessionFtsRowCount(db, sessionId) {
185879
+ let stmt = ftsRowCountStatements.get(db);
185880
+ if (!stmt) {
185881
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
185882
+ ftsRowCountStatements.set(db, stmt);
185883
+ }
185884
+ const row = stmt.get(sessionId);
185885
+ return typeof row?.n === "number" ? row.n : 0;
185886
+ }
185887
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
185888
+ let stmt = ftsMatchCountStatements.get(db);
185889
+ if (!stmt) {
185890
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
185891
+ ftsMatchCountStatements.set(db, stmt);
185892
+ }
185893
+ try {
185894
+ const row = stmt.get(sessionId, ftsQuery);
185895
+ return typeof row?.n === "number" ? row.n : 0;
185896
+ } catch {
185897
+ return 0;
185898
+ }
185899
+ }
184185
185900
  function getMessageOrdinal(value) {
184186
185901
  if (typeof value === "number" && Number.isFinite(value)) {
184187
185902
  return value;
@@ -184197,25 +185912,63 @@ async function getSemanticScores(args) {
184197
185912
  if (!args.queryEmbedding || args.memories.length === 0) {
184198
185913
  return semanticScores;
184199
185914
  }
184200
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
184201
- const embeddings = await ensureMemoryEmbeddings({
184202
- db: args.db,
184203
- projectIdentity: args.projectPath,
184204
- memories: args.memories,
184205
- existingEmbeddings: cachedEmbeddings
184206
- });
185915
+ if (!args.workspace?.isWorkspaced) {
185916
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185917
+ const embeddings = await ensureMemoryEmbeddings({
185918
+ db: args.db,
185919
+ projectIdentity: args.projectPath,
185920
+ memories: args.memories,
185921
+ existingEmbeddings: cachedEmbeddings
185922
+ });
185923
+ for (const memory of args.memories) {
185924
+ const memoryEmbedding = embeddings.get(memory.id);
185925
+ if (!memoryEmbedding) {
185926
+ continue;
185927
+ }
185928
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
185929
+ }
185930
+ return semanticScores;
185931
+ }
185932
+ if (!args.queryModelId || args.queryModelId === "off") {
185933
+ return semanticScores;
185934
+ }
185935
+ const workspace = args.workspace;
185936
+ const memoriesByIdentity = new Map;
184207
185937
  for (const memory of args.memories) {
184208
- const memoryEmbedding = embeddings.get(memory.id);
184209
- if (!memoryEmbedding) {
185938
+ const identity = memoryWorkspaceIdentity(memory, workspace);
185939
+ if (!identity)
185940
+ continue;
185941
+ const list = memoriesByIdentity.get(identity) ?? [];
185942
+ list.push(memory);
185943
+ memoriesByIdentity.set(identity, list);
185944
+ }
185945
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
185946
+ if (ownMemories.length > 0) {
185947
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185948
+ await ensureMemoryEmbeddings({
185949
+ db: args.db,
185950
+ projectIdentity: args.projectPath,
185951
+ memories: ownMemories,
185952
+ existingEmbeddings: ownEmbeddings
185953
+ });
185954
+ }
185955
+ for (const identity of workspace.identities) {
185956
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
185957
+ if (memberMemories.length === 0)
184210
185958
  continue;
185959
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
185960
+ for (const memory of memberMemories) {
185961
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
185962
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
185963
+ continue;
185964
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
184211
185965
  }
184212
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
184213
185966
  }
184214
185967
  return semanticScores;
184215
185968
  }
184216
185969
  function getFtsMatches(args) {
184217
185970
  try {
184218
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
185971
+ 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);
184219
185972
  } catch (error51) {
184220
185973
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
184221
185974
  return [];
@@ -184229,8 +185982,11 @@ function selectSemanticCandidates(args) {
184229
185982
  return args.memories;
184230
185983
  }
184231
185984
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
184232
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
184233
- if (cachedEmbeddings) {
185985
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
185986
+ for (const projectPath of embeddingProjects) {
185987
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
185988
+ if (!cachedEmbeddings)
185989
+ continue;
184234
185990
  for (const memoryId of cachedEmbeddings.keys()) {
184235
185991
  candidateIds.add(memoryId);
184236
185992
  }
@@ -184272,7 +186028,8 @@ function mergeMemoryResults(args) {
184272
186028
  score,
184273
186029
  memoryId: memory.id,
184274
186030
  category: memory.category,
184275
- matchType
186031
+ matchType,
186032
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
184276
186033
  });
184277
186034
  }
184278
186035
  return results.sort((left, right) => {
@@ -184286,7 +186043,7 @@ async function searchMemories(args) {
184286
186043
  if (!args.memoryEnabled) {
184287
186044
  return [];
184288
186045
  }
184289
- const memories = getMemoriesByProject(args.db, args.projectPath);
186046
+ 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);
184290
186047
  if (memories.length === 0) {
184291
186048
  return [];
184292
186049
  }
@@ -184294,26 +186051,43 @@ async function searchMemories(args) {
184294
186051
  db: args.db,
184295
186052
  projectPath: args.projectPath,
184296
186053
  query: args.query,
184297
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
186054
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
186055
+ workspace: args.workspace
184298
186056
  });
184299
186057
  const ftsScores = getFtsScores(ftsMatches);
184300
186058
  const semanticCandidates = selectSemanticCandidates({
184301
186059
  memories,
184302
186060
  projectPath: args.projectPath,
184303
- ftsMatches
186061
+ ftsMatches,
186062
+ workspace: args.workspace
184304
186063
  });
184305
186064
  const semanticScores = await getSemanticScores({
184306
186065
  db: args.db,
184307
186066
  projectPath: args.projectPath,
184308
186067
  memories: semanticCandidates,
184309
- queryEmbedding: args.queryEmbedding
186068
+ queryEmbedding: args.queryEmbedding,
186069
+ queryModelId: args.queryModelId,
186070
+ workspace: args.workspace
184310
186071
  });
184311
186072
  return mergeMemoryResults({
184312
186073
  memories,
184313
186074
  semanticScores,
184314
186075
  ftsScores,
184315
186076
  limit: args.limit,
184316
- visibleMemoryIds: args.visibleMemoryIds
186077
+ visibleMemoryIds: args.visibleMemoryIds,
186078
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
186079
+ memories,
186080
+ projectPath: args.projectPath,
186081
+ workspace: args.workspace ?? {
186082
+ identities: [args.projectPath],
186083
+ expandedIdentities: [args.projectPath],
186084
+ namesByIdentity: new Map,
186085
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
186086
+ ownIdentities: [args.projectPath],
186087
+ shareCategories: null,
186088
+ isWorkspaced: false
186089
+ }
186090
+ })
184317
186091
  });
184318
186092
  }
184319
186093
  function linearDecayScore(rank, total) {
@@ -184344,7 +186118,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
184344
186118
  return result;
184345
186119
  }
184346
186120
  var RRF_K = 60;
184347
- var VERBATIM_PROBE_BONUS = 0.5;
186121
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
186122
+ var IDF_FALLOFF = 100;
186123
+ function probeDiscriminationWeight(df, corpusSize) {
186124
+ if (corpusSize <= 0 || df <= 0)
186125
+ return 1;
186126
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
186127
+ }
184348
186128
  function searchMessages(args) {
184349
186129
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
184350
186130
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -184361,20 +186141,31 @@ function searchMessages(args) {
184361
186141
  role: row.role
184362
186142
  }));
184363
186143
  }
186144
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
184364
186145
  const queryLists = [];
184365
186146
  if (baseQuery.length > 0) {
184366
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
186147
+ queryLists.push({
186148
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
186149
+ weight: 1
186150
+ });
184367
186151
  }
186152
+ const probeWeights = new Map;
184368
186153
  for (const probe of probes) {
184369
186154
  const probeQuery = sanitizeFtsQuery(probe);
184370
186155
  if (probeQuery.length === 0)
184371
186156
  continue;
184372
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
186157
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
186158
+ const weight = probeDiscriminationWeight(df, corpusSize);
186159
+ probeWeights.set(probe, weight);
186160
+ queryLists.push({
186161
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
186162
+ weight
186163
+ });
184373
186164
  }
184374
186165
  const fused = new Map;
184375
186166
  for (const list of queryLists) {
184376
- list.forEach((row, rank) => {
184377
- const rrf = 1 / (RRF_K + rank);
186167
+ list.rows.forEach((row, rank) => {
186168
+ const rrf = list.weight / (RRF_K + rank);
184378
186169
  const existing = fused.get(row.messageId);
184379
186170
  if (existing) {
184380
186171
  existing.score += rrf;
@@ -184384,26 +186175,107 @@ function searchMessages(args) {
184384
186175
  });
184385
186176
  }
184386
186177
  for (const entry of fused.values()) {
184387
- if (containsProbeVerbatim(entry.row.content, probes)) {
184388
- entry.score += VERBATIM_PROBE_BONUS;
186178
+ let best = 0;
186179
+ for (const probe of probes) {
186180
+ const weight = probeWeights.get(probe) ?? 0;
186181
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
186182
+ best = weight;
186183
+ }
186184
+ }
186185
+ if (best > 0) {
186186
+ entry.score += best * VERBATIM_RANK_BONUS;
184389
186187
  }
184390
186188
  }
184391
186189
  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);
184392
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
184393
- return ranked.map((entry) => ({
186190
+ return ranked.map((entry, rank) => ({
184394
186191
  source: "message",
184395
186192
  content: previewText(entry.row.content),
184396
- score: maxScore > 0 ? entry.score / maxScore : 0,
186193
+ score: linearDecayScore(rank, ranked.length),
184397
186194
  messageOrdinal: entry.row.messageOrdinal,
184398
186195
  messageId: entry.row.messageId,
184399
186196
  role: entry.row.role
184400
186197
  }));
184401
186198
  }
186199
+ function searchCompartmentChunks(args) {
186200
+ if (!args.queryEmbedding || args.limit <= 0)
186201
+ return [];
186202
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
186203
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
186204
+ if (rows.length === 0)
186205
+ return [];
186206
+ const byCompartment = new Map;
186207
+ for (const row of rows) {
186208
+ if (cutoff !== null && row.endOrdinal > cutoff) {
186209
+ continue;
186210
+ }
186211
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
186212
+ if (score <= 0)
186213
+ continue;
186214
+ const existing = byCompartment.get(row.compartmentId);
186215
+ if (!existing || score > existing.score) {
186216
+ byCompartment.set(row.compartmentId, { row, score });
186217
+ }
186218
+ }
186219
+ 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 }) => ({
186220
+ source: "compartment",
186221
+ content: previewText(row.title),
186222
+ score: score * SINGLE_SOURCE_PENALTY,
186223
+ compartmentId: row.compartmentId,
186224
+ sessionId: row.sessionId,
186225
+ title: row.title,
186226
+ startOrdinal: row.startOrdinal,
186227
+ endOrdinal: row.endOrdinal,
186228
+ matchType: "semantic"
186229
+ }));
186230
+ }
186231
+ function mergeMessageAndCompartmentResults(args) {
186232
+ if (args.compartments.length === 0)
186233
+ return args.messages;
186234
+ if (args.messages.length === 0)
186235
+ return args.compartments;
186236
+ const fused = new Map;
186237
+ const add = (key, result, score, tieOrdinal) => {
186238
+ const existing = fused.get(key);
186239
+ if (existing) {
186240
+ existing.score += score;
186241
+ return existing;
186242
+ }
186243
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
186244
+ fused.set(key, entry);
186245
+ return entry;
186246
+ };
186247
+ args.compartments.forEach((compartment, rank) => {
186248
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
186249
+ });
186250
+ for (const [rank, message] of args.messages.entries()) {
186251
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
186252
+ const contribution = 1 / (RRF_K + rank);
186253
+ if (!containing) {
186254
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
186255
+ continue;
186256
+ }
186257
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
186258
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
186259
+ entry.snippetScore = message.score;
186260
+ entry.result = {
186261
+ ...entry.result,
186262
+ matchType: "hybrid",
186263
+ snippet: message.content
186264
+ };
186265
+ }
186266
+ }
186267
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
186268
+ return ranked.map((entry, rank) => ({
186269
+ ...entry.result,
186270
+ score: linearDecayScore(rank, ranked.length)
186271
+ }));
186272
+ }
184402
186273
  function getSourceBoost(result) {
184403
186274
  switch (result.source) {
184404
186275
  case "memory":
184405
186276
  return MEMORY_SOURCE_BOOST;
184406
186277
  case "message":
186278
+ case "compartment":
184407
186279
  return MESSAGE_SOURCE_BOOST;
184408
186280
  case "git_commit":
184409
186281
  return GIT_COMMIT_SOURCE_BOOST;
@@ -184421,6 +186293,9 @@ function compareUnifiedResults(left, right) {
184421
186293
  if (left.source === "message" && right.source === "message") {
184422
186294
  return left.messageOrdinal - right.messageOrdinal;
184423
186295
  }
186296
+ if (left.source === "compartment" && right.source === "compartment") {
186297
+ return left.startOrdinal - right.startOrdinal;
186298
+ }
184424
186299
  if (left.source === "git_commit" && right.source === "git_commit") {
184425
186300
  return right.committedAtMs - left.committedAtMs;
184426
186301
  }
@@ -184471,10 +186346,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184471
186346
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
184472
186347
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
184473
186348
  const activeSources = resolveSources(options.sources);
184474
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
186349
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
186350
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
184475
186351
  const runMessages = activeSources.has("message");
184476
186352
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
184477
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
186353
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
186354
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
184478
186355
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
184479
186356
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
184480
186357
  return null;
@@ -184490,6 +186367,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184490
186367
  probes: messageProbes
184491
186368
  }) : [];
184492
186369
  const queryEmbedding = await queryEmbeddingPromise;
186370
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
186371
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
186372
+ const embeddingModelId = embeddingSnapshot?.modelId;
186373
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
186374
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
186375
+ db,
186376
+ sessionId,
186377
+ projectPath,
186378
+ queryEmbedding,
186379
+ limit: tierLimit,
186380
+ maxOrdinal: options.maxMessageOrdinal,
186381
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
186382
+ }) : [];
186383
+ const messageLikeResults = mergeMessageAndCompartmentResults({
186384
+ messages: messageResults,
186385
+ compartments: compartmentResults,
186386
+ limit: tierLimit
186387
+ });
184493
186388
  const [memoryResults, gitCommitResults] = await Promise.all([
184494
186389
  runMemory ? searchMemories({
184495
186390
  db,
@@ -184498,6 +186393,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184498
186393
  limit: tierLimit,
184499
186394
  memoryEnabled: true,
184500
186395
  queryEmbedding,
186396
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
186397
+ workspace,
184501
186398
  visibleMemoryIds: options.visibleMemoryIds
184502
186399
  }) : Promise.resolve([]),
184503
186400
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -184508,7 +186405,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184508
186405
  queryEmbedding
184509
186406
  })) : Promise.resolve([])
184510
186407
  ]);
184511
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
186408
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
184512
186409
  const countRetrievals = options.countRetrievals ?? true;
184513
186410
  if (countRetrievals) {
184514
186411
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -184572,6 +186469,11 @@ function renderFragment(result, charCap) {
184572
186469
  const compressed = cavemanCompress(result.content, "ultra");
184573
186470
  return truncate(compressed, charCap);
184574
186471
  }
186472
+ case "compartment": {
186473
+ const source = result.snippet ?? result.title;
186474
+ const compressed = cavemanCompress(source, "ultra");
186475
+ return truncate(compressed, charCap);
186476
+ }
184575
186477
  }
184576
186478
  }
184577
186479
  function buildAutoSearchHint(results, options = {}) {
@@ -185212,7 +187114,7 @@ function isVisibleNoteReadPart(part) {
185212
187114
  }
185213
187115
 
185214
187116
  // src/hooks/magic-context/todo-view.ts
185215
- import { createHash as createHash9 } from "node:crypto";
187117
+ import { createHash as createHash11 } from "node:crypto";
185216
187118
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
185217
187119
  var TITLE_DONE_STATUSES = new Set(["completed"]);
185218
187120
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -185257,7 +187159,7 @@ function buildSyntheticTodoPart(stateJson) {
185257
187159
  };
185258
187160
  }
185259
187161
  function computeSyntheticCallId(stateJson) {
185260
- const hash2 = createHash9("sha256").update(stateJson).digest("hex").slice(0, 16);
187162
+ const hash2 = createHash11("sha256").update(stateJson).digest("hex").slice(0, 16);
185261
187163
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
185262
187164
  }
185263
187165
  function parseTodoState(stateJson) {
@@ -185308,15 +187210,25 @@ async function runPostTransformPhase(args) {
185308
187210
  const emergencyBypassCompartmentGate = forceMaterialization;
185309
187211
  const deferredMaterialize = args.canConsumeDeferredLate && deferredMaterializationWasPending;
185310
187212
  const materializationRequested = isExplicitFlush || deferredMaterialize;
185311
- const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
187213
+ const m0M1EnabledForFold = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
187214
+ const m0HardFoldThisPass = m0M1EnabledForFold && args.m0M1 ? mustMaterialize({
187215
+ db: args.db,
187216
+ sessionId: args.sessionId,
187217
+ state: args.sessionMeta,
187218
+ projectPath: args.m0M1.projectPath,
187219
+ projectDirectory: args.m0M1.projectDirectory,
187220
+ hardSignals: args.m0M1.hardSignals
187221
+ }).value : false;
187222
+ const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || m0HardFoldThisPass || compartmentRunning;
185312
187223
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
185313
187224
  const hasPendingUserOps = pendingOps.length > 0;
185314
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
185315
- const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
187225
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || m0HardFoldThisPass) && (!compartmentRunning || emergencyBypassCompartmentGate);
187226
+ const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || m0HardFoldThisPass || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
185316
187227
  const isCacheBustingPass = shouldApplyPendingOps || shouldRunHeuristics;
187228
+ const canUseEmptySentinels = modelAcceptsEmptyContent(args.resolvedProviderID);
185317
187229
  if (shouldRunHeuristics) {
185318
187230
  const subagentRerun = !args.fullFeatureMode && alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && !forceMaterialization;
185319
- 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})`;
187231
+ 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})`;
185320
187232
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
185321
187233
  }
185322
187234
  if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !materializationRequested && args.fullFeatureMode) {
@@ -185359,7 +187271,9 @@ async function runPostTransformPhase(args) {
185359
187271
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags}`);
185360
187272
  const t7 = performance.now();
185361
187273
  const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
185362
- stripClearedReasoning(args.messages);
187274
+ if (canUseEmptySentinels) {
187275
+ stripClearedReasoning(args.messages);
187276
+ }
185363
187277
  const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
185364
187278
  if (clearedReasoning > 0 || strippedInline > 0) {
185365
187279
  let maxTag = 0;
@@ -185405,7 +187319,6 @@ async function runPostTransformPhase(args) {
185405
187319
  if (args.watermark > 0) {
185406
187320
  const tWatermarkCleanup = performance.now();
185407
187321
  truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
185408
- stripProcessedImages(args.messages, args.watermark, args.messageTagNumbers);
185409
187322
  logTransformTiming(args.sessionId, "watermarkCleanup", tWatermarkCleanup);
185410
187323
  }
185411
187324
  if (shouldApplyPendingOps) {
@@ -185415,21 +187328,40 @@ async function runPostTransformPhase(args) {
185415
187328
  sessionLog(args.sessionId, "transform failed applying pending operations:", error51);
185416
187329
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: getErrorMessage(error51) });
185417
187330
  }
185418
- try {
185419
- const t8 = performance.now();
185420
- const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
185421
- const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
185422
- detect: isCacheBustingPass,
185423
- protectedCount: args.protectedTags
185424
- });
185425
- if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
185426
- addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
187331
+ if (canUseEmptySentinels) {
187332
+ try {
187333
+ const t8 = performance.now();
187334
+ const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
187335
+ const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
187336
+ detect: isCacheBustingPass,
187337
+ protectedCount: args.protectedTags
187338
+ });
187339
+ if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
187340
+ addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
187341
+ }
187342
+ logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
187343
+ } catch (error51) {
187344
+ sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
187345
+ }
187346
+ }
187347
+ if (canUseEmptySentinels && args.watermark > 0) {
187348
+ try {
187349
+ const tImg = performance.now();
187350
+ const frozenImageIds = getProcessedImageStrippedIds(args.db, args.sessionId);
187351
+ const imageResult = stripProcessedImages(args.messages, frozenImageIds, {
187352
+ detect: isCacheBustingPass,
187353
+ watermark: args.watermark,
187354
+ messageTagNumbers: args.messageTagNumbers
187355
+ });
187356
+ if (isCacheBustingPass && imageResult.newlyStrippedIds.length > 0) {
187357
+ addProcessedImageStrippedIds(args.db, args.sessionId, imageResult.newlyStrippedIds);
187358
+ }
187359
+ logTransformTiming(args.sessionId, "stripProcessedImages", tImg);
187360
+ } catch (error51) {
187361
+ sessionLog(args.sessionId, "transform failed stripping processed images:", error51);
185427
187362
  }
185428
- logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
185429
- } catch (error51) {
185430
- sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
185431
187363
  }
185432
- const m0M1Enabled = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
187364
+ const m0M1Enabled = m0M1EnabledForFold;
185433
187365
  if (m0M1Enabled && args.m0M1) {
185434
187366
  const tInjectM0M1 = performance.now();
185435
187367
  try {
@@ -185476,7 +187408,7 @@ async function runPostTransformPhase(args) {
185476
187408
  const tPlaceholder = performance.now();
185477
187409
  const persistedIds = getStrippedPlaceholderIds(args.db, args.sessionId);
185478
187410
  if (persistedIds.size > 0) {
185479
- const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.liveProviderID);
187411
+ const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.resolvedProviderID);
185480
187412
  if (replayed > 0) {
185481
187413
  sessionLog(args.sessionId, `sentinel replay: neutralized ${replayed} previously-stripped messages`);
185482
187414
  }
@@ -185487,9 +187419,9 @@ async function runPostTransformPhase(args) {
185487
187419
  }
185488
187420
  }
185489
187421
  if (isCacheBustingPass) {
185490
- const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.liveProviderID);
187422
+ const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.resolvedProviderID);
185491
187423
  const protectedTailStart = Math.max(0, args.messages.length - args.protectedTags * 2);
185492
- const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.liveProviderID);
187424
+ const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.resolvedProviderID);
185493
187425
  const newlyNeutralized = droppedResult.sentineledIds.length + systemInjectedResult.sentineledIds.length;
185494
187426
  if (newlyNeutralized > 0) {
185495
187427
  const addedIds = [
@@ -185689,6 +187621,7 @@ function clearMessageTokensCache(sessionId, messageId) {
185689
187621
  if (cache)
185690
187622
  cache.delete(messageId);
185691
187623
  }
187624
+ var recordedSessionProjectIdentity = new BoundedSessionMap(MESSAGE_TOKENS_CACHE_MAX);
185692
187625
  function findLastAssistantModel2(messages) {
185693
187626
  for (let i = messages.length - 1;i >= 0; i--) {
185694
187627
  const info = messages[i].info;
@@ -185736,9 +187669,11 @@ function createTransform(deps) {
185736
187669
  const fullFeatureMode = !reducedMode;
185737
187670
  const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false && resolveCtxReduceAvailabilityFromMessages(sessionId, messages);
185738
187671
  let sessionDirectory = deps.directory ?? "";
187672
+ let sessionDirectoryResolvedFromHost = false;
185739
187673
  const cachedDirectory = deps.sessionDirectoryBySession?.get(sessionId);
185740
187674
  if (cachedDirectory && cachedDirectory.length > 0) {
185741
187675
  sessionDirectory = cachedDirectory;
187676
+ sessionDirectoryResolvedFromHost = true;
185742
187677
  } else if (deps.client !== undefined) {
185743
187678
  try {
185744
187679
  const sessionResponse = await deps.client.session.get({ path: { id: sessionId } }).catch(() => null);
@@ -185746,6 +187681,7 @@ function createTransform(deps) {
185746
187681
  if (sessionInfo && typeof sessionInfo.directory === "string" && sessionInfo.directory.length > 0) {
185747
187682
  sessionDirectory = sessionInfo.directory;
185748
187683
  deps.sessionDirectoryBySession?.set(sessionId, sessionDirectory);
187684
+ sessionDirectoryResolvedFromHost = true;
185749
187685
  }
185750
187686
  } catch {}
185751
187687
  }
@@ -185840,6 +187776,8 @@ function createTransform(deps) {
185840
187776
  deps.liveModelBySession?.set(sessionId, recovered);
185841
187777
  }
185842
187778
  }
187779
+ const resolvedProviderID = modelForBudget?.providerID;
187780
+ const canUseEmptySentinels = modelAcceptsEmptyContent(resolvedProviderID);
185843
187781
  const resolvedContextLimit = modelForBudget ? resolveTrustedContextLimit(modelForBudget.providerID, modelForBudget.modelID, {
185844
187782
  db,
185845
187783
  sessionID: sessionId
@@ -186004,6 +187942,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186004
187942
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
186005
187943
  const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(compartmentDirectory || process.cwd()) : undefined;
186006
187944
  const sessionProjectIdentity = projectIdentity ?? (sessionDirectory ? resolveProjectIdentity(sessionDirectory) : deps.projectPath);
187945
+ if (sessionProjectIdentity && sessionDirectoryResolvedFromHost && recordedSessionProjectIdentity.get(sessionId) !== sessionProjectIdentity) {
187946
+ recordSessionProjectIdentity(db, sessionId, sessionProjectIdentity);
187947
+ recordedSessionProjectIdentity.set(sessionId, sessionProjectIdentity);
187948
+ }
186007
187949
  let triggerBoundarySnapshot;
186008
187950
  if (fullFeatureMode && historianRunnable && !sessionMeta.compartmentInProgress) {
186009
187951
  const tTrigger = performance.now();
@@ -186098,7 +188040,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186098
188040
  }
186099
188041
  }
186100
188042
  const t3 = performance.now();
186101
- const strippedStructuralNoise = stripStructuralNoise(messages);
188043
+ const strippedStructuralNoise = canUseEmptySentinels ? stripStructuralNoise(messages) : 0;
186102
188044
  logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
186103
188045
  const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
186104
188046
  if (persistedReasoningWatermark > 0) {
@@ -186119,18 +188061,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186119
188061
  logTransformTiming(sessionId, "replayCavemanCompression", tCavemanReplay);
186120
188062
  }
186121
188063
  const t4 = performance.now();
186122
- const strippedClearedReasoning = stripClearedReasoning(messages);
188064
+ const strippedClearedReasoning = canUseEmptySentinels ? stripClearedReasoning(messages) : 0;
186123
188065
  logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
186124
188066
  const tMergeStrip = performance.now();
186125
- let liveProviderID = deps.liveModelBySession?.get(sessionId)?.providerID;
186126
- if (liveProviderID === undefined) {
186127
- const recovered = findLastAssistantModelFromOpenCodeDb(sessionId);
186128
- if (recovered) {
186129
- liveProviderID = recovered.providerID;
186130
- deps.liveModelBySession?.set(sessionId, recovered);
186131
- }
186132
- }
186133
- const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, liveProviderID);
188067
+ const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, resolvedProviderID);
186134
188068
  if (strippedMergedReasoning > 0) {
186135
188069
  sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
186136
188070
  }
@@ -186249,7 +188183,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186249
188183
  sessionDirectory,
186250
188184
  autoSearch: deps.autoSearch,
186251
188185
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
186252
- liveProviderID,
188186
+ resolvedProviderID,
186253
188187
  historyRefreshSessions: deps.historyRefreshSessions,
186254
188188
  m0M1: {
186255
188189
  projectPath: projectIdentity,
@@ -187078,7 +189012,7 @@ function createToolExecuteAfterHook(args) {
187078
189012
  init_send_session_notification();
187079
189013
 
187080
189014
  // src/hooks/magic-context/system-prompt-hash.ts
187081
- import { createHash as createHash10 } from "node:crypto";
189015
+ import { createHash as createHash12 } from "node:crypto";
187082
189016
 
187083
189017
  // src/agents/magic-context-prompt.ts
187084
189018
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -187278,7 +189212,7 @@ function createSystemPromptHashHandler(deps) {
187278
189212
  `);
187279
189213
  if (systemContent.length === 0)
187280
189214
  return;
187281
- const currentHash = createHash10("md5").update(systemContent).digest("hex");
189215
+ const currentHash = createHash12("md5").update(systemContent).digest("hex");
187282
189216
  if (!sessionMetaEarly) {
187283
189217
  return;
187284
189218
  }
@@ -187539,6 +189473,46 @@ function createMagicContextHook(deps) {
187539
189473
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
187540
189474
  getNotificationParams: (sid) => getLiveNotificationParams(sid, liveModelBySession, variantBySession, agentBySession)
187541
189475
  });
189476
+ const executeEmbedHistory = async (sessionId) => {
189477
+ if (deps.config.memory?.enabled === false) {
189478
+ return "Memory is disabled for this project, so there is no semantic embedding to backfill.";
189479
+ }
189480
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
189481
+ await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
189482
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
189483
+ setRecompStarting({ recompProgressBySession }, sessionId, "Embedding history…", "embed");
189484
+ const outcome = await embedSessionCompartmentChunks(db, sessionProjectIdentity, sessionId, {
189485
+ onProgress: ({ embedded, total }) => {
189486
+ const cur = recompProgressBySession.get(sessionId);
189487
+ if (!cur || cur.phase !== "recomp")
189488
+ return;
189489
+ recompProgressBySession.set(sessionId, {
189490
+ ...cur,
189491
+ processedMessages: embedded,
189492
+ totalMessages: total,
189493
+ updatedAt: Date.now()
189494
+ });
189495
+ }
189496
+ });
189497
+ const terminal = (phase, message) => {
189498
+ setRecompTerminal({ recompProgressBySession }, sessionId, phase, message);
189499
+ return message;
189500
+ };
189501
+ switch (outcome.status) {
189502
+ case "nothing":
189503
+ return terminal("done", "All of this session's history is already embedded.");
189504
+ case "disabled":
189505
+ return terminal("skipped", "No embedding provider is configured, so there is nothing to embed.");
189506
+ case "busy":
189507
+ return terminal("skipped", `Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`);
189508
+ case "aborted":
189509
+ return terminal("done", `Embedded ${outcome.embedded} of ${outcome.total} compartments before stopping.`);
189510
+ case "stalled":
189511
+ 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.`);
189512
+ default:
189513
+ return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`);
189514
+ }
189515
+ };
187542
189516
  const sidekickRunnable = isSidekickRunnable(deps.config);
187543
189517
  const sidekickConfig = sidekickRunnable ? deps.config.sidekick : undefined;
187544
189518
  const transform2 = createTransform({
@@ -187693,6 +189667,7 @@ function createMagicContextHook(deps) {
187693
189667
  },
187694
189668
  executeRecomp: historianRunnable ? async (sessionId, options) => runManagedRecomp(buildManagedRecompCtx(sessionId), sessionId, options) : undefined,
187695
189669
  runUpgrade: historianRunnable ? async (sessionId) => runManagedUpgrade(buildManagedRecompCtx(sessionId), sessionId) : undefined,
189670
+ executeEmbedHistory,
187696
189671
  sendNotification: async (sessionId, text, params) => {
187697
189672
  await sendIgnoredMessage(deps.client, sessionId, text, {
187698
189673
  ...getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
@@ -188830,6 +190805,7 @@ init_memory();
188830
190805
  init_embedding();
188831
190806
  init_embedding_cache();
188832
190807
  init_normalize_hash();
190808
+ init_workspaces();
188833
190809
  init_logger();
188834
190810
  await init_storage();
188835
190811
  import { tool as tool2 } from "@opencode-ai/plugin";
@@ -188989,6 +190965,23 @@ function createCtxMemoryTool(deps) {
188989
190965
  }
188990
190966
  const projectPath = deps.resolveProjectPath(toolContext.directory);
188991
190967
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
190968
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectPath);
190969
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
190970
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
190971
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? projectIdentityForStoredPath(rawProjectPath) : projectIdentityForStoredPath(rawProjectPath);
190972
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectPath) : null;
190973
+ const memoryVisibleToTool = (memory) => {
190974
+ if (workspaceIdentitySet.identities.length <= 1) {
190975
+ return memoryBelongsToProject(memory, projectPath);
190976
+ }
190977
+ if (!storedPathBelongsToWorkspace(memory.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
190978
+ return false;
190979
+ }
190980
+ const isOwn = targetIdentityForStoredPath(memory.projectPath) === projectPath;
190981
+ if (isOwn)
190982
+ return true;
190983
+ return toolShareCategories === null || toolShareCategories.includes(memory.category);
190984
+ };
188992
190985
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
188993
190986
  if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188994
190987
  return getDisabledMessage();
@@ -189045,18 +191038,18 @@ function createCtxMemoryTool(deps) {
189045
191038
  }
189046
191039
  const rawProjectPath = projectPathForMemoryId(deps.db, updateId);
189047
191040
  const memory = getMemoryById(deps.db, updateId);
189048
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
191041
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
189049
191042
  return `Error: Memory with ID ${updateId} was not found.`;
189050
191043
  }
189051
191044
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
189052
191045
  return inactiveMemoryError(updateId, "updating");
189053
191046
  }
189054
191047
  const normalizedHash = computeNormalizedHash(content);
189055
- const duplicate = getMemoryByHash(deps.db, projectPath, memory.category, normalizedHash);
191048
+ const duplicate = getMemoryByHash(deps.db, targetIdentityForStoredPath(rawProjectPath), memory.category, normalizedHash);
189056
191049
  if (duplicate && duplicate.id !== memory.id) {
189057
191050
  return `Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`;
189058
191051
  }
189059
- const projectIdentity = projectIdentityForStoredPath(rawProjectPath);
191052
+ const projectIdentity = targetIdentityForStoredPath(rawProjectPath);
189060
191053
  deps.db.transaction(() => {
189061
191054
  updateMemoryContentInCurrentTransaction(deps.db, memory, content, normalizedHash);
189062
191055
  queueMemoryMutation(deps.db, {
@@ -189070,7 +191063,7 @@ function createCtxMemoryTool(deps) {
189070
191063
  queueMemoryEmbedding({
189071
191064
  deps,
189072
191065
  sessionId: toolContext.sessionID,
189073
- projectPath,
191066
+ projectPath: projectIdentity,
189074
191067
  memoryId: memory.id,
189075
191068
  content
189076
191069
  });
@@ -189093,7 +191086,7 @@ function createCtxMemoryTool(deps) {
189093
191086
  return "Error: One or more source memories were not found.";
189094
191087
  }
189095
191088
  if (toolContext.agent !== DREAMER_AGENT) {
189096
- const foreign = sourceMemories.find((memory) => !memoryBelongsToProject(memory, projectPath));
191089
+ const foreign = sourceMemories.find((memory) => !memoryVisibleToTool(memory));
189097
191090
  if (foreign) {
189098
191091
  return `Error: Memory with ID ${foreign.id} was not found.`;
189099
191092
  }
@@ -189177,15 +191170,16 @@ function createCtxMemoryTool(deps) {
189177
191170
  return `Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`;
189178
191171
  }
189179
191172
  if (args.action === "archive") {
189180
- const archiveIds = args.ids;
189181
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
191173
+ const rawArchiveIds = args.ids;
191174
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
189182
191175
  return "Error: 'ids' must contain at least one integer memory ID when action is 'archive'.";
189183
191176
  }
191177
+ const archiveIds = [...new Set(rawArchiveIds)];
189184
191178
  const targets = [];
189185
191179
  for (const memoryId of archiveIds) {
189186
191180
  const rawProjectPath = projectPathForMemoryId(deps.db, memoryId);
189187
191181
  const memory = getMemoryById(deps.db, memoryId);
189188
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
191182
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
189189
191183
  return `Error: Memory with ID ${memoryId} was not found.`;
189190
191184
  }
189191
191185
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -189193,7 +191187,7 @@ function createCtxMemoryTool(deps) {
189193
191187
  }
189194
191188
  targets.push({
189195
191189
  memoryId,
189196
- projectIdentity: projectIdentityForStoredPath(rawProjectPath)
191190
+ projectIdentity: targetIdentityForStoredPath(rawProjectPath)
189197
191191
  });
189198
191192
  }
189199
191193
  deps.db.transaction(() => {
@@ -189667,8 +191661,9 @@ function formatAge2(committedAtMs) {
189667
191661
  }
189668
191662
  function formatResult(result, index) {
189669
191663
  if (result.source === "memory") {
191664
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
189670
191665
  return [
189671
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
191666
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
189672
191667
  result.content
189673
191668
  ].join(`
189674
191669
  `);
@@ -189678,6 +191673,13 @@ function formatResult(result, index) {
189678
191673
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
189679
191674
  result.content
189680
191675
  ].join(`
191676
+ `);
191677
+ }
191678
+ if (result.source === "compartment") {
191679
+ return [
191680
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
191681
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
191682
+ ].join(`
189681
191683
  `);
189682
191684
  }
189683
191685
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -189693,7 +191695,7 @@ function formatSearchResults(query, results) {
189693
191695
  return `No results found for "${query}" across memories, git commits, or message history.`;
189694
191696
  }
189695
191697
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
189696
- if (results.some((result) => result.source === "message")) {
191698
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
189697
191699
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
189698
191700
  }
189699
191701
  const body = bodyParts.join(`
@@ -189851,11 +191853,11 @@ import { createServer } from "node:http";
189851
191853
  import { dirname as dirname8 } from "node:path";
189852
191854
 
189853
191855
  // src/shared/rpc-utils.ts
189854
- import { createHash as createHash11 } from "node:crypto";
191856
+ import { createHash as createHash13 } from "node:crypto";
189855
191857
  import { join as join24 } from "node:path";
189856
191858
  function projectHash(directory) {
189857
191859
  const normalized = directory.replace(/\/+$/, "");
189858
- return createHash11("sha256").update(normalized).digest("hex").slice(0, 16);
191860
+ return createHash13("sha256").update(normalized).digest("hex").slice(0, 16);
189859
191861
  }
189860
191862
  function rpcPortDir(storageDir, directory) {
189861
191863
  return join24(storageDir, "rpc", projectHash(directory));