@cortexkit/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 (97) hide show
  1. package/README.md +6 -0
  2. package/dist/config/schema/magic-context.d.ts +10 -3
  3. package/dist/config/schema/magic-context.d.ts.map +1 -1
  4. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  5. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +80 -0
  6. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -0
  7. package/dist/features/magic-context/compartment-embedding.d.ts +22 -26
  8. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  9. package/dist/features/magic-context/memory/embedding-backfill.d.ts +3 -2
  10. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  11. package/dist/features/magic-context/memory/embedding-cache.d.ts +3 -2
  12. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -1
  13. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -1
  14. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts +17 -0
  16. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  17. package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -0
  18. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  19. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  20. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  21. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +5 -1
  22. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  23. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -7
  24. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  25. package/dist/features/magic-context/memory/storage-memory.d.ts +18 -12
  26. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  27. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  28. package/dist/features/magic-context/project-embedding-registry.d.ts +53 -0
  29. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  30. package/dist/features/magic-context/search.d.ts +14 -1
  31. package/dist/features/magic-context/search.d.ts.map +1 -1
  32. package/dist/features/magic-context/session-project-storage.d.ts +17 -0
  33. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -0
  34. package/dist/features/magic-context/storage-db.d.ts +1 -1
  35. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  36. package/dist/features/magic-context/storage-memory-mutation-log.d.ts +2 -0
  37. package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage-meta-persisted.d.ts +16 -0
  39. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  41. package/dist/features/magic-context/storage-meta-shared.d.ts +3 -1
  42. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  43. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  44. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  45. package/dist/features/magic-context/storage-tags.d.ts +10 -1
  46. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  47. package/dist/features/magic-context/storage.d.ts +3 -2
  48. package/dist/features/magic-context/storage.d.ts.map +1 -1
  49. package/dist/features/magic-context/types.d.ts +1 -0
  50. package/dist/features/magic-context/types.d.ts.map +1 -1
  51. package/dist/features/magic-context/workspaces.d.ts +20 -0
  52. package/dist/features/magic-context/workspaces.d.ts.map +1 -0
  53. package/dist/hooks/magic-context/apply-operations.d.ts +23 -0
  54. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/command-handler.d.ts +5 -0
  57. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/compartment-runner-types.d.ts +11 -1
  62. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +7 -2
  65. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/inject-compartments.d.ts +23 -3
  69. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -1
  71. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  72. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  73. package/dist/hooks/magic-context/sentinel.d.ts +33 -28
  74. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  75. package/dist/hooks/magic-context/strip-content.d.ts +34 -17
  76. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  77. package/dist/hooks/magic-context/strip-structural-noise.d.ts +5 -7
  78. package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
  79. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +4 -5
  80. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  81. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  82. package/dist/index.js +2411 -365
  83. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  84. package/dist/shared/announcement.d.ts +1 -1
  85. package/dist/shared/rpc-types.d.ts +1 -1
  86. package/dist/shared/rpc-types.d.ts.map +1 -1
  87. package/dist/shared/tui-preferences.d.ts +32 -0
  88. package/dist/shared/tui-preferences.d.ts.map +1 -0
  89. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  90. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/src/shared/announcement.ts +6 -6
  93. package/src/shared/rpc-types.ts +1 -1
  94. package/src/shared/tui-preferences.test.ts +210 -0
  95. package/src/shared/tui-preferences.ts +303 -0
  96. package/src/tui/index.tsx +5 -3
  97. package/src/tui/slots/sidebar-content.tsx +123 -15
