@wolfx/opencode-magic-context 0.23.1 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/config/schema/magic-context.d.ts +10 -3
  2. package/dist/config/schema/magic-context.d.ts.map +1 -1
  3. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  4. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +80 -0
  5. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -0
  6. package/dist/features/magic-context/compartment-embedding.d.ts +22 -26
  7. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  8. package/dist/features/magic-context/memory/embedding-backfill.d.ts +3 -2
  9. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  10. package/dist/features/magic-context/memory/embedding-cache.d.ts +3 -2
  11. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -1
  12. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -1
  13. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding-openai.d.ts +17 -0
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  16. package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -0
  17. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  18. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  19. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +5 -1
  21. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  22. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -7
  23. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  24. package/dist/features/magic-context/memory/storage-memory.d.ts +18 -12
  25. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  26. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  27. package/dist/features/magic-context/project-embedding-registry.d.ts +53 -0
  28. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  29. package/dist/features/magic-context/search.d.ts +14 -1
  30. package/dist/features/magic-context/search.d.ts.map +1 -1
  31. package/dist/features/magic-context/session-project-storage.d.ts +17 -0
  32. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -0
  33. package/dist/features/magic-context/storage-db.d.ts +1 -1
  34. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-memory-mutation-log.d.ts +2 -0
  36. package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -1
  37. package/dist/features/magic-context/storage-meta-persisted.d.ts +16 -0
  38. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  39. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-meta-shared.d.ts +3 -1
  41. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  42. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  43. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  44. package/dist/features/magic-context/storage-tags.d.ts +10 -1
  45. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  46. package/dist/features/magic-context/storage.d.ts +3 -2
  47. package/dist/features/magic-context/storage.d.ts.map +1 -1
  48. package/dist/features/magic-context/types.d.ts +1 -0
  49. package/dist/features/magic-context/types.d.ts.map +1 -1
  50. package/dist/features/magic-context/workspaces.d.ts +20 -0
  51. package/dist/features/magic-context/workspaces.d.ts.map +1 -0
  52. package/dist/hooks/magic-context/apply-operations.d.ts +23 -0
  53. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/command-handler.d.ts +5 -0
  56. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/compartment-runner-types.d.ts +11 -1
  61. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +7 -2
  64. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/inject-compartments.d.ts +23 -3
  68. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -1
  70. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  72. package/dist/hooks/magic-context/sentinel.d.ts +33 -28
  73. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/strip-content.d.ts +34 -17
  75. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  76. package/dist/hooks/magic-context/strip-structural-noise.d.ts +5 -7
  77. package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
  78. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +4 -5
  79. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  80. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  81. package/dist/index.js +2411 -365
  82. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  83. package/dist/shared/announcement.d.ts +1 -1
  84. package/dist/shared/rpc-types.d.ts +1 -1
  85. package/dist/shared/rpc-types.d.ts.map +1 -1
  86. package/dist/shared/tui-preferences.d.ts +32 -0
  87. package/dist/shared/tui-preferences.d.ts.map +1 -0
  88. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  89. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  90. package/package.json +1 -1
  91. package/src/shared/announcement.ts +6 -6
  92. package/src/shared/rpc-types.ts +1 -1
  93. package/src/shared/tui-preferences.test.ts +210 -0
  94. package/src/shared/tui-preferences.ts +303 -0
  95. package/src/tui/index.tsx +5 -3
  96. package/src/tui/slots/sidebar-content.tsx +119 -14