package/dist/index.js CHANGED
@@ -14887,7 +14887,8 @@ var init_magic_context = __esm(() => {
14887
14887
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
14888
14888
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
14889
14889
  input_type: exports_external.string().optional().describe("Optional input_type sent in the embedding request body. Required by some openai-compatible providers (e.g. NVIDIA NIM expects 'query' or 'passage'). Omitted from the request when unset."),
14890
- truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset.")
14890
+ truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset."),
14891
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
14891
14892
  }).superRefine((data, ctx) => {
14892
14893
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
14893
14894
  ctx.addIssue({
@@ -14908,7 +14909,8 @@ var init_magic_context = __esm(() => {
14908
14909
  if (data.provider === "local") {
14909
14910
  return {
14910
14911
  provider: "local",
14911
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
14912
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
14913
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14912
14914
  };
14913
14915
  }
14914
14916
  if (data.provider === "openai-compatible") {
@@ -14921,7 +14923,8 @@ var init_magic_context = __esm(() => {
14921
14923
  endpoint: data.endpoint?.trim() ?? "",
14922
14924
  ...apiKey ? { api_key: apiKey } : {},
14923
14925
  ...inputType ? { input_type: inputType } : {},
14924
- ...truncate ? { truncate } : {}
14926
+ ...truncate ? { truncate } : {},
14927
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14925
14928
  };
14926
14929
  }
14927
14930
  return { provider: "off" };
@@ -15841,7 +15844,7 @@ function isSessionMetaRow(row) {
15841
15844
  if (row === null || typeof row !== "object")
15842
15845
  return false;
15843
15846
  const r = row;
15844
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15847
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15845
15848
  }
15846
15849
  function getDefaultSessionMeta(sessionId) {
15847
15850
  return {
@@ -15868,6 +15871,7 @@ function getDefaultSessionMeta(sessionId) {
15868
15871
  cachedM0Bytes: null,
15869
15872
  cachedM1Bytes: null,
15870
15873
  cachedM0ProjectMemoryEpoch: null,
15874
+ cachedM0WorkspaceFingerprint: null,
15871
15875
  cachedM0ProjectUserProfileVersion: null,
15872
15876
  cachedM0MaxCompartmentSeq: null,
15873
15877
  cachedM0MaxMemoryId: null,
@@ -15930,6 +15934,7 @@ function toSessionMeta(row) {
15930
15934
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
15931
15935
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
15932
15936
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
15937
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
15933
15938
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
15934
15939
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
15935
15940
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -15960,6 +15965,7 @@ function persistCachedM0(db, sessionId, payload) {
15960
15965
  db.prepare(`UPDATE session_meta SET
15961
15966
  cached_m0_bytes = ?,
15962
15967
  cached_m0_project_memory_epoch = ?,
15968
+ cached_m0_workspace_fingerprint = ?,
15963
15969
  cached_m0_project_user_profile_version = ?,
15964
15970
  cached_m0_max_compartment_seq = ?,
15965
15971
  cached_m0_max_memory_id = ?,
@@ -15972,7 +15978,7 @@ function persistCachedM0(db, sessionId, payload) {
15972
15978
  cached_m0_upgrade_state = ?,
15973
15979
  cached_m0_system_hash = ?,
15974
15980
  cached_m0_model_key = ?
15975
- WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.modelKey ?? "", sessionId);
15981
+ WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.workspaceFingerprint ?? null, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.modelKey ?? "", sessionId);
15976
15982
  }
15977
15983
  function clearCachedM0M1(db, sessionId) {
15978
15984
  ensureSessionMetaRow(db, sessionId);
@@ -15981,6 +15987,7 @@ function clearCachedM0M1(db, sessionId) {
15981
15987
  ["cached_m0_bytes", null],
15982
15988
  ["cached_m1_bytes", null],
15983
15989
  ["cached_m0_project_memory_epoch", null],
15990
+ ["cached_m0_workspace_fingerprint", null],
15984
15991
  ["cached_m0_project_user_profile_version", null],
15985
15992
  ["cached_m0_max_compartment_seq", null],
15986
15993
  ["cached_m0_max_memory_id", null],
@@ -16036,6 +16043,7 @@ var init_storage_meta_shared = __esm(() => {
16036
16043
  "cached_m0_bytes",
16037
16044
  "cached_m1_bytes",
16038
16045
  "cached_m0_project_memory_epoch",
16046
+ "cached_m0_workspace_fingerprint",
16039
16047
  "cached_m0_project_user_profile_version",
16040
16048
  "cached_m0_max_compartment_seq",
16041
16049
  "cached_m0_max_memory_id",
@@ -16083,6 +16091,7 @@ var init_storage_meta_shared = __esm(() => {
16083
16091
  cachedM0Bytes: "cached_m0_bytes",
16084
16092
  cachedM1Bytes: "cached_m1_bytes",
16085
16093
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
16094
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
16086
16095
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
16087
16096
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
16088
16097
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -16112,6 +16121,7 @@ var init_storage_meta_shared = __esm(() => {
16112
16121
  "cachedM0Bytes",
16113
16122
  "cachedM1Bytes",
16114
16123
  "cachedM0ProjectMemoryEpoch",
16124
+ "cachedM0WorkspaceFingerprint",
16115
16125
  "cachedM0ProjectUserProfileVersion",
16116
16126
  "cachedM0MaxCompartmentSeq",
16117
16127
  "cachedM0MaxMemoryId",
@@ -150823,6 +150833,35 @@ function initializeDatabase(db) {
150823
150833
  );
150824
150834
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
150825
150835
 
150836
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
150837
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
150838
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
150839
+ session_id TEXT NOT NULL,
150840
+ project_path TEXT NOT NULL,
150841
+ harness TEXT NOT NULL DEFAULT 'opencode',
150842
+ window_index INTEGER NOT NULL DEFAULT 0,
150843
+ start_ordinal INTEGER NOT NULL,
150844
+ end_ordinal INTEGER NOT NULL,
150845
+ chunk_hash TEXT NOT NULL,
150846
+ model_id TEXT NOT NULL,
150847
+ dims INTEGER NOT NULL,
150848
+ vector BLOB NOT NULL,
150849
+ created_at INTEGER NOT NULL,
150850
+ UNIQUE(compartment_id, window_index)
150851
+ );
150852
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
150853
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
150854
+
150855
+ CREATE TABLE IF NOT EXISTS session_projects (
150856
+ session_id TEXT NOT NULL,
150857
+ harness TEXT NOT NULL DEFAULT 'opencode',
150858
+ project_path TEXT NOT NULL,
150859
+ updated_at INTEGER NOT NULL,
150860
+ PRIMARY KEY(session_id, harness)
150861
+ );
150862
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
150863
+ ON session_projects(project_path);
150864
+
150826
150865
  CREATE TABLE IF NOT EXISTS compartment_events (
150827
150866
  id INTEGER PRIMARY KEY AUTOINCREMENT,
150828
150867
  session_id TEXT NOT NULL,
@@ -151008,6 +151047,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151008
151047
  rekeyed_at INTEGER NOT NULL
151009
151048
  );
151010
151049
 
151050
+ CREATE TABLE IF NOT EXISTS workspaces (
151051
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151052
+ name TEXT NOT NULL UNIQUE,
151053
+ created_at INTEGER NOT NULL,
151054
+ updated_at INTEGER NOT NULL,
151055
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151056
+ );
151057
+
151058
+ CREATE TABLE IF NOT EXISTS workspace_members (
151059
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151060
+ project_path TEXT NOT NULL,
151061
+ display_name TEXT NOT NULL,
151062
+ display_path TEXT NOT NULL,
151063
+ added_at INTEGER NOT NULL,
151064
+ PRIMARY KEY (workspace_id, project_path)
151065
+ );
151066
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151067
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151068
+
151011
151069
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151012
151070
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151013
151071
  table_name TEXT NOT NULL,
@@ -151119,6 +151177,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151119
151177
  deferred_execute_state TEXT,
151120
151178
  cached_m0_bytes BLOB,
151121
151179
  cached_m0_project_memory_epoch INTEGER,
151180
+ cached_m0_workspace_fingerprint TEXT,
151122
151181
  cached_m0_project_user_profile_version INTEGER,
151123
151182
  cached_m0_max_compartment_seq INTEGER,
151124
151183
  cached_m0_max_memory_id INTEGER,
@@ -151277,6 +151336,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151277
151336
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151278
151337
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151279
151338
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151339
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
151280
151340
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
151281
151341
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
151282
151342
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -151330,6 +151390,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151330
151390
  ensureColumn(db, "memories", "importance", "INTEGER");
151331
151391
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
151332
151392
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
151393
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
151333
151394
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
151334
151395
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
151335
151396
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -151361,6 +151422,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151361
151422
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
151362
151423
  updated_at INTEGER NOT NULL DEFAULT 0
151363
151424
  );
151425
+ CREATE TABLE IF NOT EXISTS session_projects (
151426
+ session_id TEXT NOT NULL,
151427
+ harness TEXT NOT NULL DEFAULT 'opencode',
151428
+ project_path TEXT NOT NULL,
151429
+ updated_at INTEGER NOT NULL,
151430
+ PRIMARY KEY(session_id, harness)
151431
+ );
151432
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
151433
+ ON session_projects(project_path);
151364
151434
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
151365
151435
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151366
151436
  session_id TEXT NOT NULL,
@@ -151388,6 +151458,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151388
151458
  new_project_path TEXT NOT NULL,
151389
151459
  rekeyed_at INTEGER NOT NULL
151390
151460
  );
151461
+ CREATE TABLE IF NOT EXISTS workspaces (
151462
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151463
+ name TEXT NOT NULL UNIQUE,
151464
+ created_at INTEGER NOT NULL,
151465
+ updated_at INTEGER NOT NULL,
151466
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151467
+ );
151468
+ CREATE TABLE IF NOT EXISTS workspace_members (
151469
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151470
+ project_path TEXT NOT NULL,
151471
+ display_name TEXT NOT NULL,
151472
+ display_path TEXT NOT NULL,
151473
+ added_at INTEGER NOT NULL,
151474
+ PRIMARY KEY (workspace_id, project_path)
151475
+ );
151476
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151477
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151391
151478
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151392
151479
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151393
151480
  table_name TEXT NOT NULL,
@@ -151409,6 +151496,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151409
151496
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151410
151497
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151411
151498
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151499
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
151412
151500
  }
151413
151501
  function healAllNullColumns(db) {
151414
151502
  healNullTextColumns(db);
@@ -151446,6 +151534,7 @@ function healNullTextColumns(db) {
151446
151534
  ["system_prompt_hash", ""],
151447
151535
  ["stripped_placeholder_ids", ""],
151448
151536
  ["stale_reduce_stripped_ids", ""],
151537
+ ["processed_image_stripped_ids", ""],
151449
151538
  ["memory_block_cache", ""],
151450
151539
  ["memory_block_ids", ""],
151451
151540
  ["compaction_marker_state", ""],
@@ -151490,7 +151579,7 @@ function healNullIntegerColumns(db) {
151490
151579
  }
151491
151580
  }
151492
151581
  function ensureColumn(db, table, column, definition) {
151493
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
151582
+ if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
151494
151583
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
151495
151584
  }
151496
151585
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -151576,7 +151665,7 @@ function getDatabasePersistenceError(db) {
151576
151665
  return null;
151577
151666
  return persistenceErrorByDatabase.get(db) ?? null;
151578
151667
  }
151579
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 32, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151668
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 36, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151580
151669
  var init_storage_db = __esm(async () => {
151581
151670
  init_data_path();
151582
151671
  init_logger();
@@ -151596,10 +151685,302 @@ var init_storage_db = __esm(async () => {
151596
151685
  };
151597
151686
  });
151598
151687
 
151688
+ // src/features/magic-context/memory/constants.ts
151689
+ var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
151690
+ var init_constants = __esm(() => {
151691
+ V2_MEMORY_CATEGORIES = [
151692
+ "PROJECT_RULES",
151693
+ "ARCHITECTURE",
151694
+ "CONSTRAINTS",
151695
+ "CONFIG_VALUES",
151696
+ "NAMING"
151697
+ ];
151698
+ PROMOTABLE_CATEGORIES = [
151699
+ "PROJECT_RULES",
151700
+ "ARCHITECTURE",
151701
+ "CONSTRAINTS",
151702
+ "CONFIG_VALUES",
151703
+ "NAMING",
151704
+ "ARCHITECTURE_DECISIONS",
151705
+ "CONFIG_DEFAULTS",
151706
+ "USER_PREFERENCES",
151707
+ "USER_DIRECTIVES",
151708
+ "ENVIRONMENT",
151709
+ "WORKFLOW_RULES",
151710
+ "KNOWN_ISSUES"
151711
+ ];
151712
+ CATEGORY_PRIORITY = [
151713
+ "PROJECT_RULES",
151714
+ "ARCHITECTURE",
151715
+ "CONSTRAINTS",
151716
+ "CONFIG_VALUES",
151717
+ "NAMING",
151718
+ "USER_DIRECTIVES",
151719
+ "USER_PREFERENCES",
151720
+ "CONFIG_DEFAULTS",
151721
+ "ARCHITECTURE_DECISIONS",
151722
+ "ENVIRONMENT",
151723
+ "WORKFLOW_RULES",
151724
+ "KNOWN_ISSUES"
151725
+ ];
151726
+ MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
151727
+ acc[category] = index;
151728
+ return acc;
151729
+ }, {});
151730
+ MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
151731
+ CATEGORY_DEFAULT_TTL = {
151732
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
151733
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
151734
+ };
151735
+ });
151736
+
151737
+ // src/features/magic-context/project-identity.ts
151738
+ var init_project_identity2 = __esm(() => {
151739
+ init_project_identity();
151740
+ });
151741
+
151742
+ // src/features/magic-context/workspaces.ts
151743
+ import { createHash as createHash4 } from "node:crypto";
151744
+ function tableExists(db, tableName) {
151745
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
151746
+ return Boolean(row);
151747
+ }
151748
+ function columnExists(db, tableName, columnName) {
151749
+ const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
151750
+ return rows.some((row) => row.name === columnName);
151751
+ }
151752
+ function uniqueSorted(values) {
151753
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
151754
+ }
151755
+ function placeholders(values) {
151756
+ return values.map(() => "?").join(", ");
151757
+ }
151758
+ function normalizeShareCategories(raw) {
151759
+ if (raw === null || raw === undefined)
151760
+ return null;
151761
+ if (typeof raw !== "string")
151762
+ return null;
151763
+ let parsed;
151764
+ try {
151765
+ parsed = JSON.parse(raw);
151766
+ } catch {
151767
+ return null;
151768
+ }
151769
+ if (!Array.isArray(parsed))
151770
+ return null;
151771
+ const categories = [];
151772
+ for (const value of parsed) {
151773
+ if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
151774
+ return null;
151775
+ }
151776
+ if (!categories.includes(value))
151777
+ categories.push(value);
151778
+ }
151779
+ return categories.sort((left, right) => left.localeCompare(right));
151780
+ }
151781
+ function selectWorkspaceShareCategories(db, identities) {
151782
+ const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
151783
+ if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
151784
+ return null;
151785
+ }
151786
+ const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
151787
+ FROM workspace_members AS member
151788
+ JOIN workspaces AS workspace ON workspace.id = member.workspace_id
151789
+ WHERE member.project_path IN (${placeholders(candidates)})
151790
+ ORDER BY workspace.id ASC
151791
+ LIMIT 1`).get(...candidates);
151792
+ return normalizeShareCategories(row?.shareCategories ?? null);
151793
+ }
151794
+ function resolveWorkspaceShareCategories(db, projectIdentity) {
151795
+ return selectWorkspaceShareCategories(db, [projectIdentity]);
151796
+ }
151797
+ function resolveWorkspaceIdentitySet(db, projectIdentity) {
151798
+ if (!tableExists(db, "workspace_members")) {
151799
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151800
+ }
151801
+ const rows = db.prepare(`SELECT member.project_path AS identity, member.display_name AS displayName
151802
+ FROM workspace_members AS anchor
151803
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151804
+ WHERE anchor.project_path = ?
151805
+ ORDER BY member.display_name ASC, member.project_path ASC`).all(projectIdentity);
151806
+ if (rows.length === 0) {
151807
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151808
+ }
151809
+ const namesByIdentity = new Map;
151810
+ const identities = [];
151811
+ for (const row of rows) {
151812
+ if (typeof row.identity !== "string" || row.identity.length === 0)
151813
+ continue;
151814
+ if (identities.includes(row.identity))
151815
+ continue;
151816
+ identities.push(row.identity);
151817
+ if (typeof row.displayName === "string" && row.displayName.length > 0) {
151818
+ namesByIdentity.set(row.identity, row.displayName);
151819
+ }
151820
+ }
151821
+ return identities.length > 0 ? { identities, namesByIdentity } : { identities: [projectIdentity], namesByIdentity: new Map };
151822
+ }
151823
+ function expandWorkspaceIdentitySetWithAliases(db, identities) {
151824
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151825
+ const expanded = new Set(canonical);
151826
+ const canonicalIdentityByStoredPath = new Map;
151827
+ for (const identity of canonical) {
151828
+ canonicalIdentityByStoredPath.set(identity, identity);
151829
+ }
151830
+ if (canonical.length === 0 || !tableExists(db, "v22_identity_rekey_map")) {
151831
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151832
+ }
151833
+ const rows = db.prepare(`SELECT old_project_path AS oldProjectPath, new_project_path AS newProjectPath
151834
+ FROM v22_identity_rekey_map
151835
+ WHERE new_project_path IN (${placeholders(canonical)})
151836
+ ORDER BY old_project_path ASC`).all(...canonical);
151837
+ for (const row of rows) {
151838
+ if (typeof row.oldProjectPath !== "string" || typeof row.newProjectPath !== "string") {
151839
+ continue;
151840
+ }
151841
+ if (!canonicalIdentityByStoredPath.has(row.newProjectPath))
151842
+ continue;
151843
+ expanded.add(row.oldProjectPath);
151844
+ canonicalIdentityByStoredPath.set(row.oldProjectPath, row.newProjectPath);
151845
+ }
151846
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151847
+ }
151848
+ function resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) {
151849
+ const direct = canonicalIdentityByStoredPath.get(storedProjectPath);
151850
+ if (direct)
151851
+ return direct;
151852
+ const normalized = normalizeStoredProjectPath(storedProjectPath);
151853
+ const normalizedDirect = canonicalIdentityByStoredPath.get(normalized);
151854
+ if (normalizedDirect)
151855
+ return normalizedDirect;
151856
+ if (memberIdentities.includes(normalized))
151857
+ return normalized;
151858
+ for (const identity of memberIdentities) {
151859
+ if (storedPathBelongsToIdentity(storedProjectPath, identity)) {
151860
+ return identity;
151861
+ }
151862
+ }
151863
+ return null;
151864
+ }
151865
+ function storedPathBelongsToWorkspace(storedProjectPath, memberIdentities, expandedIdentities, canonicalIdentityByStoredPath) {
151866
+ if (expandedIdentities.includes(storedProjectPath))
151867
+ return true;
151868
+ return resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) !== null;
151869
+ }
151870
+ function sourceNameForMemory(storedProjectPath, ownIdentity, memberIdentities, namesByIdentity, canonicalIdentityByStoredPath) {
151871
+ const canonicalIdentity = resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath);
151872
+ if (!canonicalIdentity || canonicalIdentity === ownIdentity)
151873
+ return;
151874
+ return namesByIdentity.get(canonicalIdentity);
151875
+ }
151876
+ function getEpochMap(db, identities) {
151877
+ if (identities.length === 0)
151878
+ return new Map;
151879
+ const rows = db.prepare(`SELECT project_path AS projectPath, project_memory_epoch AS epoch
151880
+ FROM project_state
151881
+ WHERE project_path IN (${placeholders(identities)})`).all(...identities);
151882
+ const epochs = new Map;
151883
+ for (const row of rows) {
151884
+ if (typeof row.projectPath !== "string" || typeof row.epoch !== "number")
151885
+ continue;
151886
+ epochs.set(row.projectPath, row.epoch);
151887
+ }
151888
+ return epochs;
151889
+ }
151890
+ function computeWorkspaceEpochFingerprint(db, identities) {
151891
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151892
+ const epochs = getEpochMap(db, canonical);
151893
+ const shareCategories = selectWorkspaceShareCategories(db, canonical);
151894
+ const hash2 = createHash4("sha256");
151895
+ hash2.update("share_categories", "utf8");
151896
+ hash2.update("\x00");
151897
+ hash2.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
151898
+ hash2.update(`
151899
+ `);
151900
+ for (const identity of canonical) {
151901
+ hash2.update(identity, "utf8");
151902
+ hash2.update("\x00");
151903
+ hash2.update(String(epochs.get(identity) ?? 0), "utf8");
151904
+ hash2.update(`
151905
+ `);
151906
+ }
151907
+ return hash2.digest("hex");
151908
+ }
151909
+ function isInTransaction(db) {
151910
+ const candidate = db;
151911
+ return candidate.inTransaction === true || candidate.isTransaction === true;
151912
+ }
151913
+ function workspaceMembersForIdentity(db, identity) {
151914
+ if (!tableExists(db, "workspace_members"))
151915
+ return [identity];
151916
+ const rows = db.prepare(`SELECT member.project_path AS identity
151917
+ FROM workspace_members AS anchor
151918
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151919
+ WHERE anchor.project_path = ?
151920
+ ORDER BY member.project_path ASC`).all(identity);
151921
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((value) => value.length > 0);
151922
+ return identities.length > 0 ? uniqueSorted(identities) : [identity];
151923
+ }
151924
+ function bumpEpochRows(db, identities, now) {
151925
+ const stmt = db.prepare(`INSERT INTO project_state
151926
+ (project_path, project_memory_epoch, project_user_profile_version, updated_at)
151927
+ VALUES (?, 1, 0, ?)
151928
+ ON CONFLICT(project_path) DO UPDATE SET
151929
+ project_memory_epoch = project_memory_epoch + 1,
151930
+ updated_at = excluded.updated_at`);
151931
+ for (const identity of uniqueSorted(identities)) {
151932
+ stmt.run(identity, now);
151933
+ }
151934
+ }
151935
+ function bumpEpochsForWorkspaceMembers(db, identity, now = Date.now()) {
151936
+ const run = () => bumpEpochRows(db, workspaceMembersForIdentity(db, identity), now);
151937
+ if (isInTransaction(db)) {
151938
+ run();
151939
+ return;
151940
+ }
151941
+ db.exec("BEGIN IMMEDIATE");
151942
+ try {
151943
+ run();
151944
+ db.exec("COMMIT");
151945
+ } catch (error51) {
151946
+ try {
151947
+ db.exec("ROLLBACK");
151948
+ } catch {}
151949
+ throw error51;
151950
+ }
151951
+ }
151952
+ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
151953
+ const run = () => bumpEpochRows(db, identities, now);
151954
+ if (isInTransaction(db)) {
151955
+ run();
151956
+ return;
151957
+ }
151958
+ db.exec("BEGIN IMMEDIATE");
151959
+ try {
151960
+ run();
151961
+ db.exec("COMMIT");
151962
+ } catch (error51) {
151963
+ try {
151964
+ db.exec("ROLLBACK");
151965
+ } catch {}
151966
+ throw error51;
151967
+ }
151968
+ }
151969
+ var VALID_SHARE_CATEGORIES;
151970
+ var init_workspaces = __esm(() => {
151971
+ init_constants();
151972
+ init_project_identity2();
151973
+ VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
151974
+ });
151975
+
151599
151976
  // src/features/magic-context/migrations.ts
151600
- function tableExists(db, name2) {
151977
+ function tableExists2(db, name2) {
151601
151978
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
151602
151979
  }
151980
+ function columnExists2(db, table, column) {
151981
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
151982
+ return rows.some((row) => row.name === column);
151983
+ }
151603
151984
  function ensureMigrationsTable(db) {
151604
151985
  db.exec(`
151605
151986
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -151668,6 +152049,7 @@ function runMigrations(db) {
151668
152049
  var MIGRATIONS, LATEST_MIGRATION_VERSION;
151669
152050
  var init_migrations = __esm(async () => {
151670
152051
  init_logger();
152052
+ init_workspaces();
151671
152053
  await init_storage_db();
151672
152054
  MIGRATIONS = [
151673
152055
  {
@@ -152127,9 +152509,9 @@ var init_migrations = __esm(async () => {
152127
152509
  version: 22,
152128
152510
  description: "v2.0 cache architecture schema foundation",
152129
152511
  up: (db) => {
152130
- const hasSessionMetaTable = tableExists(db, "session_meta");
152131
- const hasCompartmentsTable = tableExists(db, "compartments");
152132
- const hasMemoriesTable = tableExists(db, "memories");
152512
+ const hasSessionMetaTable = tableExists2(db, "session_meta");
152513
+ const hasCompartmentsTable = tableExists2(db, "compartments");
152514
+ const hasMemoriesTable = tableExists2(db, "memories");
152133
152515
  if (hasSessionMetaTable) {
152134
152516
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
152135
152517
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
@@ -152154,7 +152536,7 @@ var init_migrations = __esm(async () => {
152154
152536
  ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
152155
152537
  ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
152156
152538
  }
152157
- const hasRecompCompartmentsTable = tableExists(db, "recomp_compartments");
152539
+ const hasRecompCompartmentsTable = tableExists2(db, "recomp_compartments");
152158
152540
  if (hasRecompCompartmentsTable) {
152159
152541
  ensureColumn(db, "recomp_compartments", "p1", "TEXT");
152160
152542
  ensureColumn(db, "recomp_compartments", "p2", "TEXT");
@@ -152465,13 +152847,163 @@ var init_migrations = __esm(async () => {
152465
152847
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
152466
152848
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
152467
152849
  }
152850
+ },
152851
+ {
152852
+ version: 33,
152853
+ description: "Compartment chunk embeddings for semantic message-history search",
152854
+ up: (db) => {
152855
+ db.exec(`
152856
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
152857
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152858
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
152859
+ session_id TEXT NOT NULL,
152860
+ project_path TEXT NOT NULL,
152861
+ harness TEXT NOT NULL DEFAULT 'opencode',
152862
+ window_index INTEGER NOT NULL DEFAULT 0,
152863
+ start_ordinal INTEGER NOT NULL,
152864
+ end_ordinal INTEGER NOT NULL,
152865
+ chunk_hash TEXT NOT NULL,
152866
+ model_id TEXT NOT NULL,
152867
+ dims INTEGER NOT NULL,
152868
+ vector BLOB NOT NULL,
152869
+ created_at INTEGER NOT NULL,
152870
+ UNIQUE(compartment_id, window_index)
152871
+ );
152872
+ CREATE INDEX IF NOT EXISTS idx_cce_session
152873
+ ON compartment_chunk_embeddings(session_id);
152874
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model
152875
+ ON compartment_chunk_embeddings(project_path, model_id);
152876
+ `);
152877
+ }
152878
+ },
152879
+ {
152880
+ version: 34,
152881
+ description: "workspace tables and m[0] workspace fingerprint cache reset",
152882
+ up: (db) => {
152883
+ db.exec(`
152884
+ CREATE TABLE IF NOT EXISTS workspaces (
152885
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152886
+ name TEXT NOT NULL UNIQUE,
152887
+ created_at INTEGER NOT NULL,
152888
+ updated_at INTEGER NOT NULL
152889
+ );
152890
+ CREATE TABLE IF NOT EXISTS workspace_members (
152891
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
152892
+ project_path TEXT NOT NULL,
152893
+ display_name TEXT NOT NULL,
152894
+ display_path TEXT NOT NULL,
152895
+ added_at INTEGER NOT NULL,
152896
+ PRIMARY KEY (workspace_id, project_path)
152897
+ );
152898
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique
152899
+ ON workspace_members(project_path);
152900
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name
152901
+ ON workspace_members(workspace_id, display_name);
152902
+ `);
152903
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
152904
+ if (!hasSessionMeta)
152905
+ return;
152906
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
152907
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
152908
+ const clears = [
152909
+ ["cached_m0_bytes", null],
152910
+ ["cached_m1_bytes", null],
152911
+ ["cached_m0_project_memory_epoch", null],
152912
+ ["cached_m0_workspace_fingerprint", null],
152913
+ ["cached_m0_project_user_profile_version", null],
152914
+ ["cached_m0_max_compartment_seq", null],
152915
+ ["cached_m0_max_memory_id", null],
152916
+ ["cached_m0_max_mutation_id", null],
152917
+ ["cached_m0_max_memory_mutation_id", null],
152918
+ ["cached_m0_project_docs_hash", null],
152919
+ ["cached_m0_materialized_at", null],
152920
+ ["cached_m0_session_facts_version", null],
152921
+ ["cached_m0_upgrade_state", null],
152922
+ ["cached_m0_system_hash", null],
152923
+ ["cached_m0_tool_set_hash", null],
152924
+ ["cached_m0_model_key", null],
152925
+ ["cached_m0_last_baseline_end_message_id", null],
152926
+ ["memory_block_cache", ""],
152927
+ ["memory_block_ids", ""],
152928
+ ["memory_block_count", 0]
152929
+ ];
152930
+ const setClauses = [];
152931
+ const values = [];
152932
+ for (const [column, value] of clears) {
152933
+ if (!columns.has(column))
152934
+ continue;
152935
+ setClauses.push(`${column} = ?`);
152936
+ values.push(value);
152937
+ }
152938
+ if (setClauses.length > 0) {
152939
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")}`).run(...values);
152940
+ }
152941
+ }
152942
+ },
152943
+ {
152944
+ version: 35,
152945
+ description: "workspace per-category share defaults and epoch refresh",
152946
+ up: (db) => {
152947
+ db.exec(`
152948
+ CREATE TABLE IF NOT EXISTS workspaces (
152949
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152950
+ name TEXT NOT NULL UNIQUE,
152951
+ created_at INTEGER NOT NULL,
152952
+ updated_at INTEGER NOT NULL,
152953
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
152954
+ );
152955
+ `);
152956
+ if (!columnExists2(db, "workspaces", "share_categories")) {
152957
+ db.exec(`ALTER TABLE workspaces ADD COLUMN share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
152958
+ }
152959
+ db.prepare(`UPDATE workspaces
152960
+ SET share_categories = '["CONSTRAINTS"]'
152961
+ WHERE share_categories IS NULL OR share_categories = ''`).run();
152962
+ if (!tableExists2(db, "workspace_members"))
152963
+ return;
152964
+ const rows = db.prepare(`SELECT DISTINCT project_path AS identity
152965
+ FROM workspace_members
152966
+ WHERE project_path IS NOT NULL AND project_path <> ''
152967
+ ORDER BY project_path ASC`).all();
152968
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((identity) => identity.length > 0);
152969
+ if (identities.length > 0) {
152970
+ bumpEpochsForWorkspaceMemberSet(db, identities, Date.now());
152971
+ }
152972
+ }
152973
+ },
152974
+ {
152975
+ version: 36,
152976
+ description: "session project ownership map for compartment chunk backfill scoping",
152977
+ up: (db) => {
152978
+ db.exec(`
152979
+ CREATE TABLE IF NOT EXISTS session_projects (
152980
+ session_id TEXT NOT NULL,
152981
+ harness TEXT NOT NULL DEFAULT 'opencode',
152982
+ project_path TEXT NOT NULL,
152983
+ updated_at INTEGER NOT NULL,
152984
+ PRIMARY KEY(session_id, harness)
152985
+ );
152986
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
152987
+ ON session_projects(project_path);
152988
+ `);
152989
+ const hasChunkTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartment_chunk_embeddings'").get();
152990
+ if (hasChunkTable) {
152991
+ db.exec(`
152992
+ INSERT OR IGNORE INTO session_projects (session_id, harness, project_path, updated_at)
152993
+ SELECT session_id, harness, MIN(project_path), 0
152994
+ FROM compartment_chunk_embeddings
152995
+ GROUP BY session_id, harness
152996
+ HAVING COUNT(DISTINCT project_path) = 1;
152997
+ `);
152998
+ }
152999
+ }
152468
153000
  }
152469
153001
  ];
152470
153002
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
152471
153003
  });
152472
153004
 
152473
153005
  // src/features/magic-context/project-docs-hash.ts
152474
- import { createHash as createHash4 } from "node:crypto";
153006
+ import { createHash as createHash5 } from "node:crypto";
152475
153007
  import { lstatSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152476
153008
  import path4 from "node:path";
152477
153009
  function canonicalizeDocContent(raw) {
@@ -152561,7 +153093,7 @@ function hashCanonicalPieces(hashPieces) {
152561
153093
  if (hashPieces.length === 0) {
152562
153094
  return "";
152563
153095
  }
152564
- return createHash4("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
153096
+ return createHash5("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
152565
153097
  }
152566
153098
  function readProjectDocsCanonical(projectDirectory) {
152567
153099
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -152599,11 +153131,6 @@ var init_project_docs_hash = __esm(() => {
152599
153131
  MAX_PROJECT_DOC_BYTES = 256 * 1024;
152600
153132
  docsCache = new Map;
152601
153133
  });
152602
-
152603
- // src/features/magic-context/project-identity.ts
152604
- var init_project_identity2 = __esm(() => {
152605
- init_project_identity();
152606
- });
152607
153134
  // src/features/magic-context/storage-m0-mutation-log.ts
152608
153135
  function assertMutationType(mutationType) {
152609
153136
  if (!M0_MUTATION_TYPES.has(mutationType)) {
@@ -152688,18 +153215,13 @@ function getMemoryMutation(db, id) {
152688
153215
  WHERE id = ?`).get(id);
152689
153216
  return row ? toMemoryMutation(row) : null;
152690
153217
  }
152691
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
152692
- if (renderedMemoryIds.length === 0)
152693
- return [];
152694
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
152695
- const placeholders = uniqueIds.map(() => "?").join(", ");
152696
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
152697
- superseded_by_id, category, new_content, queued_at
152698
- FROM memory_mutation_log
152699
- WHERE project_path = ?
152700
- AND id > ?
152701
- AND target_memory_id IN (${placeholders})
152702
- ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153218
+ function uniqueProjectPaths(projectPaths) {
153219
+ return [...new Set(projectPaths.filter((path5) => path5.length > 0))];
153220
+ }
153221
+ function placeholders2(values) {
153222
+ return values.map(() => "?").join(", ");
153223
+ }
153224
+ function coalesceMutations(rows) {
152703
153225
  const chosenByTarget = new Map;
152704
153226
  for (const dbRow of rows) {
152705
153227
  const candidate = toMemoryMutation(dbRow);
@@ -152720,10 +153242,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
152720
153242
  }
152721
153243
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
152722
153244
  }
153245
+ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
153246
+ if (renderedMemoryIds.length === 0)
153247
+ return [];
153248
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153249
+ const placeholders3 = uniqueIds.map(() => "?").join(", ");
153250
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153251
+ superseded_by_id, category, new_content, queued_at
153252
+ FROM memory_mutation_log
153253
+ WHERE project_path = ?
153254
+ AND id > ?
153255
+ AND target_memory_id IN (${placeholders3})
153256
+ ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153257
+ return coalesceMutations(rows);
153258
+ }
153259
+ function getMemoryMutationsForRenderByProjects(db, projectPaths, afterId, renderedMemoryIds) {
153260
+ if (renderedMemoryIds.length === 0)
153261
+ return [];
153262
+ const identities = uniqueProjectPaths(projectPaths);
153263
+ if (identities.length === 0)
153264
+ return [];
153265
+ if (identities.length === 1) {
153266
+ return getMemoryMutationsForRender(db, identities[0], afterId, renderedMemoryIds);
153267
+ }
153268
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153269
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153270
+ superseded_by_id, category, new_content, queued_at
153271
+ FROM memory_mutation_log
153272
+ WHERE project_path IN (${placeholders2(identities)})
153273
+ AND id > ?
153274
+ AND target_memory_id IN (${placeholders2(uniqueIds)})
153275
+ ORDER BY id ASC`).all(...identities, afterId ?? 0, ...uniqueIds);
153276
+ return coalesceMutations(rows);
153277
+ }
152723
153278
  function getMaxMemoryMutationId(db, projectPath) {
152724
153279
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
152725
153280
  return row?.max_id ?? null;
152726
153281
  }
153282
+ function getMaxMemoryMutationIdForProjects(db, projectPaths) {
153283
+ const identities = uniqueProjectPaths(projectPaths);
153284
+ if (identities.length === 0)
153285
+ return null;
153286
+ if (identities.length === 1)
153287
+ return getMaxMemoryMutationId(db, identities[0]);
153288
+ const row = db.prepare(`SELECT MAX(id) AS max_id
153289
+ FROM memory_mutation_log
153290
+ WHERE project_path IN (${placeholders2(identities)})`).get(...identities);
153291
+ return row?.max_id ?? null;
153292
+ }
152727
153293
  var MEMORY_MUTATION_TYPES, TERMINAL_MUTATION_TYPES;
152728
153294
  var init_storage_memory_mutation_log = __esm(() => {
152729
153295
  MEMORY_MUTATION_TYPES = new Set(["archive", "delete", "update", "superseded"]);
@@ -153416,6 +153982,36 @@ function addStaleReduceStrippedIds(db, sessionId, ids) {
153416
153982
  sessionLog(sessionId, `stale_reduce_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
153417
153983
  return false;
153418
153984
  }
153985
+ function getProcessedImageStrippedIds(db, sessionId) {
153986
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153987
+ return new Set(parseStrippedBlob(row?.processed_image_stripped_ids));
153988
+ }
153989
+ function addProcessedImageStrippedIds(db, sessionId, ids) {
153990
+ const add = [...ids];
153991
+ if (add.length === 0)
153992
+ return true;
153993
+ ensureSessionMetaRow(db, sessionId);
153994
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
153995
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153996
+ const rawStored = row ? row.processed_image_stripped_ids ?? null : null;
153997
+ const current = new Set(parseStrippedBlob(rawStored));
153998
+ let changed = false;
153999
+ for (const id of add) {
154000
+ if (!current.has(id)) {
154001
+ current.add(id);
154002
+ changed = true;
154003
+ }
154004
+ }
154005
+ if (!changed)
154006
+ return true;
154007
+ const nextBlob = JSON.stringify([...current]);
154008
+ const result = db.prepare("UPDATE session_meta SET processed_image_stripped_ids = ? WHERE session_id = ? AND processed_image_stripped_ids IS ?").run(nextBlob, sessionId, rawStored);
154009
+ if (result.changes > 0)
154010
+ return true;
154011
+ }
154012
+ sessionLog(sessionId, `processed_image_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
154013
+ return false;
154014
+ }
153419
154015
  function isPendingCompactionMarker(value) {
153420
154016
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
153421
154017
  }
@@ -153594,6 +154190,8 @@ function clearSession(db, sessionId) {
153594
154190
  db.prepare("DELETE FROM source_contents WHERE session_id = ?").run(sessionId);
153595
154191
  db.prepare("DELETE FROM tags WHERE session_id = ?").run(sessionId);
153596
154192
  db.prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
154193
+ db.prepare("DELETE FROM session_projects WHERE session_id = ?").run(sessionId);
154194
+ db.prepare("DELETE FROM compartment_chunk_embeddings WHERE session_id = ?").run(sessionId);
153597
154195
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
153598
154196
  clearCompressionDepth(db, sessionId);
153599
154197
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
@@ -153700,9 +154298,9 @@ function buildStatusClause(status) {
153700
154298
  if (statuses.length === 0) {
153701
154299
  return null;
153702
154300
  }
153703
- const placeholders = statuses.map(() => "?").join(", ");
154301
+ const placeholders3 = statuses.map(() => "?").join(", ");
153704
154302
  return {
153705
- sql: `status IN (${placeholders})`,
154303
+ sql: `status IN (${placeholders3})`,
153706
154304
  params: statuses
153707
154305
  };
153708
154306
  }
@@ -153908,19 +154506,6 @@ function getProjectState(db, projectPath) {
153908
154506
  WHERE project_path = ?`).get(projectPath);
153909
154507
  return row ? toProjectState(row) : null;
153910
154508
  }
153911
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
153912
- db.prepare(`INSERT INTO project_state
153913
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
153914
- VALUES (?, 1, 0, ?)
153915
- ON CONFLICT(project_path) DO UPDATE SET
153916
- project_memory_epoch = project_memory_epoch + 1,
153917
- updated_at = excluded.updated_at`).run(projectPath, now);
153918
- const state = getProjectState(db, projectPath);
153919
- if (!state) {
153920
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
153921
- }
153922
- return state;
153923
- }
153924
154509
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
153925
154510
  db.prepare(`INSERT INTO project_state
153926
154511
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -153956,8 +154541,8 @@ function getSourceContents(db, sessionId, tagIds) {
153956
154541
  if (tagIds.length === 0) {
153957
154542
  return new Map;
153958
154543
  }
153959
- const placeholders = tagIds.map(() => "?").join(", ");
153960
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
154544
+ const placeholders3 = tagIds.map(() => "?").join(", ");
154545
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders3})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
153961
154546
  const sources = new Map;
153962
154547
  for (const row of rows) {
153963
154548
  sources.set(row.tag_id, row.content);
@@ -154021,15 +154606,22 @@ function ownerMessageIdForTagRow(row) {
154021
154606
  }
154022
154607
  return row.message_id.replace(CONTENT_ID_SUFFIX, "");
154023
154608
  }
154024
- function getActiveTagTokenAggregate(db, sessionId) {
154025
- const row = db.prepare(`SELECT
154609
+ function getActiveTagTokenAggregate(db, sessionId, protectedTags = 0) {
154610
+ const toolOutputExpr = protectedTags > 0 ? `COALESCE(SUM(CASE WHEN type = 'tool' AND tag_number < (
154611
+ SELECT tag_number FROM tags
154612
+ WHERE session_id = ? AND status = 'active'
154613
+ ORDER BY tag_number DESC LIMIT 1 OFFSET ?
154614
+ ) THEN COALESCE(token_count, 0) ELSE 0 END), 0)` : `COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)`;
154615
+ const sql = `SELECT
154026
154616
  COALESCE(SUM(CASE WHEN type != 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)
154027
154617
  + COALESCE(SUM(COALESCE(reasoning_token_count, 0)), 0) AS conversation,
154028
154618
  COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) + COALESCE(input_token_count, 0) ELSE 0 END), 0) AS tool_call,
154029
- COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0) AS tool_output,
154619
+ ${toolOutputExpr} AS tool_output,
154030
154620
  COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
154031
154621
  FROM tags
154032
- WHERE session_id = ? AND status = 'active'`).get(sessionId);
154622
+ WHERE session_id = ? AND status = 'active'`;
154623
+ const params = protectedTags > 0 ? [sessionId, protectedTags - 1, sessionId] : [sessionId];
154624
+ const row = db.prepare(sql).get(...params);
154033
154625
  return {
154034
154626
  conversation: row?.conversation ?? 0,
154035
154627
  toolCall: row?.tool_call ?? 0,
@@ -154267,8 +154859,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
154267
154859
  }
154268
154860
  return all;
154269
154861
  }
154270
- const placeholders = tagNumbers.map(() => "?").join(",");
154271
- const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
154862
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
154863
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders3}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
154272
154864
  return rows.map(toTagEntry);
154273
154865
  }
154274
154866
  function getMaxDroppedTagNumber(db, sessionId) {
@@ -154421,6 +155013,7 @@ var init_storage = __esm(async () => {
154421
155013
  init_storage_source();
154422
155014
  init_storage_tags();
154423
155015
  init_storage_v22_backfill_failures();
155016
+ init_workspaces();
154424
155017
  await __promiseAll([
154425
155018
  init_message_index(),
154426
155019
  init_migrations(),
@@ -163872,7 +164465,7 @@ function isEmbeddingRow(row) {
163872
164465
  if (row === null || typeof row !== "object")
163873
164466
  return false;
163874
164467
  const candidate = row;
163875
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
164468
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
163876
164469
  }
163877
164470
  function toFloat32Array(blob) {
163878
164471
  if (blob instanceof Uint8Array) {
@@ -163892,7 +164485,7 @@ function getSaveEmbeddingStatement(db) {
163892
164485
  function getLoadAllEmbeddingsStatement(db) {
163893
164486
  let stmt = loadAllEmbeddingsStatements.get(db);
163894
164487
  if (!stmt) {
163895
- stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
164488
+ stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding, memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
163896
164489
  loadAllEmbeddingsStatements.set(db, stmt);
163897
164490
  }
163898
164491
  return stmt;
@@ -163921,7 +164514,10 @@ function loadAllEmbeddings(db, projectPath) {
163921
164514
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
163922
164515
  const embeddings = new Map;
163923
164516
  for (const row of rows) {
163924
- embeddings.set(row.memoryId, toFloat32Array(row.embedding));
164517
+ embeddings.set(row.memoryId, {
164518
+ embedding: toFloat32Array(row.embedding),
164519
+ modelId: row.modelId
164520
+ });
163925
164521
  }
163926
164522
  return embeddings;
163927
164523
  }
@@ -163984,13 +164580,13 @@ var init_embedding_cache = __esm(() => {
163984
164580
  });
163985
164581
 
163986
164582
  // src/features/magic-context/memory/normalize-hash.ts
163987
- import { createHash as createHash6 } from "node:crypto";
164583
+ import { createHash as createHash7 } from "node:crypto";
163988
164584
  function normalizeMemoryContent(content) {
163989
164585
  return content.toLowerCase().replace(/\s+/g, " ").trim();
163990
164586
  }
163991
164587
  function computeNormalizedHash(content) {
163992
164588
  const normalized = normalizeMemoryContent(content);
163993
- return createHash6("md5").update(normalized).digest("hex");
164589
+ return createHash7("md5").update(normalized).digest("hex");
163994
164590
  }
163995
164591
  var init_normalize_hash = () => {};
163996
164592
 
@@ -164104,8 +164700,8 @@ function getMemoriesByProjectStatement(db, statuses) {
164104
164700
  }
164105
164701
  let stmt = statements.get(db);
164106
164702
  if (!stmt) {
164107
- const placeholders = statuses.map(() => "?").join(", ");
164108
- stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
164703
+ const placeholders3 = statuses.map(() => "?").join(", ");
164704
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders3}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
164109
164705
  statements.set(db, stmt);
164110
164706
  }
164111
164707
  return stmt;
@@ -164226,6 +164822,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
164226
164822
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
164227
164823
  return rows.map(toMemory);
164228
164824
  }
164825
+ function sqlPlaceholders(values) {
164826
+ return values.map(() => "?").join(", ");
164827
+ }
164828
+ function uniqueValues(values) {
164829
+ return [...new Set(values.filter((value) => value.length > 0))];
164830
+ }
164831
+ function buildWorkspaceMemorySqlFilter(args) {
164832
+ if (args.shareCategories === null || args.shareCategories === undefined) {
164833
+ return { clause: "", params: [], active: false };
164834
+ }
164835
+ const identities = uniqueValues(args.identities);
164836
+ const identitySet = new Set(identities);
164837
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
164838
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
164839
+ if (foreignIdentities.length === 0) {
164840
+ return { clause: "", params: [], active: false };
164841
+ }
164842
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
164843
+ const shareCategories = uniqueValues([...args.shareCategories]);
164844
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
164845
+ const predicates = [];
164846
+ const params = [];
164847
+ if (ownIdentities.length > 0) {
164848
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
164849
+ params.push(...ownIdentities);
164850
+ }
164851
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
164852
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
164853
+ params.push(...foreignIdentities, ...shareCategories);
164854
+ }
164855
+ if (predicates.length === 0) {
164856
+ return { clause: " AND 0 = 1", params: [], active: true };
164857
+ }
164858
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
164859
+ }
164860
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
164861
+ const identities = uniqueValues(projectPaths);
164862
+ if (identities.length === 0 || statuses.length === 0)
164863
+ return [];
164864
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164865
+ identities,
164866
+ ownIdentities,
164867
+ shareCategories
164868
+ });
164869
+ if (identities.length === 1 && !sharingFilter.active) {
164870
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
164871
+ }
164872
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164873
+ FROM memories
164874
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164875
+ AND status IN (${sqlPlaceholders(statuses)})
164876
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164877
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164878
+ return rows.map(toMemory);
164879
+ }
164880
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
164881
+ const identities = uniqueValues(projectPaths);
164882
+ if (identities.length === 0)
164883
+ return 0;
164884
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164885
+ identities,
164886
+ ownIdentities,
164887
+ shareCategories
164888
+ });
164889
+ if (identities.length === 1 && !sharingFilter.active) {
164890
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
164891
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
164892
+ }
164893
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
164894
+ FROM memories
164895
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
164896
+ return typeof row?.max_id === "number" ? row.max_id : 0;
164897
+ }
164898
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
164899
+ const identities = uniqueValues(projectPaths);
164900
+ if (identities.length === 0)
164901
+ return [];
164902
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164903
+ identities,
164904
+ ownIdentities,
164905
+ shareCategories
164906
+ });
164907
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164908
+ FROM memories
164909
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164910
+ AND id > ?
164911
+ AND status IN ('active', 'permanent')
164912
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164913
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164914
+ return rows.map(toMemory);
164915
+ }
164229
164916
  function getAllActiveMemoriesForMigration(db, projectPath) {
164230
164917
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
164231
164918
  return rows.map(toMemory);
@@ -164323,6 +165010,7 @@ function getMemoryCountsByStatus(db, projectPath) {
164323
165010
  }
164324
165011
  var COLUMN_MAP, MEMORY_CATEGORY_LOOKUP, MEMORY_STATUS_LOOKUP, MEMORY_SOURCE_TYPE_LOOKUP, VERIFICATION_STATUS_LOOKUP, insertMemoryStatements, getMemoryByHashStatements, getMemoryByIdStatements, activeMemoriesNoExpiryStatements, updateMemorySeenCountStatements, updateMemoryRetrievalCountStatements, updateMemoryStatusStatements, updateArchivedMemoryStatements, updateMemoryVerificationStatements, updateMemoryContentStatements, supersededMemoryStatements, mergeMemoryStatsStatements, deleteMemoryStatements, deleteMemoryEmbeddingStatements, deleteEmbeddingOnContentUpdateStatements, getMemoryCountStatements, getMemoryCountByProjectStatements, getMemoryCountsByStatusStatements, memoriesByProjectStatements, memoryImportanceColumnCache;
164325
165012
  var init_storage_memory = __esm(() => {
165013
+ init_constants();
164326
165014
  init_embedding_cache();
164327
165015
  init_normalize_hash();
164328
165016
  COLUMN_MAP = {
@@ -164428,8 +165116,8 @@ function getUserMemoryCandidates(db) {
164428
165116
  function deleteUserMemoryCandidates(db, ids) {
164429
165117
  if (ids.length === 0)
164430
165118
  return;
164431
- const placeholders = ids.map(() => "?").join(",");
164432
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
165119
+ const placeholders3 = ids.map(() => "?").join(",");
165120
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
164433
165121
  }
164434
165122
  function insertUserMemory(db, content, sourceCandidateIds) {
164435
165123
  const now = Date.now();
@@ -164462,6 +165150,445 @@ function parseUserMemoryRow(row) {
164462
165150
  };
164463
165151
  }
164464
165152
 
165153
+ // src/features/magic-context/compartment-chunk-embedding.ts
165154
+ import { createHash as createHash8 } from "node:crypto";
165155
+ function getLoadFtsRowsStatement(db) {
165156
+ let stmt = loadFtsRowsStatements.get(db);
165157
+ if (!stmt) {
165158
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
165159
+ FROM message_history_fts
165160
+ WHERE session_id = ?
165161
+ AND message_ordinal >= ?
165162
+ AND message_ordinal <= ?
165163
+ AND role IN ('user', 'assistant')
165164
+ ORDER BY message_ordinal ASC`);
165165
+ loadFtsRowsStatements.set(db, stmt);
165166
+ }
165167
+ return stmt;
165168
+ }
165169
+ function getExistingHashStatement(db, scopedToProject) {
165170
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
165171
+ let stmt = map2.get(db);
165172
+ if (!stmt) {
165173
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
165174
+ FROM compartment_chunk_embeddings
165175
+ WHERE compartment_id = ?
165176
+ AND model_id = ?
165177
+ ${scopedToProject ? "AND project_path = ?" : ""}
165178
+ ORDER BY window_index ASC`);
165179
+ map2.set(db, stmt);
165180
+ }
165181
+ return stmt;
165182
+ }
165183
+ function getDeleteByCompartmentStatement(db) {
165184
+ let stmt = deleteByCompartmentStatements.get(db);
165185
+ if (!stmt) {
165186
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
165187
+ deleteByCompartmentStatements.set(db, stmt);
165188
+ }
165189
+ return stmt;
165190
+ }
165191
+ function getInsertEmbeddingStatement(db) {
165192
+ let stmt = insertEmbeddingStatements.get(db);
165193
+ if (!stmt) {
165194
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
165195
+ compartment_id, session_id, project_path, harness, window_index,
165196
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
165197
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
165198
+ insertEmbeddingStatements.set(db, stmt);
165199
+ }
165200
+ return stmt;
165201
+ }
165202
+ function getDistinctModelStatement(db) {
165203
+ let stmt = distinctModelStatements.get(db);
165204
+ if (!stmt) {
165205
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
165206
+ FROM compartment_chunk_embeddings
165207
+ WHERE project_path = ?`);
165208
+ distinctModelStatements.set(db, stmt);
165209
+ }
165210
+ return stmt;
165211
+ }
165212
+ function getClearProjectStatement(db) {
165213
+ let stmt = clearProjectStatements.get(db);
165214
+ if (!stmt) {
165215
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
165216
+ clearProjectStatements.set(db, stmt);
165217
+ }
165218
+ return stmt;
165219
+ }
165220
+ function getClearProjectModelStatement(db) {
165221
+ let stmt = clearProjectModelStatements.get(db);
165222
+ if (!stmt) {
165223
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
165224
+ clearProjectModelStatements.set(db, stmt);
165225
+ }
165226
+ return stmt;
165227
+ }
165228
+ function getSearchRowsStatement(db, withModel) {
165229
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
165230
+ let stmt = map2.get(db);
165231
+ if (!stmt) {
165232
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
165233
+ e.session_id AS sessionId,
165234
+ c.title AS title,
165235
+ c.start_message AS compartmentStart,
165236
+ c.end_message AS compartmentEnd,
165237
+ e.window_index AS windowIndex,
165238
+ e.start_ordinal AS windowStart,
165239
+ e.end_ordinal AS windowEnd,
165240
+ e.chunk_hash AS chunkHash,
165241
+ e.model_id AS modelId,
165242
+ e.dims AS dims,
165243
+ e.vector AS vector
165244
+ FROM compartment_chunk_embeddings e
165245
+ JOIN compartments c ON c.id = e.compartment_id
165246
+ WHERE e.session_id = ?
165247
+ AND e.project_path = ?
165248
+ ${withModel ? "AND e.model_id = ?" : ""}
165249
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
165250
+ map2.set(db, stmt);
165251
+ }
165252
+ return stmt;
165253
+ }
165254
+ function isFinitePositiveInteger(value) {
165255
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
165256
+ }
165257
+ function normalizeCompartmentChunkMaxInputTokens(value) {
165258
+ if (!isFinitePositiveInteger(value)) {
165259
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
165260
+ }
165261
+ return Math.max(1, Math.floor(value));
165262
+ }
165263
+ function normalizeContent(text) {
165264
+ return text.replace(/\s+/g, " ").trim();
165265
+ }
165266
+ function formatOrdinalRange(start, end) {
165267
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
165268
+ }
165269
+ function rolePrefix(role) {
165270
+ if (role === "user")
165271
+ return "U";
165272
+ if (role === "assistant")
165273
+ return "A";
165274
+ return null;
165275
+ }
165276
+ function parseOrdinal(value) {
165277
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
165278
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
165279
+ }
165280
+ function parseCanonicalLineRange(line) {
165281
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
165282
+ if (!match)
165283
+ return null;
165284
+ const start = Number.parseInt(match[1], 10);
165285
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
165286
+ if (!Number.isFinite(start) || !Number.isFinite(end))
165287
+ return null;
165288
+ return { start, end };
165289
+ }
165290
+ function hashChunkText(text) {
165291
+ return createHash8("sha256").update(text).digest("hex");
165292
+ }
165293
+ function vectorBlob(vector) {
165294
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
165295
+ }
165296
+ function toFloat32Array2(blob) {
165297
+ if (blob instanceof Uint8Array) {
165298
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
165299
+ return new Float32Array(buffer2);
165300
+ }
165301
+ return new Float32Array(blob.slice(0));
165302
+ }
165303
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
165304
+ if (endOrdinal < startOrdinal)
165305
+ return "";
165306
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
165307
+ const lines = [];
165308
+ let current = null;
165309
+ const flush2 = () => {
165310
+ if (!current || current.parts.length === 0)
165311
+ return;
165312
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
165313
+ current = null;
165314
+ };
165315
+ for (const row of rows) {
165316
+ const ordinal = parseOrdinal(row.messageOrdinal);
165317
+ const prefix = rolePrefix(row.role);
165318
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
165319
+ if (ordinal === null || prefix === null || content.length === 0)
165320
+ continue;
165321
+ if (current && current.role === prefix) {
165322
+ current.end = ordinal;
165323
+ current.parts.push(content);
165324
+ continue;
165325
+ }
165326
+ flush2();
165327
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
165328
+ }
165329
+ flush2();
165330
+ return lines.join(`
165331
+ `);
165332
+ }
165333
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
165334
+ const lines = [];
165335
+ for (const rawLine of chunkText.split(/\r?\n/)) {
165336
+ const line = rawLine.trim();
165337
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
165338
+ if (!match)
165339
+ continue;
165340
+ const lineStart = Number.parseInt(match[2], 10);
165341
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
165342
+ if (startOrdinal != null && lineEnd < startOrdinal)
165343
+ continue;
165344
+ if (endOrdinal != null && lineStart > endOrdinal)
165345
+ continue;
165346
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
165347
+ const ordinalSpan = lineEnd - lineStart + 1;
165348
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
165349
+ if (ordinalSpan === rawParts.length) {
165350
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
165351
+ if (part.startsWith("TC:"))
165352
+ return false;
165353
+ if (startOrdinal != null && ordinal < startOrdinal)
165354
+ return false;
165355
+ if (endOrdinal != null && ordinal > endOrdinal)
165356
+ return false;
165357
+ return true;
165358
+ });
165359
+ if (retained.length === 0)
165360
+ continue;
165361
+ const retainedStart = retained[0].ordinal;
165362
+ const retainedEnd = retained[retained.length - 1].ordinal;
165363
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
165364
+ continue;
165365
+ }
165366
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
165367
+ if (parts.length === 0)
165368
+ continue;
165369
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
165370
+ }
165371
+ return lines.join(`
165372
+ `);
165373
+ }
165374
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
165375
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
165376
+ if (lines.length === 0 || endOrdinal < startOrdinal)
165377
+ return [];
165378
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
165379
+ const effectiveMax = Math.max(1, Math.floor(normalizedMax * CHUNK_WINDOW_SAFETY_RATIO));
165380
+ const fullText = lines.join(`
165381
+ `);
165382
+ if (estimateTokens(fullText) <= effectiveMax) {
165383
+ return [
165384
+ {
165385
+ windowIndex: 0,
165386
+ startOrdinal,
165387
+ endOrdinal,
165388
+ text: fullText,
165389
+ chunkHash: hashChunkText(fullText)
165390
+ }
165391
+ ];
165392
+ }
165393
+ const windows = [];
165394
+ let currentLines = [];
165395
+ let currentStart = null;
165396
+ let currentEnd = null;
165397
+ let currentTokens = 0;
165398
+ const flush2 = () => {
165399
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
165400
+ return;
165401
+ const text = currentLines.join(`
165402
+ `);
165403
+ windows.push({
165404
+ windowIndex: windows.length + 1,
165405
+ startOrdinal: currentStart,
165406
+ endOrdinal: currentEnd,
165407
+ text,
165408
+ chunkHash: hashChunkText(text)
165409
+ });
165410
+ currentLines = [];
165411
+ currentStart = null;
165412
+ currentEnd = null;
165413
+ currentTokens = 0;
165414
+ };
165415
+ for (const line of lines) {
165416
+ const range = parseCanonicalLineRange(line);
165417
+ const lineStart = range?.start ?? startOrdinal;
165418
+ const lineEnd = range?.end ?? lineStart;
165419
+ const lineTokens = estimateTokens(line);
165420
+ if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
165421
+ flush2();
165422
+ }
165423
+ if (currentLines.length === 0) {
165424
+ currentStart = lineStart;
165425
+ }
165426
+ currentLines.push(line);
165427
+ currentEnd = lineEnd;
165428
+ currentTokens += lineTokens;
165429
+ }
165430
+ flush2();
165431
+ return windows;
165432
+ }
165433
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
165434
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
165435
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
165436
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
165437
+ }
165438
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
165439
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
165440
+ if (existing.size !== windows.length)
165441
+ return false;
165442
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
165443
+ }
165444
+ function replaceCompartmentChunkEmbeddings(db, rows) {
165445
+ if (rows.length === 0)
165446
+ return;
165447
+ const compartmentId = rows[0].compartmentId;
165448
+ const now = Date.now();
165449
+ db.transaction(() => {
165450
+ getDeleteByCompartmentStatement(db).run(compartmentId);
165451
+ const insert = getInsertEmbeddingStatement(db);
165452
+ for (const row of rows) {
165453
+ 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);
165454
+ }
165455
+ })();
165456
+ }
165457
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
165458
+ const rows = getDistinctModelStatement(db).all(projectPath);
165459
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165460
+ }
165461
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
165462
+ if (modelId) {
165463
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
165464
+ }
165465
+ return getClearProjectStatement(db).run(projectPath).changes;
165466
+ }
165467
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
165468
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
165469
+ 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) => ({
165470
+ compartmentId: row.compartmentId,
165471
+ sessionId: row.sessionId,
165472
+ title: row.title,
165473
+ startOrdinal: row.compartmentStart,
165474
+ endOrdinal: row.compartmentEnd,
165475
+ windowIndex: row.windowIndex,
165476
+ windowStartOrdinal: row.windowStart,
165477
+ windowEndOrdinal: row.windowEnd,
165478
+ chunkHash: row.chunkHash,
165479
+ modelId: row.modelId,
165480
+ dims: row.dims,
165481
+ vector: toFloat32Array2(row.vector)
165482
+ }));
165483
+ }
165484
+ function mapBackfillCandidateRows(rows) {
165485
+ return rows.filter((row) => {
165486
+ if (row === null || typeof row !== "object")
165487
+ return false;
165488
+ const candidate = row;
165489
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
165490
+ }).map((row) => ({
165491
+ id: row.id,
165492
+ sessionId: row.sessionId,
165493
+ startMessage: row.startMessage,
165494
+ endMessage: row.endMessage,
165495
+ title: row.title
165496
+ }));
165497
+ }
165498
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
165499
+ if (excludeIds && excludeIds.length > 0) {
165500
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
165501
+ const stmt2 = db.prepare(`SELECT c.id AS id,
165502
+ c.session_id AS sessionId,
165503
+ c.start_message AS startMessage,
165504
+ c.end_message AS endMessage,
165505
+ c.title AS title
165506
+ FROM compartments c
165507
+ JOIN session_projects sp
165508
+ ON sp.session_id = c.session_id
165509
+ AND sp.harness = c.harness
165510
+ AND sp.project_path = ?
165511
+ WHERE c.session_id = ?
165512
+ AND c.start_message IS NOT NULL
165513
+ AND c.end_message IS NOT NULL
165514
+ AND c.id NOT IN (${placeholders3})
165515
+ AND NOT EXISTS (
165516
+ SELECT 1
165517
+ FROM compartment_chunk_embeddings current
165518
+ WHERE current.compartment_id = c.id
165519
+ AND current.project_path = ?
165520
+ AND current.model_id = ?
165521
+ )
165522
+ ORDER BY c.start_message ASC, c.id ASC
165523
+ LIMIT ?`);
165524
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
165525
+ return mapBackfillCandidateRows(rows2);
165526
+ }
165527
+ let stmt = sessionBackfillCandidateStatements.get(db);
165528
+ if (!stmt) {
165529
+ stmt = db.prepare(`SELECT c.id AS id,
165530
+ c.session_id AS sessionId,
165531
+ c.start_message AS startMessage,
165532
+ c.end_message AS endMessage,
165533
+ c.title AS title
165534
+ FROM compartments c
165535
+ JOIN session_projects sp
165536
+ ON sp.session_id = c.session_id
165537
+ AND sp.harness = c.harness
165538
+ AND sp.project_path = ?
165539
+ WHERE c.session_id = ?
165540
+ AND c.start_message IS NOT NULL
165541
+ AND c.end_message IS NOT NULL
165542
+ AND NOT EXISTS (
165543
+ SELECT 1
165544
+ FROM compartment_chunk_embeddings current
165545
+ WHERE current.compartment_id = c.id
165546
+ AND current.project_path = ?
165547
+ AND current.model_id = ?
165548
+ )
165549
+ ORDER BY c.start_message ASC, c.id ASC
165550
+ LIMIT ?`);
165551
+ sessionBackfillCandidateStatements.set(db, stmt);
165552
+ }
165553
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
165554
+ return mapBackfillCandidateRows(rows);
165555
+ }
165556
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
165557
+ const row = db.prepare(`SELECT COUNT(*) AS n
165558
+ FROM compartments c
165559
+ JOIN session_projects sp
165560
+ ON sp.session_id = c.session_id
165561
+ AND sp.harness = c.harness
165562
+ AND sp.project_path = ?
165563
+ WHERE c.session_id = ?
165564
+ AND c.start_message IS NOT NULL
165565
+ AND c.end_message IS NOT NULL
165566
+ AND NOT EXISTS (
165567
+ SELECT 1
165568
+ FROM compartment_chunk_embeddings current
165569
+ WHERE current.compartment_id = c.id
165570
+ AND current.project_path = ?
165571
+ AND current.model_id = ?
165572
+ )`).get(projectPath, sessionId, projectPath, modelId);
165573
+ return typeof row?.n === "number" ? row.n : 0;
165574
+ }
165575
+ 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;
165576
+ var init_compartment_chunk_embedding = __esm(() => {
165577
+ init_read_session_formatting();
165578
+ loadFtsRowsStatements = new WeakMap;
165579
+ existingHashStatements = new WeakMap;
165580
+ existingHashByProjectStatements = new WeakMap;
165581
+ deleteByCompartmentStatements = new WeakMap;
165582
+ insertEmbeddingStatements = new WeakMap;
165583
+ distinctModelStatements = new WeakMap;
165584
+ clearProjectStatements = new WeakMap;
165585
+ clearProjectModelStatements = new WeakMap;
165586
+ searchRowsStatements = new WeakMap;
165587
+ searchRowsByModelStatements = new WeakMap;
165588
+ backfillCandidateStatements = new WeakMap;
165589
+ sessionBackfillCandidateStatements = new WeakMap;
165590
+ });
165591
+
164465
165592
  // src/features/magic-context/memory/cosine-similarity.ts