package/dist/index.js CHANGED
@@ -14887,7 +14887,8 @@ var init_magic_context = __esm(() => {
14887
14887
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
14888
14888
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
14889
14889
  input_type: exports_external.string().optional().describe("Optional input_type sent in the embedding request body. Required by some openai-compatible providers (e.g. NVIDIA NIM expects 'query' or 'passage'). Omitted from the request when unset."),
14890
- truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset.")
14890
+ truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset."),
14891
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
14891
14892
  }).superRefine((data, ctx) => {
14892
14893
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
14893
14894
  ctx.addIssue({
@@ -14908,7 +14909,8 @@ var init_magic_context = __esm(() => {
14908
14909
  if (data.provider === "local") {
14909
14910
  return {
14910
14911
  provider: "local",
14911
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
14912
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
14913
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14912
14914
  };
14913
14915
  }
14914
14916
  if (data.provider === "openai-compatible") {
@@ -14921,7 +14923,8 @@ var init_magic_context = __esm(() => {
14921
14923
  endpoint: data.endpoint?.trim() ?? "",
14922
14924
  ...apiKey ? { api_key: apiKey } : {},
14923
14925
  ...inputType ? { input_type: inputType } : {},
14924
- ...truncate ? { truncate } : {}
14926
+ ...truncate ? { truncate } : {},
14927
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14925
14928
  };
14926
14929
  }
14927
14930
  return { provider: "off" };
@@ -15844,7 +15847,7 @@ function isSessionMetaRow(row) {
15844
15847
  if (row === null || typeof row !== "object")
15845
15848
  return false;
15846
15849
  const r = row;
15847
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15850
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15848
15851
  }
15849
15852
  function getDefaultSessionMeta(sessionId) {
15850
15853
  return {
@@ -15871,6 +15874,7 @@ function getDefaultSessionMeta(sessionId) {
15871
15874
  cachedM0Bytes: null,
15872
15875
  cachedM1Bytes: null,
15873
15876
  cachedM0ProjectMemoryEpoch: null,
15877
+ cachedM0WorkspaceFingerprint: null,
15874
15878
  cachedM0ProjectUserProfileVersion: null,
15875
15879
  cachedM0MaxCompartmentSeq: null,
15876
15880
  cachedM0MaxMemoryId: null,
@@ -15933,6 +15937,7 @@ function toSessionMeta(row) {
15933
15937
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
15934
15938
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
15935
15939
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
15940
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
15936
15941
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
15937
15942
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
15938
15943
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -15963,6 +15968,7 @@ function persistCachedM0(db, sessionId, payload) {
15963
15968
  db.prepare(`UPDATE session_meta SET
15964
15969
  cached_m0_bytes = ?,
15965
15970
  cached_m0_project_memory_epoch = ?,
15971
+ cached_m0_workspace_fingerprint = ?,
15966
15972
  cached_m0_project_user_profile_version = ?,
15967
15973
  cached_m0_max_compartment_seq = ?,
15968
15974
  cached_m0_max_memory_id = ?,
@@ -15975,7 +15981,7 @@ function persistCachedM0(db, sessionId, payload) {
15975
15981
  cached_m0_upgrade_state = ?,
15976
15982
  cached_m0_system_hash = ?,
15977
15983
  cached_m0_model_key = ?
15978
- WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.modelKey ?? "", sessionId);
15984
+ WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.workspaceFingerprint ?? null, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.modelKey ?? "", sessionId);
15979
15985
  }
15980
15986
  function clearCachedM0M1(db, sessionId) {
15981
15987
  ensureSessionMetaRow(db, sessionId);
@@ -15984,6 +15990,7 @@ function clearCachedM0M1(db, sessionId) {
15984
15990
  ["cached_m0_bytes", null],
15985
15991
  ["cached_m1_bytes", null],
15986
15992
  ["cached_m0_project_memory_epoch", null],
15993
+ ["cached_m0_workspace_fingerprint", null],
15987
15994
  ["cached_m0_project_user_profile_version", null],
15988
15995
  ["cached_m0_max_compartment_seq", null],
15989
15996
  ["cached_m0_max_memory_id", null],
@@ -16039,6 +16046,7 @@ var init_storage_meta_shared = __esm(() => {
16039
16046
  "cached_m0_bytes",
16040
16047
  "cached_m1_bytes",
16041
16048
  "cached_m0_project_memory_epoch",
16049
+ "cached_m0_workspace_fingerprint",
16042
16050
  "cached_m0_project_user_profile_version",
16043
16051
  "cached_m0_max_compartment_seq",
16044
16052
  "cached_m0_max_memory_id",
@@ -16086,6 +16094,7 @@ var init_storage_meta_shared = __esm(() => {
16086
16094
  cachedM0Bytes: "cached_m0_bytes",
16087
16095
  cachedM1Bytes: "cached_m1_bytes",
16088
16096
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
16097
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
16089
16098
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
16090
16099
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
16091
16100
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -16115,6 +16124,7 @@ var init_storage_meta_shared = __esm(() => {
16115
16124
  "cachedM0Bytes",
16116
16125
  "cachedM1Bytes",
16117
16126
  "cachedM0ProjectMemoryEpoch",
16127
+ "cachedM0WorkspaceFingerprint",
16118
16128
  "cachedM0ProjectUserProfileVersion",
16119
16129
  "cachedM0MaxCompartmentSeq",
16120
16130
  "cachedM0MaxMemoryId",
@@ -150826,6 +150836,35 @@ function initializeDatabase(db) {
150826
150836
  );
150827
150837
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
150828
150838
 
150839
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
150840
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
150841
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
150842
+ session_id TEXT NOT NULL,
150843
+ project_path TEXT NOT NULL,
150844
+ harness TEXT NOT NULL DEFAULT 'opencode',
150845
+ window_index INTEGER NOT NULL DEFAULT 0,
150846
+ start_ordinal INTEGER NOT NULL,
150847
+ end_ordinal INTEGER NOT NULL,
150848
+ chunk_hash TEXT NOT NULL,
150849
+ model_id TEXT NOT NULL,
150850
+ dims INTEGER NOT NULL,
150851
+ vector BLOB NOT NULL,
150852
+ created_at INTEGER NOT NULL,
150853
+ UNIQUE(compartment_id, window_index)
150854
+ );
150855
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
150856
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
150857
+
150858
+ CREATE TABLE IF NOT EXISTS session_projects (
150859
+ session_id TEXT NOT NULL,
150860
+ harness TEXT NOT NULL DEFAULT 'opencode',
150861
+ project_path TEXT NOT NULL,
150862
+ updated_at INTEGER NOT NULL,
150863
+ PRIMARY KEY(session_id, harness)
150864
+ );
150865
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
150866
+ ON session_projects(project_path);
150867
+
150829
150868
  CREATE TABLE IF NOT EXISTS compartment_events (
150830
150869
  id INTEGER PRIMARY KEY AUTOINCREMENT,
150831
150870
  session_id TEXT NOT NULL,
@@ -151011,6 +151050,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151011
151050
  rekeyed_at INTEGER NOT NULL
151012
151051
  );
151013
151052
 
151053
+ CREATE TABLE IF NOT EXISTS workspaces (
151054
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151055
+ name TEXT NOT NULL UNIQUE,
151056
+ created_at INTEGER NOT NULL,
151057
+ updated_at INTEGER NOT NULL,
151058
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151059
+ );
151060
+
151061
+ CREATE TABLE IF NOT EXISTS workspace_members (
151062
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151063
+ project_path TEXT NOT NULL,
151064
+ display_name TEXT NOT NULL,
151065
+ display_path TEXT NOT NULL,
151066
+ added_at INTEGER NOT NULL,
151067
+ PRIMARY KEY (workspace_id, project_path)
151068
+ );
151069
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151070
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151071
+
151014
151072
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151015
151073
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151016
151074
  table_name TEXT NOT NULL,
@@ -151122,6 +151180,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151122
151180
  deferred_execute_state TEXT,
151123
151181
  cached_m0_bytes BLOB,
151124
151182
  cached_m0_project_memory_epoch INTEGER,
151183
+ cached_m0_workspace_fingerprint TEXT,
151125
151184
  cached_m0_project_user_profile_version INTEGER,
151126
151185
  cached_m0_max_compartment_seq INTEGER,
151127
151186
  cached_m0_max_memory_id INTEGER,
@@ -151280,6 +151339,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151280
151339
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151281
151340
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151282
151341
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151342
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
151283
151343
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
151284
151344
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
151285
151345
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -151333,6 +151393,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151333
151393
  ensureColumn(db, "memories", "importance", "INTEGER");
151334
151394
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
151335
151395
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
151396
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
151336
151397
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
151337
151398
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
151338
151399
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -151364,6 +151425,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151364
151425
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
151365
151426
  updated_at INTEGER NOT NULL DEFAULT 0
151366
151427
  );
151428
+ CREATE TABLE IF NOT EXISTS session_projects (
151429
+ session_id TEXT NOT NULL,
151430
+ harness TEXT NOT NULL DEFAULT 'opencode',
151431
+ project_path TEXT NOT NULL,
151432
+ updated_at INTEGER NOT NULL,
151433
+ PRIMARY KEY(session_id, harness)
151434
+ );
151435
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
151436
+ ON session_projects(project_path);
151367
151437
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
151368
151438
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151369
151439
  session_id TEXT NOT NULL,
@@ -151391,6 +151461,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151391
151461
  new_project_path TEXT NOT NULL,
151392
151462
  rekeyed_at INTEGER NOT NULL
151393
151463
  );
151464
+ CREATE TABLE IF NOT EXISTS workspaces (
151465
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151466
+ name TEXT NOT NULL UNIQUE,
151467
+ created_at INTEGER NOT NULL,
151468
+ updated_at INTEGER NOT NULL,
151469
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151470
+ );
151471
+ CREATE TABLE IF NOT EXISTS workspace_members (
151472
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151473
+ project_path TEXT NOT NULL,
151474
+ display_name TEXT NOT NULL,
151475
+ display_path TEXT NOT NULL,
151476
+ added_at INTEGER NOT NULL,
151477
+ PRIMARY KEY (workspace_id, project_path)
151478
+ );
151479
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151480
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151394
151481
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151395
151482
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151396
151483
  table_name TEXT NOT NULL,
@@ -151412,6 +151499,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151412
151499
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151413
151500
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151414
151501
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151502
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
151415
151503
  }
151416
151504
  function healAllNullColumns(db) {
151417
151505
  healNullTextColumns(db);
@@ -151449,6 +151537,7 @@ function healNullTextColumns(db) {
151449
151537
  ["system_prompt_hash", ""],
151450
151538
  ["stripped_placeholder_ids", ""],
151451
151539
  ["stale_reduce_stripped_ids", ""],
151540
+ ["processed_image_stripped_ids", ""],
151452
151541
  ["memory_block_cache", ""],
151453
151542
  ["memory_block_ids", ""],
151454
151543
  ["compaction_marker_state", ""],
@@ -151493,7 +151582,7 @@ function healNullIntegerColumns(db) {
151493
151582
  }
151494
151583
  }
151495
151584
  function ensureColumn(db, table, column, definition) {
151496
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
151585
+ if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
151497
151586
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
151498
151587
  }
151499
151588
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -151579,7 +151668,7 @@ function getDatabasePersistenceError(db) {
151579
151668
  return null;
151580
151669
  return persistenceErrorByDatabase.get(db) ?? null;
151581
151670
  }
151582
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 32, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151671
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 36, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151583
151672
  var init_storage_db = __esm(async () => {
151584
151673
  init_data_path();
151585
151674
  init_logger();
@@ -151599,10 +151688,302 @@ var init_storage_db = __esm(async () => {
151599
151688
  };
151600
151689
  });
151601
151690
 
151691
+ // src/features/magic-context/memory/constants.ts
151692
+ var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
151693
+ var init_constants = __esm(() => {
151694
+ V2_MEMORY_CATEGORIES = [
151695
+ "PROJECT_RULES",
151696
+ "ARCHITECTURE",
151697
+ "CONSTRAINTS",
151698
+ "CONFIG_VALUES",
151699
+ "NAMING"
151700
+ ];
151701
+ PROMOTABLE_CATEGORIES = [
151702
+ "PROJECT_RULES",
151703
+ "ARCHITECTURE",
151704
+ "CONSTRAINTS",
151705
+ "CONFIG_VALUES",
151706
+ "NAMING",
151707
+ "ARCHITECTURE_DECISIONS",
151708
+ "CONFIG_DEFAULTS",
151709
+ "USER_PREFERENCES",
151710
+ "USER_DIRECTIVES",
151711
+ "ENVIRONMENT",
151712
+ "WORKFLOW_RULES",
151713
+ "KNOWN_ISSUES"
151714
+ ];
151715
+ CATEGORY_PRIORITY = [
151716
+ "PROJECT_RULES",
151717
+ "ARCHITECTURE",
151718
+ "CONSTRAINTS",
151719
+ "CONFIG_VALUES",
151720
+ "NAMING",
151721
+ "USER_DIRECTIVES",
151722
+ "USER_PREFERENCES",
151723
+ "CONFIG_DEFAULTS",
151724
+ "ARCHITECTURE_DECISIONS",
151725
+ "ENVIRONMENT",
151726
+ "WORKFLOW_RULES",
151727
+ "KNOWN_ISSUES"
151728
+ ];
151729
+ MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
151730
+ acc[category] = index;
151731
+ return acc;
151732
+ }, {});
151733
+ MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
151734
+ CATEGORY_DEFAULT_TTL = {
151735
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
151736
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
151737
+ };
151738
+ });
151739
+
151740
+ // src/features/magic-context/project-identity.ts
151741
+ var init_project_identity2 = __esm(() => {
151742
+ init_project_identity();
151743
+ });
151744
+
151745
+ // src/features/magic-context/workspaces.ts
151746
+ import { createHash as createHash4 } from "node:crypto";
151747
+ function tableExists(db, tableName) {
151748
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
151749
+ return Boolean(row);
151750
+ }
151751
+ function columnExists(db, tableName, columnName) {
151752
+ const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
151753
+ return rows.some((row) => row.name === columnName);
151754
+ }
151755
+ function uniqueSorted(values) {
151756
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
151757
+ }
151758
+ function placeholders(values) {
151759
+ return values.map(() => "?").join(", ");
151760
+ }
151761
+ function normalizeShareCategories(raw) {
151762
+ if (raw === null || raw === undefined)
151763
+ return null;
151764
+ if (typeof raw !== "string")
151765
+ return null;
151766
+ let parsed;
151767
+ try {
151768
+ parsed = JSON.parse(raw);
151769
+ } catch {
151770
+ return null;
151771
+ }
151772
+ if (!Array.isArray(parsed))
151773
+ return null;
151774
+ const categories = [];
151775
+ for (const value of parsed) {
151776
+ if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
151777
+ return null;
151778
+ }
151779
+ if (!categories.includes(value))
151780
+ categories.push(value);
151781
+ }
151782
+ return categories.sort((left, right) => left.localeCompare(right));
151783
+ }
151784
+ function selectWorkspaceShareCategories(db, identities) {
151785
+ const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
151786
+ if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
151787
+ return null;
151788
+ }
151789
+ const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
151790
+ FROM workspace_members AS member
151791
+ JOIN workspaces AS workspace ON workspace.id = member.workspace_id
151792
+ WHERE member.project_path IN (${placeholders(candidates)})
151793
+ ORDER BY workspace.id ASC
151794
+ LIMIT 1`).get(...candidates);
151795
+ return normalizeShareCategories(row?.shareCategories ?? null);
151796
+ }
151797
+ function resolveWorkspaceShareCategories(db, projectIdentity) {
151798
+ return selectWorkspaceShareCategories(db, [projectIdentity]);
151799
+ }
151800
+ function resolveWorkspaceIdentitySet(db, projectIdentity) {
151801
+ if (!tableExists(db, "workspace_members")) {
151802
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151803
+ }
151804
+ const rows = db.prepare(`SELECT member.project_path AS identity, member.display_name AS displayName
151805
+ FROM workspace_members AS anchor
151806
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151807
+ WHERE anchor.project_path = ?
151808
+ ORDER BY member.display_name ASC, member.project_path ASC`).all(projectIdentity);
151809
+ if (rows.length === 0) {
151810
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151811
+ }
151812
+ const namesByIdentity = new Map;
151813
+ const identities = [];
151814
+ for (const row of rows) {
151815
+ if (typeof row.identity !== "string" || row.identity.length === 0)
151816
+ continue;
151817
+ if (identities.includes(row.identity))
151818
+ continue;
151819
+ identities.push(row.identity);
151820
+ if (typeof row.displayName === "string" && row.displayName.length > 0) {
151821
+ namesByIdentity.set(row.identity, row.displayName);
151822
+ }
151823
+ }
151824
+ return identities.length > 0 ? { identities, namesByIdentity } : { identities: [projectIdentity], namesByIdentity: new Map };
151825
+ }
151826
+ function expandWorkspaceIdentitySetWithAliases(db, identities) {
151827
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151828
+ const expanded = new Set(canonical);
151829
+ const canonicalIdentityByStoredPath = new Map;
151830
+ for (const identity of canonical) {
151831
+ canonicalIdentityByStoredPath.set(identity, identity);
151832
+ }
151833
+ if (canonical.length === 0 || !tableExists(db, "v22_identity_rekey_map")) {
151834
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151835
+ }
151836
+ const rows = db.prepare(`SELECT old_project_path AS oldProjectPath, new_project_path AS newProjectPath
151837
+ FROM v22_identity_rekey_map
151838
+ WHERE new_project_path IN (${placeholders(canonical)})
151839
+ ORDER BY old_project_path ASC`).all(...canonical);
151840
+ for (const row of rows) {
151841
+ if (typeof row.oldProjectPath !== "string" || typeof row.newProjectPath !== "string") {
151842
+ continue;
151843
+ }
151844
+ if (!canonicalIdentityByStoredPath.has(row.newProjectPath))
151845
+ continue;
151846
+ expanded.add(row.oldProjectPath);
151847
+ canonicalIdentityByStoredPath.set(row.oldProjectPath, row.newProjectPath);
151848
+ }
151849
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151850
+ }
151851
+ function resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) {
151852
+ const direct = canonicalIdentityByStoredPath.get(storedProjectPath);
151853
+ if (direct)
151854
+ return direct;
151855
+ const normalized = normalizeStoredProjectPath(storedProjectPath);
151856
+ const normalizedDirect = canonicalIdentityByStoredPath.get(normalized);
151857
+ if (normalizedDirect)
151858
+ return normalizedDirect;
151859
+ if (memberIdentities.includes(normalized))
151860
+ return normalized;
151861
+ for (const identity of memberIdentities) {
151862
+ if (storedPathBelongsToIdentity(storedProjectPath, identity)) {
151863
+ return identity;
151864
+ }
151865
+ }
151866
+ return null;
151867
+ }
151868
+ function storedPathBelongsToWorkspace(storedProjectPath, memberIdentities, expandedIdentities, canonicalIdentityByStoredPath) {
151869
+ if (expandedIdentities.includes(storedProjectPath))
151870
+ return true;
151871
+ return resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) !== null;
151872
+ }
151873
+ function sourceNameForMemory(storedProjectPath, ownIdentity, memberIdentities, namesByIdentity, canonicalIdentityByStoredPath) {
151874
+ const canonicalIdentity = resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath);
151875
+ if (!canonicalIdentity || canonicalIdentity === ownIdentity)
151876
+ return;
151877
+ return namesByIdentity.get(canonicalIdentity);
151878
+ }
151879
+ function getEpochMap(db, identities) {
151880
+ if (identities.length === 0)
151881
+ return new Map;
151882
+ const rows = db.prepare(`SELECT project_path AS projectPath, project_memory_epoch AS epoch
151883
+ FROM project_state
151884
+ WHERE project_path IN (${placeholders(identities)})`).all(...identities);
151885
+ const epochs = new Map;
151886
+ for (const row of rows) {
151887
+ if (typeof row.projectPath !== "string" || typeof row.epoch !== "number")
151888
+ continue;
151889
+ epochs.set(row.projectPath, row.epoch);
151890
+ }
151891
+ return epochs;
151892
+ }
151893
+ function computeWorkspaceEpochFingerprint(db, identities) {
151894
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151895
+ const epochs = getEpochMap(db, canonical);
151896
+ const shareCategories = selectWorkspaceShareCategories(db, canonical);
151897
+ const hash2 = createHash4("sha256");
151898
+ hash2.update("share_categories", "utf8");
151899
+ hash2.update("\x00");
151900
+ hash2.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
151901
+ hash2.update(`
151902
+ `);
151903
+ for (const identity of canonical) {
151904
+ hash2.update(identity, "utf8");
151905
+ hash2.update("\x00");
151906
+ hash2.update(String(epochs.get(identity) ?? 0), "utf8");
151907
+ hash2.update(`
151908
+ `);
151909
+ }
151910
+ return hash2.digest("hex");
151911
+ }
151912
+ function isInTransaction(db) {
151913
+ const candidate = db;
151914
+ return candidate.inTransaction === true || candidate.isTransaction === true;
151915
+ }
151916
+ function workspaceMembersForIdentity(db, identity) {
151917
+ if (!tableExists(db, "workspace_members"))
151918
+ return [identity];
151919
+ const rows = db.prepare(`SELECT member.project_path AS identity
151920
+ FROM workspace_members AS anchor
151921
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151922
+ WHERE anchor.project_path = ?
151923
+ ORDER BY member.project_path ASC`).all(identity);
151924
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((value) => value.length > 0);
151925
+ return identities.length > 0 ? uniqueSorted(identities) : [identity];
151926
+ }
151927
+ function bumpEpochRows(db, identities, now) {
151928
+ const stmt = db.prepare(`INSERT INTO project_state
151929
+ (project_path, project_memory_epoch, project_user_profile_version, updated_at)
151930
+ VALUES (?, 1, 0, ?)
151931
+ ON CONFLICT(project_path) DO UPDATE SET
151932
+ project_memory_epoch = project_memory_epoch + 1,
151933
+ updated_at = excluded.updated_at`);
151934
+ for (const identity of uniqueSorted(identities)) {
151935
+ stmt.run(identity, now);
151936
+ }
151937
+ }
151938
+ function bumpEpochsForWorkspaceMembers(db, identity, now = Date.now()) {
151939
+ const run = () => bumpEpochRows(db, workspaceMembersForIdentity(db, identity), now);
151940
+ if (isInTransaction(db)) {
151941
+ run();
151942
+ return;
151943
+ }
151944
+ db.exec("BEGIN IMMEDIATE");
151945
+ try {
151946
+ run();
151947
+ db.exec("COMMIT");
151948
+ } catch (error51) {
151949
+ try {
151950
+ db.exec("ROLLBACK");
151951
+ } catch {}
151952
+ throw error51;
151953
+ }
151954
+ }
151955
+ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
151956
+ const run = () => bumpEpochRows(db, identities, now);
151957
+ if (isInTransaction(db)) {
151958
+ run();
151959
+ return;
151960
+ }
151961
+ db.exec("BEGIN IMMEDIATE");
151962
+ try {
151963
+ run();
151964
+ db.exec("COMMIT");
151965
+ } catch (error51) {
151966
+ try {
151967
+ db.exec("ROLLBACK");
151968
+ } catch {}
151969
+ throw error51;
151970
+ }
151971
+ }
151972
+ var VALID_SHARE_CATEGORIES;
151973
+ var init_workspaces = __esm(() => {
151974
+ init_constants();
151975
+ init_project_identity2();
151976
+ VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
151977
+ });
151978
+
151602
151979
  // src/features/magic-context/migrations.ts
151603
- function tableExists(db, name2) {
151980
+ function tableExists2(db, name2) {
151604
151981
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
151605
151982
  }
151983
+ function columnExists2(db, table, column) {
151984
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
151985
+ return rows.some((row) => row.name === column);
151986
+ }
151606
151987
  function ensureMigrationsTable(db) {
151607
151988
  db.exec(`
151608
151989
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -151671,6 +152052,7 @@ function runMigrations(db) {
151671
152052
  var MIGRATIONS, LATEST_MIGRATION_VERSION;
151672
152053
  var init_migrations = __esm(async () => {
151673
152054
  init_logger();
152055
+ init_workspaces();
151674
152056
  await init_storage_db();
151675
152057
  MIGRATIONS = [
151676
152058
  {
@@ -152130,9 +152512,9 @@ var init_migrations = __esm(async () => {
152130
152512
  version: 22,
152131
152513
  description: "v2.0 cache architecture schema foundation",
152132
152514
  up: (db) => {
152133
- const hasSessionMetaTable = tableExists(db, "session_meta");
152134
- const hasCompartmentsTable = tableExists(db, "compartments");
152135
- const hasMemoriesTable = tableExists(db, "memories");
152515
+ const hasSessionMetaTable = tableExists2(db, "session_meta");
152516
+ const hasCompartmentsTable = tableExists2(db, "compartments");
152517
+ const hasMemoriesTable = tableExists2(db, "memories");
152136
152518
  if (hasSessionMetaTable) {
152137
152519
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
152138
152520
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
@@ -152157,7 +152539,7 @@ var init_migrations = __esm(async () => {
152157
152539
  ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
152158
152540
  ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
152159
152541
  }
152160
- const hasRecompCompartmentsTable = tableExists(db, "recomp_compartments");
152542
+ const hasRecompCompartmentsTable = tableExists2(db, "recomp_compartments");
152161
152543
  if (hasRecompCompartmentsTable) {
152162
152544
  ensureColumn(db, "recomp_compartments", "p1", "TEXT");
152163
152545
  ensureColumn(db, "recomp_compartments", "p2", "TEXT");
@@ -152468,13 +152850,163 @@ var init_migrations = __esm(async () => {
152468
152850
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
152469
152851
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
152470
152852
  }
152853
+ },
152854
+ {
152855
+ version: 33,
152856
+ description: "Compartment chunk embeddings for semantic message-history search",
152857
+ up: (db) => {
152858
+ db.exec(`
152859
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
152860
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152861
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
152862
+ session_id TEXT NOT NULL,
152863
+ project_path TEXT NOT NULL,
152864
+ harness TEXT NOT NULL DEFAULT 'opencode',
152865
+ window_index INTEGER NOT NULL DEFAULT 0,
152866
+ start_ordinal INTEGER NOT NULL,
152867
+ end_ordinal INTEGER NOT NULL,
152868
+ chunk_hash TEXT NOT NULL,
152869
+ model_id TEXT NOT NULL,
152870
+ dims INTEGER NOT NULL,
152871
+ vector BLOB NOT NULL,
152872
+ created_at INTEGER NOT NULL,
152873
+ UNIQUE(compartment_id, window_index)
152874
+ );
152875
+ CREATE INDEX IF NOT EXISTS idx_cce_session
152876
+ ON compartment_chunk_embeddings(session_id);
152877
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model
152878
+ ON compartment_chunk_embeddings(project_path, model_id);
152879
+ `);
152880
+ }
152881
+ },
152882
+ {
152883
+ version: 34,
152884
+ description: "workspace tables and m[0] workspace fingerprint cache reset",
152885
+ up: (db) => {
152886
+ db.exec(`
152887
+ CREATE TABLE IF NOT EXISTS workspaces (
152888
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152889
+ name TEXT NOT NULL UNIQUE,
152890
+ created_at INTEGER NOT NULL,
152891
+ updated_at INTEGER NOT NULL
152892
+ );
152893
+ CREATE TABLE IF NOT EXISTS workspace_members (
152894
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
152895
+ project_path TEXT NOT NULL,
152896
+ display_name TEXT NOT NULL,
152897
+ display_path TEXT NOT NULL,
152898
+ added_at INTEGER NOT NULL,
152899
+ PRIMARY KEY (workspace_id, project_path)
152900
+ );
152901
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique
152902
+ ON workspace_members(project_path);
152903
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name
152904
+ ON workspace_members(workspace_id, display_name);
152905
+ `);
152906
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
152907
+ if (!hasSessionMeta)
152908
+ return;
152909
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
152910
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
152911
+ const clears = [
152912
+ ["cached_m0_bytes", null],
152913
+ ["cached_m1_bytes", null],
152914
+ ["cached_m0_project_memory_epoch", null],
152915
+ ["cached_m0_workspace_fingerprint", null],
152916
+ ["cached_m0_project_user_profile_version", null],
152917
+ ["cached_m0_max_compartment_seq", null],
152918
+ ["cached_m0_max_memory_id", null],
152919
+ ["cached_m0_max_mutation_id", null],
152920
+ ["cached_m0_max_memory_mutation_id", null],
152921
+ ["cached_m0_project_docs_hash", null],
152922
+ ["cached_m0_materialized_at", null],
152923
+ ["cached_m0_session_facts_version", null],
152924
+ ["cached_m0_upgrade_state", null],
152925
+ ["cached_m0_system_hash", null],
152926
+ ["cached_m0_tool_set_hash", null],
152927
+ ["cached_m0_model_key", null],
152928
+ ["cached_m0_last_baseline_end_message_id", null],
152929
+ ["memory_block_cache", ""],
152930
+ ["memory_block_ids", ""],
152931
+ ["memory_block_count", 0]
152932
+ ];
152933
+ const setClauses = [];
152934
+ const values = [];
152935
+ for (const [column, value] of clears) {
152936
+ if (!columns.has(column))
152937
+ continue;
152938
+ setClauses.push(`${column} = ?`);
152939
+ values.push(value);
152940
+ }
152941
+ if (setClauses.length > 0) {
152942
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")}`).run(...values);
152943
+ }
152944
+ }
152945
+ },
152946
+ {
152947
+ version: 35,
152948
+ description: "workspace per-category share defaults and epoch refresh",
152949
+ up: (db) => {
152950
+ db.exec(`
152951
+ CREATE TABLE IF NOT EXISTS workspaces (
152952
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152953
+ name TEXT NOT NULL UNIQUE,
152954
+ created_at INTEGER NOT NULL,
152955
+ updated_at INTEGER NOT NULL,
152956
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
152957
+ );
152958
+ `);
152959
+ if (!columnExists2(db, "workspaces", "share_categories")) {
152960
+ db.exec(`ALTER TABLE workspaces ADD COLUMN share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
152961
+ }
152962
+ db.prepare(`UPDATE workspaces
152963
+ SET share_categories = '["CONSTRAINTS"]'
152964
+ WHERE share_categories IS NULL OR share_categories = ''`).run();
152965
+ if (!tableExists2(db, "workspace_members"))
152966
+ return;
152967
+ const rows = db.prepare(`SELECT DISTINCT project_path AS identity
152968
+ FROM workspace_members
152969
+ WHERE project_path IS NOT NULL AND project_path <> ''
152970
+ ORDER BY project_path ASC`).all();
152971
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((identity) => identity.length > 0);
152972
+ if (identities.length > 0) {
152973
+ bumpEpochsForWorkspaceMemberSet(db, identities, Date.now());
152974
+ }
152975
+ }
152976
+ },
152977
+ {
152978
+ version: 36,
152979
+ description: "session project ownership map for compartment chunk backfill scoping",
152980
+ up: (db) => {
152981
+ db.exec(`
152982
+ CREATE TABLE IF NOT EXISTS session_projects (
152983
+ session_id TEXT NOT NULL,
152984
+ harness TEXT NOT NULL DEFAULT 'opencode',
152985
+ project_path TEXT NOT NULL,
152986
+ updated_at INTEGER NOT NULL,
152987
+ PRIMARY KEY(session_id, harness)
152988
+ );
152989
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
152990
+ ON session_projects(project_path);
152991
+ `);
152992
+ const hasChunkTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartment_chunk_embeddings'").get();
152993
+ if (hasChunkTable) {
152994
+ db.exec(`
152995
+ INSERT OR IGNORE INTO session_projects (session_id, harness, project_path, updated_at)
152996
+ SELECT session_id, harness, MIN(project_path), 0
152997
+ FROM compartment_chunk_embeddings
152998
+ GROUP BY session_id, harness
152999
+ HAVING COUNT(DISTINCT project_path) = 1;
153000
+ `);
153001
+ }
153002
+ }
152471
153003
  }
152472
153004
  ];
152473
153005
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
152474
153006
  });
152475
153007
 
152476
153008
  // src/features/magic-context/project-docs-hash.ts
152477
- import { createHash as createHash4 } from "node:crypto";
153009
+ import { createHash as createHash5 } from "node:crypto";
152478
153010
  import { lstatSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152479
153011
  import path4 from "node:path";
152480
153012
  function canonicalizeDocContent(raw) {
@@ -152564,7 +153096,7 @@ function hashCanonicalPieces(hashPieces) {
152564
153096
  if (hashPieces.length === 0) {
152565
153097
  return "";
152566
153098
  }
152567
- return createHash4("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
153099
+ return createHash5("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
152568
153100
  }
152569
153101
  function readProjectDocsCanonical(projectDirectory) {
152570
153102
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -152602,11 +153134,6 @@ var init_project_docs_hash = __esm(() => {
152602
153134
  MAX_PROJECT_DOC_BYTES = 256 * 1024;
152603
153135
  docsCache = new Map;
152604
153136
  });
152605
-
152606
- // src/features/magic-context/project-identity.ts
152607
- var init_project_identity2 = __esm(() => {
152608
- init_project_identity();
152609
- });
152610
153137
  // src/features/magic-context/storage-m0-mutation-log.ts
152611
153138
  function assertMutationType(mutationType) {
152612
153139
  if (!M0_MUTATION_TYPES.has(mutationType)) {
@@ -152691,18 +153218,13 @@ function getMemoryMutation(db, id) {
152691
153218
  WHERE id = ?`).get(id);
152692
153219
  return row ? toMemoryMutation(row) : null;
152693
153220
  }
152694
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
152695
- if (renderedMemoryIds.length === 0)
152696
- return [];
152697
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
152698
- const placeholders = uniqueIds.map(() => "?").join(", ");
152699
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
152700
- superseded_by_id, category, new_content, queued_at
152701
- FROM memory_mutation_log
152702
- WHERE project_path = ?
152703
- AND id > ?
152704
- AND target_memory_id IN (${placeholders})
152705
- ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153221
+ function uniqueProjectPaths(projectPaths) {
153222
+ return [...new Set(projectPaths.filter((path5) => path5.length > 0))];
153223
+ }
153224
+ function placeholders2(values) {
153225
+ return values.map(() => "?").join(", ");
153226
+ }
153227
+ function coalesceMutations(rows) {
152706
153228
  const chosenByTarget = new Map;
152707
153229
  for (const dbRow of rows) {
152708
153230
  const candidate = toMemoryMutation(dbRow);
@@ -152723,10 +153245,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
152723
153245
  }
152724
153246
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
152725
153247
  }
153248
+ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
153249
+ if (renderedMemoryIds.length === 0)
153250
+ return [];
153251
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153252
+ const placeholders3 = uniqueIds.map(() => "?").join(", ");
153253
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153254
+ superseded_by_id, category, new_content, queued_at
153255
+ FROM memory_mutation_log
153256
+ WHERE project_path = ?
153257
+ AND id > ?
153258
+ AND target_memory_id IN (${placeholders3})
153259
+ ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153260
+ return coalesceMutations(rows);
153261
+ }
153262
+ function getMemoryMutationsForRenderByProjects(db, projectPaths, afterId, renderedMemoryIds) {
153263
+ if (renderedMemoryIds.length === 0)
153264
+ return [];
153265
+ const identities = uniqueProjectPaths(projectPaths);
153266
+ if (identities.length === 0)
153267
+ return [];
153268
+ if (identities.length === 1) {
153269
+ return getMemoryMutationsForRender(db, identities[0], afterId, renderedMemoryIds);
153270
+ }
153271
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153272
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153273
+ superseded_by_id, category, new_content, queued_at
153274
+ FROM memory_mutation_log
153275
+ WHERE project_path IN (${placeholders2(identities)})
153276
+ AND id > ?
153277
+ AND target_memory_id IN (${placeholders2(uniqueIds)})
153278
+ ORDER BY id ASC`).all(...identities, afterId ?? 0, ...uniqueIds);
153279
+ return coalesceMutations(rows);
153280
+ }
152726
153281
  function getMaxMemoryMutationId(db, projectPath) {
152727
153282
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
152728
153283
  return row?.max_id ?? null;
152729
153284
  }
153285
+ function getMaxMemoryMutationIdForProjects(db, projectPaths) {
153286
+ const identities = uniqueProjectPaths(projectPaths);
153287
+ if (identities.length === 0)
153288
+ return null;
153289
+ if (identities.length === 1)
153290
+ return getMaxMemoryMutationId(db, identities[0]);
153291
+ const row = db.prepare(`SELECT MAX(id) AS max_id
153292
+ FROM memory_mutation_log
153293
+ WHERE project_path IN (${placeholders2(identities)})`).get(...identities);
153294
+ return row?.max_id ?? null;
153295
+ }
152730
153296
  var MEMORY_MUTATION_TYPES, TERMINAL_MUTATION_TYPES;
152731
153297
  var init_storage_memory_mutation_log = __esm(() => {
152732
153298
  MEMORY_MUTATION_TYPES = new Set(["archive", "delete", "update", "superseded"]);
@@ -153419,6 +153985,36 @@ function addStaleReduceStrippedIds(db, sessionId, ids) {
153419
153985
  sessionLog(sessionId, `stale_reduce_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
153420
153986
  return false;
153421
153987
  }
153988
+ function getProcessedImageStrippedIds(db, sessionId) {
153989
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153990
+ return new Set(parseStrippedBlob(row?.processed_image_stripped_ids));
153991
+ }
153992
+ function addProcessedImageStrippedIds(db, sessionId, ids) {
153993
+ const add = [...ids];
153994
+ if (add.length === 0)
153995
+ return true;
153996
+ ensureSessionMetaRow(db, sessionId);
153997
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
153998
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153999
+ const rawStored = row ? row.processed_image_stripped_ids ?? null : null;
154000
+ const current = new Set(parseStrippedBlob(rawStored));
154001
+ let changed = false;
154002
+ for (const id of add) {
154003
+ if (!current.has(id)) {
154004
+ current.add(id);
154005
+ changed = true;
154006
+ }
154007
+ }
154008
+ if (!changed)
154009
+ return true;
154010
+ const nextBlob = JSON.stringify([...current]);
154011
+ const result = db.prepare("UPDATE session_meta SET processed_image_stripped_ids = ? WHERE session_id = ? AND processed_image_stripped_ids IS ?").run(nextBlob, sessionId, rawStored);
154012
+ if (result.changes > 0)
154013
+ return true;
154014
+ }
154015
+ sessionLog(sessionId, `processed_image_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
154016
+ return false;
154017
+ }
153422
154018
  function isPendingCompactionMarker(value) {
153423
154019
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
153424
154020
  }
@@ -153597,6 +154193,8 @@ function clearSession(db, sessionId) {
153597
154193
  db.prepare("DELETE FROM source_contents WHERE session_id = ?").run(sessionId);
153598
154194
  db.prepare("DELETE FROM tags WHERE session_id = ?").run(sessionId);
153599
154195
  db.prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
154196
+ db.prepare("DELETE FROM session_projects WHERE session_id = ?").run(sessionId);
154197
+ db.prepare("DELETE FROM compartment_chunk_embeddings WHERE session_id = ?").run(sessionId);
153600
154198
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
153601
154199
  clearCompressionDepth(db, sessionId);
153602
154200
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
@@ -153703,9 +154301,9 @@ function buildStatusClause(status) {
153703
154301
  if (statuses.length === 0) {
153704
154302
  return null;
153705
154303
  }
153706
- const placeholders = statuses.map(() => "?").join(", ");
154304
+ const placeholders3 = statuses.map(() => "?").join(", ");
153707
154305
  return {
153708
- sql: `status IN (${placeholders})`,
154306
+ sql: `status IN (${placeholders3})`,
153709
154307
  params: statuses
153710
154308
  };
153711
154309
  }
@@ -153911,19 +154509,6 @@ function getProjectState(db, projectPath) {
153911
154509
  WHERE project_path = ?`).get(projectPath);
153912
154510
  return row ? toProjectState(row) : null;
153913
154511
  }
153914
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
153915
- db.prepare(`INSERT INTO project_state
153916
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
153917
- VALUES (?, 1, 0, ?)
153918
- ON CONFLICT(project_path) DO UPDATE SET
153919
- project_memory_epoch = project_memory_epoch + 1,
153920
- updated_at = excluded.updated_at`).run(projectPath, now);
153921
- const state = getProjectState(db, projectPath);
153922
- if (!state) {
153923
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
153924
- }
153925
- return state;
153926
- }
153927
154512
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
153928
154513
  db.prepare(`INSERT INTO project_state
153929
154514
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -153959,8 +154544,8 @@ function getSourceContents(db, sessionId, tagIds) {
153959
154544
  if (tagIds.length === 0) {
153960
154545
  return new Map;
153961
154546
  }
153962
- const placeholders = tagIds.map(() => "?").join(", ");
153963
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
154547
+ const placeholders3 = tagIds.map(() => "?").join(", ");
154548
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders3})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
153964
154549
  const sources = new Map;
153965
154550
  for (const row of rows) {
153966
154551
  sources.set(row.tag_id, row.content);
@@ -154024,15 +154609,22 @@ function ownerMessageIdForTagRow(row) {
154024
154609
  }
154025
154610
  return row.message_id.replace(CONTENT_ID_SUFFIX, "");
154026
154611
  }
154027
- function getActiveTagTokenAggregate(db, sessionId) {
154028
- const row = db.prepare(`SELECT
154612
+ function getActiveTagTokenAggregate(db, sessionId, protectedTags = 0) {
154613
+ const toolOutputExpr = protectedTags > 0 ? `COALESCE(SUM(CASE WHEN type = 'tool' AND tag_number < (
154614
+ SELECT tag_number FROM tags
154615
+ WHERE session_id = ? AND status = 'active'
154616
+ ORDER BY tag_number DESC LIMIT 1 OFFSET ?
154617
+ ) THEN COALESCE(token_count, 0) ELSE 0 END), 0)` : `COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)`;
154618
+ const sql = `SELECT
154029
154619
  COALESCE(SUM(CASE WHEN type != 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)
154030
154620
  + COALESCE(SUM(COALESCE(reasoning_token_count, 0)), 0) AS conversation,
154031
154621
  COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) + COALESCE(input_token_count, 0) ELSE 0 END), 0) AS tool_call,
154032
- COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0) AS tool_output,
154622
+ ${toolOutputExpr} AS tool_output,
154033
154623
  COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
154034
154624
  FROM tags
154035
- WHERE session_id = ? AND status = 'active'`).get(sessionId);
154625
+ WHERE session_id = ? AND status = 'active'`;
154626
+ const params = protectedTags > 0 ? [sessionId, protectedTags - 1, sessionId] : [sessionId];
154627
+ const row = db.prepare(sql).get(...params);
154036
154628
  return {
154037
154629
  conversation: row?.conversation ?? 0,
154038
154630
  toolCall: row?.tool_call ?? 0,
@@ -154270,8 +154862,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
154270
154862
  }
154271
154863
  return all;
154272
154864
  }
154273
- const placeholders = tagNumbers.map(() => "?").join(",");
154274
- const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
154865
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
154866
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders3}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
154275
154867
  return rows.map(toTagEntry);
154276
154868
  }
154277
154869
  function getMaxDroppedTagNumber(db, sessionId) {
@@ -154424,6 +155016,7 @@ var init_storage = __esm(async () => {
154424
155016
  init_storage_source();
154425
155017
  init_storage_tags();
154426
155018
  init_storage_v22_backfill_failures();
155019
+ init_workspaces();
154427
155020
  await __promiseAll([
154428
155021
  init_message_index(),
154429
155022
  init_migrations(),
@@ -163875,7 +164468,7 @@ function isEmbeddingRow(row) {
163875
164468
  if (row === null || typeof row !== "object")
163876
164469
  return false;
163877
164470
  const candidate = row;
163878
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
164471
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
163879
164472
  }
163880
164473
  function toFloat32Array(blob) {
163881
164474
  if (blob instanceof Uint8Array) {
@@ -163895,7 +164488,7 @@ function getSaveEmbeddingStatement(db) {
163895
164488
  function getLoadAllEmbeddingsStatement(db) {
163896
164489
  let stmt = loadAllEmbeddingsStatements.get(db);
163897
164490
  if (!stmt) {
163898
- stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
164491
+ stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding, memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
163899
164492
  loadAllEmbeddingsStatements.set(db, stmt);
163900
164493
  }
163901
164494
  return stmt;
@@ -163924,7 +164517,10 @@ function loadAllEmbeddings(db, projectPath) {
163924
164517
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
163925
164518
  const embeddings = new Map;
163926
164519
  for (const row of rows) {
163927
- embeddings.set(row.memoryId, toFloat32Array(row.embedding));
164520
+ embeddings.set(row.memoryId, {
164521
+ embedding: toFloat32Array(row.embedding),
164522
+ modelId: row.modelId
164523
+ });
163928
164524
  }
163929
164525
  return embeddings;
163930
164526
  }
@@ -163987,13 +164583,13 @@ var init_embedding_cache = __esm(() => {
163987
164583
  });
163988
164584
 
163989
164585
  // src/features/magic-context/memory/normalize-hash.ts
163990
- import { createHash as createHash6 } from "node:crypto";
164586
+ import { createHash as createHash7 } from "node:crypto";
163991
164587
  function normalizeMemoryContent(content) {
163992
164588
  return content.toLowerCase().replace(/\s+/g, " ").trim();
163993
164589
  }
163994
164590
  function computeNormalizedHash(content) {
163995
164591
  const normalized = normalizeMemoryContent(content);
163996
- return createHash6("md5").update(normalized).digest("hex");
164592
+ return createHash7("md5").update(normalized).digest("hex");
163997
164593
  }
163998
164594
  var init_normalize_hash = () => {};
163999
164595
 
@@ -164107,8 +164703,8 @@ function getMemoriesByProjectStatement(db, statuses) {
164107
164703
  }
164108
164704
  let stmt = statements.get(db);
164109
164705
  if (!stmt) {
164110
- const placeholders = statuses.map(() => "?").join(", ");
164111
- stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
164706
+ const placeholders3 = statuses.map(() => "?").join(", ");
164707
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders3}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
164112
164708
  statements.set(db, stmt);
164113
164709
  }
164114
164710
  return stmt;
@@ -164229,6 +164825,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
164229
164825
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
164230
164826
  return rows.map(toMemory);
164231
164827
  }
164828
+ function sqlPlaceholders(values) {
164829
+ return values.map(() => "?").join(", ");
164830
+ }
164831
+ function uniqueValues(values) {
164832
+ return [...new Set(values.filter((value) => value.length > 0))];
164833
+ }
164834
+ function buildWorkspaceMemorySqlFilter(args) {
164835
+ if (args.shareCategories === null || args.shareCategories === undefined) {
164836
+ return { clause: "", params: [], active: false };
164837
+ }
164838
+ const identities = uniqueValues(args.identities);
164839
+ const identitySet = new Set(identities);
164840
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
164841
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
164842
+ if (foreignIdentities.length === 0) {
164843
+ return { clause: "", params: [], active: false };
164844
+ }
164845
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
164846
+ const shareCategories = uniqueValues([...args.shareCategories]);
164847
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
164848
+ const predicates = [];
164849
+ const params = [];
164850
+ if (ownIdentities.length > 0) {
164851
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
164852
+ params.push(...ownIdentities);
164853
+ }
164854
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
164855
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
164856
+ params.push(...foreignIdentities, ...shareCategories);
164857
+ }
164858
+ if (predicates.length === 0) {
164859
+ return { clause: " AND 0 = 1", params: [], active: true };
164860
+ }
164861
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
164862
+ }
164863
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
164864
+ const identities = uniqueValues(projectPaths);
164865
+ if (identities.length === 0 || statuses.length === 0)
164866
+ return [];
164867
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164868
+ identities,
164869
+ ownIdentities,
164870
+ shareCategories
164871
+ });
164872
+ if (identities.length === 1 && !sharingFilter.active) {
164873
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
164874
+ }
164875
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164876
+ FROM memories
164877
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164878
+ AND status IN (${sqlPlaceholders(statuses)})
164879
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164880
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164881
+ return rows.map(toMemory);
164882
+ }
164883
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
164884
+ const identities = uniqueValues(projectPaths);
164885
+ if (identities.length === 0)
164886
+ return 0;
164887
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164888
+ identities,
164889
+ ownIdentities,
164890
+ shareCategories
164891
+ });
164892
+ if (identities.length === 1 && !sharingFilter.active) {
164893
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
164894
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
164895
+ }
164896
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
164897
+ FROM memories
164898
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
164899
+ return typeof row?.max_id === "number" ? row.max_id : 0;
164900
+ }
164901
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
164902
+ const identities = uniqueValues(projectPaths);
164903
+ if (identities.length === 0)
164904
+ return [];
164905
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164906
+ identities,
164907
+ ownIdentities,
164908
+ shareCategories
164909
+ });
164910
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164911
+ FROM memories
164912
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164913
+ AND id > ?
164914
+ AND status IN ('active', 'permanent')
164915
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164916
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164917
+ return rows.map(toMemory);
164918
+ }
164232
164919
  function getAllActiveMemoriesForMigration(db, projectPath) {
164233
164920
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
164234
164921
  return rows.map(toMemory);
@@ -164326,6 +165013,7 @@ function getMemoryCountsByStatus(db, projectPath) {
164326
165013
  }
164327
165014
  var COLUMN_MAP, MEMORY_CATEGORY_LOOKUP, MEMORY_STATUS_LOOKUP, MEMORY_SOURCE_TYPE_LOOKUP, VERIFICATION_STATUS_LOOKUP, insertMemoryStatements, getMemoryByHashStatements, getMemoryByIdStatements, activeMemoriesNoExpiryStatements, updateMemorySeenCountStatements, updateMemoryRetrievalCountStatements, updateMemoryStatusStatements, updateArchivedMemoryStatements, updateMemoryVerificationStatements, updateMemoryContentStatements, supersededMemoryStatements, mergeMemoryStatsStatements, deleteMemoryStatements, deleteMemoryEmbeddingStatements, deleteEmbeddingOnContentUpdateStatements, getMemoryCountStatements, getMemoryCountByProjectStatements, getMemoryCountsByStatusStatements, memoriesByProjectStatements, memoryImportanceColumnCache;
164328
165015
  var init_storage_memory = __esm(() => {
165016
+ init_constants();
164329
165017
  init_embedding_cache();
164330
165018
  init_normalize_hash();
164331
165019
  COLUMN_MAP = {
@@ -164431,8 +165119,8 @@ function getUserMemoryCandidates(db) {
164431
165119
  function deleteUserMemoryCandidates(db, ids) {
164432
165120
  if (ids.length === 0)
164433
165121
  return;
164434
- const placeholders = ids.map(() => "?").join(",");
164435
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
165122
+ const placeholders3 = ids.map(() => "?").join(",");
165123
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
164436
165124
  }
164437
165125
  function insertUserMemory(db, content, sourceCandidateIds) {
164438
165126
  const now = Date.now();
@@ -164465,6 +165153,445 @@ function parseUserMemoryRow(row) {
164465
165153
  };
164466
165154
  }
164467
165155
 
165156
+ // src/features/magic-context/compartment-chunk-embedding.ts
165157
+ import { createHash as createHash8 } from "node:crypto";
165158
+ function getLoadFtsRowsStatement(db) {
165159
+ let stmt = loadFtsRowsStatements.get(db);
165160
+ if (!stmt) {
165161
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
165162
+ FROM message_history_fts
165163
+ WHERE session_id = ?
165164
+ AND message_ordinal >= ?
165165
+ AND message_ordinal <= ?
165166
+ AND role IN ('user', 'assistant')
165167
+ ORDER BY message_ordinal ASC`);
165168
+ loadFtsRowsStatements.set(db, stmt);
165169
+ }
165170
+ return stmt;
165171
+ }
165172
+ function getExistingHashStatement(db, scopedToProject) {
165173
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
165174
+ let stmt = map2.get(db);
165175
+ if (!stmt) {
165176
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
165177
+ FROM compartment_chunk_embeddings
165178
+ WHERE compartment_id = ?
165179
+ AND model_id = ?
165180
+ ${scopedToProject ? "AND project_path = ?" : ""}
165181
+ ORDER BY window_index ASC`);
165182
+ map2.set(db, stmt);
165183
+ }
165184
+ return stmt;
165185
+ }
165186
+ function getDeleteByCompartmentStatement(db) {
165187
+ let stmt = deleteByCompartmentStatements.get(db);
165188
+ if (!stmt) {
165189
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
165190
+ deleteByCompartmentStatements.set(db, stmt);
165191
+ }
165192
+ return stmt;
165193
+ }
165194
+ function getInsertEmbeddingStatement(db) {
165195
+ let stmt = insertEmbeddingStatements.get(db);
165196
+ if (!stmt) {
165197
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
165198
+ compartment_id, session_id, project_path, harness, window_index,
165199
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
165200
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
165201
+ insertEmbeddingStatements.set(db, stmt);
165202
+ }
165203
+ return stmt;
165204
+ }
165205
+ function getDistinctModelStatement(db) {
165206
+ let stmt = distinctModelStatements.get(db);
165207
+ if (!stmt) {
165208
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
165209
+ FROM compartment_chunk_embeddings
165210
+ WHERE project_path = ?`);
165211
+ distinctModelStatements.set(db, stmt);
165212
+ }
165213
+ return stmt;
165214
+ }
165215
+ function getClearProjectStatement(db) {
165216
+ let stmt = clearProjectStatements.get(db);
165217
+ if (!stmt) {
165218
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
165219
+ clearProjectStatements.set(db, stmt);
165220
+ }
165221
+ return stmt;
165222
+ }
165223
+ function getClearProjectModelStatement(db) {
165224
+ let stmt = clearProjectModelStatements.get(db);
165225
+ if (!stmt) {
165226
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
165227
+ clearProjectModelStatements.set(db, stmt);
165228
+ }
165229
+ return stmt;
165230
+ }
165231
+ function getSearchRowsStatement(db, withModel) {
165232
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
165233
+ let stmt = map2.get(db);
165234
+ if (!stmt) {
165235
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
165236
+ e.session_id AS sessionId,
165237
+ c.title AS title,
165238
+ c.start_message AS compartmentStart,
165239
+ c.end_message AS compartmentEnd,
165240
+ e.window_index AS windowIndex,
165241
+ e.start_ordinal AS windowStart,
165242
+ e.end_ordinal AS windowEnd,
165243
+ e.chunk_hash AS chunkHash,
165244
+ e.model_id AS modelId,
165245
+ e.dims AS dims,
165246
+ e.vector AS vector
165247
+ FROM compartment_chunk_embeddings e
165248
+ JOIN compartments c ON c.id = e.compartment_id
165249
+ WHERE e.session_id = ?
165250
+ AND e.project_path = ?
165251
+ ${withModel ? "AND e.model_id = ?" : ""}
165252
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
165253
+ map2.set(db, stmt);
165254
+ }
165255
+ return stmt;
165256
+ }
165257
+ function isFinitePositiveInteger(value) {
165258
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
165259
+ }
165260
+ function normalizeCompartmentChunkMaxInputTokens(value) {
165261
+ if (!isFinitePositiveInteger(value)) {
165262
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
165263
+ }
165264
+ return Math.max(1, Math.floor(value));
165265
+ }
165266
+ function normalizeContent(text) {
165267
+ return text.replace(/\s+/g, " ").trim();
165268
+ }
165269
+ function formatOrdinalRange(start, end) {
165270
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
165271
+ }
165272
+ function rolePrefix(role) {
165273
+ if (role === "user")
165274
+ return "U";
165275
+ if (role === "assistant")
165276
+ return "A";
165277
+ return null;
165278
+ }
165279
+ function parseOrdinal(value) {
165280
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
165281
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
165282
+ }
165283
+ function parseCanonicalLineRange(line) {
165284
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
165285
+ if (!match)
165286
+ return null;
165287
+ const start = Number.parseInt(match[1], 10);
165288
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
165289
+ if (!Number.isFinite(start) || !Number.isFinite(end))
165290
+ return null;
165291
+ return { start, end };
165292
+ }
165293
+ function hashChunkText(text) {
165294
+ return createHash8("sha256").update(text).digest("hex");
165295
+ }
165296
+ function vectorBlob(vector) {
165297
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
165298
+ }
165299
+ function toFloat32Array2(blob) {
165300
+ if (blob instanceof Uint8Array) {
165301
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
165302
+ return new Float32Array(buffer2);
165303
+ }
165304
+ return new Float32Array(blob.slice(0));
165305
+ }
165306
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
165307
+ if (endOrdinal < startOrdinal)
165308
+ return "";
165309
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
165310
+ const lines = [];
165311
+ let current = null;
165312
+ const flush2 = () => {
165313
+ if (!current || current.parts.length === 0)
165314
+ return;
165315
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
165316
+ current = null;
165317
+ };
165318
+ for (const row of rows) {
165319
+ const ordinal = parseOrdinal(row.messageOrdinal);
165320
+ const prefix = rolePrefix(row.role);
165321
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
165322
+ if (ordinal === null || prefix === null || content.length === 0)
165323
+ continue;
165324
+ if (current && current.role === prefix) {
165325
+ current.end = ordinal;
165326
+ current.parts.push(content);
165327
+ continue;
165328
+ }
165329
+ flush2();
165330
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
165331
+ }
165332
+ flush2();
165333
+ return lines.join(`
165334
+ `);
165335
+ }
165336
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
165337
+ const lines = [];
165338
+ for (const rawLine of chunkText.split(/\r?\n/)) {
165339
+ const line = rawLine.trim();
165340
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
165341
+ if (!match)
165342
+ continue;
165343
+ const lineStart = Number.parseInt(match[2], 10);
165344
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
165345
+ if (startOrdinal != null && lineEnd < startOrdinal)
165346
+ continue;
165347
+ if (endOrdinal != null && lineStart > endOrdinal)
165348
+ continue;
165349
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
165350
+ const ordinalSpan = lineEnd - lineStart + 1;
165351
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
165352
+ if (ordinalSpan === rawParts.length) {
165353
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
165354
+ if (part.startsWith("TC:"))
165355
+ return false;
165356
+ if (startOrdinal != null && ordinal < startOrdinal)
165357
+ return false;
165358
+ if (endOrdinal != null && ordinal > endOrdinal)
165359
+ return false;
165360
+ return true;
165361
+ });
165362
+ if (retained.length === 0)
165363
+ continue;
165364
+ const retainedStart = retained[0].ordinal;
165365
+ const retainedEnd = retained[retained.length - 1].ordinal;
165366
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
165367
+ continue;
165368
+ }
165369
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
165370
+ if (parts.length === 0)
165371
+ continue;
165372
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
165373
+ }
165374
+ return lines.join(`
165375
+ `);
165376
+ }
165377
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
165378
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
165379
+ if (lines.length === 0 || endOrdinal < startOrdinal)
165380
+ return [];
165381
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
165382
+ const effectiveMax = Math.max(1, Math.floor(normalizedMax * CHUNK_WINDOW_SAFETY_RATIO));
165383
+ const fullText = lines.join(`
165384
+ `);
165385
+ if (estimateTokens(fullText) <= effectiveMax) {
165386
+ return [
165387
+ {
165388
+ windowIndex: 0,
165389
+ startOrdinal,
165390
+ endOrdinal,
165391
+ text: fullText,
165392
+ chunkHash: hashChunkText(fullText)
165393
+ }
165394
+ ];
165395
+ }
165396
+ const windows = [];
165397
+ let currentLines = [];
165398
+ let currentStart = null;
165399
+ let currentEnd = null;
165400
+ let currentTokens = 0;
165401
+ const flush2 = () => {
165402
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
165403
+ return;
165404
+ const text = currentLines.join(`
165405
+ `);
165406
+ windows.push({
165407
+ windowIndex: windows.length + 1,
165408
+ startOrdinal: currentStart,
165409
+ endOrdinal: currentEnd,
165410
+ text,
165411
+ chunkHash: hashChunkText(text)
165412
+ });
165413
+ currentLines = [];
165414
+ currentStart = null;
165415
+ currentEnd = null;
165416
+ currentTokens = 0;
165417
+ };
165418
+ for (const line of lines) {
165419
+ const range = parseCanonicalLineRange(line);
165420
+ const lineStart = range?.start ?? startOrdinal;
165421
+ const lineEnd = range?.end ?? lineStart;
165422
+ const lineTokens = estimateTokens(line);
165423
+ if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
165424
+ flush2();
165425
+ }
165426
+ if (currentLines.length === 0) {
165427
+ currentStart = lineStart;
165428
+ }
165429
+ currentLines.push(line);
165430
+ currentEnd = lineEnd;
165431
+ currentTokens += lineTokens;
165432
+ }
165433
+ flush2();
165434
+ return windows;
165435
+ }
165436
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
165437
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
165438
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
165439
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
165440
+ }
165441
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
165442
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
165443
+ if (existing.size !== windows.length)
165444
+ return false;
165445
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
165446
+ }
165447
+ function replaceCompartmentChunkEmbeddings(db, rows) {
165448
+ if (rows.length === 0)
165449
+ return;
165450
+ const compartmentId = rows[0].compartmentId;
165451
+ const now = Date.now();
165452
+ db.transaction(() => {
165453
+ getDeleteByCompartmentStatement(db).run(compartmentId);
165454
+ const insert = getInsertEmbeddingStatement(db);
165455
+ for (const row of rows) {
165456
+ 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);
165457
+ }
165458
+ })();
165459
+ }
165460
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
165461
+ const rows = getDistinctModelStatement(db).all(projectPath);
165462
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165463
+ }
165464
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
165465
+ if (modelId) {
165466
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
165467
+ }
165468
+ return getClearProjectStatement(db).run(projectPath).changes;
165469
+ }
165470
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
165471
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
165472
+ 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) => ({
165473
+ compartmentId: row.compartmentId,
165474
+ sessionId: row.sessionId,
165475
+ title: row.title,
165476
+ startOrdinal: row.compartmentStart,
165477
+ endOrdinal: row.compartmentEnd,
165478
+ windowIndex: row.windowIndex,
165479
+ windowStartOrdinal: row.windowStart,
165480
+ windowEndOrdinal: row.windowEnd,
165481
+ chunkHash: row.chunkHash,
165482
+ modelId: row.modelId,
165483
+ dims: row.dims,
165484
+ vector: toFloat32Array2(row.vector)
165485
+ }));
165486
+ }
165487
+ function mapBackfillCandidateRows(rows) {
165488
+ return rows.filter((row) => {
165489
+ if (row === null || typeof row !== "object")
165490
+ return false;
165491
+ const candidate = row;
165492
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
165493
+ }).map((row) => ({
165494
+ id: row.id,
165495
+ sessionId: row.sessionId,
165496
+ startMessage: row.startMessage,
165497
+ endMessage: row.endMessage,
165498
+ title: row.title
165499
+ }));
165500
+ }
165501
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
165502
+ if (excludeIds && excludeIds.length > 0) {
165503
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
165504
+ const stmt2 = db.prepare(`SELECT c.id AS id,
165505
+ c.session_id AS sessionId,
165506
+ c.start_message AS startMessage,
165507
+ c.end_message AS endMessage,
165508
+ c.title AS title
165509
+ FROM compartments c
165510
+ JOIN session_projects sp
165511
+ ON sp.session_id = c.session_id
165512
+ AND sp.harness = c.harness
165513
+ AND sp.project_path = ?
165514
+ WHERE c.session_id = ?
165515
+ AND c.start_message IS NOT NULL
165516
+ AND c.end_message IS NOT NULL
165517
+ AND c.id NOT IN (${placeholders3})
165518
+ AND NOT EXISTS (
165519
+ SELECT 1
165520
+ FROM compartment_chunk_embeddings current
165521
+ WHERE current.compartment_id = c.id
165522
+ AND current.project_path = ?
165523
+ AND current.model_id = ?
165524
+ )
165525
+ ORDER BY c.start_message ASC, c.id ASC
165526
+ LIMIT ?`);
165527
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
165528
+ return mapBackfillCandidateRows(rows2);
165529
+ }
165530
+ let stmt = sessionBackfillCandidateStatements.get(db);
165531
+ if (!stmt) {
165532
+ stmt = db.prepare(`SELECT c.id AS id,
165533
+ c.session_id AS sessionId,
165534
+ c.start_message AS startMessage,
165535
+ c.end_message AS endMessage,
165536
+ c.title AS title
165537
+ FROM compartments c
165538
+ JOIN session_projects sp
165539
+ ON sp.session_id = c.session_id
165540
+ AND sp.harness = c.harness
165541
+ AND sp.project_path = ?
165542
+ WHERE c.session_id = ?
165543
+ AND c.start_message IS NOT NULL
165544
+ AND c.end_message IS NOT NULL
165545
+ AND NOT EXISTS (
165546
+ SELECT 1
165547
+ FROM compartment_chunk_embeddings current
165548
+ WHERE current.compartment_id = c.id
165549
+ AND current.project_path = ?
165550
+ AND current.model_id = ?
165551
+ )
165552
+ ORDER BY c.start_message ASC, c.id ASC
165553
+ LIMIT ?`);
165554
+ sessionBackfillCandidateStatements.set(db, stmt);
165555
+ }
165556
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
165557
+ return mapBackfillCandidateRows(rows);
165558
+ }
165559
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
165560
+ const row = db.prepare(`SELECT COUNT(*) AS n
165561
+ FROM compartments c
165562
+ JOIN session_projects sp
165563
+ ON sp.session_id = c.session_id
165564
+ AND sp.harness = c.harness
165565
+ AND sp.project_path = ?
165566
+ WHERE c.session_id = ?
165567
+ AND c.start_message IS NOT NULL
165568
+ AND c.end_message IS NOT NULL
165569
+ AND NOT EXISTS (
165570
+ SELECT 1
165571
+ FROM compartment_chunk_embeddings current
165572
+ WHERE current.compartment_id = c.id
165573
+ AND current.project_path = ?
165574
+ AND current.model_id = ?
165575
+ )`).get(projectPath, sessionId, projectPath, modelId);
165576
+ return typeof row?.n === "number" ? row.n : 0;
165577
+ }
165578
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, CHUNK_WINDOW_SAFETY_RATIO = 0.9, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
165579
+ var init_compartment_chunk_embedding = __esm(() => {
165580
+ init_read_session_formatting();
165581
+ loadFtsRowsStatements = new WeakMap;
165582
+ existingHashStatements = new WeakMap;
165583
+ existingHashByProjectStatements = new WeakMap;
165584
+ deleteByCompartmentStatements = new WeakMap;
165585
+ insertEmbeddingStatements = new WeakMap;
165586
+ distinctModelStatements = new WeakMap;
165587
+ clearProjectStatements = new WeakMap;
165588
+ clearProjectModelStatements = new WeakMap;
165589
+ searchRowsStatements = new WeakMap;
165590
+ searchRowsByModelStatements = new WeakMap;
165591
+ backfillCandidateStatements = new WeakMap;
165592
+ sessionBackfillCandidateStatements = new WeakMap;
165593
+ });
165594
+
164468
165595
  // src/features/magic-context/memory/cosine-similarity.ts