164466
165593
  function cosineSimilarity(a, b) {
164467
165594
  if (a.length !== b.length) {
@@ -164619,19 +165746,19 @@ function isArrayLikeNumber(value) {
164619
165746
  }
164620
165747
  return arr.length === 0 || typeof arr[0] === "number";
164621
165748
  }
164622
- function toFloat32Array2(values) {
165749
+ function toFloat32Array3(values) {
164623
165750
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
164624
165751
  }
164625
165752
  function extractBatchEmbeddings(result, expectedCount) {
164626
165753
  const { data } = result;
164627
165754
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
164628
- return data.map((entry) => toFloat32Array2(entry));
165755
+ return data.map((entry) => toFloat32Array3(entry));
164629
165756
  }
164630
165757
  if (!isArrayLikeNumber(data)) {
164631
165758
  log("[magic-context] embedding batch returned unexpected data shape");
164632
165759
  return Array.from({ length: expectedCount }, () => null);
164633
165760
  }
164634
- const flatData = toFloat32Array2(data);
165761
+ const flatData = toFloat32Array3(data);
164635
165762
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
164636
165763
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
164637
165764
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -164646,6 +165773,7 @@ function extractBatchEmbeddings(result, expectedCount) {
164646
165773
 
164647
165774
  class LocalEmbeddingProvider {
164648
165775
  modelId;
165776
+ maxInputTokens;
164649
165777
  model;
164650
165778
  pipeline = null;
164651
165779
  initPromise = null;
@@ -164653,8 +165781,9 @@ class LocalEmbeddingProvider {
164653
165781
  disposing = false;
164654
165782
  disposePromise = null;
164655
165783
  inFlightWaiters = [];
164656
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
165784
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
164657
165785
  this.model = model;
165786
+ this.maxInputTokens = maxInputTokens;
164658
165787
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
164659
165788
  }
164660
165789
  async initialize() {
@@ -164911,9 +166040,17 @@ var init_embedding_ssrf = __esm(() => {
164911
166040
  function normalizeEndpoint3(endpoint) {
164912
166041
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
164913
166042
  }
166043
+ function embeddingModelsMatch(served, requested) {
166044
+ const a = served.trim().toLowerCase();
166045
+ const b = requested.trim().toLowerCase();
166046
+ if (a.length === 0 || b.length === 0)
166047
+ return true;
166048
+ return a === b || a.includes(b) || b.includes(a);
166049
+ }
164914
166050
 
164915
166051
  class OpenAICompatibleEmbeddingProvider {
164916
166052
  modelId;
166053
+ maxInputTokens;
164917
166054
  endpoint;
164918
166055
  model;
164919
166056
  apiKey;
@@ -164923,6 +166060,7 @@ class OpenAICompatibleEmbeddingProvider {
164923
166060
  failureTimes = [];
164924
166061
  circuitOpenUntil = 0;
164925
166062
  openLogged = false;
166063
+ modelMismatchLogged = false;
164926
166064
  halfOpenProbeInFlight = false;
164927
166065
  constructor(options) {
164928
166066
  this.endpoint = normalizeEndpoint3(options.endpoint);
@@ -164930,11 +166068,13 @@ class OpenAICompatibleEmbeddingProvider {
164930
166068
  this.apiKey = options.apiKey?.trim() ?? "";
164931
166069
  this.inputType = options.inputType?.trim() ?? "";
164932
166070
  this.truncate = options.truncate?.trim() ?? "";
166071
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
164933
166072
  this.modelId = getEmbeddingProviderIdentity({
164934
166073
  provider: "openai-compatible",
164935
166074
  endpoint: this.endpoint,
164936
166075
  model: this.model,
164937
- ...this.apiKey ? { api_key: this.apiKey } : {}
166076
+ ...this.apiKey ? { api_key: this.apiKey } : {},
166077
+ ...this.inputType ? { input_type: this.inputType } : {}
164938
166078
  });
164939
166079
  }
164940
166080
  async initialize() {
@@ -165019,6 +166159,15 @@ class OpenAICompatibleEmbeddingProvider {
165019
166159
  this.recordFailure(isProbe);
165020
166160
  return Array.from({ length: texts.length }, () => null);
165021
166161
  }
166162
+ const servedModel = typeof body.model === "string" ? body.model : "";
166163
+ if (this.model && servedModel && !embeddingModelsMatch(servedModel, this.model)) {
166164
+ if (!this.modelMismatchLogged) {
166165
+ 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.`);
166166
+ this.modelMismatchLogged = true;
166167
+ }
166168
+ this.recordFailure(isProbe);
166169
+ return Array.from({ length: texts.length }, () => null);
166170
+ }
165022
166171
  const items = Array.isArray(body.data) ? body.data : [];
165023
166172
  const results = Array.from({ length: texts.length }, (_, index) => {
165024
166173
  const embedding = items[index]?.embedding;
@@ -165178,12 +166327,12 @@ function getCountEmbeddedStatement(db) {
165178
166327
  }
165179
166328
  return stmt;
165180
166329
  }
165181
- function getClearProjectStatement(db) {
165182
- let stmt = clearProjectStatements.get(db);
166330
+ function getClearProjectStatement2(db) {
166331
+ let stmt = clearProjectStatements2.get(db);
165183
166332
  if (!stmt) {
165184
166333
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
165185
166334
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
165186
- clearProjectStatements.set(db, stmt);
166335
+ clearProjectStatements2.set(db, stmt);
165187
166336
  }
165188
166337
  return stmt;
165189
166338
  }
@@ -165219,19 +166368,19 @@ function countEmbeddedCommits(db, projectPath) {
165219
166368
  return row?.count ?? 0;
165220
166369
  }
165221
166370
  function clearProjectCommitEmbeddings(db, projectPath) {
165222
- return getClearProjectStatement(db).run(projectPath).changes;
166371
+ return getClearProjectStatement2(db).run(projectPath).changes;
165223
166372
  }
165224
166373
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
165225
166374
  const rows = getDistinctModelIdStatement(db).all(projectPath);
165226
166375
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165227
166376
  }
165228
- var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements, distinctModelIdStatements;
166377
+ var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements2, distinctModelIdStatements;
165229
166378
  var init_storage_git_commit_embeddings = __esm(() => {
165230
166379
  saveStatements = new WeakMap;
165231
166380
  loadProjectStatements = new WeakMap;
165232
166381
  loadUnembeddedStatements = new WeakMap;
165233
166382
  countEmbeddedStatements = new WeakMap;
165234
- clearProjectStatements = new WeakMap;
166383
+ clearProjectStatements2 = new WeakMap;
165235
166384
  distinctModelIdStatements = new WeakMap;
165236
166385
  });
165237
166386
 
@@ -165357,13 +166506,90 @@ var init_sweep_coordinator = __esm(() => {
165357
166506
  GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
165358
166507
  });
165359
166508
 
166509
+ // src/features/magic-context/session-project-storage.ts
166510
+ function getUpsertSessionProjectStatement(db) {
166511
+ let stmt = upsertSessionProjectStatements.get(db);
166512
+ if (!stmt) {
166513
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
166514
+ VALUES (?, ?, ?, ?)
166515
+ ON CONFLICT(session_id, harness) DO UPDATE SET
166516
+ project_path = excluded.project_path,
166517
+ updated_at = excluded.updated_at
166518
+ WHERE session_projects.project_path <> excluded.project_path`);
166519
+ upsertSessionProjectStatements.set(db, stmt);
166520
+ }
166521
+ return stmt;
166522
+ }
166523
+ function getRepairSessionChunkProjectStatement(db) {
166524
+ let stmt = repairSessionChunkProjectStatements.get(db);
166525
+ if (!stmt) {
166526
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166527
+ SET project_path = ?
166528
+ WHERE session_id = ?
166529
+ AND harness = ?
166530
+ AND project_path <> ?`);
166531
+ repairSessionChunkProjectStatements.set(db, stmt);
166532
+ }
166533
+ return stmt;
166534
+ }
166535
+ function getRepairProjectChunkProjectStatement(db) {
166536
+ let stmt = repairProjectChunkProjectStatements.get(db);
166537
+ if (!stmt) {
166538
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166539
+ SET project_path = (
166540
+ SELECT sp.project_path
166541
+ FROM session_projects sp
166542
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166543
+ AND sp.harness = compartment_chunk_embeddings.harness
166544
+ LIMIT 1
166545
+ )
166546
+ WHERE EXISTS (
166547
+ SELECT 1
166548
+ FROM session_projects sp
166549
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166550
+ AND sp.harness = compartment_chunk_embeddings.harness
166551
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
166552
+ AND (
166553
+ sp.project_path = ?
166554
+ OR compartment_chunk_embeddings.project_path = ?
166555
+ )
166556
+ )`);
166557
+ repairProjectChunkProjectStatements.set(db, stmt);
166558
+ }
166559
+ return stmt;
166560
+ }
166561
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
166562
+ if (!sessionId || !projectPath)
166563
+ return;
166564
+ const harness = getHarness();
166565
+ const now = Date.now();
166566
+ db.transaction(() => {
166567
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
166568
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
166569
+ })();
166570
+ }
166571
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
166572
+ if (!projectPath)
166573
+ return 0;
166574
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
166575
+ }
166576
+ var upsertSessionProjectStatements, repairSessionChunkProjectStatements, repairProjectChunkProjectStatements;
166577
+ var init_session_project_storage = __esm(() => {
166578
+ upsertSessionProjectStatements = new WeakMap;
166579
+ repairSessionChunkProjectStatements = new WeakMap;
166580
+ repairProjectChunkProjectStatements = new WeakMap;
166581
+ });
166582
+
165360
166583
  // src/features/magic-context/project-embedding-registry.ts
165361
- import { createHash as createHash7, randomUUID } from "node:crypto";
166584
+ import { createHash as createHash9, randomUUID } from "node:crypto";
165362
166585
  function resolveEmbeddingConfig(config2) {
165363
166586
  if (!config2 || config2.provider === "local") {
165364
166587
  return {
165365
166588
  provider: "local",
165366
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
166589
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
166590
+ ...config2?.max_input_tokens ? {
166591
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166592
+ } : {}
165367
166593
  };
165368
166594
  }
165369
166595
  if (config2.provider === "openai-compatible") {
@@ -165376,7 +166602,10 @@ function resolveEmbeddingConfig(config2) {
165376
166602
  endpoint: config2.endpoint.trim(),
165377
166603
  ...apiKey ? { api_key: apiKey } : {},
165378
166604
  ...inputType ? { input_type: inputType } : {},
165379
- ...truncate ? { truncate } : {}
166605
+ ...truncate ? { truncate } : {},
166606
+ ...config2.max_input_tokens ? {
166607
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166608
+ } : {}
165380
166609
  };
165381
166610
  }
165382
166611
  return { provider: "off" };
@@ -165394,10 +166623,11 @@ function createProvider(config2) {
165394
166623
  model: config2.model,
165395
166624
  apiKey: config2.api_key,
165396
166625
  inputType: config2.input_type,
165397
- truncate: config2.truncate
166626
+ truncate: config2.truncate,
166627
+ maxInputTokens: config2.max_input_tokens
165398
166628
  });
165399
166629
  }
165400
- return new LocalEmbeddingProvider(config2.model);
166630
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165401
166631
  }
165402
166632
  function stableStringify2(value) {
165403
166633
  if (Array.isArray(value)) {
@@ -165410,7 +166640,7 @@ function stableStringify2(value) {
165410
166640
  return JSON.stringify(value);
165411
166641
  }
165412
166642
  function sha256Prefix(value, length = 16) {
165413
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
166643
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
165414
166644
  }
165415
166645
  function getRuntimeFingerprint(config2) {
165416
166646
  if (config2.provider === "off") {
@@ -165418,6 +166648,18 @@ function getRuntimeFingerprint(config2) {
165418
166648
  }
165419
166649
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
165420
166650
  }
166651
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
166652
+ if (config2.provider === "off") {
166653
+ return OFF_PROVIDER_IDENTITY;
166654
+ }
166655
+ const chunkIdentity = {
166656
+ providerIdentity,
166657
+ chunkerVersion: 2,
166658
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
166659
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
166660
+ };
166661
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
166662
+ }
165421
166663
  function sameFeatures(a, b) {
165422
166664
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
165423
166665
  }
@@ -165434,7 +166676,8 @@ function snapshotFor(registration) {
165434
166676
  features: { ...registration.features },
165435
166677
  enabled,
165436
166678
  gitCommitEnabled,
165437
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
166679
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
166680
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
165438
166681
  };
165439
166682
  }
165440
166683
  function disposeProvider(provider) {
@@ -165454,7 +166697,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
165454
166697
  }
165455
166698
  return false;
165456
166699
  }
165457
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
166700
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
165458
166701
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
165459
166702
  return false;
165460
166703
  }
@@ -165475,6 +166718,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
165475
166718
  wiped = true;
165476
166719
  }
165477
166720
  }
166721
+ if (features.memoryEnabled) {
166722
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
166723
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
166724
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
166725
+ clearChunkEmbeddingsForProject(db, projectIdentity);
166726
+ wiped = true;
166727
+ }
166728
+ }
165478
166729
  })();
165479
166730
  return wiped;
165480
166731
  }
@@ -165482,10 +166733,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165482
166733
  const resolvedConfig = resolveEmbeddingConfig(config2);
165483
166734
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
165484
166735
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
166736
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
165485
166737
  const prior = projectRegistrations.get(projectIdentity);
165486
166738
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
165487
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
165488
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
166739
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
166740
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
165489
166741
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
165490
166742
  const registration = {
165491
166743
  projectIdentity,
@@ -165497,6 +166749,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165497
166749
  generation,
165498
166750
  features: { ...features },
165499
166751
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
166752
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
165500
166753
  observationMode: false
165501
166754
  };
165502
166755
  projectRegistrations.set(projectIdentity, registration);
@@ -165519,6 +166772,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
165519
166772
  generation,
165520
166773
  features: { memoryEnabled: false, gitCommitEnabled: false },
165521
166774
  modelId: "off",
166775
+ chunkModelId: "off",
165522
166776
  observationMode: true
165523
166777
  };
165524
166778
  projectRegistrations.set(projectIdentity, registration);
@@ -165529,6 +166783,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
165529
166783
  const registration = projectRegistrations.get(projectIdentity);
165530
166784
  return registration ? snapshotFor(registration) : null;
165531
166785
  }
166786
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
166787
+ const registration = projectRegistrations.get(projectIdentity);
166788
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
166789
+ }
166790
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
166791
+ const registration = projectRegistrations.get(projectIdentity);
166792
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
166793
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
166794
+ }
165532
166795
  function getOrCreateProjectProvider(registration) {
165533
166796
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
165534
166797
  return null;
@@ -165623,10 +166886,136 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
165623
166886
  return 0;
165624
166887
  }
165625
166888
  }
165626
- var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
166889
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
166890
+ const noWork = [];
166891
+ if (candidates.length === 0)
166892
+ return { embedded: 0, noWork };
166893
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
166894
+ const prepared = [];
166895
+ for (const candidate of candidates) {
166896
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
166897
+ if (canonicalText.length === 0) {
166898
+ noWork.push(candidate.id);
166899
+ continue;
166900
+ }
166901
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
166902
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
166903
+ noWork.push(candidate.id);
166904
+ continue;
166905
+ }
166906
+ prepared.push({ candidate, windows });
166907
+ }
166908
+ if (prepared.length === 0)
166909
+ return { embedded: 0, noWork };
166910
+ let embedded = 0;
166911
+ let i = 0;
166912
+ while (i < prepared.length) {
166913
+ if (signal?.aborted)
166914
+ break;
166915
+ const slice = [];
166916
+ let windowCount = 0;
166917
+ do {
166918
+ const item = prepared[i];
166919
+ slice.push(item);
166920
+ windowCount += item.windows.length;
166921
+ i += 1;
166922
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
166923
+ const texts = [];
166924
+ for (const item of slice)
166925
+ texts.push(...item.windows.map((w) => w.text));
166926
+ try {
166927
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
166928
+ if (!result)
166929
+ continue;
166930
+ if (signal?.aborted)
166931
+ break;
166932
+ let offset = 0;
166933
+ for (const item of slice) {
166934
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
166935
+ offset += item.windows.length;
166936
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
166937
+ continue;
166938
+ }
166939
+ const rows = item.windows.map((window, index) => ({
166940
+ compartmentId: item.candidate.id,
166941
+ sessionId: item.candidate.sessionId,
166942
+ projectPath: projectIdentity,
166943
+ window,
166944
+ modelId,
166945
+ vector: vectors[index]
166946
+ }));
166947
+ replaceCompartmentChunkEmbeddings(db, rows);
166948
+ embedded += 1;
166949
+ }
166950
+ } catch (error51) {
166951
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
166952
+ }
166953
+ }
166954
+ return { embedded, noWork };
166955
+ }
166956
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
166957
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
166958
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
166959
+ return { status: "disabled", embedded: 0, total: 0 };
166960
+ }
166961
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
166962
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
166963
+ if (total === 0)
166964
+ return { status: "nothing", embedded: 0, total: 0 };
166965
+ const holderId = `session-embed-${randomUUID()}`;
166966
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
166967
+ if (!lease2.acquired)
166968
+ return { status: "busy", embedded: 0, total };
166969
+ const renewal = setInterval(() => {
166970
+ try {
166971
+ renewGitSweepLease(db, projectIdentity, holderId);
166972
+ } catch {}
166973
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
166974
+ renewal.unref?.();
166975
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
166976
+ const skipIds = [];
166977
+ let embedded = 0;
166978
+ let aborted2 = false;
166979
+ let providerStalled = false;
166980
+ try {
166981
+ options?.onProgress?.({ embedded, total });
166982
+ for (;; ) {
166983
+ if (options?.signal?.aborted) {
166984
+ aborted2 = true;
166985
+ break;
166986
+ }
166987
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
166988
+ if (candidates.length === 0)
166989
+ break;
166990
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
166991
+ for (const id of noWork)
166992
+ skipIds.push(id);
166993
+ if (n === 0 && noWork.length === 0) {
166994
+ providerStalled = true;
166995
+ break;
166996
+ }
166997
+ embedded += n;
166998
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
166999
+ await new Promise((resolve7) => setTimeout(resolve7, 0));
167000
+ }
167001
+ } finally {
167002
+ clearInterval(renewal);
167003
+ releaseGitSweepLease(db, projectIdentity, holderId);
167004
+ }
167005
+ if (aborted2)
167006
+ return { status: "aborted", embedded, total };
167007
+ if (providerStalled) {
167008
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
167009
+ if (remaining > 0)
167010
+ return { status: "stalled", embedded, total, remaining };
167011
+ }
167012
+ return { status: "done", embedded, total };
167013
+ }
167014
+ var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, CHUNK_DRAIN_BATCH_SIZE = 8, MAX_WINDOWS_PER_EMBED_CALL = 16, SESSION_EMBED_LEASE_RENEWAL_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
165627
167015
  var init_project_embedding_registry = __esm(() => {
165628
167016
  init_magic_context();
165629
167017
  init_logger();
167018
+ init_compartment_chunk_embedding();
165630
167019
  init_storage_git_commit_embeddings();
165631
167020
  init_sweep_coordinator();
165632
167021
  init_embedding_cache();
@@ -165634,7 +167023,9 @@ var init_project_embedding_registry = __esm(() => {
165634
167023
  init_embedding_local();
165635
167024
  init_embedding_openai();
165636
167025
  init_storage_memory_embeddings();
167026
+ init_session_project_storage();
165637
167027
  SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167028
+ SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
165638
167029
  projectRegistrations = new Map;
165639
167030
  loadUnembeddedMemoriesStatements = new WeakMap;
165640
167031
  });
@@ -165650,10 +167041,11 @@ function createProvider2(config2) {
165650
167041
  model: config2.model,
165651
167042
  apiKey: config2.api_key,
165652
167043
  inputType: config2.input_type,
165653
- truncate: config2.truncate
167044
+ truncate: config2.truncate,
167045
+ maxInputTokens: config2.max_input_tokens
165654
167046
  });
165655
167047
  }
165656
- return new LocalEmbeddingProvider(config2.model);
167048
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165657
167049
  }