164469
165596
  function cosineSimilarity(a, b) {
164470
165597
  if (a.length !== b.length) {
@@ -164622,19 +165749,19 @@ function isArrayLikeNumber(value) {
164622
165749
  }
164623
165750
  return arr.length === 0 || typeof arr[0] === "number";
164624
165751
  }
164625
- function toFloat32Array2(values) {
165752
+ function toFloat32Array3(values) {
164626
165753
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
164627
165754
  }
164628
165755
  function extractBatchEmbeddings(result, expectedCount) {
164629
165756
  const { data } = result;
164630
165757
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
164631
- return data.map((entry) => toFloat32Array2(entry));
165758
+ return data.map((entry) => toFloat32Array3(entry));
164632
165759
  }
164633
165760
  if (!isArrayLikeNumber(data)) {
164634
165761
  log("[magic-context] embedding batch returned unexpected data shape");
164635
165762
  return Array.from({ length: expectedCount }, () => null);
164636
165763
  }
164637
- const flatData = toFloat32Array2(data);
165764
+ const flatData = toFloat32Array3(data);
164638
165765
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
164639
165766
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
164640
165767
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -164649,6 +165776,7 @@ function extractBatchEmbeddings(result, expectedCount) {
164649
165776
 
164650
165777
  class LocalEmbeddingProvider {
164651
165778
  modelId;
165779
+ maxInputTokens;
164652
165780
  model;
164653
165781
  pipeline = null;
164654
165782
  initPromise = null;
@@ -164656,8 +165784,9 @@ class LocalEmbeddingProvider {
164656
165784
  disposing = false;
164657
165785
  disposePromise = null;
164658
165786
  inFlightWaiters = [];
164659
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
165787
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
164660
165788
  this.model = model;
165789
+ this.maxInputTokens = maxInputTokens;
164661
165790
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
164662
165791
  }
164663
165792
  async initialize() {
@@ -164914,9 +166043,17 @@ var init_embedding_ssrf = __esm(() => {
164914
166043
  function normalizeEndpoint3(endpoint) {
164915
166044
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
164916
166045
  }
166046
+ function embeddingModelsMatch(served, requested) {
166047
+ const a = served.trim().toLowerCase();
166048
+ const b = requested.trim().toLowerCase();
166049
+ if (a.length === 0 || b.length === 0)
166050
+ return true;
166051
+ return a === b || a.includes(b) || b.includes(a);
166052
+ }
164917
166053
 
164918
166054
  class OpenAICompatibleEmbeddingProvider {
164919
166055
  modelId;
166056
+ maxInputTokens;
164920
166057
  endpoint;
164921
166058
  model;
164922
166059
  apiKey;
@@ -164926,6 +166063,7 @@ class OpenAICompatibleEmbeddingProvider {
164926
166063
  failureTimes = [];
164927
166064
  circuitOpenUntil = 0;
164928
166065
  openLogged = false;
166066
+ modelMismatchLogged = false;
164929
166067
  halfOpenProbeInFlight = false;
164930
166068
  constructor(options) {
164931
166069
  this.endpoint = normalizeEndpoint3(options.endpoint);
@@ -164933,11 +166071,13 @@ class OpenAICompatibleEmbeddingProvider {
164933
166071
  this.apiKey = options.apiKey?.trim() ?? "";
164934
166072
  this.inputType = options.inputType?.trim() ?? "";
164935
166073
  this.truncate = options.truncate?.trim() ?? "";
166074
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
164936
166075
  this.modelId = getEmbeddingProviderIdentity({
164937
166076
  provider: "openai-compatible",
164938
166077
  endpoint: this.endpoint,
164939
166078
  model: this.model,
164940
- ...this.apiKey ? { api_key: this.apiKey } : {}
166079
+ ...this.apiKey ? { api_key: this.apiKey } : {},
166080
+ ...this.inputType ? { input_type: this.inputType } : {}
164941
166081
  });
164942
166082
  }
164943
166083
  async initialize() {
@@ -165022,6 +166162,15 @@ class OpenAICompatibleEmbeddingProvider {
165022
166162
  this.recordFailure(isProbe);
165023
166163
  return Array.from({ length: texts.length }, () => null);
165024
166164
  }
166165
+ const servedModel = typeof body.model === "string" ? body.model : "";
166166
+ if (this.model && servedModel && !embeddingModelsMatch(servedModel, this.model)) {
166167
+ if (!this.modelMismatchLogged) {
166168
+ log(`[magic-context] embedding endpoint served a DIFFERENT model than requested — refusing the substituted vectors (they have the wrong dimensions/space). requested="${this.model}" served="${servedModel}". The endpoint likely substituted a loaded model; load/select "${this.model}" on the endpoint, or set embedding.model to the served model.`);
166169
+ this.modelMismatchLogged = true;
166170
+ }
166171
+ this.recordFailure(isProbe);
166172
+ return Array.from({ length: texts.length }, () => null);
166173
+ }
165025
166174
  const items = Array.isArray(body.data) ? body.data : [];
165026
166175
  const results = Array.from({ length: texts.length }, (_, index) => {
165027
166176
  const embedding = items[index]?.embedding;
@@ -165181,12 +166330,12 @@ function getCountEmbeddedStatement(db) {
165181
166330
  }
165182
166331
  return stmt;
165183
166332
  }
165184
- function getClearProjectStatement(db) {
165185
- let stmt = clearProjectStatements.get(db);
166333
+ function getClearProjectStatement2(db) {
166334
+ let stmt = clearProjectStatements2.get(db);
165186
166335
  if (!stmt) {
165187
166336
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
165188
166337
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
165189
- clearProjectStatements.set(db, stmt);
166338
+ clearProjectStatements2.set(db, stmt);
165190
166339
  }
165191
166340
  return stmt;
165192
166341
  }
@@ -165222,19 +166371,19 @@ function countEmbeddedCommits(db, projectPath) {
165222
166371
  return row?.count ?? 0;
165223
166372
  }
165224
166373
  function clearProjectCommitEmbeddings(db, projectPath) {
165225
- return getClearProjectStatement(db).run(projectPath).changes;
166374
+ return getClearProjectStatement2(db).run(projectPath).changes;
165226
166375
  }
165227
166376
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
165228
166377
  const rows = getDistinctModelIdStatement(db).all(projectPath);
165229
166378
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165230
166379
  }
165231
- var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements, distinctModelIdStatements;
166380
+ var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements2, distinctModelIdStatements;
165232
166381
  var init_storage_git_commit_embeddings = __esm(() => {
165233
166382
  saveStatements = new WeakMap;
165234
166383
  loadProjectStatements = new WeakMap;
165235
166384
  loadUnembeddedStatements = new WeakMap;
165236
166385
  countEmbeddedStatements = new WeakMap;
165237
- clearProjectStatements = new WeakMap;
166386
+ clearProjectStatements2 = new WeakMap;
165238
166387
  distinctModelIdStatements = new WeakMap;
165239
166388
  });
165240
166389
 
@@ -165360,13 +166509,90 @@ var init_sweep_coordinator = __esm(() => {
165360
166509
  GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
165361
166510
  });
165362
166511
 
166512
+ // src/features/magic-context/session-project-storage.ts
166513
+ function getUpsertSessionProjectStatement(db) {
166514
+ let stmt = upsertSessionProjectStatements.get(db);
166515
+ if (!stmt) {
166516
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
166517
+ VALUES (?, ?, ?, ?)
166518
+ ON CONFLICT(session_id, harness) DO UPDATE SET
166519
+ project_path = excluded.project_path,
166520
+ updated_at = excluded.updated_at
166521
+ WHERE session_projects.project_path <> excluded.project_path`);
166522
+ upsertSessionProjectStatements.set(db, stmt);
166523
+ }
166524
+ return stmt;
166525
+ }
166526
+ function getRepairSessionChunkProjectStatement(db) {
166527
+ let stmt = repairSessionChunkProjectStatements.get(db);
166528
+ if (!stmt) {
166529
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166530
+ SET project_path = ?
166531
+ WHERE session_id = ?
166532
+ AND harness = ?
166533
+ AND project_path <> ?`);
166534
+ repairSessionChunkProjectStatements.set(db, stmt);
166535
+ }
166536
+ return stmt;
166537
+ }
166538
+ function getRepairProjectChunkProjectStatement(db) {
166539
+ let stmt = repairProjectChunkProjectStatements.get(db);
166540
+ if (!stmt) {
166541
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166542
+ SET project_path = (
166543
+ SELECT sp.project_path
166544
+ FROM session_projects sp
166545
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166546
+ AND sp.harness = compartment_chunk_embeddings.harness
166547
+ LIMIT 1
166548
+ )
166549
+ WHERE EXISTS (
166550
+ SELECT 1
166551
+ FROM session_projects sp
166552
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166553
+ AND sp.harness = compartment_chunk_embeddings.harness
166554
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
166555
+ AND (
166556
+ sp.project_path = ?
166557
+ OR compartment_chunk_embeddings.project_path = ?
166558
+ )
166559
+ )`);
166560
+ repairProjectChunkProjectStatements.set(db, stmt);
166561
+ }
166562
+ return stmt;
166563
+ }
166564
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
166565
+ if (!sessionId || !projectPath)
166566
+ return;
166567
+ const harness = getHarness();
166568
+ const now = Date.now();
166569
+ db.transaction(() => {
166570
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
166571
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
166572
+ })();
166573
+ }
166574
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
166575
+ if (!projectPath)
166576
+ return 0;
166577
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
166578
+ }
166579
+ var upsertSessionProjectStatements, repairSessionChunkProjectStatements, repairProjectChunkProjectStatements;
166580
+ var init_session_project_storage = __esm(() => {
166581
+ upsertSessionProjectStatements = new WeakMap;
166582
+ repairSessionChunkProjectStatements = new WeakMap;
166583
+ repairProjectChunkProjectStatements = new WeakMap;
166584
+ });
166585
+
165363
166586
  // src/features/magic-context/project-embedding-registry.ts
165364
- import { createHash as createHash7, randomUUID } from "node:crypto";
166587
+ import { createHash as createHash9, randomUUID } from "node:crypto";
165365
166588
  function resolveEmbeddingConfig(config2) {
165366
166589
  if (!config2 || config2.provider === "local") {
165367
166590
  return {
165368
166591
  provider: "local",
165369
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
166592
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
166593
+ ...config2?.max_input_tokens ? {
166594
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166595
+ } : {}
165370
166596
  };
165371
166597
  }
165372
166598
  if (config2.provider === "openai-compatible") {
@@ -165379,7 +166605,10 @@ function resolveEmbeddingConfig(config2) {
165379
166605
  endpoint: config2.endpoint.trim(),
165380
166606
  ...apiKey ? { api_key: apiKey } : {},
165381
166607
  ...inputType ? { input_type: inputType } : {},
165382
- ...truncate ? { truncate } : {}
166608
+ ...truncate ? { truncate } : {},
166609
+ ...config2.max_input_tokens ? {
166610
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166611
+ } : {}
165383
166612
  };
165384
166613
  }
165385
166614
  return { provider: "off" };
@@ -165397,10 +166626,11 @@ function createProvider(config2) {
165397
166626
  model: config2.model,
165398
166627
  apiKey: config2.api_key,
165399
166628
  inputType: config2.input_type,
165400
- truncate: config2.truncate
166629
+ truncate: config2.truncate,
166630
+ maxInputTokens: config2.max_input_tokens
165401
166631
  });
165402
166632
  }
165403
- return new LocalEmbeddingProvider(config2.model);
166633
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165404
166634
  }
165405
166635
  function stableStringify2(value) {
165406
166636
  if (Array.isArray(value)) {
@@ -165413,7 +166643,7 @@ function stableStringify2(value) {
165413
166643
  return JSON.stringify(value);
165414
166644
  }
165415
166645
  function sha256Prefix(value, length = 16) {
165416
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
166646
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
165417
166647
  }
165418
166648
  function getRuntimeFingerprint(config2) {
165419
166649
  if (config2.provider === "off") {
@@ -165421,6 +166651,18 @@ function getRuntimeFingerprint(config2) {
165421
166651
  }
165422
166652
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
165423
166653
  }
166654
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
166655
+ if (config2.provider === "off") {
166656
+ return OFF_PROVIDER_IDENTITY;
166657
+ }
166658
+ const chunkIdentity = {
166659
+ providerIdentity,
166660
+ chunkerVersion: 2,
166661
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
166662
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
166663
+ };
166664
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
166665
+ }
165424
166666
  function sameFeatures(a, b) {
165425
166667
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
165426
166668
  }
@@ -165437,7 +166679,8 @@ function snapshotFor(registration) {
165437
166679
  features: { ...registration.features },
165438
166680
  enabled,
165439
166681
  gitCommitEnabled,
165440
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
166682
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
166683
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
165441
166684
  };
165442
166685
  }
165443
166686
  function disposeProvider(provider) {
@@ -165457,7 +166700,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
165457
166700
  }
165458
166701
  return false;
165459
166702
  }
165460
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
166703
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
165461
166704
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
165462
166705
  return false;
165463
166706
  }
@@ -165478,6 +166721,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
165478
166721
  wiped = true;
165479
166722
  }
165480
166723
  }
166724
+ if (features.memoryEnabled) {
166725
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
166726
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
166727
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
166728
+ clearChunkEmbeddingsForProject(db, projectIdentity);
166729
+ wiped = true;
166730
+ }
166731
+ }
165481
166732
  })();
165482
166733
  return wiped;
165483
166734
  }
@@ -165485,10 +166736,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165485
166736
  const resolvedConfig = resolveEmbeddingConfig(config2);
165486
166737
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
165487
166738
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
166739
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
165488
166740
  const prior = projectRegistrations.get(projectIdentity);
165489
166741
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
165490
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
165491
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
166742
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
166743
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
165492
166744
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
165493
166745
  const registration = {
165494
166746
  projectIdentity,
@@ -165500,6 +166752,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165500
166752
  generation,
165501
166753
  features: { ...features },
165502
166754
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
166755
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
165503
166756
  observationMode: false
165504
166757
  };
165505
166758
  projectRegistrations.set(projectIdentity, registration);
@@ -165522,6 +166775,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
165522
166775
  generation,
165523
166776
  features: { memoryEnabled: false, gitCommitEnabled: false },
165524
166777
  modelId: "off",
166778
+ chunkModelId: "off",
165525
166779
  observationMode: true
165526
166780
  };
165527
166781
  projectRegistrations.set(projectIdentity, registration);
@@ -165532,6 +166786,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
165532
166786
  const registration = projectRegistrations.get(projectIdentity);
165533
166787
  return registration ? snapshotFor(registration) : null;
165534
166788
  }
166789
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
166790
+ const registration = projectRegistrations.get(projectIdentity);
166791
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
166792
+ }
166793
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
166794
+ const registration = projectRegistrations.get(projectIdentity);
166795
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
166796
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
166797
+ }
165535
166798
  function getOrCreateProjectProvider(registration) {
165536
166799
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
165537
166800
  return null;
@@ -165626,10 +166889,136 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
165626
166889
  return 0;
165627
166890
  }
165628
166891
  }
165629
- var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
166892
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
166893
+ const noWork = [];
166894
+ if (candidates.length === 0)
166895
+ return { embedded: 0, noWork };
166896
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
166897
+ const prepared = [];
166898
+ for (const candidate of candidates) {
166899
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
166900
+ if (canonicalText.length === 0) {
166901
+ noWork.push(candidate.id);
166902
+ continue;
166903
+ }
166904
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
166905
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
166906
+ noWork.push(candidate.id);
166907
+ continue;
166908
+ }
166909
+ prepared.push({ candidate, windows });
166910
+ }
166911
+ if (prepared.length === 0)
166912
+ return { embedded: 0, noWork };
166913
+ let embedded = 0;
166914
+ let i = 0;
166915
+ while (i < prepared.length) {
166916
+ if (signal?.aborted)
166917
+ break;
166918
+ const slice = [];
166919
+ let windowCount = 0;
166920
+ do {
166921
+ const item = prepared[i];
166922
+ slice.push(item);
166923
+ windowCount += item.windows.length;
166924
+ i += 1;
166925
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
166926
+ const texts = [];
166927
+ for (const item of slice)
166928
+ texts.push(...item.windows.map((w) => w.text));
166929
+ try {
166930
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
166931
+ if (!result)
166932
+ continue;
166933
+ if (signal?.aborted)
166934
+ break;
166935
+ let offset = 0;
166936
+ for (const item of slice) {
166937
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
166938
+ offset += item.windows.length;
166939
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
166940
+ continue;
166941
+ }
166942
+ const rows = item.windows.map((window, index) => ({
166943
+ compartmentId: item.candidate.id,
166944
+ sessionId: item.candidate.sessionId,
166945
+ projectPath: projectIdentity,
166946
+ window,
166947
+ modelId,
166948
+ vector: vectors[index]
166949
+ }));
166950
+ replaceCompartmentChunkEmbeddings(db, rows);
166951
+ embedded += 1;
166952
+ }
166953
+ } catch (error51) {
166954
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
166955
+ }
166956
+ }
166957
+ return { embedded, noWork };
166958
+ }
166959
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
166960
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
166961
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
166962
+ return { status: "disabled", embedded: 0, total: 0 };
166963
+ }
166964
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
166965
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
166966
+ if (total === 0)
166967
+ return { status: "nothing", embedded: 0, total: 0 };
166968
+ const holderId = `session-embed-${randomUUID()}`;
166969
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
166970
+ if (!lease2.acquired)
166971
+ return { status: "busy", embedded: 0, total };
166972
+ const renewal = setInterval(() => {
166973
+ try {
166974
+ renewGitSweepLease(db, projectIdentity, holderId);
166975
+ } catch {}
166976
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
166977
+ renewal.unref?.();
166978
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
166979
+ const skipIds = [];
166980
+ let embedded = 0;
166981
+ let aborted2 = false;
166982
+ let providerStalled = false;
166983
+ try {
166984
+ options?.onProgress?.({ embedded, total });
166985
+ for (;; ) {
166986
+ if (options?.signal?.aborted) {
166987
+ aborted2 = true;
166988
+ break;
166989
+ }
166990
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
166991
+ if (candidates.length === 0)
166992
+ break;
166993
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
166994
+ for (const id of noWork)
166995
+ skipIds.push(id);
166996
+ if (n === 0 && noWork.length === 0) {
166997
+ providerStalled = true;
166998
+ break;
166999
+ }
167000
+ embedded += n;
167001
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
167002
+ await new Promise((resolve6) => setTimeout(resolve6, 0));
167003
+ }
167004
+ } finally {
167005
+ clearInterval(renewal);
167006
+ releaseGitSweepLease(db, projectIdentity, holderId);
167007
+ }
167008
+ if (aborted2)
167009
+ return { status: "aborted", embedded, total };
167010
+ if (providerStalled) {
167011
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
167012
+ if (remaining > 0)
167013
+ return { status: "stalled", embedded, total, remaining };
167014
+ }
167015
+ return { status: "done", embedded, total };
167016
+ }
167017
+ var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, CHUNK_DRAIN_BATCH_SIZE = 8, MAX_WINDOWS_PER_EMBED_CALL = 16, SESSION_EMBED_LEASE_RENEWAL_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
165630
167018
  var init_project_embedding_registry = __esm(() => {
165631
167019
  init_magic_context();
165632
167020
  init_logger();
167021
+ init_compartment_chunk_embedding();
165633
167022
  init_storage_git_commit_embeddings();
165634
167023
  init_sweep_coordinator();
165635
167024
  init_embedding_cache();
@@ -165637,7 +167026,9 @@ var init_project_embedding_registry = __esm(() => {
165637
167026
  init_embedding_local();
165638
167027
  init_embedding_openai();
165639
167028
  init_storage_memory_embeddings();
167029
+ init_session_project_storage();
165640
167030
  SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167031
+ SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
165641
167032
  projectRegistrations = new Map;
165642
167033
  loadUnembeddedMemoriesStatements = new WeakMap;
165643
167034
  });
@@ -165653,10 +167044,11 @@ function createProvider2(config2) {
165653
167044
  model: config2.model,
165654
167045
  apiKey: config2.api_key,
165655
167046
  inputType: config2.input_type,
165656
- truncate: config2.truncate
167047
+ truncate: config2.truncate,
167048
+ maxInputTokens: config2.max_input_tokens
165657
167049
  });
165658
167050
  }
165659
- return new LocalEmbeddingProvider(config2.model);
167051
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165660
167052
  }
165661
167053
  function getOrCreateProvider() {
165662
167054
  if (provider) {
@@ -165682,6 +167074,7 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
165682
167074
  var init_embedding = __esm(() => {
165683
167075
  init_magic_context();
165684
167076
  init_logger();
167077
+ init_compartment_chunk_embedding();
165685
167078
  init_embedding_identity();
165686
167079
  init_embedding_local();
165687
167080
  init_embedding_openai();
@@ -165705,6 +167098,23 @@ function getSearchStatement(db) {
165705
167098
  }
165706
167099
  return stmt;
165707
167100
  }
167101
+ function getUnionSearchStatement(db, arity) {
167102
+ let statements = unionSearchStatements.get(arity);
167103
+ if (!statements) {
167104
+ statements = new WeakMap;
167105
+ unionSearchStatements.set(arity, statements);
167106
+ }
167107
+ let stmt = statements.get(db);
167108
+ if (!stmt) {
167109
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
167110
+ 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 ?`);
167111
+ statements.set(db, stmt);
167112
+ }
167113
+ return stmt;
167114
+ }
167115
+ function uniqueProjectPaths2(projectPaths) {
167116
+ return [...new Set(projectPaths.filter((path6) => path6.length > 0))];
167117
+ }
165708
167118
  function sanitizeFtsQuery(query) {
165709
167119
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
165710
167120
  if (tokens.length === 0)
@@ -165723,10 +167133,33 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
165723
167133
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
165724
167134
  return rows.map(toMemory);
165725
167135
  }
165726
- var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
167136
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
167137
+ const identities = uniqueProjectPaths2(projectPaths);
167138
+ if (identities.length === 0)
167139
+ return [];
167140
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
167141
+ identities,
167142
+ ownIdentities,
167143
+ shareCategories,
167144
+ tableName: "memories"
167145
+ });
167146
+ if (identities.length === 1 && !sharingFilter.active) {
167147
+ return searchMemoriesFTS(db, identities[0], query, limit);
167148
+ }
167149
+ const trimmedQuery = query.trim();
167150
+ if (trimmedQuery.length === 0 || limit <= 0)
167151
+ return [];
167152
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
167153
+ if (sanitized.length === 0)
167154
+ return [];
167155
+ 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);
167156
+ return rows.map(toMemory);
167157
+ }
167158
+ var DEFAULT_SEARCH_LIMIT = 10, searchStatements, unionSearchStatements;
165727
167159
  var init_storage_memory_fts = __esm(() => {
165728
167160
  init_storage_memory();
165729
167161
  searchStatements = new WeakMap;
167162
+ unionSearchStatements = new Map;
165730
167163
  });
165731
167164
 
165732
167165
  // src/shared/models-dev-cache.ts
@@ -165920,26 +167353,54 @@ var init_rpc_notifications = __esm(() => {
165920
167353
  });
165921
167354
 
165922
167355
  // src/features/magic-context/compartment-embedding.ts
165923
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
167356
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
165924
167357
  if (compartments.length === 0)
165925
167358
  return;
165926
- const update = db.prepare("UPDATE compartments SET p1_embedding = ?, p1_embedding_model_id = ? WHERE id = ?");
165927
- for (const c of compartments) {
165928
- if (!c.p1 || c.p1.length === 0)
165929
- continue;
167359
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
167360
+ for (const compartment of compartments) {
165930
167361
  try {
165931
- const result = await embedTextForProject(projectPath, c.p1);
165932
- if (result) {
165933
- const blob = Buffer.from(result.vector.buffer, result.vector.byteOffset, result.vector.byteLength);
165934
- update.run(blob, result.modelId, c.id);
167362
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
167363
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
167364
+ if (canonicalText.length === 0)
167365
+ continue;
167366
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
167367
+ if (windows.length === 0)
167368
+ continue;
167369
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
167370
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167371
+ continue;
167372
+ }
167373
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
167374
+ if (!result)
167375
+ continue;
167376
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167377
+ continue;
167378
+ }
167379
+ const rows = [];
167380
+ for (const [index, window] of windows.entries()) {
167381
+ const vector = result.vectors[index];
167382
+ if (!vector)
167383
+ continue;
167384
+ rows.push({
167385
+ compartmentId: compartment.id,
167386
+ sessionId,
167387
+ projectPath,
167388
+ window,
167389
+ modelId: currentModelId,
167390
+ vector
167391
+ });
167392
+ }
167393
+ if (rows.length === windows.length) {
167394
+ replaceCompartmentChunkEmbeddings(db, rows);
165935
167395
  }
165936
167396
  } catch (error51) {
165937
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
167397
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
165938
167398
  }
165939
167399
  }
165940
167400
  }
165941
167401
  var init_compartment_embedding = __esm(() => {
165942
167402
  init_logger();
167403
+ init_compartment_chunk_embedding();
165943
167404
  init_project_embedding_registry();
165944
167405
  });
165945
167406
 
@@ -167053,55 +168514,6 @@ var init_historian_state_file = __esm(() => {
167053
168514
  init_data_path();
167054
168515
  });
167055
168516
 
167056
- // src/features/magic-context/memory/constants.ts
167057
- var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
167058
- var init_constants = __esm(() => {
167059
- V2_MEMORY_CATEGORIES = [
167060
- "PROJECT_RULES",
167061
- "ARCHITECTURE",
167062
- "CONSTRAINTS",
167063
- "CONFIG_VALUES",
167064
- "NAMING"
167065
- ];
167066
- PROMOTABLE_CATEGORIES = [
167067
- "PROJECT_RULES",
167068
- "ARCHITECTURE",
167069
- "CONSTRAINTS",
167070
- "CONFIG_VALUES",
167071
- "NAMING",
167072
- "ARCHITECTURE_DECISIONS",
167073
- "CONFIG_DEFAULTS",
167074
- "USER_PREFERENCES",
167075
- "USER_DIRECTIVES",
167076
- "ENVIRONMENT",
167077
- "WORKFLOW_RULES",
167078
- "KNOWN_ISSUES"
167079
- ];
167080
- CATEGORY_PRIORITY = [
167081
- "PROJECT_RULES",
167082
- "ARCHITECTURE",
167083
- "CONSTRAINTS",
167084
- "CONFIG_VALUES",
167085
- "NAMING",
167086
- "USER_DIRECTIVES",
167087
- "USER_PREFERENCES",
167088
- "CONFIG_DEFAULTS",
167089
- "ARCHITECTURE_DECISIONS",
167090
- "ENVIRONMENT",
167091
- "WORKFLOW_RULES",
167092
- "KNOWN_ISSUES"
167093
- ];
167094
- MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
167095
- acc[category] = index;
167096
- return acc;
167097
- }, {});
167098
- MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
167099
- CATEGORY_DEFAULT_TTL = {
167100
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
167101
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
167102
- };
167103
- });
167104
-
167105
168517
  // src/features/magic-context/memory/embedding-backfill.ts
167106
168518
  async function ensureMemoryEmbeddings(args) {
167107
168519
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167125,7 +168537,7 @@ async function ensureMemoryEmbeddings(args) {
167125
168537
  continue;
167126
168538
  }
167127
168539
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
167128
- staged.set(memory.id, embedding);
168540
+ staged.set(memory.id, { embedding, modelId: result.modelId });
167129
168541
  }
167130
168542
  })();
167131
168543
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167884,7 +169296,7 @@ ${prepared.block}
167884
169296
  if (!firstMessage || !textPart || isDroppedPlaceholder(textPart.text)) {
167885
169297
  messages.unshift({
167886
169298
  info: { role: "user", sessionID: sessionId },
167887
- parts: [{ type: "text", text: historyBlock }]
169299
+ parts: [{ type: "text", text: historyBlock, synthetic: true }]
167888
169300
  });
167889
169301
  } else {
167890
169302
  textPart.text = `${historyBlock}
@@ -167922,6 +169334,71 @@ function lastCompartmentBoundaryId(compartments) {
167922
169334
  const last = compartments.at(-1);
167923
169335
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
167924
169336
  }
169337
+ function resolveWorkspaceRenderContext(args) {
169338
+ if (!args.projectPath) {
169339
+ return {
169340
+ identities: [],
169341
+ expandedIdentities: [],
169342
+ ownIdentities: [],
169343
+ shareCategories: null,
169344
+ namesByIdentity: new Map,
169345
+ canonicalIdentityByStoredPath: new Map,
169346
+ isWorkspaced: false
169347
+ };
169348
+ }
169349
+ const identitySet = args.workspaceIdentitySet ?? resolveWorkspaceIdentitySet(args.db, args.projectPath);
169350
+ const isWorkspaced = identitySet.identities.length > 1;
169351
+ const expanded = expandWorkspaceIdentitySetWithAliases(args.db, identitySet.identities);
169352
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
169353
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
169354
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === args.projectPath);
169355
+ if (ownIdentities.length === 0 && expandedIdentities.includes(args.projectPath)) {
169356
+ ownIdentities = [args.projectPath];
169357
+ }
169358
+ return {
169359
+ identities: identitySet.identities,
169360
+ expandedIdentities,
169361
+ ownIdentities,
169362
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(args.db, args.projectPath) : null,
169363
+ namesByIdentity: identitySet.namesByIdentity,
169364
+ canonicalIdentityByStoredPath,
169365
+ isWorkspaced
169366
+ };
169367
+ }
169368
+ function sourceNamesForMemories(args) {
169369
+ if (!args.projectPath || !args.workspace.isWorkspaced)
169370
+ return;
169371
+ const names = new Map;
169372
+ for (const memory of args.memories) {
169373
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
169374
+ if (source)
169375
+ names.set(memory.id, source);
169376
+ }
169377
+ return names.size > 0 ? names : undefined;
169378
+ }
169379
+ function memoryCanonicalIdentity(memory, workspace) {
169380
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
169381
+ }
169382
+ function memorySelectionOrder(left, right) {
169383
+ if (left.status === "permanent" && right.status !== "permanent")
169384
+ return -1;
169385
+ if (right.status === "permanent" && left.status !== "permanent")
169386
+ return 1;
169387
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
169388
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
169389
+ const importanceDiff = rightImportance - leftImportance;
169390
+ if (importanceDiff !== 0)
169391
+ return importanceDiff;
169392
+ return left.id - right.id;
169393
+ }
169394
+ function memoryRenderOrder(left, right) {
169395
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169396
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169397
+ const categoryDiff = aPriority - bPriority;
169398
+ if (categoryDiff !== 0)
169399
+ return categoryDiff;
169400
+ return left.id - right.id;
169401
+ }
167925
169402
  function cachedStatement(cache, db, sql) {
167926
169403
  let stmt = cache.get(db);
167927
169404
  if (!stmt) {
@@ -167964,13 +169441,19 @@ function getGlobalUserProfileVersion(db) {
167964
169441
  function readCurrentM0SnapshotMarkers(args) {
167965
169442
  const projectDirectory = args.projectDirectory ?? args.projectPath ?? "";
167966
169443
  const hard = args.hardSignals ?? EMPTY_HARD_SIGNALS;
169444
+ const workspace = resolveWorkspaceRenderContext({
169445
+ db: args.db,
169446
+ projectPath: args.projectPath,
169447
+ workspaceIdentitySet: args.workspaceIdentitySet
169448
+ });
167967
169449
  return {
167968
169450
  projectMemoryEpoch: getProjectMemoryEpoch(args.db, args.projectPath),
169451
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(args.db, workspace.identities) : null,
167969
169452
  projectUserProfileVersion: getGlobalUserProfileVersion(args.db),
167970
169453
  maxCompartmentSeq: getMaxCompartmentSeq(args.db, args.sessionId),
167971
- maxMemoryId: getMaxMemoryId(args.db, args.projectPath),
169454
+ maxMemoryId: workspace.isWorkspaced ? getMaxMemoryIdForProjects(args.db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryId(args.db, args.projectPath),
167972
169455
  maxMutationId: getMaxM0MutationId(args.db, args.sessionId) ?? 0,
167973
- maxMemoryMutationId: args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
169456
+ maxMemoryMutationId: workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(args.db, workspace.expandedIdentities) ?? 0 : args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
167974
169457
  projectDocsHash: projectDirectory ? computeProjectDocsHash(projectDirectory) : "",
167975
169458
  materializedAt: Date.now(),
167976
169459
  sessionFactsVersion: getSessionFactsVersion(args.db, args.sessionId),
@@ -167998,6 +169481,7 @@ function snapshotMarkersFromCachedM0(state) {
167998
169481
  return null;
167999
169482
  return {
168000
169483
  projectMemoryEpoch: state.cachedM0ProjectMemoryEpoch,
169484
+ workspaceFingerprint: state.cachedM0WorkspaceFingerprint,
168001
169485
  projectUserProfileVersion: state.cachedM0ProjectUserProfileVersion,
168002
169486
  maxCompartmentSeq: state.cachedM0MaxCompartmentSeq,
168003
169487
  maxMemoryId: state.cachedM0MaxMemoryId,
@@ -168027,35 +169511,27 @@ function mustMaterialize(args) {
168027
169511
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (args.state.cachedM0MaterializedAt ?? 0)) {
168028
169512
  return { value: true, reason: "ttl_idle" };
168029
169513
  }
168030
- if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
169514
+ if (current.workspaceFingerprint !== null || (args.state.cachedM0WorkspaceFingerprint ?? null) !== null) {
169515
+ if ((args.state.cachedM0WorkspaceFingerprint ?? null) !== current.workspaceFingerprint) {
169516
+ return { value: true, reason: "project_memory_epoch" };
169517
+ }
169518
+ } else if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
168031
169519
  return { value: true, reason: "project_memory_epoch" };
168032
169520
  }
168033
169521
  if (args.state.cachedM0MaxMutationId !== current.maxMutationId) {
168034
169522
  return { value: true, reason: "max_mutation_id" };
168035
169523
  }
168036
- if ((args.state.cachedM0ProjectDocsHash ?? "") !== current.projectDocsHash) {
168037
- return { value: true, reason: "project_docs_hash" };
168038
- }
168039
169524
  if ((args.state.cachedM0UpgradeState ?? null) !== current.upgradeState) {
168040
169525
  return { value: true, reason: "upgrade_state" };
168041
169526
  }
168042
169527
  return { value: false, reason: null };
168043
169528
  }
168044
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168045
- const selectionOrder = [...memories].sort((a, b) => {
168046
- if (a.status === "permanent" && b.status !== "permanent")
168047
- return -1;
168048
- if (b.status === "permanent" && a.status !== "permanent")
168049
- return 1;
168050
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
168051
- if (importanceDiff !== 0)
168052
- return importanceDiff;
168053
- return a.id - b.id;
168054
- });
169529
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
169530
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
168055
169531
  const selected = [];
168056
169532
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
168057
169533
  for (const memory of selectionOrder) {
168058
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
169534
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168059
169535
  if (usedTokens + memoryTokens > budgetTokens)
168060
169536
  continue;
168061
169537
  selected.push(memory);
@@ -168064,16 +169540,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168064
169540
  if (selected.length < memories.length) {
168065
169541
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
168066
169542
  }
168067
- const renderOrder = [...selected].sort((a, b) => {
168068
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168069
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168070
- const categoryDiff = aPriority - bPriority;
168071
- if (categoryDiff !== 0)
168072
- return categoryDiff;
168073
- return a.id - b.id;
168074
- });
169543
+ const renderOrder = [...selected].sort(memoryRenderOrder);
168075
169544
  return { selected, renderOrder };
168076
169545
  }
169546
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
169547
+ if (!workspace.isWorkspaced) {
169548
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
169549
+ }
169550
+ const selected = [];
169551
+ const selectedIds = new Set;
169552
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
169553
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
169554
+ const trySelect = (memory) => {
169555
+ if (selectedIds.has(memory.id))
169556
+ return false;
169557
+ const tokens = tokenCost(memory);
169558
+ if (usedTokens + tokens > budgetTokens)
169559
+ return false;
169560
+ selected.push(memory);
169561
+ selectedIds.add(memory.id);
169562
+ usedTokens += tokens;
169563
+ return true;
169564
+ };
169565
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
169566
+ trySelect(memory);
169567
+ }
169568
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
169569
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
169570
+ const byIdentity = new Map;
169571
+ for (const memory of memories) {
169572
+ if (memory.status === "permanent")
169573
+ continue;
169574
+ const identity = memoryCanonicalIdentity(memory, workspace);
169575
+ if (!identity)
169576
+ continue;
169577
+ const list = byIdentity.get(identity) ?? [];
169578
+ list.push(memory);
169579
+ byIdentity.set(identity, list);
169580
+ }
169581
+ for (const identity of workspace.identities) {
169582
+ let memberTokens = 0;
169583
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
169584
+ for (const memory of candidates) {
169585
+ if (selectedIds.has(memory.id))
169586
+ continue;
169587
+ const tokens = tokenCost(memory);
169588
+ if (memberTokens + tokens > floorTokens)
169589
+ continue;
169590
+ if (usedTokens + tokens > budgetTokens)
169591
+ continue;
169592
+ selected.push(memory);
169593
+ selectedIds.add(memory.id);
169594
+ usedTokens += tokens;
169595
+ memberTokens += tokens;
169596
+ }
169597
+ }
169598
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
169599
+ for (const memory of remaining) {
169600
+ trySelect(memory);
169601
+ }
169602
+ if (selected.length < memories.length) {
169603
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
169604
+ }
169605
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
169606
+ }
168077
169607
  function safeGetActiveUserMemories(db) {
168078
169608
  try {
168079
169609
  return getActiveUserMemories(db);
@@ -168149,15 +169679,16 @@ function readNewMemoriesForM1(db, projectPath, afterId, expiryCutoff) {
168149
169679
  ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(projectPath, afterId, expiryCutoff).filter(isMemoryRow);
168150
169680
  return rows.map((row) => ({ ...row }));
168151
169681
  }
168152
- function renderMemoryLineV2(memory) {
168153
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
169682
+ function renderMemoryLineV2(memory, sourceName) {
169683
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
169684
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
168154
169685
  }
168155
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
169686
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
168156
169687
  if (memories.length === 0)
168157
169688
  return "";
168158
169689
  const lines = [`<${wrapper}>`];
168159
169690
  for (const memory of memories) {
168160
- lines.push(renderMemoryLineV2(memory));
169691
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168161
169692
  }
168162
169693
  lines.push(`</${wrapper}>`);
168163
169694
  return lines.join(`
@@ -168196,7 +169727,7 @@ function renderM0(args) {
168196
169727
  sections.push(sessionHistory.length > 0 ? `<session-history>
168197
169728
  ${sessionHistory}
168198
169729
  </session-history>` : M0_EMPTY_BODY);
168199
- const memoriesBlock = renderMemoryBlockV2(args.memories);
169730
+ const memoriesBlock = renderMemoryBlockV2(args.memories, "project-memory", args.memoryRenderOptions);
168200
169731
  if (memoriesBlock)
168201
169732
  sections.push(memoriesBlock);
168202
169733
  return sections.join(`
@@ -168208,6 +169739,7 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
168208
169739
  if (m1Bytes)
168209
169740
  state.cachedM1Bytes = m1Bytes;
168210
169741
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
169742
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168211
169743
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168212
169744
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168213
169745
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168233,24 +169765,38 @@ function materializeM0(options) {
168233
169765
  let facts = [];
168234
169766
  let memories = [];
168235
169767
  let userMemories = [];
169768
+ let workspace = resolveWorkspaceRenderContext({
169769
+ db: options.db,
169770
+ projectPath,
169771
+ workspaceIdentitySet: options.workspaceIdentitySet
169772
+ });
168236
169773
  let docs = {
168237
169774
  renderedBlock: "",
168238
169775
  canonicalHash: ""
168239
169776
  };
168240
169777
  options.db.exec("BEGIN");
168241
169778
  try {
169779
+ workspace = resolveWorkspaceRenderContext({
169780
+ db: options.db,
169781
+ projectPath,
169782
+ workspaceIdentitySet: options.workspaceIdentitySet
169783
+ });
168242
169784
  snapshotMarkers = readCurrentM0SnapshotMarkers({
168243
169785
  db: options.db,
168244
169786
  sessionId: options.sessionId,
168245
169787
  projectPath,
168246
169788
  projectDirectory,
168247
- hardSignals: options.hardSignals
169789
+ hardSignals: options.hardSignals,
169790
+ workspaceIdentitySet: {
169791
+ identities: workspace.identities,
169792
+ namesByIdentity: workspace.namesByIdentity
169793
+ }
168248
169794
  });
168249
169795
  docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168250
169796
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168251
169797
  compartments = readM0Compartments(options.db, options.sessionId);
168252
169798
  facts = [];
168253
- memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
169799
+ memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
168254
169800
  userMemories = safeGetActiveUserMemories(options.db);
168255
169801
  options.db.exec("COMMIT");
168256
169802
  } catch (error51) {
@@ -168260,7 +169806,14 @@ function materializeM0(options) {
168260
169806
  throw error51;
168261
169807
  }
168262
169808
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168263
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
169809
+ const memoryRenderOptions = {
169810
+ sourceNameByMemoryId: sourceNamesForMemories({
169811
+ memories,
169812
+ projectPath,
169813
+ workspace
169814
+ })
169815
+ };
169816
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168264
169817
  let decayPressureMultiplier = 1;
168265
169818
  let m0Text = renderM0({
168266
169819
  projectDocs: docs.renderedBlock,
@@ -168268,6 +169821,7 @@ function materializeM0(options) {
168268
169821
  compartments,
168269
169822
  memories: trimmed.renderOrder,
168270
169823
  facts,
169824
+ memoryRenderOptions,
168271
169825
  historyBudgetTokens: options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS,
168272
169826
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168273
169827
  decayPressureMultiplier
@@ -168282,6 +169836,7 @@ function materializeM0(options) {
168282
169836
  compartments,
168283
169837
  memories: trimmed.renderOrder,
168284
169838
  facts,
169839
+ memoryRenderOptions,
168285
169840
  historyBudgetTokens: budget,
168286
169841
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168287
169842
  decayPressureMultiplier
@@ -168300,13 +169855,19 @@ function materializeM0(options) {
168300
169855
  let m1Bytes = Buffer4.from(m1Text, "utf8");
168301
169856
  options.db.exec("BEGIN IMMEDIATE");
168302
169857
  try {
169858
+ const currentWorkspace = resolveWorkspaceRenderContext({
169859
+ db: options.db,
169860
+ projectPath,
169861
+ workspaceIdentitySet: options.workspaceIdentitySet
169862
+ });
168303
169863
  const current = {
168304
169864
  projectMemoryEpoch: getProjectMemoryEpoch(options.db, projectPath),
169865
+ workspaceFingerprint: currentWorkspace.isWorkspaced ? computeWorkspaceEpochFingerprint(options.db, currentWorkspace.identities) : null,
168305
169866
  projectUserProfileVersion: getGlobalUserProfileVersion(options.db),
168306
169867
  maxCompartmentSeq: getMaxCompartmentSeq(options.db, options.sessionId),
168307
- maxMemoryId: getMaxMemoryId(options.db, projectPath),
169868
+ maxMemoryId: currentWorkspace.isWorkspaced ? getMaxMemoryIdForProjects(options.db, currentWorkspace.expandedIdentities, currentWorkspace.ownIdentities, currentWorkspace.shareCategories) : getMaxMemoryId(options.db, projectPath),
168308
169869
  maxMutationId: getMaxM0MutationId(options.db, options.sessionId) ?? 0,
168309
- maxMemoryMutationId: projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
169870
+ maxMemoryMutationId: currentWorkspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(options.db, currentWorkspace.expandedIdentities) ?? 0 : projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
168310
169871
  projectDocsHash: phase3ProjectDocsHash,
168311
169872
  materializedAt: Date.now(),
168312
169873
  sessionFactsVersion: getSessionFactsVersion(options.db, options.sessionId),
@@ -168314,17 +169875,26 @@ function materializeM0(options) {
168314
169875
  systemHash: snapshotMarkers.systemHash,
168315
169876
  modelKey: snapshotMarkers.modelKey
168316
169877
  };
168317
- const stale = current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.projectDocsHash !== snapshotMarkers.projectDocsHash || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
169878
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
169879
+ const stale = memoryEpochStale || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
168318
169880
  if (stale) {
168319
169881
  options.db.exec("ROLLBACK");
168320
169882
  throw new MaterializeContentionError({ reason: "snapshot changed before Phase 3" });
168321
169883
  }
168322
- const m1Render = renderM1WithMetadata({ ...options, preRenderedKeyFilesBlock }, snapshotMarkers, renderedMemoryIds);
169884
+ const m1Render = renderM1WithMetadata({
169885
+ ...options,
169886
+ preRenderedKeyFilesBlock,
169887
+ workspaceIdentitySet: {
169888
+ identities: workspace.identities,
169889
+ namesByIdentity: workspace.namesByIdentity
169890
+ }
169891
+ }, snapshotMarkers, renderedMemoryIds);
168323
169892
  m1Text = m1Render.text;
168324
169893
  m1Bytes = Buffer4.from(m1Text, "utf8");
168325
169894
  persistCachedM0(options.db, options.sessionId, {
168326
169895
  m0Bytes,
168327
169896
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
169897
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
168328
169898
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
168329
169899
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
168330
169900
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -168387,7 +169957,7 @@ function renderMemoryUpdatesBlock(args) {
168387
169957
  return { block: "", count: 0 };
168388
169958
  }
168389
169959
  const renderedIds = new Set(args.renderedMemoryIds);
168390
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
169960
+ const mutations = args.workspace.isWorkspaced ? getMemoryMutationsForRenderByProjects(args.db, args.workspace.expandedIdentities, args.afterId, args.renderedMemoryIds) : getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
168391
169961
  if (mutations.length === 0)
168392
169962
  return { block: "", count: 0 };
168393
169963
  const lines = ["These memories changed since the snapshot below — trust these:"];
@@ -168419,12 +169989,18 @@ function renderM1WithMetadata(options, markers, renderedMemoryIds) {
168419
169989
  throw new RenderM1InvalidMarkersError(options.sessionId);
168420
169990
  }
168421
169991
  const blocks = [];
169992
+ const workspace = resolveWorkspaceRenderContext({
169993
+ db: options.db,
169994
+ projectPath: options.projectPath,
169995
+ workspaceIdentitySet: options.workspaceIdentitySet
169996
+ });
168422
169997
  const keyFiles = renderedKeyFilesBlock(options);
168423
169998
  if (keyFiles)
168424
169999
  blocks.push(keyFiles);
168425
170000
  const memoryUpdates = renderMemoryUpdatesBlock({
168426
170001
  db: options.db,
168427
170002
  projectPath: options.projectPath,
170003
+ workspace,
168428
170004
  afterId: markers.maxMemoryMutationId,
168429
170005
  renderedMemoryIds
168430
170006
  });
@@ -168438,9 +170014,16 @@ ${newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).
168438
170014
  `)}
168439
170015
  </new-compartments>`);
168440
170016
  }
168441
- const newMemories = readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
168442
- const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25))).renderOrder;
168443
- const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories");
170017
+ 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);
170018
+ const newMemoryRenderOptions = {
170019
+ sourceNameByMemoryId: sourceNamesForMemories({
170020
+ memories: newMemories,
170021
+ projectPath: options.projectPath,
170022
+ workspace
170023
+ })
170024
+ };
170025
+ const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25)), newMemoryRenderOptions).renderOrder;
170026
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", newMemoryRenderOptions);
168444
170027
  if (newMemoriesBlock)
168445
170028
  blocks.push(newMemoriesBlock);
168446
170029
  const currentUserProfileVersion = getGlobalUserProfileVersion(options.db);
@@ -168488,6 +170071,7 @@ function parseMemoryBlockIds(raw) {
168488
170071
  function readCachedM0M1Row(db, sessionId) {
168489
170072
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
168490
170073
  cached_m0_project_memory_epoch,
170074
+ cached_m0_workspace_fingerprint,
168491
170075
  cached_m0_project_user_profile_version,
168492
170076
  cached_m0_max_compartment_seq,
168493
170077
  cached_m0_max_memory_id,
@@ -168522,6 +170106,7 @@ function markersFromCachedRow(row) {
168522
170106
  return null;
168523
170107
  return {
168524
170108
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
170109
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
168525
170110
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
168526
170111
  maxCompartmentSeq: row.cached_m0_max_compartment_seq,
168527
170112
  maxMemoryId: row.cached_m0_max_memory_id,
@@ -168536,7 +170121,7 @@ function markersFromCachedRow(row) {
168536
170121
  };
168537
170122
  }
168538
170123
  function cachedRowMatchesState(row, state) {
168539
- return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && (row.cached_m0_project_docs_hash ?? "") === (state.cachedM0ProjectDocsHash ?? "") && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
170124
+ return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && (row.cached_m0_workspace_fingerprint ?? null) === (state.cachedM0WorkspaceFingerprint ?? null) && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
168540
170125
  }
168541
170126
  function applyCachedRowToState(state, row) {
168542
170127
  const markers = markersFromCachedRow(row);
@@ -168546,6 +170131,7 @@ function applyCachedRowToState(state, row) {
168546
170131
  state.cachedM0Bytes = toBuffer(row.cached_m0_bytes);
168547
170132
  state.cachedM1Bytes = toBuffer(row.cached_m1_bytes);
168548
170133
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
170134
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168549
170135
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168550
170136
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168551
170137
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168600,29 +170186,51 @@ function softRefreshCachedM1(options) {
168600
170186
  function prependM0M1Messages(sessionId, messages, m0Text, m1Text) {
168601
170187
  messages.unshift({
168602
170188
  info: { role: "user", sessionID: sessionId },
168603
- parts: [{ type: "text", text: m0Text.length > 0 ? m0Text : M0_EMPTY_BODY }]
170189
+ parts: [
170190
+ {
170191
+ type: "text",
170192
+ text: m0Text.length > 0 ? m0Text : M0_EMPTY_BODY,
170193
+ synthetic: true
170194
+ }
170195
+ ]
168604
170196
  }, {
168605
170197
  info: { role: "user", sessionID: sessionId },
168606
- parts: [{ type: "text", text: m1Text }]
170198
+ parts: [{ type: "text", text: m1Text, synthetic: true }]
168607
170199
  });
168608
170200
  }
168609
170201
  function renderFreshM0NonPersisted(options) {
168610
170202
  const projectPath = options.projectPath;
168611
170203
  const projectDirectory = options.projectDirectory;
170204
+ const workspace = resolveWorkspaceRenderContext({
170205
+ db: options.db,
170206
+ projectPath,
170207
+ workspaceIdentitySet: options.workspaceIdentitySet
170208
+ });
168612
170209
  const snapshotMarkers = readCurrentM0SnapshotMarkers({
168613
170210
  db: options.db,
168614
170211
  sessionId: options.sessionId,
168615
170212
  projectPath,
168616
- projectDirectory
170213
+ projectDirectory,
170214
+ workspaceIdentitySet: {
170215
+ identities: workspace.identities,
170216
+ namesByIdentity: workspace.namesByIdentity
170217
+ }
168617
170218
  });
168618
170219
  const docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168619
170220
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168620
170221
  snapshotMarkers.materializedAt = options.state.cachedM0MaterializedAt ?? 0;
168621
170222
  const compartments = readM0Compartments(options.db, options.sessionId);
168622
- const memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
170223
+ const memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], snapshotMarkers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
168623
170224
  const userMemories = safeGetActiveUserMemories(options.db);
168624
170225
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168625
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
170226
+ const memoryRenderOptions = {
170227
+ sourceNameByMemoryId: sourceNamesForMemories({
170228
+ memories,
170229
+ projectPath,
170230
+ workspace
170231
+ })
170232
+ };
170233
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168626
170234
  const budget = options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
168627
170235
  let decayPressureMultiplier = 1;
168628
170236
  let m0Text = renderM0({
@@ -168631,6 +170239,7 @@ function renderFreshM0NonPersisted(options) {
168631
170239
  compartments,
168632
170240
  memories: trimmed.renderOrder,
168633
170241
  facts: [],
170242
+ memoryRenderOptions,
168634
170243
  historyBudgetTokens: budget,
168635
170244
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168636
170245
  decayPressureMultiplier
@@ -168644,6 +170253,7 @@ function renderFreshM0NonPersisted(options) {
168644
170253
  compartments,
168645
170254
  memories: trimmed.renderOrder,
168646
170255
  facts: [],
170256
+ memoryRenderOptions,
168647
170257
  historyBudgetTokens: budget,
168648
170258
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168649
170259
  decayPressureMultiplier
@@ -168659,6 +170269,12 @@ function renderFreshM0NonPersisted(options) {
168659
170269
  };
168660
170270
  }
168661
170271
  function injectM0M1(options) {
170272
+ if (!options.workspaceIdentitySet && options.projectPath) {
170273
+ options = {
170274
+ ...options,
170275
+ workspaceIdentitySet: resolveWorkspaceIdentitySet(options.db, options.projectPath)
170276
+ };
170277
+ }
168662
170278
  const skipped = {
168663
170279
  injected: false,
168664
170280
  m0RematerializedThisPass: false,
@@ -168675,7 +170291,8 @@ function injectM0M1(options) {
168675
170291
  state: options.state,
168676
170292
  projectPath: options.projectPath,
168677
170293
  projectDirectory: options.projectDirectory,
168678
- hardSignals: options.hardSignals
170294
+ hardSignals: options.hardSignals,
170295
+ workspaceIdentitySet: options.workspaceIdentitySet
168679
170296
  });
168680
170297
  let rematerialized = false;
168681
170298
  let contentionExhausted = false;
@@ -168769,6 +170386,7 @@ var init_inject_compartments = __esm(async () => {
168769
170386
  init_compartment_storage();
168770
170387
  init_constants();
168771
170388
  init_storage_memory();
170389
+ init_workspaces();
168772
170390
  init_logger();
168773
170391
  init_decay_render();
168774
170392
  init_key_files_block();
@@ -171908,18 +173526,38 @@ async function runCompartmentAgent(deps) {
171908
173526
  return;
171909
173527
  }
171910
173528
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
171911
- const boundarySnapshot = deps.boundarySnapshot ?? null;
173529
+ let boundarySnapshot = deps.boundarySnapshot ?? null;
171912
173530
  if (!boundarySnapshot) {
171913
173531
  telemetry.failureReason = "missing protected-tail boundary snapshot";
171914
173532
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from trigger decision");
171915
173533
  rollbackDrainReservation();
171916
173534
  return;
171917
173535
  }
171918
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
173536
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
171919
173537
  db,
171920
173538
  snapshot: boundarySnapshot,
171921
173539
  currentContextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit
171922
173540
  }) : { ok: true };
173541
+ if (!validation.ok && validation.reason === "stale_snapshot") {
173542
+ const refreshed = resolveOpenCodeProtectedTailBoundary({
173543
+ db,
173544
+ sessionId,
173545
+ mode: "incremental-runner",
173546
+ contextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit,
173547
+ executeThresholdPercentage: boundarySnapshot.executeThresholdPercentage,
173548
+ usage: {
173549
+ percentage: boundarySnapshot.usagePercentage,
173550
+ inputTokens: boundarySnapshot.usageInputTokens
173551
+ },
173552
+ usageSource: boundarySnapshot.usageSource,
173553
+ emergencyTailScale: boundarySnapshot.emergencyTailScale
173554
+ });
173555
+ if (hasRunnableCompartmentWindow(refreshed)) {
173556
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
173557
+ boundarySnapshot = refreshed;
173558
+ validation = { ok: true };
173559
+ }
173560
+ }
171923
173561
  if (!validation.ok) {
171924
173562
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
171925
173563
  telemetry.status = "noop";
@@ -171988,6 +173626,7 @@ async function runCompartmentAgent(deps) {
171988
173626
  rollbackDrainReservation();
171989
173627
  return;
171990
173628
  }
173629
+ deps.onHistorianRunStarted?.();
171991
173630
  const projectPath = resolveProjectIdentity(directory ?? process.cwd());
171992
173631
  const memories = getMemoriesByProject(db, projectPath, ["active", "permanent"]);
171993
173632
  const projectMemory = renderMemoryBlock(memories) ?? "";
@@ -172120,8 +173759,13 @@ ${chunkText}`,
172120
173759
  }
172121
173760
  if (embeddingActive) {
172122
173761
  const projectIdentity = resolveProjectIdentity(promotionDirectory);
172123
- const toEmbed = persistedCompartments.map((c, i) => ({ id: persistedIds[i], p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172124
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173762
+ const chunksToEmbed = persistedCompartments.map((c, i) => ({
173763
+ id: persistedIds[i],
173764
+ startMessage: c.startMessage,
173765
+ endMessage: c.endMessage,
173766
+ sourceChunkText: chunk.text
173767
+ })).filter((c) => typeof c.id === "number");
173768
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172125
173769
  }
172126
173770
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
172127
173771
  deps.onCompartmentStatePublished?.(sessionId);
@@ -172352,8 +173996,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
172352
173996
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172353
173997
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172354
173998
  const liveCompartments = getCompartments(db, sessionId);
172355
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172356
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173999
+ const chunksToEmbed = liveCompartments.map((c) => ({
174000
+ id: c.id,
174001
+ startMessage: c.startMessage,
174002
+ endMessage: c.endMessage
174003
+ }));
174004
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172357
174005
  }
172358
174006
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
172359
174007
  if (lastCompartmentEnd2 > 0) {
@@ -172566,8 +174214,12 @@ Another process acquired the compartment-state lease before recomp could publish
172566
174214
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172567
174215
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172568
174216
  const liveCompartments = getCompartments(db, sessionId);
172569
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172570
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
174217
+ const chunksToEmbed = liveCompartments.map((c) => ({
174218
+ id: c.id,
174219
+ startMessage: c.startMessage,
174220
+ endMessage: c.endMessage
174221
+ }));
174222
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172571
174223
  }
172572
174224
  if (lastCompartmentEnd > 0) {
172573
174225
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -172748,8 +174400,12 @@ Could not acquire the compartment-state lease for this session.`;
172748
174400
  if (deps.memoryEnabled !== false) {
172749
174401
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172750
174402
  const liveCompartments = getCompartments(db, sessionId);
172751
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172752
- Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed));
174403
+ const chunksToEmbed = liveCompartments.map((c) => ({
174404
+ id: c.id,
174405
+ startMessage: c.startMessage,
174406
+ endMessage: c.endMessage
174407
+ }));
174408
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
172753
174409
  }
172754
174410
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
172755
174411
  if (lastEnd > 0) {
@@ -174971,7 +176627,14 @@ function startCompartmentAgent(deps) {
174971
176627
  return;
174972
176628
  }
174973
176629
  const renewal = startLeaseRenewal(deps, holderId);
174974
- const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
176630
+ let realRunStarted = false;
176631
+ const runnerDeps = withPublishedCallback({
176632
+ ...deps,
176633
+ compartmentLeaseHolderId: holderId,
176634
+ onHistorianRunStarted: () => {
176635
+ realRunStarted = true;
176636
+ }
176637
+ });
174975
176638
  const promise2 = runCompartmentAgent(runnerDeps).catch((err) => {
174976
176639
  sessionLog(deps.sessionId, "compartment agent: unhandled rejection:", err);
174977
176640
  try {
@@ -174985,6 +176648,9 @@ function startCompartmentAgent(deps) {
174985
176648
  }
174986
176649
  });
174987
176650
  activeRuns.set(deps.sessionId, { promise: promise2, published: false });
176651
+ if (!realRunStarted && activeRuns.get(deps.sessionId)?.promise === promise2) {
176652
+ activeRuns.delete(deps.sessionId);
176653
+ }
174988
176654
  }
174989
176655
  async function executeContextRecompWithResult(deps, options = {}) {
174990
176656
  const { sessionId } = deps;
@@ -175119,7 +176785,7 @@ function applyMemoryMigration(db, projectPath, result) {
175119
176785
  inserted++;
175120
176786
  }
175121
176787
  if (removed > 0 || inserted > 0) {
175122
- bumpProjectMemoryEpoch(db, projectPath);
176788
+ bumpEpochsForWorkspaceMembers(db, projectPath);
175123
176789
  }
175124
176790
  })();
175125
176791
  return { removed, inserted };
@@ -175419,6 +177085,11 @@ async function runManagedRecomp(ctx, sessionId, options) {
175419
177085
  try {
175420
177086
  const message = await executeContextRecomp(buildRecompDeps(ctx, sessionId), options);
175421
177087
  const terminalPhase = isRecompSkip(message) ? "skipped" : isRecompFailure(message) ? "failed" : "done";
177088
+ if (terminalPhase === "done") {
177089
+ try {
177090
+ clearEmergencyRecovery(ctx.db, sessionId);
177091
+ } catch {}
177092
+ }
175422
177093
  setRecompTerminal(ctx.liveSessionState, sessionId, terminalPhase, extractRecompReason(message));
175423
177094
  return message;
175424
177095
  } catch (error51) {
@@ -175517,6 +177188,7 @@ var RECOMP_DONE_GRACE_MS = 30000;
175517
177188
  var init_recomp_orchestrator = __esm(async () => {
175518
177189
  init_compartment_storage();
175519
177190
  init_project_identity2();
177191
+ init_storage_meta_persisted();
175520
177192
  await __promiseAll([
175521
177193
  init_memory_migration(),
175522
177194
  init_compartment_runner()
@@ -175577,15 +177249,15 @@ function shouldShowAnnouncement() {
175577
177249
  }
175578
177250
  return state.version !== ANNOUNCEMENT_VERSION;
175579
177251
  }
175580
- var ANNOUNCEMENT_VERSION = "0.23.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
177252
+ var ANNOUNCEMENT_VERSION = "0.24.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
175581
177253
  var init_announcement = __esm(() => {
175582
177254
  init_data_path();
175583
177255
  ANNOUNCEMENT_FEATURES = [
175584
- "Smarter context nudges: gentle <system-reminder> notes on tool outputs replace the old chat nudgesquieter, cache-safe, and they now also help subagents manage their own context.",
175585
- "The agent can now maintain its own project memories: update, archive (batch), and merge the memories it sees not just write new ones. Memory categories are schema-enforced.",
175586
- "Big-session performance: historian trigger and token math moved off the database hot pathmulti-second stalls on large sessions are gone (measured 250ms → 2.4ms per pass).",
175587
- "History compaction unstuck for sparse sessions: the protected-tail boundary is now size-based, so sessions with few user turns compact reliably instead of growing forever (issue #132).",
175588
- "Fixed: session titles no longer fail to generate in fresh directories (issue #129), plus 20+ correctness fixes from three audit rounds."
177256
+ "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).",
177257
+ "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.",
177258
+ "Pi: fixed sessions overflowing the model context while still showing moderate usagePi now sheds context before a tool-heavy turn overflows.",
177259
+ "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
177260
+ "Setup wizard now lists your actual models with type-ahead instead of fixed recommendations, and explains the historian/dreamer roles (issue #144). Plus a GitHub Copilot tool-pairing fix (#135)."
175589
177261
  ];
175590
177262
  });
175591
177263
  // src/agents/permissions.ts
@@ -176313,6 +177985,10 @@ function getMagicContextBuiltinCommands() {
176313
177985
  "ctx-dream": {
176314
177986
  template: "ctx-dream",
176315
177987
  description: "Run the hidden dreamer maintenance pass for this project now"
177988
+ },
177989
+ "ctx-embed-history": {
177990
+ template: "ctx-embed-history",
177991
+ description: "Embed all of this session's history compartments for semantic search, in one pass"
176316
177992
  }
176317
177993
  };
176318
177994
  }
@@ -176806,7 +178482,7 @@ await init_storage_db();
176806
178482
  // src/features/magic-context/v22-deferred-backfill.ts
176807
178483
  init_logger();
176808
178484
  init_project_identity();
176809
- import { createHash as createHash5 } from "node:crypto";
178485
+ import { createHash as createHash6 } from "node:crypto";
176810
178486
  import { realpathSync as realpathSync2 } from "node:fs";
176811
178487
  import path5 from "node:path";
176812
178488
  var BATCH_SIZE = 25;
@@ -176860,7 +178536,7 @@ function computeLegacyRustDirIdentity(rawProjectPath) {
176860
178536
  } catch {
176861
178537
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
176862
178538
  }
176863
- return `dir:${createHash5("sha256").update(canonical, "utf8").digest("hex")}`;
178539
+ return `dir:${createHash6("sha256").update(canonical, "utf8").digest("hex")}`;
176864
178540
  }
176865
178541
  function upsertRekeyMap(db, oldProjectPath, newProjectPath, rekeyedAt) {
176866
178542
  db.prepare(`INSERT INTO v22_identity_rekey_map (old_project_path, new_project_path, rekeyed_at)
@@ -179401,7 +181077,7 @@ init_project_identity();
179401
181077
  // src/plugin/embedding-bootstrap-helpers.ts
179402
181078
  init_embedding();
179403
181079
  init_logger();
179404
- import { createHash as createHash8 } from "node:crypto";
181080
+ import { createHash as createHash10 } from "node:crypto";
179405
181081
  var EMBEDDING_AFFECTING_KEYS = new Set([
179406
181082
  "embedding.api_key",
179407
181083
  "embedding.endpoint",
@@ -179422,7 +181098,7 @@ var EMBEDDING_WARNING_TERMS = [
179422
181098
  ];
179423
181099
  var loggedFailureSignatures = new Map;
179424
181100
  function sha256Prefix2(value, length = 16) {
179425
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
181101
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
179426
181102
  }
179427
181103
  function warningLooksEmbeddingRelated(message) {
179428
181104
  const lower = message.toLowerCase();
@@ -180058,6 +181734,7 @@ function createTagger() {
180058
181734
  // src/hooks/magic-context/hook.ts
180059
181735
  init_magic_context();
180060
181736
  init_project_identity();
181737
+ init_project_embedding_registry();
180061
181738
  await init_storage();
180062
181739
  init_logger();
180063
181740
  init_resolve_fallbacks();
@@ -180651,6 +182328,7 @@ function createMagicContextCommandHandler(deps) {
180651
182328
  const isAugCommand = (command) => command === "ctx-aug";
180652
182329
  const isDreamCommand = (command) => command === "ctx-dream";
180653
182330
  const isSessionUpgradeCommand = (command) => command === "ctx-session-upgrade";
182331
+ const isEmbedHistoryCommand = (command) => command === "ctx-embed-history";
180654
182332
  return {
180655
182333
  "command.execute.before": async (input, _output, _params) => {
180656
182334
  const isStatus = isStatusCommand(input.command);
@@ -180659,7 +182337,8 @@ function createMagicContextCommandHandler(deps) {
180659
182337
  const isAug = isAugCommand(input.command);
180660
182338
  const isDream = isDreamCommand(input.command);
180661
182339
  const isSessionUpgrade = isSessionUpgradeCommand(input.command);
180662
- if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade) {
182340
+ const isEmbedHistory = isEmbedHistoryCommand(input.command);
182341
+ if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbedHistory) {
180663
182342
  return;
180664
182343
  }
180665
182344
  const sessionId = input.sessionID;
@@ -180672,6 +182351,11 @@ function createMagicContextCommandHandler(deps) {
180672
182351
  await executeDreaming(deps, sessionId);
180673
182352
  return;
180674
182353
  }
182354
+ if (isEmbedHistory) {
182355
+ const summary = deps.executeEmbedHistory ? await deps.executeEmbedHistory(sessionId) : "Semantic embedding is not configured for this project, so there is nothing to embed.";
182356
+ await deps.sendNotification(sessionId, summary, {});
182357
+ throwSentinel(input.command);
182358
+ }
180675
182359
  if (isFlush) {
180676
182360
  result = executeFlush(deps.db, sessionId);
180677
182361
  clearCachedM0M1(deps.db, sessionId);
@@ -181039,8 +182723,8 @@ var CHANNEL1_SENTINEL = "<system-reminder>";
181039
182723
  var TOKENS_PER_BYTE = 0.25;
181040
182724
  var CHANNEL1_FLOOR_TOKENS = 1e4;
181041
182725
  var CHANNEL1_REFIRE_FLOOR_TOKENS = 1e4;
181042
- function channel1RefireTokens(historyBudgetTokens) {
181043
- const scaled = Math.round(0.05 * Math.max(0, historyBudgetTokens));
182726
+ function channel1RefireTokens(workingWindowTokens) {
182727
+ const scaled = Math.round(0.05 * Math.max(0, workingWindowTokens));
181044
182728
  return Math.max(CHANNEL1_REFIRE_FLOOR_TOKENS, scaled);
181045
182729
  }
181046
182730
  var S_GENTLE = 0.2;
@@ -181110,7 +182794,7 @@ function computeTailTokenEstimate(messages) {
181110
182794
  };
181111
182795
  }
181112
182796
  function decideChannel1(input) {
181113
- const { undroppedTokens, pressure, historyBudgetTokens, hasRecentReduce } = input;
182797
+ const { undroppedTokens, pressure, workingWindowTokens, hasRecentReduce } = input;
181114
182798
  const resetCycle = hasRecentReduce || undroppedTokens < input.lastNudgeUndropped;
181115
182799
  const lastNudge = resetCycle ? 0 : input.lastNudgeUndropped;
181116
182800
  const lastLevel = resetCycle ? "" : input.lastNudgeLevel;
@@ -181125,7 +182809,7 @@ function decideChannel1(input) {
181125
182809
  return quiet();
181126
182810
  if (undroppedTokens < CHANNEL1_FLOOR_TOKENS)
181127
182811
  return quiet();
181128
- const budget = historyBudgetTokens > 0 ? historyBudgetTokens : undroppedTokens || 1;
182812
+ const budget = workingWindowTokens > 0 ? workingWindowTokens : undroppedTokens || 1;
181129
182813
  const severity = undroppedTokens / budget * pressure;
181130
182814
  if (severity < S_GENTLE)
181131
182815
  return quiet();
@@ -181137,7 +182821,7 @@ function decideChannel1(input) {
181137
182821
  else
181138
182822
  level = "gentle";
181139
182823
  if (lastLevel === "") {
181140
- if (undroppedTokens < lastNudge + channel1RefireTokens(historyBudgetTokens)) {
182824
+ if (undroppedTokens < lastNudge + channel1RefireTokens(workingWindowTokens)) {
181141
182825
  return quiet();
181142
182826
  }
181143
182827
  } else if (LEVEL_RANK[level] <= LEVEL_RANK[lastLevel]) {
@@ -181182,13 +182866,13 @@ function buildChannel1Reminder(level, undroppedTokens) {
181182
182866
  let body;
181183
182867
  switch (level) {
181184
182868
  case "gentle":
181185
- body = `You have ~${amount} tokens of tool output you have not reduced. ` + `Once you are done with earlier outputs, drop them with ctx_reduce to keep context lean.`;
182869
+ body = `You have ~${amount} tokens of tool output you have not reduced. ` + `When you are done with earlier outputs, dropping them with ctx_reduce keeps context lean.`;
181186
182870
  break;
181187
182871
  case "firm":
181188
- body = `~${amount} tokens of unreduced tool output is accumulating. ` + `Drop what you have already processed with ctx_reduce before continuing.`;
182872
+ body = `~${amount} tokens of unreduced tool output has built up. ` + `At your next natural stopping point, consider dropping what you have already processed with ctx_reduce.`;
181189
182873
  break;
181190
182874
  case "urgent":
181191
- body = `~${amount} tokens of unreduced tool output remain. ` + `A large span of this session will be comparted soon; drop spent outputs with ctx_reduce first so the archived span is the part that matters.`;
182875
+ body = `~${amount} tokens of unreduced tool output remain, and a large span of this session will be comparted before long. ` + `Consider dropping spent outputs with ctx_reduce so the archived span is the part that matters.`;
181192
182876
  break;
181193
182877
  }
181194
182878
  return `
@@ -181412,6 +183096,7 @@ await init_read_session_chunk();
181412
183096
  // src/hooks/magic-context/transform.ts
181413
183097
  init_project_identity();
181414
183098
  import * as crypto2 from "node:crypto";
183099
+ init_session_project_storage();
181415
183100
  init_storage_meta_persisted();
181416
183101
  init_logger();
181417
183102
  await init_storage();
@@ -182012,6 +183697,7 @@ await __promiseAll([
182012
183697
  init_read_session_chunk(),
182013
183698
  init_read_session_db()
182014
183699
  ]);
183700
+
182015
183701
  // src/hooks/magic-context/sentinel.ts
182016
183702
  var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
182017
183703
  function modelAcceptsEmptyContent(providerID) {
@@ -182069,7 +183755,6 @@ function replaySentinelByMessageIds(messages, ids, providerID) {
182069
183755
  missingIds.push(id);
182070
183756
  return { replayed, missingIds };
182071
183757
  }
182072
-
182073
183758
  // src/hooks/magic-context/strip-content.ts
182074
183759
  var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped §\d+§\]$/;
182075
183760
  var TAG_PREFIX_PATTERN = /^§\d+§\s*/;
@@ -182422,8 +184107,10 @@ function stripReasoningFromMergedAssistants(messages, providerID) {
182422
184107
  }
182423
184108
  return stripped;
182424
184109
  }
182425
- function stripProcessedImages(messages, watermark, messageTagNumbers) {
184110
+ function stripProcessedImages(messages, frozenIds, options) {
184111
+ const { detect, watermark, messageTagNumbers } = options;
182426
184112
  let stripped = 0;
184113
+ const newlyStrippedIds = [];
182427
184114
  let hasAssistantResponse = false;
182428
184115
  for (let i = messages.length - 1;i >= 0; i--) {
182429
184116
  const msg = messages[i];
@@ -182431,13 +184118,17 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
182431
184118
  hasAssistantResponse = true;
182432
184119
  continue;
182433
184120
  }
182434
- if (msg.info.role !== "user" || !hasAssistantResponse) {
184121
+ if (msg.info.role !== "user") {
182435
184122
  continue;
182436
184123
  }
184124
+ const id = typeof msg.info.id === "string" ? msg.info.id : undefined;
184125
+ const inFrozen = id !== undefined && frozenIds.has(id);
182437
184126
  const maxTag = messageTagNumbers.get(msg) ?? 0;
182438
- if (maxTag > watermark) {
184127
+ const isNewDetection = !inFrozen && detect && hasAssistantResponse && id !== undefined && maxTag <= watermark;
184128
+ if (!inFrozen && !isNewDetection) {
182439
184129
  continue;
182440
184130
  }
184131
+ let touchedThisMsg = false;
182441
184132
  for (let j = 0;j < msg.parts.length; j++) {
182442
184133
  const part = msg.parts[j];
182443
184134
  if (!isRecord(part) || part.type !== "file") {
@@ -182449,10 +184140,14 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
182449
184140
  if (typeof part.url === "string" && part.url.startsWith("data:") && part.url.length > 200) {
182450
184141
  msg.parts[j] = makeSentinel(part);
182451
184142
  stripped++;
184143
+ touchedThisMsg = true;
182452
184144
  }
182453
184145
  }
184146
+ if (touchedThisMsg && isNewDetection && id !== undefined) {
184147
+ newlyStrippedIds.push(id);
184148
+ }
182454
184149
  }
182455
- return stripped;
184150
+ return { stripped, newlyStrippedIds };
182456
184151
  }
182457
184152
 
182458
184153
  // src/hooks/magic-context/transform.ts
@@ -182765,8 +184460,55 @@ function appendReminderToUserMessage(message, reminder) {
182765
184460
  }
182766
184461
 
182767
184462
  // src/hooks/magic-context/apply-operations.ts
182768
- init_tag_part_guards();
182769
184463
  await init_storage();
184464
+
184465
+ // src/hooks/magic-context/system-injection-stripper.ts
184466
+ var SYSTEM_INJECTION_MARKERS = [
184467
+ "<!-- OMO_INTERNAL_INITIATOR -->",
184468
+ "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
184469
+ "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
184470
+ "[Category+Skill Reminder]",
184471
+ "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
184472
+ "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
184473
+ "[EMERGENCY CONTEXT WINDOW WARNING]",
184474
+ "Unstable background agent appears idle",
184475
+ "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
184476
+ ];
184477
+ var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
184478
+ var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
184479
+ function stripSystemInjection(text) {
184480
+ let hasInjection = false;
184481
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
184482
+ if (text.includes(marker)) {
184483
+ hasInjection = true;
184484
+ break;
184485
+ }
184486
+ }
184487
+ if (SYSTEM_REMINDER_REGEX.test(text))
184488
+ hasInjection = true;
184489
+ SYSTEM_REMINDER_REGEX.lastIndex = 0;
184490
+ if (!hasInjection)
184491
+ return null;
184492
+ let cleaned = text;
184493
+ cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
184494
+ cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
184495
+ cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
184496
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
184497
+ if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
184498
+ continue;
184499
+ const idx = cleaned.indexOf(marker);
184500
+ if (idx === -1)
184501
+ continue;
184502
+ const blockEnd = cleaned.indexOf(`
184503
+
184504
+ `, idx + marker.length);
184505
+ cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
184506
+ }
184507
+ return cleaned.trim();
184508
+ }
184509
+
184510
+ // src/hooks/magic-context/apply-operations.ts
184511
+ init_tag_part_guards();
182770
184512
  var USER_DROP_PREVIEW_CHARS = 250;
182771
184513
  var RECENT_TOOL_SKELETON_WINDOW = 20;
182772
184514
  function buildReplacementContent(tagId, target) {
@@ -182775,6 +184517,10 @@ function buildReplacementContent(tagId, target) {
182775
184517
  return `[dropped §${tagId}§]`;
182776
184518
  }
182777
184519
  const currentContent = target.getContent?.() ?? "";
184520
+ const strippedInjection = stripSystemInjection(currentContent);
184521
+ if (strippedInjection !== null && stripTagPrefix(strippedInjection).trim().length === 0) {
184522
+ return `[dropped §${tagId}§]`;
184523
+ }
182778
184524
  const originalText = stripTagPrefix(currentContent);
182779
184525
  if (originalText.length <= USER_DROP_PREVIEW_CHARS) {
182780
184526
  return `[truncated §${tagId}§]
@@ -183395,6 +185141,7 @@ init_embedding();
183395
185141
 
183396
185142
  // src/features/magic-context/search.ts
183397
185143
  init_logger();
185144
+ init_compartment_chunk_embedding();
183398
185145
 
183399
185146
  // src/features/magic-context/literal-probes.ts
183400
185147
  var MAX_PROBES = 5;
@@ -183456,6 +185203,7 @@ function containsProbeVerbatim(text, probes) {
183456
185203
  init_memory();
183457
185204
  init_embedding();
183458
185205
  init_storage_memory_fts();
185206
+ init_workspaces();
183459
185207
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
183460
185208
  var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
183461
185209
  var SEMANTIC_WEIGHT = 0.7;
@@ -183485,6 +185233,37 @@ function previewText(text) {
183485
185233
  }
183486
185234
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
183487
185235
  }
185236
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
185237
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
185238
+ const isWorkspaced = resolved.identities.length > 1;
185239
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
185240
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
185241
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
185242
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
185243
+ return {
185244
+ identities: resolved.identities,
185245
+ expandedIdentities,
185246
+ ownIdentities,
185247
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
185248
+ namesByIdentity: resolved.namesByIdentity,
185249
+ canonicalIdentityByStoredPath,
185250
+ isWorkspaced
185251
+ };
185252
+ }
185253
+ function memoryWorkspaceIdentity(memory, workspace) {
185254
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
185255
+ }
185256
+ function sourceNamesForSearchMemories(args) {
185257
+ if (!args.workspace.isWorkspaced)
185258
+ return;
185259
+ const sourceNames = new Map;
185260
+ for (const memory of args.memories) {
185261
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
185262
+ if (source)
185263
+ sourceNames.set(memory.id, source);
185264
+ }
185265
+ return sourceNames.size > 0 ? sourceNames : undefined;
185266
+ }
183488
185267
  function getMessageSearchStatement(db) {
183489
185268
  let stmt = messageSearchStatements.get(db);
183490
185269
  if (!stmt) {
@@ -183493,6 +185272,30 @@ function getMessageSearchStatement(db) {
183493
185272
  }
183494
185273
  return stmt;
183495
185274
  }
185275
+ var ftsRowCountStatements = new WeakMap;
185276
+ var ftsMatchCountStatements = new WeakMap;
185277
+ function getSessionFtsRowCount(db, sessionId) {
185278
+ let stmt = ftsRowCountStatements.get(db);
185279
+ if (!stmt) {
185280
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
185281
+ ftsRowCountStatements.set(db, stmt);
185282
+ }
185283
+ const row = stmt.get(sessionId);
185284
+ return typeof row?.n === "number" ? row.n : 0;
185285
+ }
185286
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
185287
+ let stmt = ftsMatchCountStatements.get(db);
185288
+ if (!stmt) {
185289
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
185290
+ ftsMatchCountStatements.set(db, stmt);
185291
+ }
185292
+ try {
185293
+ const row = stmt.get(sessionId, ftsQuery);
185294
+ return typeof row?.n === "number" ? row.n : 0;
185295
+ } catch {
185296
+ return 0;
185297
+ }
185298
+ }
183496
185299
  function getMessageOrdinal(value) {
183497
185300
  if (typeof value === "number" && Number.isFinite(value)) {
183498
185301
  return value;
@@ -183508,25 +185311,63 @@ async function getSemanticScores(args) {
183508
185311
  if (!args.queryEmbedding || args.memories.length === 0) {
183509
185312
  return semanticScores;
183510
185313
  }
183511
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
183512
- const embeddings = await ensureMemoryEmbeddings({
183513
- db: args.db,
183514
- projectIdentity: args.projectPath,
183515
- memories: args.memories,
183516
- existingEmbeddings: cachedEmbeddings
183517
- });
185314
+ if (!args.workspace?.isWorkspaced) {
185315
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185316
+ const embeddings = await ensureMemoryEmbeddings({
185317
+ db: args.db,
185318
+ projectIdentity: args.projectPath,
185319
+ memories: args.memories,
185320
+ existingEmbeddings: cachedEmbeddings
185321
+ });
185322
+ for (const memory of args.memories) {
185323
+ const memoryEmbedding = embeddings.get(memory.id);
185324
+ if (!memoryEmbedding) {
185325
+ continue;
185326
+ }
185327
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
185328
+ }
185329
+ return semanticScores;
185330
+ }
185331
+ if (!args.queryModelId || args.queryModelId === "off") {
185332
+ return semanticScores;
185333
+ }
185334
+ const workspace = args.workspace;
185335
+ const memoriesByIdentity = new Map;
183518
185336
  for (const memory of args.memories) {
183519
- const memoryEmbedding = embeddings.get(memory.id);
183520
- if (!memoryEmbedding) {
185337
+ const identity = memoryWorkspaceIdentity(memory, workspace);
185338
+ if (!identity)
185339
+ continue;
185340
+ const list = memoriesByIdentity.get(identity) ?? [];
185341
+ list.push(memory);
185342
+ memoriesByIdentity.set(identity, list);
185343
+ }
185344
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
185345
+ if (ownMemories.length > 0) {
185346
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185347
+ await ensureMemoryEmbeddings({
185348
+ db: args.db,
185349
+ projectIdentity: args.projectPath,
185350
+ memories: ownMemories,
185351
+ existingEmbeddings: ownEmbeddings
185352
+ });
185353
+ }
185354
+ for (const identity of workspace.identities) {
185355
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
185356
+ if (memberMemories.length === 0)
183521
185357
  continue;
185358
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
185359
+ for (const memory of memberMemories) {
185360
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
185361
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
185362
+ continue;
185363
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
183522
185364
  }
183523
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
183524
185365
  }
183525
185366
  return semanticScores;
183526
185367
  }
183527
185368
  function getFtsMatches(args) {
183528
185369
  try {
183529
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
185370
+ return args.workspace?.isWorkspaced ? searchMemoriesFTSUnion(args.db, args.workspace.expandedIdentities, args.query, args.limit, args.workspace.ownIdentities, args.workspace.shareCategories) : searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
183530
185371
  } catch (error51) {
183531
185372
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
183532
185373
  return [];
@@ -183540,8 +185381,11 @@ function selectSemanticCandidates(args) {
183540
185381
  return args.memories;
183541
185382
  }
183542
185383
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
183543
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
183544
- if (cachedEmbeddings) {
185384
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
185385
+ for (const projectPath of embeddingProjects) {
185386
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
185387
+ if (!cachedEmbeddings)
185388
+ continue;
183545
185389
  for (const memoryId of cachedEmbeddings.keys()) {
183546
185390
  candidateIds.add(memoryId);
183547
185391
  }
@@ -183583,7 +185427,8 @@ function mergeMemoryResults(args) {
183583
185427
  score,
183584
185428
  memoryId: memory.id,
183585
185429
  category: memory.category,
183586
- matchType
185430
+ matchType,
185431
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
183587
185432
  });
183588
185433
  }
183589
185434
  return results.sort((left, right) => {
@@ -183597,7 +185442,7 @@ async function searchMemories(args) {
183597
185442
  if (!args.memoryEnabled) {
183598
185443
  return [];
183599
185444
  }
183600
- const memories = getMemoriesByProject(args.db, args.projectPath);
185445
+ const memories = args.workspace?.isWorkspaced ? getMemoriesByProjects(args.db, args.workspace.expandedIdentities, ["active", "permanent"], Date.now(), args.workspace.ownIdentities, args.workspace.shareCategories) : getMemoriesByProject(args.db, args.projectPath);
183601
185446
  if (memories.length === 0) {
183602
185447
  return [];
183603
185448
  }
@@ -183605,26 +185450,43 @@ async function searchMemories(args) {
183605
185450
  db: args.db,
183606
185451
  projectPath: args.projectPath,
183607
185452
  query: args.query,
183608
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
185453
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
185454
+ workspace: args.workspace
183609
185455
  });
183610
185456
  const ftsScores = getFtsScores(ftsMatches);
183611
185457
  const semanticCandidates = selectSemanticCandidates({
183612
185458
  memories,
183613
185459
  projectPath: args.projectPath,
183614
- ftsMatches
185460
+ ftsMatches,
185461
+ workspace: args.workspace
183615
185462
  });
183616
185463
  const semanticScores = await getSemanticScores({
183617
185464
  db: args.db,
183618
185465
  projectPath: args.projectPath,
183619
185466
  memories: semanticCandidates,
183620
- queryEmbedding: args.queryEmbedding
185467
+ queryEmbedding: args.queryEmbedding,
185468
+ queryModelId: args.queryModelId,
185469
+ workspace: args.workspace
183621
185470
  });
183622
185471
  return mergeMemoryResults({
183623
185472
  memories,
183624
185473
  semanticScores,
183625
185474
  ftsScores,
183626
185475
  limit: args.limit,
183627
- visibleMemoryIds: args.visibleMemoryIds
185476
+ visibleMemoryIds: args.visibleMemoryIds,
185477
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
185478
+ memories,
185479
+ projectPath: args.projectPath,
185480
+ workspace: args.workspace ?? {
185481
+ identities: [args.projectPath],
185482
+ expandedIdentities: [args.projectPath],
185483
+ namesByIdentity: new Map,
185484
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
185485
+ ownIdentities: [args.projectPath],
185486
+ shareCategories: null,
185487
+ isWorkspaced: false
185488
+ }
185489
+ })
183628
185490
  });
183629
185491
  }
183630
185492
  function linearDecayScore(rank, total) {
@@ -183655,7 +185517,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
183655
185517
  return result;
183656
185518
  }
183657
185519
  var RRF_K = 60;
183658
- var VERBATIM_PROBE_BONUS = 0.5;
185520
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
185521
+ var IDF_FALLOFF = 100;
185522
+ function probeDiscriminationWeight(df, corpusSize) {
185523
+ if (corpusSize <= 0 || df <= 0)
185524
+ return 1;
185525
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
185526
+ }
183659
185527
  function searchMessages(args) {
183660
185528
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
183661
185529
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -183672,20 +185540,31 @@ function searchMessages(args) {
183672
185540
  role: row.role
183673
185541
  }));
183674
185542
  }
185543
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
183675
185544
  const queryLists = [];
183676
185545
  if (baseQuery.length > 0) {
183677
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
185546
+ queryLists.push({
185547
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
185548
+ weight: 1
185549
+ });
183678
185550
  }
185551
+ const probeWeights = new Map;
183679
185552
  for (const probe of probes) {
183680
185553
  const probeQuery = sanitizeFtsQuery(probe);
183681
185554
  if (probeQuery.length === 0)
183682
185555
  continue;
183683
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
185556
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
185557
+ const weight = probeDiscriminationWeight(df, corpusSize);
185558
+ probeWeights.set(probe, weight);
185559
+ queryLists.push({
185560
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
185561
+ weight
185562
+ });
183684
185563
  }
183685
185564
  const fused = new Map;
183686
185565
  for (const list of queryLists) {
183687
- list.forEach((row, rank) => {
183688
- const rrf = 1 / (RRF_K + rank);
185566
+ list.rows.forEach((row, rank) => {
185567
+ const rrf = list.weight / (RRF_K + rank);
183689
185568
  const existing = fused.get(row.messageId);
183690
185569
  if (existing) {
183691
185570
  existing.score += rrf;
@@ -183695,26 +185574,107 @@ function searchMessages(args) {
183695
185574
  });
183696
185575
  }
183697
185576
  for (const entry of fused.values()) {
183698
- if (containsProbeVerbatim(entry.row.content, probes)) {
183699
- entry.score += VERBATIM_PROBE_BONUS;
185577
+ let best = 0;
185578
+ for (const probe of probes) {
185579
+ const weight = probeWeights.get(probe) ?? 0;
185580
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
185581
+ best = weight;
185582
+ }
185583
+ }
185584
+ if (best > 0) {
185585
+ entry.score += best * VERBATIM_RANK_BONUS;
183700
185586
  }
183701
185587
  }
183702
185588
  const ranked = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.row.messageOrdinal - b.row.messageOrdinal).slice(0, args.limit);
183703
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
183704
- return ranked.map((entry) => ({
185589
+ return ranked.map((entry, rank) => ({
183705
185590
  source: "message",
183706
185591
  content: previewText(entry.row.content),
183707
- score: maxScore > 0 ? entry.score / maxScore : 0,
185592
+ score: linearDecayScore(rank, ranked.length),
183708
185593
  messageOrdinal: entry.row.messageOrdinal,
183709
185594
  messageId: entry.row.messageId,
183710
185595
  role: entry.row.role
183711
185596
  }));
183712
185597
  }
185598
+ function searchCompartmentChunks(args) {
185599
+ if (!args.queryEmbedding || args.limit <= 0)
185600
+ return [];
185601
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
185602
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
185603
+ if (rows.length === 0)
185604
+ return [];
185605
+ const byCompartment = new Map;
185606
+ for (const row of rows) {
185607
+ if (cutoff !== null && row.endOrdinal > cutoff) {
185608
+ continue;
185609
+ }
185610
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
185611
+ if (score <= 0)
185612
+ continue;
185613
+ const existing = byCompartment.get(row.compartmentId);
185614
+ if (!existing || score > existing.score) {
185615
+ byCompartment.set(row.compartmentId, { row, score });
185616
+ }
185617
+ }
185618
+ 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 }) => ({
185619
+ source: "compartment",
185620
+ content: previewText(row.title),
185621
+ score: score * SINGLE_SOURCE_PENALTY,
185622
+ compartmentId: row.compartmentId,
185623
+ sessionId: row.sessionId,
185624
+ title: row.title,
185625
+ startOrdinal: row.startOrdinal,
185626
+ endOrdinal: row.endOrdinal,
185627
+ matchType: "semantic"
185628
+ }));
185629
+ }
185630
+ function mergeMessageAndCompartmentResults(args) {
185631
+ if (args.compartments.length === 0)
185632
+ return args.messages;
185633
+ if (args.messages.length === 0)
185634
+ return args.compartments;
185635
+ const fused = new Map;
185636
+ const add = (key, result, score, tieOrdinal) => {
185637
+ const existing = fused.get(key);
185638
+ if (existing) {
185639
+ existing.score += score;
185640
+ return existing;
185641
+ }
185642
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
185643
+ fused.set(key, entry);
185644
+ return entry;
185645
+ };
185646
+ args.compartments.forEach((compartment, rank) => {
185647
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
185648
+ });
185649
+ for (const [rank, message] of args.messages.entries()) {
185650
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
185651
+ const contribution = 1 / (RRF_K + rank);
185652
+ if (!containing) {
185653
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
185654
+ continue;
185655
+ }
185656
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
185657
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
185658
+ entry.snippetScore = message.score;
185659
+ entry.result = {
185660
+ ...entry.result,
185661
+ matchType: "hybrid",
185662
+ snippet: message.content
185663
+ };
185664
+ }
185665
+ }
185666
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
185667
+ return ranked.map((entry, rank) => ({
185668
+ ...entry.result,
185669
+ score: linearDecayScore(rank, ranked.length)
185670
+ }));
185671
+ }
183713
185672
  function getSourceBoost(result) {
183714
185673
  switch (result.source) {
183715
185674
  case "memory":
183716
185675
  return MEMORY_SOURCE_BOOST;
183717
185676
  case "message":
185677
+ case "compartment":
183718
185678
  return MESSAGE_SOURCE_BOOST;
183719
185679
  case "git_commit":
183720
185680
  return GIT_COMMIT_SOURCE_BOOST;
@@ -183732,6 +185692,9 @@ function compareUnifiedResults(left, right) {
183732
185692
  if (left.source === "message" && right.source === "message") {
183733
185693
  return left.messageOrdinal - right.messageOrdinal;
183734
185694
  }
185695
+ if (left.source === "compartment" && right.source === "compartment") {
185696
+ return left.startOrdinal - right.startOrdinal;
185697
+ }
183735
185698
  if (left.source === "git_commit" && right.source === "git_commit") {
183736
185699
  return right.committedAtMs - left.committedAtMs;
183737
185700
  }
@@ -183782,10 +185745,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183782
185745
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
183783
185746
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
183784
185747
  const activeSources = resolveSources(options.sources);
183785
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
185748
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
185749
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
183786
185750
  const runMessages = activeSources.has("message");
183787
185751
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
183788
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
185752
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
185753
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
183789
185754
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
183790
185755
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
183791
185756
  return null;
@@ -183801,6 +185766,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183801
185766
  probes: messageProbes
183802
185767
  }) : [];
183803
185768
  const queryEmbedding = await queryEmbeddingPromise;
185769
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
185770
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
185771
+ const embeddingModelId = embeddingSnapshot?.modelId;
185772
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
185773
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
185774
+ db,
185775
+ sessionId,
185776
+ projectPath,
185777
+ queryEmbedding,
185778
+ limit: tierLimit,
185779
+ maxOrdinal: options.maxMessageOrdinal,
185780
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
185781
+ }) : [];
185782
+ const messageLikeResults = mergeMessageAndCompartmentResults({
185783
+ messages: messageResults,
185784
+ compartments: compartmentResults,
185785
+ limit: tierLimit
185786
+ });
183804
185787
  const [memoryResults, gitCommitResults] = await Promise.all([
183805
185788
  runMemory ? searchMemories({
183806
185789
  db,
@@ -183809,6 +185792,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183809
185792
  limit: tierLimit,
183810
185793
  memoryEnabled: true,
183811
185794
  queryEmbedding,
185795
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
185796
+ workspace,
183812
185797
  visibleMemoryIds: options.visibleMemoryIds
183813
185798
  }) : Promise.resolve([]),
183814
185799
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -183819,7 +185804,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183819
185804
  queryEmbedding
183820
185805
  })) : Promise.resolve([])
183821
185806
  ]);
183822
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
185807
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
183823
185808
  const countRetrievals = options.countRetrievals ?? true;
183824
185809
  if (countRetrievals) {
183825
185810
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -183883,6 +185868,11 @@ function renderFragment(result, charCap) {
183883
185868
  const compressed = cavemanCompress(result.content, "ultra");
183884
185869
  return truncate(compressed, charCap);
183885
185870
  }
185871
+ case "compartment": {
185872
+ const source = result.snippet ?? result.title;
185873
+ const compressed = cavemanCompress(source, "ultra");
185874
+ return truncate(compressed, charCap);
185875
+ }
183886
185876
  }
183887
185877
  }
183888
185878
  function buildAutoSearchHint(results, options = {}) {
@@ -184237,51 +186227,6 @@ function planEmergencyDrop(input) {
184237
186227
  };
184238
186228
  }
184239
186229
 
184240
- // src/hooks/magic-context/system-injection-stripper.ts
184241
- var SYSTEM_INJECTION_MARKERS = [
184242
- "<!-- OMO_INTERNAL_INITIATOR -->",
184243
- "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
184244
- "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
184245
- "[Category+Skill Reminder]",
184246
- "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
184247
- "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
184248
- "[EMERGENCY CONTEXT WINDOW WARNING]",
184249
- "Unstable background agent appears idle",
184250
- "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
184251
- ];
184252
- var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
184253
- var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
184254
- function stripSystemInjection(text) {
184255
- let hasInjection = false;
184256
- for (const marker of SYSTEM_INJECTION_MARKERS) {
184257
- if (text.includes(marker)) {
184258
- hasInjection = true;
184259
- break;
184260
- }
184261
- }
184262
- if (SYSTEM_REMINDER_REGEX.test(text))
184263
- hasInjection = true;
184264
- SYSTEM_REMINDER_REGEX.lastIndex = 0;
184265
- if (!hasInjection)
184266
- return null;
184267
- let cleaned = text;
184268
- cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
184269
- cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
184270
- cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
184271
- for (const marker of SYSTEM_INJECTION_MARKERS) {
184272
- if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
184273
- continue;
184274
- const idx = cleaned.indexOf(marker);
184275
- if (idx === -1)
184276
- continue;
184277
- const blockEnd = cleaned.indexOf(`
184278
-
184279
- `, idx + marker.length);
184280
- cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
184281
- }
184282
- return cleaned.trim();
184283
- }
184284
-
184285
186230
  // src/hooks/magic-context/heuristic-cleanup.ts
184286
186231
  init_tag_part_guards();
184287
186232
  var DEDUP_SAFE_TOOLS = new Set([
@@ -184523,7 +186468,7 @@ function isVisibleNoteReadPart(part) {
184523
186468
  }
184524
186469
 
184525
186470
  // src/hooks/magic-context/todo-view.ts
184526
- import { createHash as createHash9 } from "node:crypto";
186471
+ import { createHash as createHash11 } from "node:crypto";
184527
186472
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
184528
186473
  var TITLE_DONE_STATUSES = new Set(["completed"]);
184529
186474
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -184568,7 +186513,7 @@ function buildSyntheticTodoPart(stateJson) {
184568
186513
  };
184569
186514
  }
184570
186515
  function computeSyntheticCallId(stateJson) {
184571
- const hash2 = createHash9("sha256").update(stateJson).digest("hex").slice(0, 16);
186516
+ const hash2 = createHash11("sha256").update(stateJson).digest("hex").slice(0, 16);
184572
186517
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
184573
186518
  }
184574
186519
  function parseTodoState(stateJson) {
@@ -184619,15 +186564,25 @@ async function runPostTransformPhase(args) {
184619
186564
  const emergencyBypassCompartmentGate = forceMaterialization;
184620
186565
  const deferredMaterialize = args.canConsumeDeferredLate && deferredMaterializationWasPending;
184621
186566
  const materializationRequested = isExplicitFlush || deferredMaterialize;
184622
- const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
186567
+ const m0M1EnabledForFold = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
186568
+ const m0HardFoldThisPass = m0M1EnabledForFold && args.m0M1 ? mustMaterialize({
186569
+ db: args.db,
186570
+ sessionId: args.sessionId,
186571
+ state: args.sessionMeta,
186572
+ projectPath: args.m0M1.projectPath,
186573
+ projectDirectory: args.m0M1.projectDirectory,
186574
+ hardSignals: args.m0M1.hardSignals
186575
+ }).value : false;
186576
+ const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || m0HardFoldThisPass || compartmentRunning;
184623
186577
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
184624
186578
  const hasPendingUserOps = pendingOps.length > 0;
184625
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
184626
- const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
186579
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || m0HardFoldThisPass) && (!compartmentRunning || emergencyBypassCompartmentGate);
186580
+ const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || m0HardFoldThisPass || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
184627
186581
  const isCacheBustingPass = shouldApplyPendingOps || shouldRunHeuristics;
186582
+ const canUseEmptySentinels = modelAcceptsEmptyContent(args.resolvedProviderID);
184628
186583
  if (shouldRunHeuristics) {
184629
186584
  const subagentRerun = !args.fullFeatureMode && alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && !forceMaterialization;
184630
- const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
186585
+ const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : m0HardFoldThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into known m[0] bust, scheduler=${args.schedulerDecision})` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
184631
186586
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
184632
186587
  }
184633
186588
  if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !materializationRequested && args.fullFeatureMode) {
@@ -184670,7 +186625,9 @@ async function runPostTransformPhase(args) {
184670
186625
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags}`);
184671
186626
  const t7 = performance.now();
184672
186627
  const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
184673
- stripClearedReasoning(args.messages);
186628
+ if (canUseEmptySentinels) {
186629
+ stripClearedReasoning(args.messages);
186630
+ }
184674
186631
  const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
184675
186632
  if (clearedReasoning > 0 || strippedInline > 0) {
184676
186633
  let maxTag = 0;
@@ -184716,7 +186673,6 @@ async function runPostTransformPhase(args) {
184716
186673
  if (args.watermark > 0) {
184717
186674
  const tWatermarkCleanup = performance.now();
184718
186675
  truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
184719
- stripProcessedImages(args.messages, args.watermark, args.messageTagNumbers);
184720
186676
  logTransformTiming(args.sessionId, "watermarkCleanup", tWatermarkCleanup);
184721
186677
  }
184722
186678
  if (shouldApplyPendingOps) {
@@ -184726,21 +186682,40 @@ async function runPostTransformPhase(args) {
184726
186682
  sessionLog(args.sessionId, "transform failed applying pending operations:", error51);
184727
186683
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: getErrorMessage(error51) });
184728
186684
  }
184729
- try {
184730
- const t8 = performance.now();
184731
- const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
184732
- const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
184733
- detect: isCacheBustingPass,
184734
- protectedCount: args.protectedTags
184735
- });
184736
- if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
184737
- addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
186685
+ if (canUseEmptySentinels) {
186686
+ try {
186687
+ const t8 = performance.now();
186688
+ const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
186689
+ const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
186690
+ detect: isCacheBustingPass,
186691
+ protectedCount: args.protectedTags
186692
+ });
186693
+ if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
186694
+ addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
186695
+ }
186696
+ logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
186697
+ } catch (error51) {
186698
+ sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
184738
186699
  }
184739
- logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
184740
- } catch (error51) {
184741
- sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
184742
186700
  }
184743
- const m0M1Enabled = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
186701
+ if (canUseEmptySentinels && args.watermark > 0) {
186702
+ try {
186703
+ const tImg = performance.now();
186704
+ const frozenImageIds = getProcessedImageStrippedIds(args.db, args.sessionId);
186705
+ const imageResult = stripProcessedImages(args.messages, frozenImageIds, {
186706
+ detect: isCacheBustingPass,
186707
+ watermark: args.watermark,
186708
+ messageTagNumbers: args.messageTagNumbers
186709
+ });
186710
+ if (isCacheBustingPass && imageResult.newlyStrippedIds.length > 0) {
186711
+ addProcessedImageStrippedIds(args.db, args.sessionId, imageResult.newlyStrippedIds);
186712
+ }
186713
+ logTransformTiming(args.sessionId, "stripProcessedImages", tImg);
186714
+ } catch (error51) {
186715
+ sessionLog(args.sessionId, "transform failed stripping processed images:", error51);
186716
+ }
186717
+ }
186718
+ const m0M1Enabled = m0M1EnabledForFold;
184744
186719
  if (m0M1Enabled && args.m0M1) {
184745
186720
  const tInjectM0M1 = performance.now();
184746
186721
  try {
@@ -184787,7 +186762,7 @@ async function runPostTransformPhase(args) {
184787
186762
  const tPlaceholder = performance.now();
184788
186763
  const persistedIds = getStrippedPlaceholderIds(args.db, args.sessionId);
184789
186764
  if (persistedIds.size > 0) {
184790
- const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.liveProviderID);
186765
+ const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.resolvedProviderID);
184791
186766
  if (replayed > 0) {
184792
186767
  sessionLog(args.sessionId, `sentinel replay: neutralized ${replayed} previously-stripped messages`);
184793
186768
  }
@@ -184798,9 +186773,9 @@ async function runPostTransformPhase(args) {
184798
186773
  }
184799
186774
  }
184800
186775
  if (isCacheBustingPass) {
184801
- const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.liveProviderID);
186776
+ const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.resolvedProviderID);
184802
186777
  const protectedTailStart = Math.max(0, args.messages.length - args.protectedTags * 2);
184803
- const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.liveProviderID);
186778
+ const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.resolvedProviderID);
184804
186779
  const newlyNeutralized = droppedResult.sentineledIds.length + systemInjectedResult.sentineledIds.length;
184805
186780
  if (newlyNeutralized > 0) {
184806
186781
  const addedIds = [
@@ -185000,6 +186975,7 @@ function clearMessageTokensCache(sessionId, messageId) {
185000
186975
  if (cache)
185001
186976
  cache.delete(messageId);
185002
186977
  }
186978
+ var recordedSessionProjectIdentity = new BoundedSessionMap(MESSAGE_TOKENS_CACHE_MAX);
185003
186979
  function findLastAssistantModel2(messages) {
185004
186980
  for (let i = messages.length - 1;i >= 0; i--) {
185005
186981
  const info = messages[i].info;
@@ -185047,9 +187023,11 @@ function createTransform(deps) {
185047
187023
  const fullFeatureMode = !reducedMode;
185048
187024
  const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false && resolveCtxReduceAvailabilityFromMessages(sessionId, messages);
185049
187025
  let sessionDirectory = deps.directory ?? "";
187026
+ let sessionDirectoryResolvedFromHost = false;
185050
187027
  const cachedDirectory = deps.sessionDirectoryBySession?.get(sessionId);
185051
187028
  if (cachedDirectory && cachedDirectory.length > 0) {
185052
187029
  sessionDirectory = cachedDirectory;
187030
+ sessionDirectoryResolvedFromHost = true;
185053
187031
  } else if (deps.client !== undefined) {
185054
187032
  try {
185055
187033
  const sessionResponse = await deps.client.session.get({ path: { id: sessionId } }).catch(() => null);
@@ -185057,6 +187035,7 @@ function createTransform(deps) {
185057
187035
  if (sessionInfo && typeof sessionInfo.directory === "string" && sessionInfo.directory.length > 0) {
185058
187036
  sessionDirectory = sessionInfo.directory;
185059
187037
  deps.sessionDirectoryBySession?.set(sessionId, sessionDirectory);
187038
+ sessionDirectoryResolvedFromHost = true;
185060
187039
  }
185061
187040
  } catch {}
185062
187041
  }
@@ -185151,6 +187130,8 @@ function createTransform(deps) {
185151
187130
  deps.liveModelBySession?.set(sessionId, recovered);
185152
187131
  }
185153
187132
  }
187133
+ const resolvedProviderID = modelForBudget?.providerID;
187134
+ const canUseEmptySentinels = modelAcceptsEmptyContent(resolvedProviderID);
185154
187135
  const resolvedContextLimit = modelForBudget ? resolveTrustedContextLimit(modelForBudget.providerID, modelForBudget.modelID, {
185155
187136
  db,
185156
187137
  sessionID: sessionId
@@ -185315,6 +187296,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185315
187296
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
185316
187297
  const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(compartmentDirectory || process.cwd()) : undefined;
185317
187298
  const sessionProjectIdentity = projectIdentity ?? (sessionDirectory ? resolveProjectIdentity(sessionDirectory) : deps.projectPath);
187299
+ if (sessionProjectIdentity && sessionDirectoryResolvedFromHost && recordedSessionProjectIdentity.get(sessionId) !== sessionProjectIdentity) {
187300
+ recordSessionProjectIdentity(db, sessionId, sessionProjectIdentity);
187301
+ recordedSessionProjectIdentity.set(sessionId, sessionProjectIdentity);
187302
+ }
185318
187303
  let triggerBoundarySnapshot;
185319
187304
  if (fullFeatureMode && historianRunnable && !sessionMeta.compartmentInProgress) {
185320
187305
  const tTrigger = performance.now();
@@ -185409,7 +187394,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185409
187394
  }
185410
187395
  }
185411
187396
  const t3 = performance.now();
185412
- const strippedStructuralNoise = stripStructuralNoise(messages);
187397
+ const strippedStructuralNoise = canUseEmptySentinels ? stripStructuralNoise(messages) : 0;
185413
187398
  logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
185414
187399
  const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
185415
187400
  if (persistedReasoningWatermark > 0) {
@@ -185430,18 +187415,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185430
187415
  logTransformTiming(sessionId, "replayCavemanCompression", tCavemanReplay);
185431
187416
  }
185432
187417
  const t4 = performance.now();
185433
- const strippedClearedReasoning = stripClearedReasoning(messages);
187418
+ const strippedClearedReasoning = canUseEmptySentinels ? stripClearedReasoning(messages) : 0;
185434
187419
  logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
185435
187420
  const tMergeStrip = performance.now();
185436
- let liveProviderID = deps.liveModelBySession?.get(sessionId)?.providerID;
185437
- if (liveProviderID === undefined) {
185438
- const recovered = findLastAssistantModelFromOpenCodeDb(sessionId);
185439
- if (recovered) {
185440
- liveProviderID = recovered.providerID;
185441
- deps.liveModelBySession?.set(sessionId, recovered);
185442
- }
185443
- }
185444
- const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, liveProviderID);
187421
+ const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, resolvedProviderID);
185445
187422
  if (strippedMergedReasoning > 0) {
185446
187423
  sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
185447
187424
  }
@@ -185560,7 +187537,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185560
187537
  sessionDirectory,
185561
187538
  autoSearch: deps.autoSearch,
185562
187539
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
185563
- liveProviderID,
187540
+ resolvedProviderID,
185564
187541
  historyRefreshSessions: deps.historyRefreshSessions,
185565
187542
  m0M1: {
185566
187543
  projectPath: projectIdentity,
@@ -185713,7 +187690,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185713
187690
  let tailToolTokens;
185714
187691
  let liveTailTokens;
185715
187692
  try {
185716
- const agg = getActiveTagTokenAggregate(db, sessionId);
187693
+ const agg = getActiveTagTokenAggregate(db, sessionId, deps.protectedTags);
185717
187694
  tailToolTokens = agg.toolOutput;
185718
187695
  liveTailTokens = agg.conversation + agg.toolCall;
185719
187696
  } catch {
@@ -186348,10 +188325,11 @@ function maybeInjectChannel1Nudge(args, sessionId, tool, output) {
186348
188325
  contextLimit: state.contextLimit,
186349
188326
  executeThresholdPercentage: state.executeThresholdPercentage
186350
188327
  });
188328
+ const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
186351
188329
  const decision = decideChannel1({
186352
188330
  undroppedTokens,
186353
188331
  pressure,
186354
- historyBudgetTokens: state.historyBudgetTokens,
188332
+ workingWindowTokens,
186355
188333
  lastNudgeUndropped: getLastNudgeUndropped(args.db, sessionId),
186356
188334
  lastNudgeLevel: getLastNudgeLevel(args.db, sessionId),
186357
188335
  hasRecentReduce: false
@@ -186413,7 +188391,7 @@ function createToolExecuteAfterHook(args) {
186413
188391
  init_send_session_notification();
186414
188392
 
186415
188393
  // src/hooks/magic-context/system-prompt-hash.ts
186416
- import { createHash as createHash10 } from "node:crypto";
188394
+ import { createHash as createHash12 } from "node:crypto";
186417
188395
 
186418
188396
  // src/agents/magic-context-prompt.ts
186419
188397
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -186613,7 +188591,7 @@ function createSystemPromptHashHandler(deps) {
186613
188591
  `);
186614
188592
  if (systemContent.length === 0)
186615
188593
  return;
186616
- const currentHash = createHash10("md5").update(systemContent).digest("hex");
188594
+ const currentHash = createHash12("md5").update(systemContent).digest("hex");
186617
188595
  if (!sessionMetaEarly) {
186618
188596
  return;
186619
188597
  }
@@ -186874,6 +188852,46 @@ function createMagicContextHook(deps) {
186874
188852
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
186875
188853
  getNotificationParams: (sid) => getLiveNotificationParams(sid, liveModelBySession, variantBySession, agentBySession)
186876
188854
  });
188855
+ const executeEmbedHistory = async (sessionId) => {
188856
+ if (deps.config.memory?.enabled === false) {
188857
+ return "Memory is disabled for this project, so there is no semantic embedding to backfill.";
188858
+ }
188859
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
188860
+ await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
188861
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
188862
+ setRecompStarting({ recompProgressBySession }, sessionId, "Embedding history…", "embed");
188863
+ const outcome = await embedSessionCompartmentChunks(db, sessionProjectIdentity, sessionId, {
188864
+ onProgress: ({ embedded, total }) => {
188865
+ const cur = recompProgressBySession.get(sessionId);
188866
+ if (!cur || cur.phase !== "recomp")
188867
+ return;
188868
+ recompProgressBySession.set(sessionId, {
188869
+ ...cur,
188870
+ processedMessages: embedded,
188871
+ totalMessages: total,
188872
+ updatedAt: Date.now()
188873
+ });
188874
+ }
188875
+ });
188876
+ const terminal = (phase, message) => {
188877
+ setRecompTerminal({ recompProgressBySession }, sessionId, phase, message);
188878
+ return message;
188879
+ };
188880
+ switch (outcome.status) {
188881
+ case "nothing":
188882
+ return terminal("done", "All of this session's history is already embedded.");
188883
+ case "disabled":
188884
+ return terminal("skipped", "No embedding provider is configured, so there is nothing to embed.");
188885
+ case "busy":
188886
+ return terminal("skipped", `Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`);
188887
+ case "aborted":
188888
+ return terminal("done", `Embedded ${outcome.embedded} of ${outcome.total} compartments before stopping.`);
188889
+ case "stalled":
188890
+ 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.`);
188891
+ default:
188892
+ return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`);
188893
+ }
188894
+ };
186877
188895
  const sidekickRunnable = isSidekickRunnable(deps.config);
186878
188896
  const sidekickConfig = sidekickRunnable ? deps.config.sidekick : undefined;
186879
188897
  const transform2 = createTransform({
@@ -187028,6 +189046,7 @@ function createMagicContextHook(deps) {
187028
189046
  },
187029
189047
  executeRecomp: historianRunnable ? async (sessionId, options) => runManagedRecomp(buildManagedRecompCtx(sessionId), sessionId, options) : undefined,
187030
189048
  runUpgrade: historianRunnable ? async (sessionId) => runManagedUpgrade(buildManagedRecompCtx(sessionId), sessionId) : undefined,
189049
+ executeEmbedHistory,
187031
189050
  sendNotification: async (sessionId, text, params) => {
187032
189051
  await sendIgnoredMessage(deps.client, sessionId, text, {
187033
189052
  ...getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
@@ -188166,6 +190185,7 @@ init_memory();
188166
190185
  init_embedding();
188167
190186
  init_embedding_cache();
188168
190187
  init_normalize_hash();
190188
+ init_workspaces();
188169
190189
  init_logger();
188170
190190
  await init_storage();
188171
190191
  import { tool as tool2 } from "@opencode-ai/plugin";
@@ -188325,6 +190345,23 @@ function createCtxMemoryTool(deps) {
188325
190345
  }
188326
190346
  const projectPath = deps.resolveProjectPath(toolContext.directory);
188327
190347
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
190348
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectPath);
190349
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
190350
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
190351
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? projectIdentityForStoredPath(rawProjectPath) : projectIdentityForStoredPath(rawProjectPath);
190352
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectPath) : null;
190353
+ const memoryVisibleToTool = (memory) => {
190354
+ if (workspaceIdentitySet.identities.length <= 1) {
190355
+ return memoryBelongsToProject(memory, projectPath);
190356
+ }
190357
+ if (!storedPathBelongsToWorkspace(memory.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
190358
+ return false;
190359
+ }
190360
+ const isOwn = targetIdentityForStoredPath(memory.projectPath) === projectPath;
190361
+ if (isOwn)
190362
+ return true;
190363
+ return toolShareCategories === null || toolShareCategories.includes(memory.category);
190364
+ };
188328
190365
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
188329
190366
  if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188330
190367
  return getDisabledMessage();
@@ -188381,18 +190418,18 @@ function createCtxMemoryTool(deps) {
188381
190418
  }
188382
190419
  const rawProjectPath = projectPathForMemoryId(deps.db, updateId);
188383
190420
  const memory = getMemoryById(deps.db, updateId);
188384
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
190421
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
188385
190422
  return `Error: Memory with ID ${updateId} was not found.`;
188386
190423
  }
188387
190424
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
188388
190425
  return inactiveMemoryError(updateId, "updating");
188389
190426
  }
188390
190427
  const normalizedHash = computeNormalizedHash(content);
188391
- const duplicate = getMemoryByHash(deps.db, projectPath, memory.category, normalizedHash);
190428
+ const duplicate = getMemoryByHash(deps.db, targetIdentityForStoredPath(rawProjectPath), memory.category, normalizedHash);
188392
190429
  if (duplicate && duplicate.id !== memory.id) {
188393
190430
  return `Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`;
188394
190431
  }
188395
- const projectIdentity = projectIdentityForStoredPath(rawProjectPath);
190432
+ const projectIdentity = targetIdentityForStoredPath(rawProjectPath);
188396
190433
  deps.db.transaction(() => {
188397
190434
  updateMemoryContentInCurrentTransaction(deps.db, memory, content, normalizedHash);
188398
190435
  queueMemoryMutation(deps.db, {
@@ -188406,7 +190443,7 @@ function createCtxMemoryTool(deps) {
188406
190443
  queueMemoryEmbedding({
188407
190444
  deps,
188408
190445
  sessionId: toolContext.sessionID,
188409
- projectPath,
190446
+ projectPath: projectIdentity,
188410
190447
  memoryId: memory.id,
188411
190448
  content
188412
190449
  });
@@ -188429,7 +190466,7 @@ function createCtxMemoryTool(deps) {
188429
190466
  return "Error: One or more source memories were not found.";
188430
190467
  }
188431
190468
  if (toolContext.agent !== DREAMER_AGENT) {
188432
- const foreign = sourceMemories.find((memory) => !memoryBelongsToProject(memory, projectPath));
190469
+ const foreign = sourceMemories.find((memory) => !memoryVisibleToTool(memory));
188433
190470
  if (foreign) {
188434
190471
  return `Error: Memory with ID ${foreign.id} was not found.`;
188435
190472
  }
@@ -188513,15 +190550,16 @@ function createCtxMemoryTool(deps) {
188513
190550
  return `Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`;
188514
190551
  }
188515
190552
  if (args.action === "archive") {
188516
- const archiveIds = args.ids;
188517
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
190553
+ const rawArchiveIds = args.ids;
190554
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
188518
190555
  return "Error: 'ids' must contain at least one integer memory ID when action is 'archive'.";
188519
190556
  }
190557
+ const archiveIds = [...new Set(rawArchiveIds)];
188520
190558
  const targets = [];
188521
190559
  for (const memoryId of archiveIds) {
188522
190560
  const rawProjectPath = projectPathForMemoryId(deps.db, memoryId);
188523
190561
  const memory = getMemoryById(deps.db, memoryId);
188524
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
190562
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
188525
190563
  return `Error: Memory with ID ${memoryId} was not found.`;
188526
190564
  }
188527
190565
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -188529,7 +190567,7 @@ function createCtxMemoryTool(deps) {
188529
190567
  }
188530
190568
  targets.push({
188531
190569
  memoryId,
188532
- projectIdentity: projectIdentityForStoredPath(rawProjectPath)
190570
+ projectIdentity: targetIdentityForStoredPath(rawProjectPath)
188533
190571
  });
188534
190572
  }
188535
190573
  deps.db.transaction(() => {
@@ -189003,8 +191041,9 @@ function formatAge2(committedAtMs) {
189003
191041
  }
189004
191042
  function formatResult(result, index) {
189005
191043
  if (result.source === "memory") {
191044
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
189006
191045
  return [
189007
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
191046
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
189008
191047
  result.content
189009
191048
  ].join(`
189010
191049
  `);
@@ -189014,6 +191053,13 @@ function formatResult(result, index) {
189014
191053
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
189015
191054
  result.content
189016
191055
  ].join(`
191056
+ `);
191057
+ }
191058
+ if (result.source === "compartment") {
191059
+ return [
191060
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
191061
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
191062
+ ].join(`
189017
191063
  `);
189018
191064
  }
189019
191065
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -189029,7 +191075,7 @@ function formatSearchResults(query, results) {
189029
191075
  return `No results found for "${query}" across memories, git commits, or message history.`;
189030
191076
  }
189031
191077
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
189032
- if (results.some((result) => result.source === "message")) {
191078
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
189033
191079
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
189034
191080
  }
189035
191081
  const body = bodyParts.join(`
@@ -189187,11 +191233,11 @@ import { createServer } from "node:http";
189187
191233
  import { dirname as dirname5 } from "node:path";
189188
191234
 
189189
191235
  // src/shared/rpc-utils.ts
189190
- import { createHash as createHash11 } from "node:crypto";
191236
+ import { createHash as createHash13 } from "node:crypto";
189191
191237
  import { join as join20 } from "node:path";
189192
191238
  function projectHash(directory) {
189193
191239
  const normalized = directory.replace(/\/+$/, "");
189194
- return createHash11("sha256").update(normalized).digest("hex").slice(0, 16);
191240
+ return createHash13("sha256").update(normalized).digest("hex").slice(0, 16);
189195
191241
  }
189196
191242
  function rpcPortDir(storageDir, directory) {
189197
191243
  return join20(storageDir, "rpc", projectHash(directory));