165658
167050
  function getOrCreateProvider() {
165659
167051
  if (provider) {
@@ -165679,6 +167071,7 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
165679
167071
  var init_embedding = __esm(() => {
165680
167072
  init_magic_context();
165681
167073
  init_logger();
167074
+ init_compartment_chunk_embedding();
165682
167075
  init_embedding_identity();
165683
167076
  init_embedding_local();
165684
167077
  init_embedding_openai();
@@ -165702,6 +167095,23 @@ function getSearchStatement(db) {
165702
167095
  }
165703
167096
  return stmt;
165704
167097
  }
167098
+ function getUnionSearchStatement(db, arity) {
167099
+ let statements = unionSearchStatements.get(arity);
167100
+ if (!statements) {
167101
+ statements = new WeakMap;
167102
+ unionSearchStatements.set(arity, statements);
167103
+ }
167104
+ let stmt = statements.get(db);
167105
+ if (!stmt) {
167106
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
167107
+ 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 ?`);
167108
+ statements.set(db, stmt);
167109
+ }
167110
+ return stmt;
167111
+ }
167112
+ function uniqueProjectPaths2(projectPaths) {
167113
+ return [...new Set(projectPaths.filter((path6) => path6.length > 0))];
167114
+ }
165705
167115
  function sanitizeFtsQuery(query) {
165706
167116
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
165707
167117
  if (tokens.length === 0)
@@ -165720,10 +167130,33 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
165720
167130
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
165721
167131
  return rows.map(toMemory);
165722
167132
  }
165723
- var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
167133
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
167134
+ const identities = uniqueProjectPaths2(projectPaths);
167135
+ if (identities.length === 0)
167136
+ return [];
167137
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
167138
+ identities,
167139
+ ownIdentities,
167140
+ shareCategories,
167141
+ tableName: "memories"
167142
+ });
167143
+ if (identities.length === 1 && !sharingFilter.active) {
167144
+ return searchMemoriesFTS(db, identities[0], query, limit);
167145
+ }
167146
+ const trimmedQuery = query.trim();
167147
+ if (trimmedQuery.length === 0 || limit <= 0)
167148
+ return [];
167149
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
167150
+ if (sanitized.length === 0)
167151
+ return [];
167152
+ 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);
167153
+ return rows.map(toMemory);
167154
+ }
167155
+ var DEFAULT_SEARCH_LIMIT = 10, searchStatements, unionSearchStatements;
165724
167156
  var init_storage_memory_fts = __esm(() => {
165725
167157
  init_storage_memory();
165726
167158
  searchStatements = new WeakMap;
167159
+ unionSearchStatements = new Map;
165727
167160
  });
165728
167161
 
165729
167162
  // src/shared/models-dev-cache.ts
@@ -165917,26 +167350,54 @@ var init_rpc_notifications = __esm(() => {
165917
167350
  });
165918
167351
 
165919
167352
  // src/features/magic-context/compartment-embedding.ts
165920
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
167353
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
165921
167354
  if (compartments.length === 0)
165922
167355
  return;
165923
- const update = db.prepare("UPDATE compartments SET p1_embedding = ?, p1_embedding_model_id = ? WHERE id = ?");
165924
- for (const c of compartments) {
165925
- if (!c.p1 || c.p1.length === 0)
165926
- continue;
167356
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
167357
+ for (const compartment of compartments) {
165927
167358
  try {
165928
- const result = await embedTextForProject(projectPath, c.p1);
165929
- if (result) {
165930
- const blob = Buffer.from(result.vector.buffer, result.vector.byteOffset, result.vector.byteLength);
165931
- update.run(blob, result.modelId, c.id);
167359
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
167360
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
167361
+ if (canonicalText.length === 0)
167362
+ continue;
167363
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
167364
+ if (windows.length === 0)
167365
+ continue;
167366
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
167367
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167368
+ continue;
167369
+ }
167370
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
167371
+ if (!result)
167372
+ continue;
167373
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167374
+ continue;
167375
+ }
167376
+ const rows = [];
167377
+ for (const [index, window] of windows.entries()) {
167378
+ const vector = result.vectors[index];
167379
+ if (!vector)
167380
+ continue;
167381
+ rows.push({
167382
+ compartmentId: compartment.id,
167383
+ sessionId,
167384
+ projectPath,
167385
+ window,
167386
+ modelId: currentModelId,
167387
+ vector
167388
+ });
167389
+ }
167390
+ if (rows.length === windows.length) {
167391
+ replaceCompartmentChunkEmbeddings(db, rows);
165932
167392
  }
165933
167393
  } catch (error51) {
165934
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
167394
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
165935
167395
  }
165936
167396
  }
165937
167397
  }
165938
167398
  var init_compartment_embedding = __esm(() => {
165939
167399
  init_logger();
167400
+ init_compartment_chunk_embedding();
165940
167401
  init_project_embedding_registry();
165941
167402
  });
165942
167403
 
@@ -167050,55 +168511,6 @@ var init_historian_state_file = __esm(() => {
167050
168511
  init_data_path();
167051
168512
  });
167052
168513
 
167053
- // src/features/magic-context/memory/constants.ts
167054
- var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
167055
- var init_constants = __esm(() => {
167056
- V2_MEMORY_CATEGORIES = [
167057
- "PROJECT_RULES",
167058
- "ARCHITECTURE",
167059
- "CONSTRAINTS",
167060
- "CONFIG_VALUES",
167061
- "NAMING"
167062
- ];
167063
- PROMOTABLE_CATEGORIES = [
167064
- "PROJECT_RULES",
167065
- "ARCHITECTURE",
167066
- "CONSTRAINTS",
167067
- "CONFIG_VALUES",
167068
- "NAMING",
167069
- "ARCHITECTURE_DECISIONS",
167070
- "CONFIG_DEFAULTS",
167071
- "USER_PREFERENCES",
167072
- "USER_DIRECTIVES",
167073
- "ENVIRONMENT",
167074
- "WORKFLOW_RULES",
167075
- "KNOWN_ISSUES"
167076
- ];
167077
- CATEGORY_PRIORITY = [
167078
- "PROJECT_RULES",
167079
- "ARCHITECTURE",
167080
- "CONSTRAINTS",
167081
- "CONFIG_VALUES",
167082
- "NAMING",
167083
- "USER_DIRECTIVES",
167084
- "USER_PREFERENCES",
167085
- "CONFIG_DEFAULTS",
167086
- "ARCHITECTURE_DECISIONS",
167087
- "ENVIRONMENT",
167088
- "WORKFLOW_RULES",
167089
- "KNOWN_ISSUES"
167090
- ];
167091
- MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
167092
- acc[category] = index;
167093
- return acc;
167094
- }, {});
167095
- MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
167096
- CATEGORY_DEFAULT_TTL = {
167097
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
167098
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
167099
- };
167100
- });
167101
-
167102
168514
  // src/features/magic-context/memory/embedding-backfill.ts
167103
168515
  async function ensureMemoryEmbeddings(args) {
167104
168516
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167122,7 +168534,7 @@ async function ensureMemoryEmbeddings(args) {
167122
168534
  continue;
167123
168535
  }
167124
168536
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
167125
- staged.set(memory.id, embedding);
168537
+ staged.set(memory.id, { embedding, modelId: result.modelId });
167126
168538
  }
167127
168539
  })();
167128
168540
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167881,7 +169293,7 @@ ${prepared.block}
167881
169293
  if (!firstMessage || !textPart || isDroppedPlaceholder(textPart.text)) {
167882
169294
  messages.unshift({
167883
169295
  info: { role: "user", sessionID: sessionId },
167884
- parts: [{ type: "text", text: historyBlock }]
169296
+ parts: [{ type: "text", text: historyBlock, synthetic: true }]
167885
169297
  });
167886
169298
  } else {
167887
169299
  textPart.text = `${historyBlock}
@@ -167919,6 +169331,71 @@ function lastCompartmentBoundaryId(compartments) {
167919
169331
  const last = compartments.at(-1);
167920
169332
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
167921
169333
  }
169334
+ function resolveWorkspaceRenderContext(args) {
169335
+ if (!args.projectPath) {
169336
+ return {
169337
+ identities: [],
169338
+ expandedIdentities: [],
169339
+ ownIdentities: [],
169340
+ shareCategories: null,
169341
+ namesByIdentity: new Map,
169342
+ canonicalIdentityByStoredPath: new Map,
169343
+ isWorkspaced: false
169344
+ };
169345
+ }
169346
+ const identitySet = args.workspaceIdentitySet ?? resolveWorkspaceIdentitySet(args.db, args.projectPath);
169347
+ const isWorkspaced = identitySet.identities.length > 1;
169348
+ const expanded = expandWorkspaceIdentitySetWithAliases(args.db, identitySet.identities);
169349
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
169350
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
169351
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === args.projectPath);
169352
+ if (ownIdentities.length === 0 && expandedIdentities.includes(args.projectPath)) {
169353
+ ownIdentities = [args.projectPath];
169354
+ }
169355
+ return {
169356
+ identities: identitySet.identities,
169357
+ expandedIdentities,
169358
+ ownIdentities,
169359
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(args.db, args.projectPath) : null,
169360
+ namesByIdentity: identitySet.namesByIdentity,
169361
+ canonicalIdentityByStoredPath,
169362
+ isWorkspaced
169363
+ };
169364
+ }
169365
+ function sourceNamesForMemories(args) {
169366
+ if (!args.projectPath || !args.workspace.isWorkspaced)
169367
+ return;
169368
+ const names = new Map;
169369
+ for (const memory of args.memories) {
169370
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
169371
+ if (source)
169372
+ names.set(memory.id, source);
169373
+ }
169374
+ return names.size > 0 ? names : undefined;
169375
+ }
169376
+ function memoryCanonicalIdentity(memory, workspace) {
169377
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
169378
+ }
169379
+ function memorySelectionOrder(left, right) {
169380
+ if (left.status === "permanent" && right.status !== "permanent")
169381
+ return -1;
169382
+ if (right.status === "permanent" && left.status !== "permanent")
169383
+ return 1;
169384
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
169385
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
169386
+ const importanceDiff = rightImportance - leftImportance;
169387
+ if (importanceDiff !== 0)
169388
+ return importanceDiff;
169389
+ return left.id - right.id;
169390
+ }
169391
+ function memoryRenderOrder(left, right) {
169392
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169393
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169394
+ const categoryDiff = aPriority - bPriority;
169395
+ if (categoryDiff !== 0)
169396
+ return categoryDiff;
169397
+ return left.id - right.id;
169398
+ }
167922
169399
  function cachedStatement(cache, db, sql) {
167923
169400
  let stmt = cache.get(db);
167924
169401
  if (!stmt) {
@@ -167961,13 +169438,19 @@ function getGlobalUserProfileVersion(db) {
167961
169438
  function readCurrentM0SnapshotMarkers(args) {
167962
169439
  const projectDirectory = args.projectDirectory ?? args.projectPath ?? "";
167963
169440
  const hard = args.hardSignals ?? EMPTY_HARD_SIGNALS;
169441
+ const workspace = resolveWorkspaceRenderContext({
169442
+ db: args.db,
169443
+ projectPath: args.projectPath,
169444
+ workspaceIdentitySet: args.workspaceIdentitySet
169445
+ });
167964
169446
  return {
167965
169447
  projectMemoryEpoch: getProjectMemoryEpoch(args.db, args.projectPath),
169448
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(args.db, workspace.identities) : null,
167966
169449
  projectUserProfileVersion: getGlobalUserProfileVersion(args.db),
167967
169450
  maxCompartmentSeq: getMaxCompartmentSeq(args.db, args.sessionId),
167968
- maxMemoryId: getMaxMemoryId(args.db, args.projectPath),
169451
+ maxMemoryId: workspace.isWorkspaced ? getMaxMemoryIdForProjects(args.db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryId(args.db, args.projectPath),
167969
169452
  maxMutationId: getMaxM0MutationId(args.db, args.sessionId) ?? 0,
167970
- maxMemoryMutationId: args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
169453
+ maxMemoryMutationId: workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(args.db, workspace.expandedIdentities) ?? 0 : args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
167971
169454
  projectDocsHash: projectDirectory ? computeProjectDocsHash(projectDirectory) : "",
167972
169455
  materializedAt: Date.now(),
167973
169456
  sessionFactsVersion: getSessionFactsVersion(args.db, args.sessionId),
@@ -167995,6 +169478,7 @@ function snapshotMarkersFromCachedM0(state) {
167995
169478
  return null;
167996
169479
  return {
167997
169480
  projectMemoryEpoch: state.cachedM0ProjectMemoryEpoch,
169481
+ workspaceFingerprint: state.cachedM0WorkspaceFingerprint,
167998
169482
  projectUserProfileVersion: state.cachedM0ProjectUserProfileVersion,
167999
169483
  maxCompartmentSeq: state.cachedM0MaxCompartmentSeq,
168000
169484
  maxMemoryId: state.cachedM0MaxMemoryId,
@@ -168024,35 +169508,27 @@ function mustMaterialize(args) {
168024
169508
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (args.state.cachedM0MaterializedAt ?? 0)) {
168025
169509
  return { value: true, reason: "ttl_idle" };
168026
169510
  }
168027
- if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
169511
+ if (current.workspaceFingerprint !== null || (args.state.cachedM0WorkspaceFingerprint ?? null) !== null) {
169512
+ if ((args.state.cachedM0WorkspaceFingerprint ?? null) !== current.workspaceFingerprint) {
169513
+ return { value: true, reason: "project_memory_epoch" };
169514
+ }
169515
+ } else if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
168028
169516
  return { value: true, reason: "project_memory_epoch" };
168029
169517
  }
168030
169518
  if (args.state.cachedM0MaxMutationId !== current.maxMutationId) {
168031
169519
  return { value: true, reason: "max_mutation_id" };
168032
169520
  }
168033
- if ((args.state.cachedM0ProjectDocsHash ?? "") !== current.projectDocsHash) {
168034
- return { value: true, reason: "project_docs_hash" };
168035
- }
168036
169521
  if ((args.state.cachedM0UpgradeState ?? null) !== current.upgradeState) {
168037
169522
  return { value: true, reason: "upgrade_state" };
168038
169523
  }
168039
169524
  return { value: false, reason: null };
168040
169525
  }
168041
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168042
- const selectionOrder = [...memories].sort((a, b) => {
168043
- if (a.status === "permanent" && b.status !== "permanent")
168044
- return -1;
168045
- if (b.status === "permanent" && a.status !== "permanent")
168046
- return 1;
168047
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
168048
- if (importanceDiff !== 0)
168049
- return importanceDiff;
168050
- return a.id - b.id;
168051
- });
169526
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
169527
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
168052
169528
  const selected = [];
168053
169529
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
168054
169530
  for (const memory of selectionOrder) {
168055
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
169531
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168056
169532
  if (usedTokens + memoryTokens > budgetTokens)
168057
169533
  continue;
168058
169534
  selected.push(memory);
@@ -168061,16 +169537,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168061
169537
  if (selected.length < memories.length) {
168062
169538
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
168063
169539
  }
168064
- const renderOrder = [...selected].sort((a, b) => {
168065
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168066
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168067
- const categoryDiff = aPriority - bPriority;
168068
- if (categoryDiff !== 0)
168069
- return categoryDiff;
168070
- return a.id - b.id;
168071
- });
169540
+ const renderOrder = [...selected].sort(memoryRenderOrder);
168072
169541
  return { selected, renderOrder };
168073
169542
  }
169543
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
169544
+ if (!workspace.isWorkspaced) {
169545
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
169546
+ }
169547
+ const selected = [];
169548
+ const selectedIds = new Set;
169549
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
169550
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
169551
+ const trySelect = (memory) => {
169552
+ if (selectedIds.has(memory.id))
169553
+ return false;
169554
+ const tokens = tokenCost(memory);
169555
+ if (usedTokens + tokens > budgetTokens)
169556
+ return false;
169557
+ selected.push(memory);
169558
+ selectedIds.add(memory.id);
169559
+ usedTokens += tokens;
169560
+ return true;
169561
+ };
169562
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
169563
+ trySelect(memory);
169564
+ }
169565
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
169566
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
169567
+ const byIdentity = new Map;
169568
+ for (const memory of memories) {
169569
+ if (memory.status === "permanent")
169570
+ continue;
169571
+ const identity = memoryCanonicalIdentity(memory, workspace);
169572
+ if (!identity)
169573
+ continue;
169574
+ const list = byIdentity.get(identity) ?? [];
169575
+ list.push(memory);
169576
+ byIdentity.set(identity, list);
169577
+ }
169578
+ for (const identity of workspace.identities) {
169579
+ let memberTokens = 0;
169580
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
169581
+ for (const memory of candidates) {
169582
+ if (selectedIds.has(memory.id))
169583
+ continue;
169584
+ const tokens = tokenCost(memory);
169585
+ if (memberTokens + tokens > floorTokens)
169586
+ continue;
169587
+ if (usedTokens + tokens > budgetTokens)
169588
+ continue;
169589
+ selected.push(memory);
169590
+ selectedIds.add(memory.id);
169591
+ usedTokens += tokens;
169592
+ memberTokens += tokens;
169593
+ }
169594
+ }
169595
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
169596
+ for (const memory of remaining) {
169597
+ trySelect(memory);
169598
+ }
169599
+ if (selected.length < memories.length) {
169600
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
169601
+ }
169602
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
169603
+ }
168074
169604
  function safeGetActiveUserMemories(db) {
168075
169605
  try {
168076
169606
  return getActiveUserMemories(db);
@@ -168146,15 +169676,16 @@ function readNewMemoriesForM1(db, projectPath, afterId, expiryCutoff) {
168146
169676
  ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(projectPath, afterId, expiryCutoff).filter(isMemoryRow);
168147
169677
  return rows.map((row) => ({ ...row }));
168148
169678
  }
168149
- function renderMemoryLineV2(memory) {
168150
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
169679
+ function renderMemoryLineV2(memory, sourceName) {
169680
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
169681
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
168151
169682
  }
168152
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
169683
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
168153
169684
  if (memories.length === 0)
168154
169685
  return "";
168155
169686
  const lines = [`<${wrapper}>`];
168156
169687
  for (const memory of memories) {
168157
- lines.push(renderMemoryLineV2(memory));
169688
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168158
169689
  }
168159
169690
  lines.push(`</${wrapper}>`);
168160
169691
  return lines.join(`
@@ -168193,7 +169724,7 @@ function renderM0(args) {
168193
169724
  sections.push(sessionHistory.length > 0 ? `<session-history>
168194
169725
  ${sessionHistory}
168195
169726
  </session-history>` : M0_EMPTY_BODY);
168196
- const memoriesBlock = renderMemoryBlockV2(args.memories);
169727
+ const memoriesBlock = renderMemoryBlockV2(args.memories, "project-memory", args.memoryRenderOptions);
168197
169728
  if (memoriesBlock)
168198
169729
  sections.push(memoriesBlock);
168199
169730
  return sections.join(`
@@ -168205,6 +169736,7 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
168205
169736
  if (m1Bytes)
168206
169737
  state.cachedM1Bytes = m1Bytes;
168207
169738
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
169739
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168208
169740
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168209
169741
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168210
169742
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168230,24 +169762,38 @@ function materializeM0(options) {
168230
169762
  let facts = [];
168231
169763
  let memories = [];
168232
169764
  let userMemories = [];
169765
+ let workspace = resolveWorkspaceRenderContext({
169766
+ db: options.db,
169767
+ projectPath,
169768
+ workspaceIdentitySet: options.workspaceIdentitySet
169769
+ });
168233
169770
  let docs = {
168234
169771
  renderedBlock: "",
168235
169772
  canonicalHash: ""
168236
169773
  };
168237
169774
  options.db.exec("BEGIN");
168238
169775
  try {
169776
+ workspace = resolveWorkspaceRenderContext({
169777
+ db: options.db,
169778
+ projectPath,
169779
+ workspaceIdentitySet: options.workspaceIdentitySet
169780
+ });
168239
169781
  snapshotMarkers = readCurrentM0SnapshotMarkers({
168240
169782
  db: options.db,
168241
169783
  sessionId: options.sessionId,
168242
169784
  projectPath,
168243
169785
  projectDirectory,
168244
- hardSignals: options.hardSignals
169786
+ hardSignals: options.hardSignals,
169787
+ workspaceIdentitySet: {
169788
+ identities: workspace.identities,
169789
+ namesByIdentity: workspace.namesByIdentity
169790
+ }
168245
169791
  });
168246
169792
  docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168247
169793
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168248
169794
  compartments = readM0Compartments(options.db, options.sessionId);
168249
169795
  facts = [];
168250
- memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
169796
+ memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
168251
169797
  userMemories = safeGetActiveUserMemories(options.db);
168252
169798
  options.db.exec("COMMIT");
168253
169799
  } catch (error51) {
@@ -168257,7 +169803,14 @@ function materializeM0(options) {
168257
169803
  throw error51;
168258
169804
  }
168259
169805
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168260
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
169806
+ const memoryRenderOptions = {
169807
+ sourceNameByMemoryId: sourceNamesForMemories({
169808
+ memories,
169809
+ projectPath,
169810
+ workspace
169811
+ })
169812
+ };
169813
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168261
169814
  let decayPressureMultiplier = 1;
168262
169815
  let m0Text = renderM0({
168263
169816
  projectDocs: docs.renderedBlock,
@@ -168265,6 +169818,7 @@ function materializeM0(options) {
168265
169818
  compartments,
168266
169819
  memories: trimmed.renderOrder,
168267
169820
  facts,
169821
+ memoryRenderOptions,
168268
169822
  historyBudgetTokens: options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS,
168269
169823
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168270
169824
  decayPressureMultiplier
@@ -168279,6 +169833,7 @@ function materializeM0(options) {
168279
169833
  compartments,
168280
169834
  memories: trimmed.renderOrder,
168281
169835
  facts,
169836
+ memoryRenderOptions,
168282
169837
  historyBudgetTokens: budget,
168283
169838
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168284
169839
  decayPressureMultiplier
@@ -168297,13 +169852,19 @@ function materializeM0(options) {
168297
169852
  let m1Bytes = Buffer4.from(m1Text, "utf8");
168298
169853
  options.db.exec("BEGIN IMMEDIATE");
168299
169854
  try {
169855
+ const currentWorkspace = resolveWorkspaceRenderContext({
169856
+ db: options.db,
169857
+ projectPath,
169858
+ workspaceIdentitySet: options.workspaceIdentitySet
169859
+ });
168300
169860
  const current = {
168301
169861
  projectMemoryEpoch: getProjectMemoryEpoch(options.db, projectPath),
169862
+ workspaceFingerprint: currentWorkspace.isWorkspaced ? computeWorkspaceEpochFingerprint(options.db, currentWorkspace.identities) : null,
168302
169863
  projectUserProfileVersion: getGlobalUserProfileVersion(options.db),
168303
169864
  maxCompartmentSeq: getMaxCompartmentSeq(options.db, options.sessionId),
168304
- maxMemoryId: getMaxMemoryId(options.db, projectPath),
169865
+ maxMemoryId: currentWorkspace.isWorkspaced ? getMaxMemoryIdForProjects(options.db, currentWorkspace.expandedIdentities, currentWorkspace.ownIdentities, currentWorkspace.shareCategories) : getMaxMemoryId(options.db, projectPath),
168305
169866
  maxMutationId: getMaxM0MutationId(options.db, options.sessionId) ?? 0,
168306
- maxMemoryMutationId: projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
169867
+ maxMemoryMutationId: currentWorkspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(options.db, currentWorkspace.expandedIdentities) ?? 0 : projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
168307
169868
  projectDocsHash: phase3ProjectDocsHash,
168308
169869
  materializedAt: Date.now(),
168309
169870
  sessionFactsVersion: getSessionFactsVersion(options.db, options.sessionId),
@@ -168311,17 +169872,26 @@ function materializeM0(options) {
168311
169872
  systemHash: snapshotMarkers.systemHash,
168312
169873
  modelKey: snapshotMarkers.modelKey
168313
169874
  };
168314
- const stale = current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.projectDocsHash !== snapshotMarkers.projectDocsHash || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
169875
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
169876
+ const stale = memoryEpochStale || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
168315
169877
  if (stale) {
168316
169878
  options.db.exec("ROLLBACK");
168317
169879
  throw new MaterializeContentionError({ reason: "snapshot changed before Phase 3" });
168318
169880
  }
168319
- const m1Render = renderM1WithMetadata({ ...options, preRenderedKeyFilesBlock }, snapshotMarkers, renderedMemoryIds);
169881
+ const m1Render = renderM1WithMetadata({
169882
+ ...options,
169883
+ preRenderedKeyFilesBlock,
169884
+ workspaceIdentitySet: {
169885
+ identities: workspace.identities,
169886
+ namesByIdentity: workspace.namesByIdentity
169887
+ }
169888
+ }, snapshotMarkers, renderedMemoryIds);
168320
169889
  m1Text = m1Render.text;
168321
169890
  m1Bytes = Buffer4.from(m1Text, "utf8");
168322
169891
  persistCachedM0(options.db, options.sessionId, {
168323
169892
  m0Bytes,
168324
169893
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
169894
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
168325
169895
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
168326
169896
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
168327
169897
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -168384,7 +169954,7 @@ function renderMemoryUpdatesBlock(args) {
168384
169954
  return { block: "", count: 0 };
168385
169955
  }
168386
169956
  const renderedIds = new Set(args.renderedMemoryIds);
168387
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
169957
+ const mutations = args.workspace.isWorkspaced ? getMemoryMutationsForRenderByProjects(args.db, args.workspace.expandedIdentities, args.afterId, args.renderedMemoryIds) : getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
168388
169958
  if (mutations.length === 0)
168389
169959
  return { block: "", count: 0 };
168390
169960
  const lines = ["These memories changed since the snapshot below — trust these:"];
@@ -168416,12 +169986,18 @@ function renderM1WithMetadata(options, markers, renderedMemoryIds) {
168416
169986
  throw new RenderM1InvalidMarkersError(options.sessionId);
168417
169987
  }
168418
169988
  const blocks = [];
169989
+ const workspace = resolveWorkspaceRenderContext({
169990
+ db: options.db,
169991
+ projectPath: options.projectPath,
169992
+ workspaceIdentitySet: options.workspaceIdentitySet
169993
+ });
168419
169994
  const keyFiles = renderedKeyFilesBlock(options);
168420
169995
  if (keyFiles)
168421
169996
  blocks.push(keyFiles);
168422
169997
  const memoryUpdates = renderMemoryUpdatesBlock({
168423
169998
  db: options.db,
168424
169999
  projectPath: options.projectPath,
170000
+ workspace,
168425
170001
  afterId: markers.maxMemoryMutationId,
168426
170002
  renderedMemoryIds
168427
170003
  });
@@ -168435,9 +170011,16 @@ ${newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).
168435
170011
  `)}
168436
170012
  </new-compartments>`);
168437
170013
  }
168438
- const newMemories = readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
168439
- const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25))).renderOrder;
168440
- const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories");
170014
+ 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);
170015
+ const newMemoryRenderOptions = {
170016
+ sourceNameByMemoryId: sourceNamesForMemories({
170017
+ memories: newMemories,
170018
+ projectPath: options.projectPath,
170019
+ workspace
170020
+ })
170021
+ };
170022
+ const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25)), newMemoryRenderOptions).renderOrder;
170023
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", newMemoryRenderOptions);
168441
170024
  if (newMemoriesBlock)
168442
170025
  blocks.push(newMemoriesBlock);
168443
170026
  const currentUserProfileVersion = getGlobalUserProfileVersion(options.db);
@@ -168485,6 +170068,7 @@ function parseMemoryBlockIds(raw) {
168485
170068
  function readCachedM0M1Row(db, sessionId) {
168486
170069
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
168487
170070
  cached_m0_project_memory_epoch,
170071
+ cached_m0_workspace_fingerprint,
168488
170072
  cached_m0_project_user_profile_version,
168489
170073
  cached_m0_max_compartment_seq,
168490
170074
  cached_m0_max_memory_id,
@@ -168519,6 +170103,7 @@ function markersFromCachedRow(row) {
168519
170103
  return null;
168520
170104
  return {
168521
170105
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
170106
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
168522
170107
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
168523
170108
  maxCompartmentSeq: row.cached_m0_max_compartment_seq,
168524
170109
  maxMemoryId: row.cached_m0_max_memory_id,
@@ -168533,7 +170118,7 @@ function markersFromCachedRow(row) {
168533
170118
  };
168534
170119
  }
168535
170120
  function cachedRowMatchesState(row, state) {
168536
- return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && (row.cached_m0_project_docs_hash ?? "") === (state.cachedM0ProjectDocsHash ?? "") && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
170121
+ return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && (row.cached_m0_workspace_fingerprint ?? null) === (state.cachedM0WorkspaceFingerprint ?? null) && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
168537
170122
  }
168538
170123
  function applyCachedRowToState(state, row) {
168539
170124
  const markers = markersFromCachedRow(row);
@@ -168543,6 +170128,7 @@ function applyCachedRowToState(state, row) {
168543
170128
  state.cachedM0Bytes = toBuffer(row.cached_m0_bytes);
168544
170129
  state.cachedM1Bytes = toBuffer(row.cached_m1_bytes);
168545
170130
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
170131
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168546
170132
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168547
170133
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168548
170134
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168597,29 +170183,51 @@ function softRefreshCachedM1(options) {
168597
170183
  function prependM0M1Messages(sessionId, messages, m0Text, m1Text) {
168598
170184
  messages.unshift({
168599
170185
  info: { role: "user", sessionID: sessionId },
168600
- parts: [{ type: "text", text: m0Text.length > 0 ? m0Text : M0_EMPTY_BODY }]
170186
+ parts: [
170187
+ {
170188
+ type: "text",
170189
+ text: m0Text.length > 0 ? m0Text : M0_EMPTY_BODY,
170190
+ synthetic: true
170191
+ }
170192
+ ]
168601
170193
  }, {
168602
170194
  info: { role: "user", sessionID: sessionId },
168603
- parts: [{ type: "text", text: m1Text }]
170195
+ parts: [{ type: "text", text: m1Text, synthetic: true }]
168604
170196
  });
168605
170197
  }
168606
170198
  function renderFreshM0NonPersisted(options) {
168607
170199
  const projectPath = options.projectPath;
168608
170200
  const projectDirectory = options.projectDirectory;
170201
+ const workspace = resolveWorkspaceRenderContext({
170202
+ db: options.db,
170203
+ projectPath,
170204
+ workspaceIdentitySet: options.workspaceIdentitySet
170205
+ });
168609
170206
  const snapshotMarkers = readCurrentM0SnapshotMarkers({
168610
170207
  db: options.db,
168611
170208
  sessionId: options.sessionId,
168612
170209
  projectPath,
168613
- projectDirectory
170210
+ projectDirectory,
170211
+ workspaceIdentitySet: {
170212
+ identities: workspace.identities,
170213
+ namesByIdentity: workspace.namesByIdentity
170214
+ }
168614
170215
  });
168615
170216
  const docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168616
170217
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168617
170218
  snapshotMarkers.materializedAt = options.state.cachedM0MaterializedAt ?? 0;
168618
170219
  const compartments = readM0Compartments(options.db, options.sessionId);
168619
- const memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
170220
+ const memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], snapshotMarkers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
168620
170221
  const userMemories = safeGetActiveUserMemories(options.db);
168621
170222
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168622
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
170223
+ const memoryRenderOptions = {
170224
+ sourceNameByMemoryId: sourceNamesForMemories({
170225
+ memories,
170226
+ projectPath,
170227
+ workspace
170228
+ })
170229
+ };
170230
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168623
170231
  const budget = options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
168624
170232
  let decayPressureMultiplier = 1;
168625
170233
  let m0Text = renderM0({
@@ -168628,6 +170236,7 @@ function renderFreshM0NonPersisted(options) {
168628
170236
  compartments,
168629
170237
  memories: trimmed.renderOrder,
168630
170238
  facts: [],
170239
+ memoryRenderOptions,
168631
170240
  historyBudgetTokens: budget,
168632
170241
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168633
170242
  decayPressureMultiplier
@@ -168641,6 +170250,7 @@ function renderFreshM0NonPersisted(options) {
168641
170250
  compartments,
168642
170251
  memories: trimmed.renderOrder,
168643
170252
  facts: [],
170253
+ memoryRenderOptions,
168644
170254
  historyBudgetTokens: budget,
168645
170255
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168646
170256
  decayPressureMultiplier
@@ -168656,6 +170266,12 @@ function renderFreshM0NonPersisted(options) {
168656
170266
  };
168657
170267
  }
168658
170268
  function injectM0M1(options) {
170269
+ if (!options.workspaceIdentitySet && options.projectPath) {
170270
+ options = {
170271
+ ...options,
170272
+ workspaceIdentitySet: resolveWorkspaceIdentitySet(options.db, options.projectPath)
170273
+ };
170274
+ }
168659
170275
  const skipped = {
168660
170276
  injected: false,
168661
170277
  m0RematerializedThisPass: false,
@@ -168672,7 +170288,8 @@ function injectM0M1(options) {
168672
170288
  state: options.state,
168673
170289
  projectPath: options.projectPath,
168674
170290
  projectDirectory: options.projectDirectory,
168675
- hardSignals: options.hardSignals
170291
+ hardSignals: options.hardSignals,
170292
+ workspaceIdentitySet: options.workspaceIdentitySet
168676
170293
  });
168677
170294
  let rematerialized = false;
168678
170295
  let contentionExhausted = false;
@@ -168766,6 +170383,7 @@ var init_inject_compartments = __esm(async () => {
168766
170383
  init_compartment_storage();
168767
170384
  init_constants();
168768
170385
  init_storage_memory();
170386
+ init_workspaces();
168769
170387
  init_logger();
168770
170388
  init_decay_render();
168771
170389
  init_key_files_block();
@@ -171905,18 +173523,38 @@ async function runCompartmentAgent(deps) {
171905
173523
  return;
171906
173524
  }
171907
173525
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
171908
- const boundarySnapshot = deps.boundarySnapshot ?? null;
173526
+ let boundarySnapshot = deps.boundarySnapshot ?? null;
171909
173527
  if (!boundarySnapshot) {
171910
173528
  telemetry.failureReason = "missing protected-tail boundary snapshot";
171911
173529
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from trigger decision");
171912
173530
  rollbackDrainReservation();
171913
173531
  return;
171914
173532
  }
171915
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
173533
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
171916
173534
  db,
171917
173535
  snapshot: boundarySnapshot,
171918
173536
  currentContextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit
171919
173537
  }) : { ok: true };
173538
+ if (!validation.ok && validation.reason === "stale_snapshot") {
173539
+ const refreshed = resolveOpenCodeProtectedTailBoundary({
173540
+ db,
173541
+ sessionId,
173542
+ mode: "incremental-runner",
173543
+ contextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit,
173544
+ executeThresholdPercentage: boundarySnapshot.executeThresholdPercentage,
173545
+ usage: {
173546
+ percentage: boundarySnapshot.usagePercentage,
173547
+ inputTokens: boundarySnapshot.usageInputTokens
173548
+ },
173549
+ usageSource: boundarySnapshot.usageSource,
173550
+ emergencyTailScale: boundarySnapshot.emergencyTailScale
173551
+ });
173552
+ if (hasRunnableCompartmentWindow(refreshed)) {
173553
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
173554
+ boundarySnapshot = refreshed;
173555
+ validation = { ok: true };
173556
+ }
173557
+ }
171920
173558
  if (!validation.ok) {
171921
173559
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
171922
173560
  telemetry.status = "noop";
@@ -171985,6 +173623,7 @@ async function runCompartmentAgent(deps) {
171985
173623
  rollbackDrainReservation();
171986
173624
  return;
171987
173625
  }
173626
+ deps.onHistorianRunStarted?.();
171988
173627
  const projectPath = resolveProjectIdentity(directory ?? process.cwd());
171989
173628
  const memories = getMemoriesByProject(db, projectPath, ["active", "permanent"]);
171990
173629
  const projectMemory = renderMemoryBlock(memories) ?? "";
@@ -172117,8 +173756,13 @@ ${chunkText}`,
172117
173756
  }
172118
173757
  if (embeddingActive) {
172119
173758
  const projectIdentity = resolveProjectIdentity(promotionDirectory);
172120
- const toEmbed = persistedCompartments.map((c, i) => ({ id: persistedIds[i], p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172121
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173759
+ const chunksToEmbed = persistedCompartments.map((c, i) => ({
173760
+ id: persistedIds[i],
173761
+ startMessage: c.startMessage,
173762
+ endMessage: c.endMessage,
173763
+ sourceChunkText: chunk.text
173764
+ })).filter((c) => typeof c.id === "number");
173765
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172122
173766
  }
172123
173767
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
172124
173768
  deps.onCompartmentStatePublished?.(sessionId);
@@ -172349,8 +173993,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
172349
173993
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172350
173994
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172351
173995
  const liveCompartments = getCompartments(db, sessionId);
172352
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172353
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173996
+ const chunksToEmbed = liveCompartments.map((c) => ({
173997
+ id: c.id,
173998
+ startMessage: c.startMessage,
173999
+ endMessage: c.endMessage
174000
+ }));
174001
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172354
174002
  }
172355
174003
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
172356
174004
  if (lastCompartmentEnd2 > 0) {
@@ -172563,8 +174211,12 @@ Another process acquired the compartment-state lease before recomp could publish
172563
174211
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172564
174212
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172565
174213
  const liveCompartments = getCompartments(db, sessionId);
172566
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172567
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
174214
+ const chunksToEmbed = liveCompartments.map((c) => ({
174215
+ id: c.id,
174216
+ startMessage: c.startMessage,
174217
+ endMessage: c.endMessage
174218
+ }));
174219
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172568
174220
  }
172569
174221
  if (lastCompartmentEnd > 0) {
172570
174222
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -172745,8 +174397,12 @@ Could not acquire the compartment-state lease for this session.`;
172745
174397
  if (deps.memoryEnabled !== false) {
172746
174398
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172747
174399
  const liveCompartments = getCompartments(db, sessionId);
172748
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172749
- Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed));
174400
+ const chunksToEmbed = liveCompartments.map((c) => ({
174401
+ id: c.id,
174402
+ startMessage: c.startMessage,
174403
+ endMessage: c.endMessage
174404
+ }));
174405
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
172750
174406
  }
172751
174407
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
172752
174408
  if (lastEnd > 0) {
@@ -174996,7 +176652,14 @@ function startCompartmentAgent(deps) {
174996
176652
  return;
174997
176653
  }
174998
176654
  const renewal = startLeaseRenewal(deps, holderId);
174999
- const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
176655
+ let realRunStarted = false;
176656
+ const runnerDeps = withPublishedCallback({
176657
+ ...deps,
176658
+ compartmentLeaseHolderId: holderId,
176659
+ onHistorianRunStarted: () => {
176660
+ realRunStarted = true;
176661
+ }
176662
+ });
175000
176663
  const promise2 = runCompartmentAgent(runnerDeps).catch((err) => {
175001
176664
  sessionLog(deps.sessionId, "compartment agent: unhandled rejection:", err);
175002
176665
  try {
@@ -175010,6 +176673,9 @@ function startCompartmentAgent(deps) {
175010
176673
  }
175011
176674
  });
175012
176675
  activeRuns.set(deps.sessionId, { promise: promise2, published: false });
176676
+ if (!realRunStarted && activeRuns.get(deps.sessionId)?.promise === promise2) {
176677
+ activeRuns.delete(deps.sessionId);
176678
+ }
175013
176679
  }
175014
176680
  async function executeContextRecompWithResult(deps, options = {}) {
175015
176681
  const { sessionId } = deps;
@@ -175144,7 +176810,7 @@ function applyMemoryMigration(db, projectPath, result) {
175144
176810
  inserted++;
175145
176811
  }
175146
176812
  if (removed > 0 || inserted > 0) {
175147
- bumpProjectMemoryEpoch(db, projectPath);
176813
+ bumpEpochsForWorkspaceMembers(db, projectPath);
175148
176814
  }
175149
176815
  })();
175150
176816
  return { removed, inserted };
@@ -175444,6 +177110,11 @@ async function runManagedRecomp(ctx, sessionId, options) {
175444
177110
  try {
175445
177111
  const message = await executeContextRecomp(buildRecompDeps(ctx, sessionId), options);
175446
177112
  const terminalPhase = isRecompSkip(message) ? "skipped" : isRecompFailure(message) ? "failed" : "done";
177113
+ if (terminalPhase === "done") {
177114
+ try {
177115
+ clearEmergencyRecovery(ctx.db, sessionId);
177116
+ } catch {}
177117
+ }
175447
177118
  setRecompTerminal(ctx.liveSessionState, sessionId, terminalPhase, extractRecompReason(message));
175448
177119
  return message;
175449
177120
  } catch (error51) {
@@ -175542,6 +177213,7 @@ var RECOMP_DONE_GRACE_MS = 30000;
175542
177213
  var init_recomp_orchestrator = __esm(async () => {
175543
177214
  init_compartment_storage();
175544
177215
  init_project_identity2();
177216
+ init_storage_meta_persisted();
175545
177217
  await __promiseAll([
175546
177218
  init_memory_migration(),
175547
177219
  init_compartment_runner()
@@ -175602,15 +177274,15 @@ function shouldShowAnnouncement() {
175602
177274
  }
175603
177275
  return state.version !== ANNOUNCEMENT_VERSION;
175604
177276
  }
175605
- var ANNOUNCEMENT_VERSION = "0.23.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
177277
+ var ANNOUNCEMENT_VERSION = "0.24.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
175606
177278
  var init_announcement = __esm(() => {
175607
177279
  init_data_path();
175608
177280
  ANNOUNCEMENT_FEATURES = [
175609
- "Smarter context nudges: gentle <system-reminder> notes on tool outputs replace the old chat nudgesquieter, cache-safe, and they now also help subagents manage their own context.",
175610
- "The agent can now maintain its own project memories: update, archive (batch), and merge the memories it sees not just write new ones. Memory categories are schema-enforced.",
175611
- "Big-session performance: historian trigger and token math moved off the database hot pathmulti-second stalls on large sessions are gone (measured 250ms → 2.4ms per pass).",
175612
- "History compaction unstuck for sparse sessions: the protected-tail boundary is now size-based, so sessions with few user turns compact reliably instead of growing forever (issue #132).",
175613
- "Fixed: session titles no longer fail to generate in fresh directories (issue #129), plus 20+ correctness fixes from three audit rounds."
177281
+ "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).",
177282
+ "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.",
177283
+ "Pi: fixed sessions overflowing the model context while still showing moderate usagePi now sheds context before a tool-heavy turn overflows.",
177284
+ "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
177285
+ "Setup wizard now lists your actual models with type-ahead instead of fixed recommendations, and explains the historian/dreamer roles (issue #144). Plus a GitHub Copilot tool-pairing fix (#135)."
175614
177286
  ];
175615
177287
  });
175616
177288
 
@@ -176404,6 +178076,10 @@ function getMagicContextBuiltinCommands() {
176404
178076
  "ctx-dream": {
176405
178077
  template: "ctx-dream",
176406
178078
  description: "Run the hidden dreamer maintenance pass for this project now"
178079
+ },
178080
+ "ctx-embed-history": {
178081
+ template: "ctx-embed-history",
178082
+ description: "Embed all of this session's history compartments for semantic search, in one pass"
176407
178083
  }
176408
178084
  };
176409
178085
  }
@@ -176897,7 +178573,7 @@ await init_storage_db();
176897
178573
  // src/features/magic-context/v22-deferred-backfill.ts
176898
178574
  init_logger();
176899
178575
  init_project_identity();
176900
- import { createHash as createHash5 } from "node:crypto";
178576
+ import { createHash as createHash6 } from "node:crypto";
176901
178577
  import { realpathSync as realpathSync2 } from "node:fs";
176902
178578
  import path5 from "node:path";
176903
178579
  var BATCH_SIZE = 25;
@@ -176951,7 +178627,7 @@ function computeLegacyRustDirIdentity(rawProjectPath) {
176951
178627
  } catch {
176952
178628
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
176953
178629
  }
176954
- return `dir:${createHash5("sha256").update(canonical, "utf8").digest("hex")}`;
178630
+ return `dir:${createHash6("sha256").update(canonical, "utf8").digest("hex")}`;
176955
178631
  }
176956
178632
  function upsertRekeyMap(db, oldProjectPath, newProjectPath, rekeyedAt) {
176957
178633
  db.prepare(`INSERT INTO v22_identity_rekey_map (old_project_path, new_project_path, rekeyed_at)
@@ -180089,7 +181765,7 @@ init_project_identity();
180089
181765
  // src/plugin/embedding-bootstrap-helpers.ts
180090
181766
  init_embedding();
180091
181767
  init_logger();
180092
- import { createHash as createHash8 } from "node:crypto";
181768
+ import { createHash as createHash10 } from "node:crypto";
180093
181769
  var EMBEDDING_AFFECTING_KEYS = new Set([
180094
181770
  "embedding.api_key",
180095
181771
  "embedding.endpoint",
@@ -180110,7 +181786,7 @@ var EMBEDDING_WARNING_TERMS = [
180110
181786
  ];
180111
181787
  var loggedFailureSignatures = new Map;
180112
181788
  function sha256Prefix2(value, length = 16) {
180113
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
181789
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
180114
181790
  }
180115
181791
  function warningLooksEmbeddingRelated(message) {
180116
181792
  const lower = message.toLowerCase();
@@ -180747,6 +182423,7 @@ function createTagger() {
180747
182423
  // src/hooks/magic-context/hook.ts
180748
182424
  init_magic_context();
180749
182425
  init_project_identity();
182426
+ init_project_embedding_registry();
180750
182427
  await init_storage();
180751
182428
  init_logger();
180752
182429
  init_resolve_fallbacks();
@@ -181340,6 +183017,7 @@ function createMagicContextCommandHandler(deps) {
181340
183017
  const isAugCommand = (command) => command === "ctx-aug";
181341
183018
  const isDreamCommand = (command) => command === "ctx-dream";
181342
183019
  const isSessionUpgradeCommand = (command) => command === "ctx-session-upgrade";
183020
+ const isEmbedHistoryCommand = (command) => command === "ctx-embed-history";
181343
183021
  return {
181344
183022
  "command.execute.before": async (input, _output, _params) => {
181345
183023
  const isStatus = isStatusCommand(input.command);
@@ -181348,7 +183026,8 @@ function createMagicContextCommandHandler(deps) {
181348
183026
  const isAug = isAugCommand(input.command);
181349
183027
  const isDream = isDreamCommand(input.command);
181350
183028
  const isSessionUpgrade = isSessionUpgradeCommand(input.command);
181351
- if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade) {
183029
+ const isEmbedHistory = isEmbedHistoryCommand(input.command);
183030
+ if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbedHistory) {
181352
183031
  return;
181353
183032
  }
181354
183033
  const sessionId = input.sessionID;
@@ -181361,6 +183040,11 @@ function createMagicContextCommandHandler(deps) {
181361
183040
  await executeDreaming(deps, sessionId);
181362
183041
  return;
181363
183042
  }
183043
+ if (isEmbedHistory) {
183044
+ const summary = deps.executeEmbedHistory ? await deps.executeEmbedHistory(sessionId) : "Semantic embedding is not configured for this project, so there is nothing to embed.";
183045
+ await deps.sendNotification(sessionId, summary, {});
183046
+ throwSentinel(input.command);
183047
+ }
181364
183048
  if (isFlush) {
181365
183049
  result = executeFlush(deps.db, sessionId);
181366
183050
  clearCachedM0M1(deps.db, sessionId);
@@ -181728,8 +183412,8 @@ var CHANNEL1_SENTINEL = "<system-reminder>";
181728
183412
  var TOKENS_PER_BYTE = 0.25;
181729
183413
  var CHANNEL1_FLOOR_TOKENS = 1e4;
181730
183414
  var CHANNEL1_REFIRE_FLOOR_TOKENS = 1e4;
181731
- function channel1RefireTokens(historyBudgetTokens) {
181732
- const scaled = Math.round(0.05 * Math.max(0, historyBudgetTokens));
183415
+ function channel1RefireTokens(workingWindowTokens) {
183416
+ const scaled = Math.round(0.05 * Math.max(0, workingWindowTokens));
181733
183417
  return Math.max(CHANNEL1_REFIRE_FLOOR_TOKENS, scaled);
181734
183418
  }
181735
183419
  var S_GENTLE = 0.2;
@@ -181799,7 +183483,7 @@ function computeTailTokenEstimate(messages) {
181799
183483
  };
181800
183484
  }
181801
183485
  function decideChannel1(input) {
181802
- const { undroppedTokens, pressure, historyBudgetTokens, hasRecentReduce } = input;
183486
+ const { undroppedTokens, pressure, workingWindowTokens, hasRecentReduce } = input;
181803
183487
  const resetCycle = hasRecentReduce || undroppedTokens < input.lastNudgeUndropped;
181804
183488
  const lastNudge = resetCycle ? 0 : input.lastNudgeUndropped;
181805
183489
  const lastLevel = resetCycle ? "" : input.lastNudgeLevel;
@@ -181814,7 +183498,7 @@ function decideChannel1(input) {
181814
183498
  return quiet();
181815
183499
  if (undroppedTokens < CHANNEL1_FLOOR_TOKENS)
181816
183500
  return quiet();
181817
- const budget = historyBudgetTokens > 0 ? historyBudgetTokens : undroppedTokens || 1;
183501
+ const budget = workingWindowTokens > 0 ? workingWindowTokens : undroppedTokens || 1;
181818
183502
  const severity = undroppedTokens / budget * pressure;
181819
183503
  if (severity < S_GENTLE)
181820
183504
  return quiet();
@@ -181826,7 +183510,7 @@ function decideChannel1(input) {
181826
183510
  else
181827
183511
  level = "gentle";
181828
183512
  if (lastLevel === "") {
181829
- if (undroppedTokens < lastNudge + channel1RefireTokens(historyBudgetTokens)) {
183513
+ if (undroppedTokens < lastNudge + channel1RefireTokens(workingWindowTokens)) {
181830
183514
  return quiet();
181831
183515
  }
181832
183516
  } else if (LEVEL_RANK[level] <= LEVEL_RANK[lastLevel]) {
@@ -181871,13 +183555,13 @@ function buildChannel1Reminder(level, undroppedTokens) {
181871
183555
  let body;
181872
183556
  switch (level) {
181873
183557
  case "gentle":
181874
- 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.`;
183558
+ 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.`;
181875
183559
  break;
181876
183560
  case "firm":
181877
- body = `~${amount} tokens of unreduced tool output is accumulating. ` + `Drop what you have already processed with ctx_reduce before continuing.`;
183561
+ 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.`;
181878
183562
  break;
181879
183563
  case "urgent":
181880
- 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.`;
183564
+ 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.`;
181881
183565
  break;
181882
183566
  }
181883
183567
  return `
@@ -182101,6 +183785,7 @@ await init_read_session_chunk();
182101
183785
  // src/hooks/magic-context/transform.ts
182102
183786
  init_project_identity();
182103
183787
  import * as crypto2 from "node:crypto";
183788
+ init_session_project_storage();
182104
183789
  init_storage_meta_persisted();
182105
183790
  init_logger();
182106
183791
  await init_storage();
@@ -182701,6 +184386,7 @@ await __promiseAll([
182701
184386
  init_read_session_chunk(),
182702
184387
  init_read_session_db()
182703
184388
  ]);
184389
+
182704
184390
  // src/hooks/magic-context/sentinel.ts
182705
184391
  var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
182706
184392
  function modelAcceptsEmptyContent(providerID) {
@@ -182758,7 +184444,6 @@ function replaySentinelByMessageIds(messages, ids, providerID) {
182758
184444
  missingIds.push(id);
182759
184445
  return { replayed, missingIds };
182760
184446
  }
182761
-
182762
184447
  // src/hooks/magic-context/strip-content.ts
182763
184448
  var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped §\d+§\]$/;
182764
184449
  var TAG_PREFIX_PATTERN = /^§\d+§\s*/;
@@ -183111,8 +184796,10 @@ function stripReasoningFromMergedAssistants(messages, providerID) {
183111
184796
  }
183112
184797
  return stripped;
183113
184798
  }
183114
- function stripProcessedImages(messages, watermark, messageTagNumbers) {
184799
+ function stripProcessedImages(messages, frozenIds, options) {
184800
+ const { detect, watermark, messageTagNumbers } = options;
183115
184801
  let stripped = 0;
184802
+ const newlyStrippedIds = [];
183116
184803
  let hasAssistantResponse = false;
183117
184804
  for (let i = messages.length - 1;i >= 0; i--) {
183118
184805
  const msg = messages[i];
@@ -183120,13 +184807,17 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
183120
184807
  hasAssistantResponse = true;
183121
184808
  continue;
183122
184809
  }
183123
- if (msg.info.role !== "user" || !hasAssistantResponse) {
184810
+ if (msg.info.role !== "user") {
183124
184811
  continue;
183125
184812
  }
184813
+ const id = typeof msg.info.id === "string" ? msg.info.id : undefined;
184814
+ const inFrozen = id !== undefined && frozenIds.has(id);
183126
184815
  const maxTag = messageTagNumbers.get(msg) ?? 0;
183127
- if (maxTag > watermark) {
184816
+ const isNewDetection = !inFrozen && detect && hasAssistantResponse && id !== undefined && maxTag <= watermark;
184817
+ if (!inFrozen && !isNewDetection) {
183128
184818
  continue;
183129
184819
  }
184820
+ let touchedThisMsg = false;
183130
184821
  for (let j = 0;j < msg.parts.length; j++) {
183131
184822
  const part = msg.parts[j];
183132
184823
  if (!isRecord(part) || part.type !== "file") {
@@ -183138,10 +184829,14 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
183138
184829
  if (typeof part.url === "string" && part.url.startsWith("data:") && part.url.length > 200) {
183139
184830
  msg.parts[j] = makeSentinel(part);
183140
184831
  stripped++;
184832
+ touchedThisMsg = true;
183141
184833
  }
183142
184834
  }
184835
+ if (touchedThisMsg && isNewDetection && id !== undefined) {
184836
+ newlyStrippedIds.push(id);
184837
+ }
183143
184838
  }
183144
- return stripped;
184839
+ return { stripped, newlyStrippedIds };
183145
184840
  }
183146
184841
 
183147
184842
  // src/hooks/magic-context/transform.ts
@@ -183454,8 +185149,55 @@ function appendReminderToUserMessage(message, reminder) {
183454
185149
  }
183455
185150
 
183456
185151
  // src/hooks/magic-context/apply-operations.ts
183457
- init_tag_part_guards();
183458
185152
  await init_storage();
185153
+
185154
+ // src/hooks/magic-context/system-injection-stripper.ts
185155
+ var SYSTEM_INJECTION_MARKERS = [
185156
+ "<!-- OMO_INTERNAL_INITIATOR -->",
185157
+ "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
185158
+ "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
185159
+ "[Category+Skill Reminder]",
185160
+ "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
185161
+ "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
185162
+ "[EMERGENCY CONTEXT WINDOW WARNING]",
185163
+ "Unstable background agent appears idle",
185164
+ "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
185165
+ ];
185166
+ var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
185167
+ var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
185168
+ function stripSystemInjection(text) {
185169
+ let hasInjection = false;
185170
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
185171
+ if (text.includes(marker)) {
185172
+ hasInjection = true;
185173
+ break;
185174
+ }
185175
+ }
185176
+ if (SYSTEM_REMINDER_REGEX.test(text))
185177
+ hasInjection = true;
185178
+ SYSTEM_REMINDER_REGEX.lastIndex = 0;
185179
+ if (!hasInjection)
185180
+ return null;
185181
+ let cleaned = text;
185182
+ cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
185183
+ cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
185184
+ cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
185185
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
185186
+ if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
185187
+ continue;
185188
+ const idx = cleaned.indexOf(marker);
185189
+ if (idx === -1)
185190
+ continue;
185191
+ const blockEnd = cleaned.indexOf(`
185192
+
185193
+ `, idx + marker.length);
185194
+ cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
185195
+ }
185196
+ return cleaned.trim();
185197
+ }
185198
+
185199
+ // src/hooks/magic-context/apply-operations.ts
185200
+ init_tag_part_guards();
183459
185201
  var USER_DROP_PREVIEW_CHARS = 250;
183460
185202
  var RECENT_TOOL_SKELETON_WINDOW = 20;
183461
185203
  function buildReplacementContent(tagId, target) {
@@ -183464,6 +185206,10 @@ function buildReplacementContent(tagId, target) {
183464
185206
  return `[dropped §${tagId}§]`;
183465
185207
  }
183466
185208
  const currentContent = target.getContent?.() ?? "";
185209
+ const strippedInjection = stripSystemInjection(currentContent);
185210
+ if (strippedInjection !== null && stripTagPrefix(strippedInjection).trim().length === 0) {
185211
+ return `[dropped §${tagId}§]`;
185212
+ }
183467
185213
  const originalText = stripTagPrefix(currentContent);
183468
185214
  if (originalText.length <= USER_DROP_PREVIEW_CHARS) {
183469
185215
  return `[truncated §${tagId}§]
@@ -184084,6 +185830,7 @@ init_embedding();
184084
185830
 
184085
185831
  // src/features/magic-context/search.ts
184086
185832
  init_logger();
185833
+ init_compartment_chunk_embedding();
184087
185834
 
184088
185835
  // src/features/magic-context/literal-probes.ts
184089
185836
  var MAX_PROBES = 5;
@@ -184145,6 +185892,7 @@ function containsProbeVerbatim(text, probes) {
184145
185892
  init_memory();
184146
185893
  init_embedding();
184147
185894
  init_storage_memory_fts();
185895
+ init_workspaces();
184148
185896
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
184149
185897
  var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
184150
185898
  var SEMANTIC_WEIGHT = 0.7;
@@ -184174,6 +185922,37 @@ function previewText(text) {
184174
185922
  }
184175
185923
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
184176
185924
  }
185925
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
185926
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
185927
+ const isWorkspaced = resolved.identities.length > 1;
185928
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
185929
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
185930
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
185931
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
185932
+ return {
185933
+ identities: resolved.identities,
185934
+ expandedIdentities,
185935
+ ownIdentities,
185936
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
185937
+ namesByIdentity: resolved.namesByIdentity,
185938
+ canonicalIdentityByStoredPath,
185939
+ isWorkspaced
185940
+ };
185941
+ }
185942
+ function memoryWorkspaceIdentity(memory, workspace) {
185943
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
185944
+ }
185945
+ function sourceNamesForSearchMemories(args) {
185946
+ if (!args.workspace.isWorkspaced)
185947
+ return;
185948
+ const sourceNames = new Map;
185949
+ for (const memory of args.memories) {
185950
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
185951
+ if (source)
185952
+ sourceNames.set(memory.id, source);
185953
+ }
185954
+ return sourceNames.size > 0 ? sourceNames : undefined;
185955
+ }
184177
185956
  function getMessageSearchStatement(db) {
184178
185957
  let stmt = messageSearchStatements.get(db);
184179
185958
  if (!stmt) {
@@ -184182,6 +185961,30 @@ function getMessageSearchStatement(db) {
184182
185961
  }
184183
185962
  return stmt;
184184
185963
  }
185964
+ var ftsRowCountStatements = new WeakMap;
185965
+ var ftsMatchCountStatements = new WeakMap;
185966
+ function getSessionFtsRowCount(db, sessionId) {
185967
+ let stmt = ftsRowCountStatements.get(db);
185968
+ if (!stmt) {
185969
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
185970
+ ftsRowCountStatements.set(db, stmt);
185971
+ }
185972
+ const row = stmt.get(sessionId);
185973
+ return typeof row?.n === "number" ? row.n : 0;
185974
+ }
185975
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
185976
+ let stmt = ftsMatchCountStatements.get(db);
185977
+ if (!stmt) {
185978
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
185979
+ ftsMatchCountStatements.set(db, stmt);
185980
+ }
185981
+ try {
185982
+ const row = stmt.get(sessionId, ftsQuery);
185983
+ return typeof row?.n === "number" ? row.n : 0;
185984
+ } catch {
185985
+ return 0;
185986
+ }
185987
+ }
184185
185988
  function getMessageOrdinal(value) {
184186
185989
  if (typeof value === "number" && Number.isFinite(value)) {
184187
185990
  return value;
@@ -184197,25 +186000,63 @@ async function getSemanticScores(args) {
184197
186000
  if (!args.queryEmbedding || args.memories.length === 0) {
184198
186001
  return semanticScores;
184199
186002
  }
184200
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
184201
- const embeddings = await ensureMemoryEmbeddings({
184202
- db: args.db,
184203
- projectIdentity: args.projectPath,
184204
- memories: args.memories,
184205
- existingEmbeddings: cachedEmbeddings
184206
- });
186003
+ if (!args.workspace?.isWorkspaced) {
186004
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
186005
+ const embeddings = await ensureMemoryEmbeddings({
186006
+ db: args.db,
186007
+ projectIdentity: args.projectPath,
186008
+ memories: args.memories,
186009
+ existingEmbeddings: cachedEmbeddings
186010
+ });
186011
+ for (const memory of args.memories) {
186012
+ const memoryEmbedding = embeddings.get(memory.id);
186013
+ if (!memoryEmbedding) {
186014
+ continue;
186015
+ }
186016
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
186017
+ }
186018
+ return semanticScores;
186019
+ }
186020
+ if (!args.queryModelId || args.queryModelId === "off") {
186021
+ return semanticScores;
186022
+ }
186023
+ const workspace = args.workspace;
186024
+ const memoriesByIdentity = new Map;
184207
186025
  for (const memory of args.memories) {
184208
- const memoryEmbedding = embeddings.get(memory.id);
184209
- if (!memoryEmbedding) {
186026
+ const identity = memoryWorkspaceIdentity(memory, workspace);
186027
+ if (!identity)
186028
+ continue;
186029
+ const list = memoriesByIdentity.get(identity) ?? [];
186030
+ list.push(memory);
186031
+ memoriesByIdentity.set(identity, list);
186032
+ }
186033
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
186034
+ if (ownMemories.length > 0) {
186035
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
186036
+ await ensureMemoryEmbeddings({
186037
+ db: args.db,
186038
+ projectIdentity: args.projectPath,
186039
+ memories: ownMemories,
186040
+ existingEmbeddings: ownEmbeddings
186041
+ });
186042
+ }
186043
+ for (const identity of workspace.identities) {
186044
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
186045
+ if (memberMemories.length === 0)
184210
186046
  continue;
186047
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
186048
+ for (const memory of memberMemories) {
186049
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
186050
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
186051
+ continue;
186052
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
184211
186053
  }
184212
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
184213
186054
  }
184214
186055
  return semanticScores;
184215
186056
  }
184216
186057
  function getFtsMatches(args) {
184217
186058
  try {
184218
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
186059
+ return args.workspace?.isWorkspaced ? searchMemoriesFTSUnion(args.db, args.workspace.expandedIdentities, args.query, args.limit, args.workspace.ownIdentities, args.workspace.shareCategories) : searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
184219
186060
  } catch (error51) {
184220
186061
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
184221
186062
  return [];
@@ -184229,8 +186070,11 @@ function selectSemanticCandidates(args) {
184229
186070
  return args.memories;
184230
186071
  }
184231
186072
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
184232
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
184233
- if (cachedEmbeddings) {
186073
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
186074
+ for (const projectPath of embeddingProjects) {
186075
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
186076
+ if (!cachedEmbeddings)
186077
+ continue;
184234
186078
  for (const memoryId of cachedEmbeddings.keys()) {
184235
186079
  candidateIds.add(memoryId);
184236
186080
  }
@@ -184272,7 +186116,8 @@ function mergeMemoryResults(args) {
184272
186116
  score,
184273
186117
  memoryId: memory.id,
184274
186118
  category: memory.category,
184275
- matchType
186119
+ matchType,
186120
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
184276
186121
  });
184277
186122
  }
184278
186123
  return results.sort((left, right) => {
@@ -184286,7 +186131,7 @@ async function searchMemories(args) {
184286
186131
  if (!args.memoryEnabled) {
184287
186132
  return [];
184288
186133
  }
184289
- const memories = getMemoriesByProject(args.db, args.projectPath);
186134
+ const memories = args.workspace?.isWorkspaced ? getMemoriesByProjects(args.db, args.workspace.expandedIdentities, ["active", "permanent"], Date.now(), args.workspace.ownIdentities, args.workspace.shareCategories) : getMemoriesByProject(args.db, args.projectPath);
184290
186135
  if (memories.length === 0) {
184291
186136
  return [];
184292
186137
  }
@@ -184294,26 +186139,43 @@ async function searchMemories(args) {
184294
186139
  db: args.db,
184295
186140
  projectPath: args.projectPath,
184296
186141
  query: args.query,
184297
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
186142
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
186143
+ workspace: args.workspace
184298
186144
  });
184299
186145
  const ftsScores = getFtsScores(ftsMatches);
184300
186146
  const semanticCandidates = selectSemanticCandidates({
184301
186147
  memories,
184302
186148
  projectPath: args.projectPath,
184303
- ftsMatches
186149
+ ftsMatches,
186150
+ workspace: args.workspace
184304
186151
  });
184305
186152
  const semanticScores = await getSemanticScores({
184306
186153
  db: args.db,
184307
186154
  projectPath: args.projectPath,
184308
186155
  memories: semanticCandidates,
184309
- queryEmbedding: args.queryEmbedding
186156
+ queryEmbedding: args.queryEmbedding,
186157
+ queryModelId: args.queryModelId,
186158
+ workspace: args.workspace
184310
186159
  });
184311
186160
  return mergeMemoryResults({
184312
186161
  memories,
184313
186162
  semanticScores,
184314
186163
  ftsScores,
184315
186164
  limit: args.limit,
184316
- visibleMemoryIds: args.visibleMemoryIds
186165
+ visibleMemoryIds: args.visibleMemoryIds,
186166
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
186167
+ memories,
186168
+ projectPath: args.projectPath,
186169
+ workspace: args.workspace ?? {
186170
+ identities: [args.projectPath],
186171
+ expandedIdentities: [args.projectPath],
186172
+ namesByIdentity: new Map,
186173
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
186174
+ ownIdentities: [args.projectPath],
186175
+ shareCategories: null,
186176
+ isWorkspaced: false
186177
+ }
186178
+ })
184317
186179
  });
184318
186180
  }
184319
186181
  function linearDecayScore(rank, total) {
@@ -184344,7 +186206,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
184344
186206
  return result;
184345
186207
  }
184346
186208
  var RRF_K = 60;
184347
- var VERBATIM_PROBE_BONUS = 0.5;
186209
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
186210
+ var IDF_FALLOFF = 100;
186211
+ function probeDiscriminationWeight(df, corpusSize) {
186212
+ if (corpusSize <= 0 || df <= 0)
186213
+ return 1;
186214
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
186215
+ }
184348
186216
  function searchMessages(args) {
184349
186217
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
184350
186218
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -184361,20 +186229,31 @@ function searchMessages(args) {
184361
186229
  role: row.role
184362
186230
  }));
184363
186231
  }
186232
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
184364
186233
  const queryLists = [];
184365
186234
  if (baseQuery.length > 0) {
184366
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
186235
+ queryLists.push({
186236
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
186237
+ weight: 1
186238
+ });
184367
186239
  }
186240
+ const probeWeights = new Map;
184368
186241
  for (const probe of probes) {
184369
186242
  const probeQuery = sanitizeFtsQuery(probe);
184370
186243
  if (probeQuery.length === 0)
184371
186244
  continue;
184372
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
186245
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
186246
+ const weight = probeDiscriminationWeight(df, corpusSize);
186247
+ probeWeights.set(probe, weight);
186248
+ queryLists.push({
186249
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
186250
+ weight
186251
+ });
184373
186252
  }
184374
186253
  const fused = new Map;
184375
186254
  for (const list of queryLists) {
184376
- list.forEach((row, rank) => {
184377
- const rrf = 1 / (RRF_K + rank);
186255
+ list.rows.forEach((row, rank) => {
186256
+ const rrf = list.weight / (RRF_K + rank);
184378
186257
  const existing = fused.get(row.messageId);
184379
186258
  if (existing) {
184380
186259
  existing.score += rrf;
@@ -184384,26 +186263,107 @@ function searchMessages(args) {
184384
186263
  });
184385
186264
  }
184386
186265
  for (const entry of fused.values()) {
184387
- if (containsProbeVerbatim(entry.row.content, probes)) {
184388
- entry.score += VERBATIM_PROBE_BONUS;
186266
+ let best = 0;
186267
+ for (const probe of probes) {
186268
+ const weight = probeWeights.get(probe) ?? 0;
186269
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
186270
+ best = weight;
186271
+ }
186272
+ }
186273
+ if (best > 0) {
186274
+ entry.score += best * VERBATIM_RANK_BONUS;
184389
186275
  }
184390
186276
  }
184391
186277
  const ranked = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.row.messageOrdinal - b.row.messageOrdinal).slice(0, args.limit);
184392
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
184393
- return ranked.map((entry) => ({
186278
+ return ranked.map((entry, rank) => ({
184394
186279
  source: "message",
184395
186280
  content: previewText(entry.row.content),
184396
- score: maxScore > 0 ? entry.score / maxScore : 0,
186281
+ score: linearDecayScore(rank, ranked.length),
184397
186282
  messageOrdinal: entry.row.messageOrdinal,
184398
186283
  messageId: entry.row.messageId,
184399
186284
  role: entry.row.role
184400
186285
  }));
184401
186286
  }
186287
+ function searchCompartmentChunks(args) {
186288
+ if (!args.queryEmbedding || args.limit <= 0)
186289
+ return [];
186290
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
186291
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
186292
+ if (rows.length === 0)
186293
+ return [];
186294
+ const byCompartment = new Map;
186295
+ for (const row of rows) {
186296
+ if (cutoff !== null && row.endOrdinal > cutoff) {
186297
+ continue;
186298
+ }
186299
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
186300
+ if (score <= 0)
186301
+ continue;
186302
+ const existing = byCompartment.get(row.compartmentId);
186303
+ if (!existing || score > existing.score) {
186304
+ byCompartment.set(row.compartmentId, { row, score });
186305
+ }
186306
+ }
186307
+ 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 }) => ({
186308
+ source: "compartment",
186309
+ content: previewText(row.title),
186310
+ score: score * SINGLE_SOURCE_PENALTY,
186311
+ compartmentId: row.compartmentId,
186312
+ sessionId: row.sessionId,
186313
+ title: row.title,
186314
+ startOrdinal: row.startOrdinal,
186315
+ endOrdinal: row.endOrdinal,
186316
+ matchType: "semantic"
186317
+ }));
186318
+ }
186319
+ function mergeMessageAndCompartmentResults(args) {
186320
+ if (args.compartments.length === 0)
186321
+ return args.messages;
186322
+ if (args.messages.length === 0)
186323
+ return args.compartments;
186324
+ const fused = new Map;
186325
+ const add = (key, result, score, tieOrdinal) => {
186326
+ const existing = fused.get(key);
186327
+ if (existing) {
186328
+ existing.score += score;
186329
+ return existing;
186330
+ }
186331
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
186332
+ fused.set(key, entry);
186333
+ return entry;
186334
+ };
186335
+ args.compartments.forEach((compartment, rank) => {
186336
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
186337
+ });
186338
+ for (const [rank, message] of args.messages.entries()) {
186339
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
186340
+ const contribution = 1 / (RRF_K + rank);
186341
+ if (!containing) {
186342
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
186343
+ continue;
186344
+ }
186345
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
186346
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
186347
+ entry.snippetScore = message.score;
186348
+ entry.result = {
186349
+ ...entry.result,
186350
+ matchType: "hybrid",
186351
+ snippet: message.content
186352
+ };
186353
+ }
186354
+ }
186355
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
186356
+ return ranked.map((entry, rank) => ({
186357
+ ...entry.result,
186358
+ score: linearDecayScore(rank, ranked.length)
186359
+ }));
186360
+ }
184402
186361
  function getSourceBoost(result) {
184403
186362
  switch (result.source) {
184404
186363
  case "memory":
184405
186364
  return MEMORY_SOURCE_BOOST;
184406
186365
  case "message":
186366
+ case "compartment":
184407
186367
  return MESSAGE_SOURCE_BOOST;
184408
186368
  case "git_commit":
184409
186369
  return GIT_COMMIT_SOURCE_BOOST;
@@ -184421,6 +186381,9 @@ function compareUnifiedResults(left, right) {
184421
186381
  if (left.source === "message" && right.source === "message") {
184422
186382
  return left.messageOrdinal - right.messageOrdinal;
184423
186383
  }
186384
+ if (left.source === "compartment" && right.source === "compartment") {
186385
+ return left.startOrdinal - right.startOrdinal;
186386
+ }
184424
186387
  if (left.source === "git_commit" && right.source === "git_commit") {
184425
186388
  return right.committedAtMs - left.committedAtMs;
184426
186389
  }
@@ -184471,10 +186434,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184471
186434
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
184472
186435
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
184473
186436
  const activeSources = resolveSources(options.sources);
184474
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
186437
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
186438
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
184475
186439
  const runMessages = activeSources.has("message");
184476
186440
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
184477
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
186441
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
186442
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
184478
186443
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
184479
186444
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
184480
186445
  return null;
@@ -184490,6 +186455,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184490
186455
  probes: messageProbes
184491
186456
  }) : [];
184492
186457
  const queryEmbedding = await queryEmbeddingPromise;
186458
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
186459
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
186460
+ const embeddingModelId = embeddingSnapshot?.modelId;
186461
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
186462
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
186463
+ db,
186464
+ sessionId,
186465
+ projectPath,
186466
+ queryEmbedding,
186467
+ limit: tierLimit,
186468
+ maxOrdinal: options.maxMessageOrdinal,
186469
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
186470
+ }) : [];
186471
+ const messageLikeResults = mergeMessageAndCompartmentResults({
186472
+ messages: messageResults,
186473
+ compartments: compartmentResults,
186474
+ limit: tierLimit
186475
+ });
184493
186476
  const [memoryResults, gitCommitResults] = await Promise.all([
184494
186477
  runMemory ? searchMemories({
184495
186478
  db,
@@ -184498,6 +186481,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184498
186481
  limit: tierLimit,
184499
186482
  memoryEnabled: true,
184500
186483
  queryEmbedding,
186484
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
186485
+ workspace,
184501
186486
  visibleMemoryIds: options.visibleMemoryIds
184502
186487
  }) : Promise.resolve([]),
184503
186488
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -184508,7 +186493,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184508
186493
  queryEmbedding
184509
186494
  })) : Promise.resolve([])
184510
186495
  ]);
184511
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
186496
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
184512
186497
  const countRetrievals = options.countRetrievals ?? true;
184513
186498
  if (countRetrievals) {
184514
186499
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -184572,6 +186557,11 @@ function renderFragment(result, charCap) {
184572
186557
  const compressed = cavemanCompress(result.content, "ultra");
184573
186558
  return truncate(compressed, charCap);
184574
186559
  }
186560
+ case "compartment": {
186561
+ const source = result.snippet ?? result.title;
186562
+ const compressed = cavemanCompress(source, "ultra");
186563
+ return truncate(compressed, charCap);
186564
+ }
184575
186565
  }
184576
186566
  }
184577
186567
  function buildAutoSearchHint(results, options = {}) {
@@ -184926,51 +186916,6 @@ function planEmergencyDrop(input) {
184926
186916
  };
184927
186917
  }
184928
186918
 
184929
- // src/hooks/magic-context/system-injection-stripper.ts
184930
- var SYSTEM_INJECTION_MARKERS = [
184931
- "<!-- OMO_INTERNAL_INITIATOR -->",
184932
- "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
184933
- "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
184934
- "[Category+Skill Reminder]",
184935
- "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
184936
- "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
184937
- "[EMERGENCY CONTEXT WINDOW WARNING]",
184938
- "Unstable background agent appears idle",
184939
- "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
184940
- ];
184941
- var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
184942
- var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
184943
- function stripSystemInjection(text) {
184944
- let hasInjection = false;
184945
- for (const marker of SYSTEM_INJECTION_MARKERS) {
184946
- if (text.includes(marker)) {
184947
- hasInjection = true;
184948
- break;
184949
- }
184950
- }
184951
- if (SYSTEM_REMINDER_REGEX.test(text))
184952
- hasInjection = true;
184953
- SYSTEM_REMINDER_REGEX.lastIndex = 0;
184954
- if (!hasInjection)
184955
- return null;
184956
- let cleaned = text;
184957
- cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
184958
- cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
184959
- cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
184960
- for (const marker of SYSTEM_INJECTION_MARKERS) {
184961
- if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
184962
- continue;
184963
- const idx = cleaned.indexOf(marker);
184964
- if (idx === -1)
184965
- continue;
184966
- const blockEnd = cleaned.indexOf(`
184967
-
184968
- `, idx + marker.length);
184969
- cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
184970
- }
184971
- return cleaned.trim();
184972
- }
184973
-
184974
186919
  // src/hooks/magic-context/heuristic-cleanup.ts
184975
186920
  init_tag_part_guards();
184976
186921
  var DEDUP_SAFE_TOOLS = new Set([
@@ -185212,7 +187157,7 @@ function isVisibleNoteReadPart(part) {
185212
187157
  }
185213
187158
 
185214
187159
  // src/hooks/magic-context/todo-view.ts
185215
- import { createHash as createHash9 } from "node:crypto";
187160
+ import { createHash as createHash11 } from "node:crypto";
185216
187161
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
185217
187162
  var TITLE_DONE_STATUSES = new Set(["completed"]);
185218
187163
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -185257,7 +187202,7 @@ function buildSyntheticTodoPart(stateJson) {
185257
187202
  };
185258
187203
  }
185259
187204
  function computeSyntheticCallId(stateJson) {
185260
- const hash2 = createHash9("sha256").update(stateJson).digest("hex").slice(0, 16);
187205
+ const hash2 = createHash11("sha256").update(stateJson).digest("hex").slice(0, 16);
185261
187206
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
185262
187207
  }
185263
187208
  function parseTodoState(stateJson) {
@@ -185308,15 +187253,25 @@ async function runPostTransformPhase(args) {
185308
187253
  const emergencyBypassCompartmentGate = forceMaterialization;
185309
187254
  const deferredMaterialize = args.canConsumeDeferredLate && deferredMaterializationWasPending;
185310
187255
  const materializationRequested = isExplicitFlush || deferredMaterialize;
185311
- const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
187256
+ const m0M1EnabledForFold = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
187257
+ const m0HardFoldThisPass = m0M1EnabledForFold && args.m0M1 ? mustMaterialize({
187258
+ db: args.db,
187259
+ sessionId: args.sessionId,
187260
+ state: args.sessionMeta,
187261
+ projectPath: args.m0M1.projectPath,
187262
+ projectDirectory: args.m0M1.projectDirectory,
187263
+ hardSignals: args.m0M1.hardSignals
187264
+ }).value : false;
187265
+ const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || m0HardFoldThisPass || compartmentRunning;
185312
187266
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
185313
187267
  const hasPendingUserOps = pendingOps.length > 0;
185314
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
185315
- const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
187268
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || m0HardFoldThisPass) && (!compartmentRunning || emergencyBypassCompartmentGate);
187269
+ const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || m0HardFoldThisPass || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
185316
187270
  const isCacheBustingPass = shouldApplyPendingOps || shouldRunHeuristics;
187271
+ const canUseEmptySentinels = modelAcceptsEmptyContent(args.resolvedProviderID);
185317
187272
  if (shouldRunHeuristics) {
185318
187273
  const subagentRerun = !args.fullFeatureMode && alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && !forceMaterialization;
185319
- const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
187274
+ const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : m0HardFoldThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into known m[0] bust, scheduler=${args.schedulerDecision})` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
185320
187275
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
185321
187276
  }
185322
187277
  if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !materializationRequested && args.fullFeatureMode) {
@@ -185359,7 +187314,9 @@ async function runPostTransformPhase(args) {
185359
187314
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags}`);
185360
187315
  const t7 = performance.now();
185361
187316
  const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
185362
- stripClearedReasoning(args.messages);
187317
+ if (canUseEmptySentinels) {
187318
+ stripClearedReasoning(args.messages);
187319
+ }
185363
187320
  const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
185364
187321
  if (clearedReasoning > 0 || strippedInline > 0) {
185365
187322
  let maxTag = 0;
@@ -185405,7 +187362,6 @@ async function runPostTransformPhase(args) {
185405
187362
  if (args.watermark > 0) {
185406
187363
  const tWatermarkCleanup = performance.now();
185407
187364
  truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
185408
- stripProcessedImages(args.messages, args.watermark, args.messageTagNumbers);
185409
187365
  logTransformTiming(args.sessionId, "watermarkCleanup", tWatermarkCleanup);
185410
187366
  }
185411
187367
  if (shouldApplyPendingOps) {
@@ -185415,21 +187371,40 @@ async function runPostTransformPhase(args) {
185415
187371
  sessionLog(args.sessionId, "transform failed applying pending operations:", error51);
185416
187372
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: getErrorMessage(error51) });
185417
187373
  }
185418
- try {
185419
- const t8 = performance.now();
185420
- const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
185421
- const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
185422
- detect: isCacheBustingPass,
185423
- protectedCount: args.protectedTags
185424
- });
185425
- if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
185426
- addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
187374
+ if (canUseEmptySentinels) {
187375
+ try {
187376
+ const t8 = performance.now();
187377
+ const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
187378
+ const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
187379
+ detect: isCacheBustingPass,
187380
+ protectedCount: args.protectedTags
187381
+ });
187382
+ if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
187383
+ addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
187384
+ }
187385
+ logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
187386
+ } catch (error51) {
187387
+ sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
185427
187388
  }
185428
- logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
185429
- } catch (error51) {
185430
- sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
185431
187389
  }
185432
- const m0M1Enabled = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
187390
+ if (canUseEmptySentinels && args.watermark > 0) {
187391
+ try {
187392
+ const tImg = performance.now();
187393
+ const frozenImageIds = getProcessedImageStrippedIds(args.db, args.sessionId);
187394
+ const imageResult = stripProcessedImages(args.messages, frozenImageIds, {
187395
+ detect: isCacheBustingPass,
187396
+ watermark: args.watermark,
187397
+ messageTagNumbers: args.messageTagNumbers
187398
+ });
187399
+ if (isCacheBustingPass && imageResult.newlyStrippedIds.length > 0) {
187400
+ addProcessedImageStrippedIds(args.db, args.sessionId, imageResult.newlyStrippedIds);
187401
+ }
187402
+ logTransformTiming(args.sessionId, "stripProcessedImages", tImg);
187403
+ } catch (error51) {
187404
+ sessionLog(args.sessionId, "transform failed stripping processed images:", error51);
187405
+ }
187406
+ }
187407
+ const m0M1Enabled = m0M1EnabledForFold;
185433
187408
  if (m0M1Enabled && args.m0M1) {
185434
187409
  const tInjectM0M1 = performance.now();
185435
187410
  try {
@@ -185476,7 +187451,7 @@ async function runPostTransformPhase(args) {
185476
187451
  const tPlaceholder = performance.now();
185477
187452
  const persistedIds = getStrippedPlaceholderIds(args.db, args.sessionId);
185478
187453
  if (persistedIds.size > 0) {
185479
- const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.liveProviderID);
187454
+ const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.resolvedProviderID);
185480
187455
  if (replayed > 0) {
185481
187456
  sessionLog(args.sessionId, `sentinel replay: neutralized ${replayed} previously-stripped messages`);
185482
187457
  }
@@ -185487,9 +187462,9 @@ async function runPostTransformPhase(args) {
185487
187462
  }
185488
187463
  }
185489
187464
  if (isCacheBustingPass) {
185490
- const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.liveProviderID);
187465
+ const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.resolvedProviderID);
185491
187466
  const protectedTailStart = Math.max(0, args.messages.length - args.protectedTags * 2);
185492
- const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.liveProviderID);
187467
+ const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.resolvedProviderID);
185493
187468
  const newlyNeutralized = droppedResult.sentineledIds.length + systemInjectedResult.sentineledIds.length;
185494
187469
  if (newlyNeutralized > 0) {
185495
187470
  const addedIds = [
@@ -185689,6 +187664,7 @@ function clearMessageTokensCache(sessionId, messageId) {
185689
187664
  if (cache)
185690
187665
  cache.delete(messageId);
185691
187666
  }
187667
+ var recordedSessionProjectIdentity = new BoundedSessionMap(MESSAGE_TOKENS_CACHE_MAX);
185692
187668
  function findLastAssistantModel2(messages) {
185693
187669
  for (let i = messages.length - 1;i >= 0; i--) {
185694
187670
  const info = messages[i].info;
@@ -185736,9 +187712,11 @@ function createTransform(deps) {
185736
187712
  const fullFeatureMode = !reducedMode;
185737
187713
  const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false && resolveCtxReduceAvailabilityFromMessages(sessionId, messages);
185738
187714
  let sessionDirectory = deps.directory ?? "";
187715
+ let sessionDirectoryResolvedFromHost = false;
185739
187716
  const cachedDirectory = deps.sessionDirectoryBySession?.get(sessionId);
185740
187717
  if (cachedDirectory && cachedDirectory.length > 0) {
185741
187718
  sessionDirectory = cachedDirectory;
187719
+ sessionDirectoryResolvedFromHost = true;
185742
187720
  } else if (deps.client !== undefined) {
185743
187721
  try {
185744
187722
  const sessionResponse = await deps.client.session.get({ path: { id: sessionId } }).catch(() => null);
@@ -185746,6 +187724,7 @@ function createTransform(deps) {
185746
187724
  if (sessionInfo && typeof sessionInfo.directory === "string" && sessionInfo.directory.length > 0) {
185747
187725
  sessionDirectory = sessionInfo.directory;
185748
187726
  deps.sessionDirectoryBySession?.set(sessionId, sessionDirectory);
187727
+ sessionDirectoryResolvedFromHost = true;
185749
187728
  }
185750
187729
  } catch {}
185751
187730
  }
@@ -185840,6 +187819,8 @@ function createTransform(deps) {
185840
187819
  deps.liveModelBySession?.set(sessionId, recovered);
185841
187820
  }
185842
187821
  }
187822
+ const resolvedProviderID = modelForBudget?.providerID;
187823
+ const canUseEmptySentinels = modelAcceptsEmptyContent(resolvedProviderID);
185843
187824
  const resolvedContextLimit = modelForBudget ? resolveTrustedContextLimit(modelForBudget.providerID, modelForBudget.modelID, {
185844
187825
  db,
185845
187826
  sessionID: sessionId
@@ -186004,6 +187985,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186004
187985
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
186005
187986
  const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(compartmentDirectory || process.cwd()) : undefined;
186006
187987
  const sessionProjectIdentity = projectIdentity ?? (sessionDirectory ? resolveProjectIdentity(sessionDirectory) : deps.projectPath);
187988
+ if (sessionProjectIdentity && sessionDirectoryResolvedFromHost && recordedSessionProjectIdentity.get(sessionId) !== sessionProjectIdentity) {
187989
+ recordSessionProjectIdentity(db, sessionId, sessionProjectIdentity);
187990
+ recordedSessionProjectIdentity.set(sessionId, sessionProjectIdentity);
187991
+ }
186007
187992
  let triggerBoundarySnapshot;
186008
187993
  if (fullFeatureMode && historianRunnable && !sessionMeta.compartmentInProgress) {
186009
187994
  const tTrigger = performance.now();
@@ -186098,7 +188083,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186098
188083
  }
186099
188084
  }
186100
188085
  const t3 = performance.now();
186101
- const strippedStructuralNoise = stripStructuralNoise(messages);
188086
+ const strippedStructuralNoise = canUseEmptySentinels ? stripStructuralNoise(messages) : 0;
186102
188087
  logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
186103
188088
  const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
186104
188089
  if (persistedReasoningWatermark > 0) {
@@ -186119,18 +188104,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186119
188104
  logTransformTiming(sessionId, "replayCavemanCompression", tCavemanReplay);
186120
188105
  }
186121
188106
  const t4 = performance.now();
186122
- const strippedClearedReasoning = stripClearedReasoning(messages);
188107
+ const strippedClearedReasoning = canUseEmptySentinels ? stripClearedReasoning(messages) : 0;
186123
188108
  logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
186124
188109
  const tMergeStrip = performance.now();
186125
- let liveProviderID = deps.liveModelBySession?.get(sessionId)?.providerID;
186126
- if (liveProviderID === undefined) {
186127
- const recovered = findLastAssistantModelFromOpenCodeDb(sessionId);
186128
- if (recovered) {
186129
- liveProviderID = recovered.providerID;
186130
- deps.liveModelBySession?.set(sessionId, recovered);
186131
- }
186132
- }
186133
- const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, liveProviderID);
188110
+ const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, resolvedProviderID);
186134
188111
  if (strippedMergedReasoning > 0) {
186135
188112
  sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
186136
188113
  }
@@ -186249,7 +188226,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186249
188226
  sessionDirectory,
186250
188227
  autoSearch: deps.autoSearch,
186251
188228
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
186252
- liveProviderID,
188229
+ resolvedProviderID,
186253
188230
  historyRefreshSessions: deps.historyRefreshSessions,
186254
188231
  m0M1: {
186255
188232
  projectPath: projectIdentity,
@@ -186402,7 +188379,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186402
188379
  let tailToolTokens;
186403
188380
  let liveTailTokens;
186404
188381
  try {
186405
- const agg = getActiveTagTokenAggregate(db, sessionId);
188382
+ const agg = getActiveTagTokenAggregate(db, sessionId, deps.protectedTags);
186406
188383
  tailToolTokens = agg.toolOutput;
186407
188384
  liveTailTokens = agg.conversation + agg.toolCall;
186408
188385
  } catch {
@@ -187013,10 +188990,11 @@ function maybeInjectChannel1Nudge(args, sessionId, tool, output) {
187013
188990
  contextLimit: state.contextLimit,
187014
188991
  executeThresholdPercentage: state.executeThresholdPercentage
187015
188992
  });
188993
+ const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
187016
188994
  const decision = decideChannel1({
187017
188995
  undroppedTokens,
187018
188996
  pressure,
187019
- historyBudgetTokens: state.historyBudgetTokens,
188997
+ workingWindowTokens,
187020
188998
  lastNudgeUndropped: getLastNudgeUndropped(args.db, sessionId),
187021
188999
  lastNudgeLevel: getLastNudgeLevel(args.db, sessionId),
187022
189000
  hasRecentReduce: false
@@ -187078,7 +189056,7 @@ function createToolExecuteAfterHook(args) {
187078
189056
  init_send_session_notification();
187079
189057
 
187080
189058
  // src/hooks/magic-context/system-prompt-hash.ts
187081
- import { createHash as createHash10 } from "node:crypto";
189059
+ import { createHash as createHash12 } from "node:crypto";
187082
189060
 
187083
189061
  // src/agents/magic-context-prompt.ts
187084
189062
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -187278,7 +189256,7 @@ function createSystemPromptHashHandler(deps) {
187278
189256
  `);
187279
189257
  if (systemContent.length === 0)
187280
189258
  return;
187281
- const currentHash = createHash10("md5").update(systemContent).digest("hex");
189259
+ const currentHash = createHash12("md5").update(systemContent).digest("hex");
187282
189260
  if (!sessionMetaEarly) {
187283
189261
  return;
187284
189262
  }
@@ -187539,6 +189517,46 @@ function createMagicContextHook(deps) {
187539
189517
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
187540
189518
  getNotificationParams: (sid) => getLiveNotificationParams(sid, liveModelBySession, variantBySession, agentBySession)
187541
189519
  });
189520
+ const executeEmbedHistory = async (sessionId) => {
189521
+ if (deps.config.memory?.enabled === false) {
189522
+ return "Memory is disabled for this project, so there is no semantic embedding to backfill.";
189523
+ }
189524
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
189525
+ await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
189526
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
189527
+ setRecompStarting({ recompProgressBySession }, sessionId, "Embedding history…", "embed");
189528
+ const outcome = await embedSessionCompartmentChunks(db, sessionProjectIdentity, sessionId, {
189529
+ onProgress: ({ embedded, total }) => {
189530
+ const cur = recompProgressBySession.get(sessionId);
189531
+ if (!cur || cur.phase !== "recomp")
189532
+ return;
189533
+ recompProgressBySession.set(sessionId, {
189534
+ ...cur,
189535
+ processedMessages: embedded,
189536
+ totalMessages: total,
189537
+ updatedAt: Date.now()
189538
+ });
189539
+ }
189540
+ });
189541
+ const terminal = (phase, message) => {
189542
+ setRecompTerminal({ recompProgressBySession }, sessionId, phase, message);
189543
+ return message;
189544
+ };
189545
+ switch (outcome.status) {
189546
+ case "nothing":
189547
+ return terminal("done", "All of this session's history is already embedded.");
189548
+ case "disabled":
189549
+ return terminal("skipped", "No embedding provider is configured, so there is nothing to embed.");
189550
+ case "busy":
189551
+ return terminal("skipped", `Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`);
189552
+ case "aborted":
189553
+ return terminal("done", `Embedded ${outcome.embedded} of ${outcome.total} compartments before stopping.`);
189554
+ case "stalled":
189555
+ 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.`);
189556
+ default:
189557
+ return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`);
189558
+ }
189559
+ };
187542
189560
  const sidekickRunnable = isSidekickRunnable(deps.config);
187543
189561
  const sidekickConfig = sidekickRunnable ? deps.config.sidekick : undefined;
187544
189562
  const transform2 = createTransform({
@@ -187693,6 +189711,7 @@ function createMagicContextHook(deps) {
187693
189711
  },
187694
189712
  executeRecomp: historianRunnable ? async (sessionId, options) => runManagedRecomp(buildManagedRecompCtx(sessionId), sessionId, options) : undefined,
187695
189713
  runUpgrade: historianRunnable ? async (sessionId) => runManagedUpgrade(buildManagedRecompCtx(sessionId), sessionId) : undefined,
189714
+ executeEmbedHistory,
187696
189715
  sendNotification: async (sessionId, text, params) => {
187697
189716
  await sendIgnoredMessage(deps.client, sessionId, text, {
187698
189717
  ...getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
@@ -188830,6 +190849,7 @@ init_memory();
188830
190849
  init_embedding();
188831
190850
  init_embedding_cache();
188832
190851
  init_normalize_hash();
190852
+ init_workspaces();
188833
190853
  init_logger();
188834
190854
  await init_storage();
188835
190855
  import { tool as tool2 } from "@opencode-ai/plugin";
@@ -188989,6 +191009,23 @@ function createCtxMemoryTool(deps) {
188989
191009
  }
188990
191010
  const projectPath = deps.resolveProjectPath(toolContext.directory);
188991
191011
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
191012
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectPath);
191013
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
191014
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
191015
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? projectIdentityForStoredPath(rawProjectPath) : projectIdentityForStoredPath(rawProjectPath);
191016
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectPath) : null;
191017
+ const memoryVisibleToTool = (memory) => {
191018
+ if (workspaceIdentitySet.identities.length <= 1) {
191019
+ return memoryBelongsToProject(memory, projectPath);
191020
+ }
191021
+ if (!storedPathBelongsToWorkspace(memory.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
191022
+ return false;
191023
+ }
191024
+ const isOwn = targetIdentityForStoredPath(memory.projectPath) === projectPath;
191025
+ if (isOwn)
191026
+ return true;
191027
+ return toolShareCategories === null || toolShareCategories.includes(memory.category);
191028
+ };
188992
191029
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
188993
191030
  if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188994
191031
  return getDisabledMessage();
@@ -189045,18 +191082,18 @@ function createCtxMemoryTool(deps) {
189045
191082
  }
189046
191083
  const rawProjectPath = projectPathForMemoryId(deps.db, updateId);
189047
191084
  const memory = getMemoryById(deps.db, updateId);
189048
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
191085
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
189049
191086
  return `Error: Memory with ID ${updateId} was not found.`;
189050
191087
  }
189051
191088
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
189052
191089
  return inactiveMemoryError(updateId, "updating");
189053
191090
  }
189054
191091
  const normalizedHash = computeNormalizedHash(content);
189055
- const duplicate = getMemoryByHash(deps.db, projectPath, memory.category, normalizedHash);
191092
+ const duplicate = getMemoryByHash(deps.db, targetIdentityForStoredPath(rawProjectPath), memory.category, normalizedHash);
189056
191093
  if (duplicate && duplicate.id !== memory.id) {
189057
191094
  return `Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`;
189058
191095
  }
189059
- const projectIdentity = projectIdentityForStoredPath(rawProjectPath);
191096
+ const projectIdentity = targetIdentityForStoredPath(rawProjectPath);
189060
191097
  deps.db.transaction(() => {
189061
191098
  updateMemoryContentInCurrentTransaction(deps.db, memory, content, normalizedHash);
189062
191099
  queueMemoryMutation(deps.db, {
@@ -189070,7 +191107,7 @@ function createCtxMemoryTool(deps) {
189070
191107
  queueMemoryEmbedding({
189071
191108
  deps,
189072
191109
  sessionId: toolContext.sessionID,
189073
- projectPath,
191110
+ projectPath: projectIdentity,
189074
191111
  memoryId: memory.id,
189075
191112
  content
189076
191113
  });
@@ -189093,7 +191130,7 @@ function createCtxMemoryTool(deps) {
189093
191130
  return "Error: One or more source memories were not found.";
189094
191131
  }
189095
191132
  if (toolContext.agent !== DREAMER_AGENT) {
189096
- const foreign = sourceMemories.find((memory) => !memoryBelongsToProject(memory, projectPath));
191133
+ const foreign = sourceMemories.find((memory) => !memoryVisibleToTool(memory));
189097
191134
  if (foreign) {
189098
191135
  return `Error: Memory with ID ${foreign.id} was not found.`;
189099
191136
  }
@@ -189177,15 +191214,16 @@ function createCtxMemoryTool(deps) {
189177
191214
  return `Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`;
189178
191215
  }
189179
191216
  if (args.action === "archive") {
189180
- const archiveIds = args.ids;
189181
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
191217
+ const rawArchiveIds = args.ids;
191218
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
189182
191219
  return "Error: 'ids' must contain at least one integer memory ID when action is 'archive'.";
189183
191220
  }
191221
+ const archiveIds = [...new Set(rawArchiveIds)];
189184
191222
  const targets = [];
189185
191223
  for (const memoryId of archiveIds) {
189186
191224
  const rawProjectPath = projectPathForMemoryId(deps.db, memoryId);
189187
191225
  const memory = getMemoryById(deps.db, memoryId);
189188
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
191226
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
189189
191227
  return `Error: Memory with ID ${memoryId} was not found.`;
189190
191228
  }
189191
191229
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -189193,7 +191231,7 @@ function createCtxMemoryTool(deps) {
189193
191231
  }
189194
191232
  targets.push({
189195
191233
  memoryId,
189196
- projectIdentity: projectIdentityForStoredPath(rawProjectPath)
191234
+ projectIdentity: targetIdentityForStoredPath(rawProjectPath)
189197
191235
  });
189198
191236
  }
189199
191237
  deps.db.transaction(() => {
@@ -189667,8 +191705,9 @@ function formatAge2(committedAtMs) {
189667
191705
  }
189668
191706
  function formatResult(result, index) {
189669
191707
  if (result.source === "memory") {
191708
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
189670
191709
  return [
189671
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
191710
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
189672
191711
  result.content
189673
191712
  ].join(`
189674
191713
  `);
@@ -189678,6 +191717,13 @@ function formatResult(result, index) {
189678
191717
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
189679
191718
  result.content
189680
191719
  ].join(`
191720
+ `);
191721
+ }
191722
+ if (result.source === "compartment") {
191723
+ return [
191724
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
191725
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
191726
+ ].join(`
189681
191727
  `);
189682
191728
  }
189683
191729
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -189693,7 +191739,7 @@ function formatSearchResults(query, results) {
189693
191739
  return `No results found for "${query}" across memories, git commits, or message history.`;
189694
191740
  }
189695
191741
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
189696
- if (results.some((result) => result.source === "message")) {
191742
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
189697
191743
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
189698
191744
  }
189699
191745
  const body = bodyParts.join(`
@@ -189851,11 +191897,11 @@ import { createServer } from "node:http";
189851
191897
  import { dirname as dirname8 } from "node:path";
189852
191898
 
189853
191899
  // src/shared/rpc-utils.ts
189854
- import { createHash as createHash11 } from "node:crypto";
191900
+ import { createHash as createHash13 } from "node:crypto";
189855
191901
  import { join as join24 } from "node:path";
189856
191902
  function projectHash(directory) {
189857
191903
  const normalized = directory.replace(/\/+$/, "");
189858
- return createHash11("sha256").update(normalized).digest("hex").slice(0, 16);
191904
+ return createHash13("sha256").update(normalized).digest("hex").slice(0, 16);
189859
191905
  }
189860
191906
  function rpcPortDir(storageDir, directory) {
189861
191907
  return join24(storageDir, "rpc", projectHash(directory));