@cortexkit/opencode-magic-context 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +70 -0
  6. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -0
  7. package/dist/features/magic-context/compartment-embedding.d.ts +22 -26
  8. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  9. package/dist/features/magic-context/memory/embedding-backfill.d.ts +3 -2
  10. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  11. package/dist/features/magic-context/memory/embedding-cache.d.ts +3 -2
  12. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -1
  13. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -1
  14. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts +3 -0
  16. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  17. package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -0
  18. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  19. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  20. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  21. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +5 -1
  22. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  23. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -7
  24. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  25. package/dist/features/magic-context/memory/storage-memory.d.ts +18 -12
  26. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  27. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  28. package/dist/features/magic-context/project-embedding-registry.d.ts +53 -0
  29. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  30. package/dist/features/magic-context/search.d.ts +14 -1
  31. package/dist/features/magic-context/search.d.ts.map +1 -1
  32. package/dist/features/magic-context/session-project-storage.d.ts +17 -0
  33. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -0
  34. package/dist/features/magic-context/storage-db.d.ts +1 -1
  35. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  36. package/dist/features/magic-context/storage-memory-mutation-log.d.ts +2 -0
  37. package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage-meta-persisted.d.ts +16 -0
  39. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  41. package/dist/features/magic-context/storage-meta-shared.d.ts +3 -2
  42. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  43. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  44. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  45. package/dist/features/magic-context/storage.d.ts +3 -2
  46. package/dist/features/magic-context/storage.d.ts.map +1 -1
  47. package/dist/features/magic-context/tool-definition-tokens.d.ts +0 -21
  48. package/dist/features/magic-context/tool-definition-tokens.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.map +1 -1
  54. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/command-handler.d.ts +5 -0
  56. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/compartment-runner-types.d.ts +11 -1
  61. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +18 -0
  64. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -0
  65. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/inject-compartments.d.ts +23 -5
  67. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/issue-135-wire-fixtures.d.ts +8 -0
  69. package/dist/hooks/magic-context/issue-135-wire-fixtures.d.ts.map +1 -0
  70. package/dist/hooks/magic-context/openai-compat-adjacency.d.ts +38 -0
  71. package/dist/hooks/magic-context/openai-compat-adjacency.d.ts.map +1 -0
  72. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -1
  73. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  75. package/dist/hooks/magic-context/sentinel.d.ts +33 -28
  76. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  77. package/dist/hooks/magic-context/strip-content.d.ts +34 -17
  78. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  79. package/dist/hooks/magic-context/strip-structural-noise.d.ts +5 -7
  80. package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
  81. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  82. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +4 -5
  83. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  84. package/dist/hooks/magic-context/transform.d.ts +0 -8
  85. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  86. package/dist/index.js +2387 -348
  87. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  88. package/dist/shared/announcement.d.ts +1 -1
  89. package/dist/shared/rpc-types.d.ts +1 -1
  90. package/dist/shared/rpc-types.d.ts.map +1 -1
  91. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  92. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  93. package/package.json +1 -1
  94. package/src/shared/announcement.ts +6 -6
  95. package/src/shared/rpc-types.ts +1 -1
  96. package/src/tui/index.tsx +5 -3
  97. package/src/tui/slots/sidebar-content.tsx +24 -5
package/dist/index.js CHANGED
@@ -14887,7 +14887,8 @@ var init_magic_context = __esm(() => {
14887
14887
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
14888
14888
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
14889
14889
  input_type: exports_external.string().optional().describe("Optional input_type sent in the embedding request body. Required by some openai-compatible providers (e.g. NVIDIA NIM expects 'query' or 'passage'). Omitted from the request when unset."),
14890
- truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset.")
14890
+ truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset."),
14891
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
14891
14892
  }).superRefine((data, ctx) => {
14892
14893
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
14893
14894
  ctx.addIssue({
@@ -14908,7 +14909,8 @@ var init_magic_context = __esm(() => {
14908
14909
  if (data.provider === "local") {
14909
14910
  return {
14910
14911
  provider: "local",
14911
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
14912
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
14913
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14912
14914
  };
14913
14915
  }
14914
14916
  if (data.provider === "openai-compatible") {
@@ -14921,7 +14923,8 @@ var init_magic_context = __esm(() => {
14921
14923
  endpoint: data.endpoint?.trim() ?? "",
14922
14924
  ...apiKey ? { api_key: apiKey } : {},
14923
14925
  ...inputType ? { input_type: inputType } : {},
14924
- ...truncate ? { truncate } : {}
14926
+ ...truncate ? { truncate } : {},
14927
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14925
14928
  };
14926
14929
  }
14927
14930
  return { provider: "off" };
@@ -15841,7 +15844,7 @@ function isSessionMetaRow(row) {
15841
15844
  if (row === null || typeof row !== "object")
15842
15845
  return false;
15843
15846
  const r = row;
15844
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15847
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15845
15848
  }
15846
15849
  function getDefaultSessionMeta(sessionId) {
15847
15850
  return {
@@ -15868,6 +15871,7 @@ function getDefaultSessionMeta(sessionId) {
15868
15871
  cachedM0Bytes: null,
15869
15872
  cachedM1Bytes: null,
15870
15873
  cachedM0ProjectMemoryEpoch: null,
15874
+ cachedM0WorkspaceFingerprint: null,
15871
15875
  cachedM0ProjectUserProfileVersion: null,
15872
15876
  cachedM0MaxCompartmentSeq: null,
15873
15877
  cachedM0MaxMemoryId: null,
@@ -15930,6 +15934,7 @@ function toSessionMeta(row) {
15930
15934
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
15931
15935
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
15932
15936
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
15937
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
15933
15938
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
15934
15939
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
15935
15940
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -15960,6 +15965,7 @@ function persistCachedM0(db, sessionId, payload) {
15960
15965
  db.prepare(`UPDATE session_meta SET
15961
15966
  cached_m0_bytes = ?,
15962
15967
  cached_m0_project_memory_epoch = ?,
15968
+ cached_m0_workspace_fingerprint = ?,
15963
15969
  cached_m0_project_user_profile_version = ?,
15964
15970
  cached_m0_max_compartment_seq = ?,
15965
15971
  cached_m0_max_memory_id = ?,
@@ -15971,9 +15977,8 @@ function persistCachedM0(db, sessionId, payload) {
15971
15977
  cached_m0_session_facts_version = ?,
15972
15978
  cached_m0_upgrade_state = ?,
15973
15979
  cached_m0_system_hash = ?,
15974
- cached_m0_tool_set_hash = ?,
15975
15980
  cached_m0_model_key = ?
15976
- 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.toolSetHash ?? "", 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);
15977
15982
  }
15978
15983
  function clearCachedM0M1(db, sessionId) {
15979
15984
  ensureSessionMetaRow(db, sessionId);
@@ -15982,6 +15987,7 @@ function clearCachedM0M1(db, sessionId) {
15982
15987
  ["cached_m0_bytes", null],
15983
15988
  ["cached_m1_bytes", null],
15984
15989
  ["cached_m0_project_memory_epoch", null],
15990
+ ["cached_m0_workspace_fingerprint", null],
15985
15991
  ["cached_m0_project_user_profile_version", null],
15986
15992
  ["cached_m0_max_compartment_seq", null],
15987
15993
  ["cached_m0_max_memory_id", null],
@@ -16037,6 +16043,7 @@ var init_storage_meta_shared = __esm(() => {
16037
16043
  "cached_m0_bytes",
16038
16044
  "cached_m1_bytes",
16039
16045
  "cached_m0_project_memory_epoch",
16046
+ "cached_m0_workspace_fingerprint",
16040
16047
  "cached_m0_project_user_profile_version",
16041
16048
  "cached_m0_max_compartment_seq",
16042
16049
  "cached_m0_max_memory_id",
@@ -16084,6 +16091,7 @@ var init_storage_meta_shared = __esm(() => {
16084
16091
  cachedM0Bytes: "cached_m0_bytes",
16085
16092
  cachedM1Bytes: "cached_m1_bytes",
16086
16093
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
16094
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
16087
16095
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
16088
16096
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
16089
16097
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -16113,6 +16121,7 @@ var init_storage_meta_shared = __esm(() => {
16113
16121
  "cachedM0Bytes",
16114
16122
  "cachedM1Bytes",
16115
16123
  "cachedM0ProjectMemoryEpoch",
16124
+ "cachedM0WorkspaceFingerprint",
16116
16125
  "cachedM0ProjectUserProfileVersion",
16117
16126
  "cachedM0MaxCompartmentSeq",
16118
16127
  "cachedM0MaxMemoryId",
@@ -150317,18 +150326,6 @@ function keyFor(providerID, modelID, agentName) {
150317
150326
  function fingerprintFor(description, parameters) {
150318
150327
  return createHash3("sha256").update(description).update("\x00").update(stableStringify(parameters)).digest("hex");
150319
150328
  }
150320
- function getCurrentToolSetHash(providerID, modelID, agentName) {
150321
- const key = keyFor(providerID, modelID, agentName);
150322
- const inner = fingerprints.get(key);
150323
- if (!inner || inner.size === 0)
150324
- return "";
150325
- const parts = [];
150326
- for (const [toolID, fp] of inner)
150327
- parts.push(`${toolID}\x00${fp}`);
150328
- parts.sort();
150329
- return createHash3("sha256").update(parts.join(`
150330
- `)).digest("hex");
150331
- }
150332
150329
  function setDatabase(db) {
150333
150330
  persistenceDb = db;
150334
150331
  cachedInsertStmt = null;
@@ -150836,6 +150833,35 @@ function initializeDatabase(db) {
150836
150833
  );
150837
150834
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
150838
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
+
150839
150865
  CREATE TABLE IF NOT EXISTS compartment_events (
150840
150866
  id INTEGER PRIMARY KEY AUTOINCREMENT,
150841
150867
  session_id TEXT NOT NULL,
@@ -151021,6 +151047,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151021
151047
  rekeyed_at INTEGER NOT NULL
151022
151048
  );
151023
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
+
151024
151069
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151025
151070
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151026
151071
  table_name TEXT NOT NULL,
@@ -151132,6 +151177,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151132
151177
  deferred_execute_state TEXT,
151133
151178
  cached_m0_bytes BLOB,
151134
151179
  cached_m0_project_memory_epoch INTEGER,
151180
+ cached_m0_workspace_fingerprint TEXT,
151135
151181
  cached_m0_project_user_profile_version INTEGER,
151136
151182
  cached_m0_max_compartment_seq INTEGER,
151137
151183
  cached_m0_max_memory_id INTEGER,
@@ -151290,6 +151336,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151290
151336
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151291
151337
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151292
151338
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151339
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
151293
151340
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
151294
151341
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
151295
151342
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -151343,6 +151390,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151343
151390
  ensureColumn(db, "memories", "importance", "INTEGER");
151344
151391
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
151345
151392
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
151393
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
151346
151394
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
151347
151395
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
151348
151396
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -151374,6 +151422,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151374
151422
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
151375
151423
  updated_at INTEGER NOT NULL DEFAULT 0
151376
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);
151377
151434
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
151378
151435
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151379
151436
  session_id TEXT NOT NULL,
@@ -151401,6 +151458,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151401
151458
  new_project_path TEXT NOT NULL,
151402
151459
  rekeyed_at INTEGER NOT NULL
151403
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);
151404
151478
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151405
151479
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151406
151480
  table_name TEXT NOT NULL,
@@ -151422,6 +151496,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151422
151496
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151423
151497
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151424
151498
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151499
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
151425
151500
  }
151426
151501
  function healAllNullColumns(db) {
151427
151502
  healNullTextColumns(db);
@@ -151459,6 +151534,7 @@ function healNullTextColumns(db) {
151459
151534
  ["system_prompt_hash", ""],
151460
151535
  ["stripped_placeholder_ids", ""],
151461
151536
  ["stale_reduce_stripped_ids", ""],
151537
+ ["processed_image_stripped_ids", ""],
151462
151538
  ["memory_block_cache", ""],
151463
151539
  ["memory_block_ids", ""],
151464
151540
  ["compaction_marker_state", ""],
@@ -151503,7 +151579,7 @@ function healNullIntegerColumns(db) {
151503
151579
  }
151504
151580
  }
151505
151581
  function ensureColumn(db, table, column, definition) {
151506
- 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)) {
151507
151583
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
151508
151584
  }
151509
151585
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -151589,7 +151665,7 @@ function getDatabasePersistenceError(db) {
151589
151665
  return null;
151590
151666
  return persistenceErrorByDatabase.get(db) ?? null;
151591
151667
  }
151592
- 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;
151593
151669
  var init_storage_db = __esm(async () => {
151594
151670
  init_data_path();
151595
151671
  init_logger();
@@ -151609,10 +151685,302 @@ var init_storage_db = __esm(async () => {
151609
151685
  };
151610
151686
  });
151611
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
+
151612
151976
  // src/features/magic-context/migrations.ts
151613
- function tableExists(db, name2) {
151977
+ function tableExists2(db, name2) {
151614
151978
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
151615
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
+ }
151616
151984
  function ensureMigrationsTable(db) {
151617
151985
  db.exec(`
151618
151986
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -151681,6 +152049,7 @@ function runMigrations(db) {
151681
152049
  var MIGRATIONS, LATEST_MIGRATION_VERSION;
151682
152050
  var init_migrations = __esm(async () => {
151683
152051
  init_logger();
152052
+ init_workspaces();
151684
152053
  await init_storage_db();
151685
152054
  MIGRATIONS = [
151686
152055
  {
@@ -152140,9 +152509,9 @@ var init_migrations = __esm(async () => {
152140
152509
  version: 22,
152141
152510
  description: "v2.0 cache architecture schema foundation",
152142
152511
  up: (db) => {
152143
- const hasSessionMetaTable = tableExists(db, "session_meta");
152144
- const hasCompartmentsTable = tableExists(db, "compartments");
152145
- const hasMemoriesTable = tableExists(db, "memories");
152512
+ const hasSessionMetaTable = tableExists2(db, "session_meta");
152513
+ const hasCompartmentsTable = tableExists2(db, "compartments");
152514
+ const hasMemoriesTable = tableExists2(db, "memories");
152146
152515
  if (hasSessionMetaTable) {
152147
152516
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
152148
152517
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
@@ -152167,7 +152536,7 @@ var init_migrations = __esm(async () => {
152167
152536
  ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
152168
152537
  ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
152169
152538
  }
152170
- const hasRecompCompartmentsTable = tableExists(db, "recomp_compartments");
152539
+ const hasRecompCompartmentsTable = tableExists2(db, "recomp_compartments");
152171
152540
  if (hasRecompCompartmentsTable) {
152172
152541
  ensureColumn(db, "recomp_compartments", "p1", "TEXT");
152173
152542
  ensureColumn(db, "recomp_compartments", "p2", "TEXT");
@@ -152478,13 +152847,163 @@ var init_migrations = __esm(async () => {
152478
152847
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
152479
152848
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
152480
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
+ }
152481
153000
  }
152482
153001
  ];
152483
153002
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
152484
153003
  });
152485
153004
 
152486
153005
  // src/features/magic-context/project-docs-hash.ts
152487
- import { createHash as createHash4 } from "node:crypto";
153006
+ import { createHash as createHash5 } from "node:crypto";
152488
153007
  import { lstatSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152489
153008
  import path4 from "node:path";
152490
153009
  function canonicalizeDocContent(raw) {
@@ -152574,7 +153093,7 @@ function hashCanonicalPieces(hashPieces) {
152574
153093
  if (hashPieces.length === 0) {
152575
153094
  return "";
152576
153095
  }
152577
- 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");
152578
153097
  }
152579
153098
  function readProjectDocsCanonical(projectDirectory) {
152580
153099
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -152612,11 +153131,6 @@ var init_project_docs_hash = __esm(() => {
152612
153131
  MAX_PROJECT_DOC_BYTES = 256 * 1024;
152613
153132
  docsCache = new Map;
152614
153133
  });
152615
-
152616
- // src/features/magic-context/project-identity.ts
152617
- var init_project_identity2 = __esm(() => {
152618
- init_project_identity();
152619
- });
152620
153134
  // src/features/magic-context/storage-m0-mutation-log.ts
152621
153135
  function assertMutationType(mutationType) {
152622
153136
  if (!M0_MUTATION_TYPES.has(mutationType)) {
@@ -152701,18 +153215,13 @@ function getMemoryMutation(db, id) {
152701
153215
  WHERE id = ?`).get(id);
152702
153216
  return row ? toMemoryMutation(row) : null;
152703
153217
  }
152704
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
152705
- if (renderedMemoryIds.length === 0)
152706
- return [];
152707
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
152708
- const placeholders = uniqueIds.map(() => "?").join(", ");
152709
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
152710
- superseded_by_id, category, new_content, queued_at
152711
- FROM memory_mutation_log
152712
- WHERE project_path = ?
152713
- AND id > ?
152714
- AND target_memory_id IN (${placeholders})
152715
- 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) {
152716
153225
  const chosenByTarget = new Map;
152717
153226
  for (const dbRow of rows) {
152718
153227
  const candidate = toMemoryMutation(dbRow);
@@ -152733,10 +153242,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
152733
153242
  }
152734
153243
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
152735
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
+ }
152736
153278
  function getMaxMemoryMutationId(db, projectPath) {
152737
153279
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
152738
153280
  return row?.max_id ?? null;
152739
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
+ }
152740
153293
  var MEMORY_MUTATION_TYPES, TERMINAL_MUTATION_TYPES;
152741
153294
  var init_storage_memory_mutation_log = __esm(() => {
152742
153295
  MEMORY_MUTATION_TYPES = new Set(["archive", "delete", "update", "superseded"]);
@@ -153429,6 +153982,36 @@ function addStaleReduceStrippedIds(db, sessionId, ids) {
153429
153982
  sessionLog(sessionId, `stale_reduce_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
153430
153983
  return false;
153431
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
+ }
153432
154015
  function isPendingCompactionMarker(value) {
153433
154016
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
153434
154017
  }
@@ -153607,6 +154190,8 @@ function clearSession(db, sessionId) {
153607
154190
  db.prepare("DELETE FROM source_contents WHERE session_id = ?").run(sessionId);
153608
154191
  db.prepare("DELETE FROM tags WHERE session_id = ?").run(sessionId);
153609
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);
153610
154195
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
153611
154196
  clearCompressionDepth(db, sessionId);
153612
154197
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
@@ -153713,9 +154298,9 @@ function buildStatusClause(status) {
153713
154298
  if (statuses.length === 0) {
153714
154299
  return null;
153715
154300
  }
153716
- const placeholders = statuses.map(() => "?").join(", ");
154301
+ const placeholders3 = statuses.map(() => "?").join(", ");
153717
154302
  return {
153718
- sql: `status IN (${placeholders})`,
154303
+ sql: `status IN (${placeholders3})`,
153719
154304
  params: statuses
153720
154305
  };
153721
154306
  }
@@ -153921,19 +154506,6 @@ function getProjectState(db, projectPath) {
153921
154506
  WHERE project_path = ?`).get(projectPath);
153922
154507
  return row ? toProjectState(row) : null;
153923
154508
  }
153924
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
153925
- db.prepare(`INSERT INTO project_state
153926
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
153927
- VALUES (?, 1, 0, ?)
153928
- ON CONFLICT(project_path) DO UPDATE SET
153929
- project_memory_epoch = project_memory_epoch + 1,
153930
- updated_at = excluded.updated_at`).run(projectPath, now);
153931
- const state = getProjectState(db, projectPath);
153932
- if (!state) {
153933
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
153934
- }
153935
- return state;
153936
- }
153937
154509
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
153938
154510
  db.prepare(`INSERT INTO project_state
153939
154511
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -153969,8 +154541,8 @@ function getSourceContents(db, sessionId, tagIds) {
153969
154541
  if (tagIds.length === 0) {
153970
154542
  return new Map;
153971
154543
  }
153972
- const placeholders = tagIds.map(() => "?").join(", ");
153973
- 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);
153974
154546
  const sources = new Map;
153975
154547
  for (const row of rows) {
153976
154548
  sources.set(row.tag_id, row.content);
@@ -154280,8 +154852,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
154280
154852
  }
154281
154853
  return all;
154282
154854
  }
154283
- const placeholders = tagNumbers.map(() => "?").join(",");
154284
- const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
154855
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
154856
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders3}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
154285
154857
  return rows.map(toTagEntry);
154286
154858
  }
154287
154859
  function getMaxDroppedTagNumber(db, sessionId) {
@@ -154434,6 +155006,7 @@ var init_storage = __esm(async () => {
154434
155006
  init_storage_source();
154435
155007
  init_storage_tags();
154436
155008
  init_storage_v22_backfill_failures();
155009
+ init_workspaces();
154437
155010
  await __promiseAll([
154438
155011
  init_message_index(),
154439
155012
  init_migrations(),
@@ -163885,7 +164458,7 @@ function isEmbeddingRow(row) {
163885
164458
  if (row === null || typeof row !== "object")
163886
164459
  return false;
163887
164460
  const candidate = row;
163888
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
164461
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
163889
164462
  }
163890
164463
  function toFloat32Array(blob) {
163891
164464
  if (blob instanceof Uint8Array) {
@@ -163905,7 +164478,7 @@ function getSaveEmbeddingStatement(db) {
163905
164478
  function getLoadAllEmbeddingsStatement(db) {
163906
164479
  let stmt = loadAllEmbeddingsStatements.get(db);
163907
164480
  if (!stmt) {
163908
- stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
164481
+ stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding, memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
163909
164482
  loadAllEmbeddingsStatements.set(db, stmt);
163910
164483
  }
163911
164484
  return stmt;
@@ -163934,7 +164507,10 @@ function loadAllEmbeddings(db, projectPath) {
163934
164507
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
163935
164508
  const embeddings = new Map;
163936
164509
  for (const row of rows) {
163937
- embeddings.set(row.memoryId, toFloat32Array(row.embedding));
164510
+ embeddings.set(row.memoryId, {
164511
+ embedding: toFloat32Array(row.embedding),
164512
+ modelId: row.modelId
164513
+ });
163938
164514
  }
163939
164515
  return embeddings;
163940
164516
  }
@@ -163997,13 +164573,13 @@ var init_embedding_cache = __esm(() => {
163997
164573
  });
163998
164574
 
163999
164575
  // src/features/magic-context/memory/normalize-hash.ts
164000
- import { createHash as createHash6 } from "node:crypto";
164576
+ import { createHash as createHash7 } from "node:crypto";
164001
164577
  function normalizeMemoryContent(content) {
164002
164578
  return content.toLowerCase().replace(/\s+/g, " ").trim();
164003
164579
  }
164004
164580
  function computeNormalizedHash(content) {
164005
164581
  const normalized = normalizeMemoryContent(content);
164006
- return createHash6("md5").update(normalized).digest("hex");
164582
+ return createHash7("md5").update(normalized).digest("hex");
164007
164583
  }
164008
164584
  var init_normalize_hash = () => {};
164009
164585
 
@@ -164117,8 +164693,8 @@ function getMemoriesByProjectStatement(db, statuses) {
164117
164693
  }
164118
164694
  let stmt = statements.get(db);
164119
164695
  if (!stmt) {
164120
- const placeholders = statuses.map(() => "?").join(", ");
164121
- stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
164696
+ const placeholders3 = statuses.map(() => "?").join(", ");
164697
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders3}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
164122
164698
  statements.set(db, stmt);
164123
164699
  }
164124
164700
  return stmt;
@@ -164239,6 +164815,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
164239
164815
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
164240
164816
  return rows.map(toMemory);
164241
164817
  }
164818
+ function sqlPlaceholders(values) {
164819
+ return values.map(() => "?").join(", ");
164820
+ }
164821
+ function uniqueValues(values) {
164822
+ return [...new Set(values.filter((value) => value.length > 0))];
164823
+ }
164824
+ function buildWorkspaceMemorySqlFilter(args) {
164825
+ if (args.shareCategories === null || args.shareCategories === undefined) {
164826
+ return { clause: "", params: [], active: false };
164827
+ }
164828
+ const identities = uniqueValues(args.identities);
164829
+ const identitySet = new Set(identities);
164830
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
164831
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
164832
+ if (foreignIdentities.length === 0) {
164833
+ return { clause: "", params: [], active: false };
164834
+ }
164835
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
164836
+ const shareCategories = uniqueValues([...args.shareCategories]);
164837
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
164838
+ const predicates = [];
164839
+ const params = [];
164840
+ if (ownIdentities.length > 0) {
164841
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
164842
+ params.push(...ownIdentities);
164843
+ }
164844
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
164845
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
164846
+ params.push(...foreignIdentities, ...shareCategories);
164847
+ }
164848
+ if (predicates.length === 0) {
164849
+ return { clause: " AND 0 = 1", params: [], active: true };
164850
+ }
164851
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
164852
+ }
164853
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
164854
+ const identities = uniqueValues(projectPaths);
164855
+ if (identities.length === 0 || statuses.length === 0)
164856
+ return [];
164857
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164858
+ identities,
164859
+ ownIdentities,
164860
+ shareCategories
164861
+ });
164862
+ if (identities.length === 1 && !sharingFilter.active) {
164863
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
164864
+ }
164865
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164866
+ FROM memories
164867
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164868
+ AND status IN (${sqlPlaceholders(statuses)})
164869
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164870
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164871
+ return rows.map(toMemory);
164872
+ }
164873
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
164874
+ const identities = uniqueValues(projectPaths);
164875
+ if (identities.length === 0)
164876
+ return 0;
164877
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164878
+ identities,
164879
+ ownIdentities,
164880
+ shareCategories
164881
+ });
164882
+ if (identities.length === 1 && !sharingFilter.active) {
164883
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
164884
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
164885
+ }
164886
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
164887
+ FROM memories
164888
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
164889
+ return typeof row?.max_id === "number" ? row.max_id : 0;
164890
+ }
164891
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
164892
+ const identities = uniqueValues(projectPaths);
164893
+ if (identities.length === 0)
164894
+ return [];
164895
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164896
+ identities,
164897
+ ownIdentities,
164898
+ shareCategories
164899
+ });
164900
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164901
+ FROM memories
164902
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164903
+ AND id > ?
164904
+ AND status IN ('active', 'permanent')
164905
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164906
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164907
+ return rows.map(toMemory);
164908
+ }
164242
164909
  function getAllActiveMemoriesForMigration(db, projectPath) {
164243
164910
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
164244
164911
  return rows.map(toMemory);
@@ -164336,6 +165003,7 @@ function getMemoryCountsByStatus(db, projectPath) {
164336
165003
  }
164337
165004
  var COLUMN_MAP, MEMORY_CATEGORY_LOOKUP, MEMORY_STATUS_LOOKUP, MEMORY_SOURCE_TYPE_LOOKUP, VERIFICATION_STATUS_LOOKUP, insertMemoryStatements, getMemoryByHashStatements, getMemoryByIdStatements, activeMemoriesNoExpiryStatements, updateMemorySeenCountStatements, updateMemoryRetrievalCountStatements, updateMemoryStatusStatements, updateArchivedMemoryStatements, updateMemoryVerificationStatements, updateMemoryContentStatements, supersededMemoryStatements, mergeMemoryStatsStatements, deleteMemoryStatements, deleteMemoryEmbeddingStatements, deleteEmbeddingOnContentUpdateStatements, getMemoryCountStatements, getMemoryCountByProjectStatements, getMemoryCountsByStatusStatements, memoriesByProjectStatements, memoryImportanceColumnCache;
164338
165005
  var init_storage_memory = __esm(() => {
165006
+ init_constants();
164339
165007
  init_embedding_cache();
164340
165008
  init_normalize_hash();
164341
165009
  COLUMN_MAP = {
@@ -164441,8 +165109,8 @@ function getUserMemoryCandidates(db) {
164441
165109
  function deleteUserMemoryCandidates(db, ids) {
164442
165110
  if (ids.length === 0)
164443
165111
  return;
164444
- const placeholders = ids.map(() => "?").join(",");
164445
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
165112
+ const placeholders3 = ids.map(() => "?").join(",");
165113
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
164446
165114
  }
164447
165115
  function insertUserMemory(db, content, sourceCandidateIds) {
164448
165116
  const now = Date.now();
@@ -164475,6 +165143,444 @@ function parseUserMemoryRow(row) {
164475
165143
  };
164476
165144
  }
164477
165145
 
165146
+ // src/features/magic-context/compartment-chunk-embedding.ts
165147
+ import { createHash as createHash8 } from "node:crypto";
165148
+ function getLoadFtsRowsStatement(db) {
165149
+ let stmt = loadFtsRowsStatements.get(db);
165150
+ if (!stmt) {
165151
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
165152
+ FROM message_history_fts
165153
+ WHERE session_id = ?
165154
+ AND message_ordinal >= ?
165155
+ AND message_ordinal <= ?
165156
+ AND role IN ('user', 'assistant')
165157
+ ORDER BY message_ordinal ASC`);
165158
+ loadFtsRowsStatements.set(db, stmt);
165159
+ }
165160
+ return stmt;
165161
+ }
165162
+ function getExistingHashStatement(db, scopedToProject) {
165163
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
165164
+ let stmt = map2.get(db);
165165
+ if (!stmt) {
165166
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
165167
+ FROM compartment_chunk_embeddings
165168
+ WHERE compartment_id = ?
165169
+ AND model_id = ?
165170
+ ${scopedToProject ? "AND project_path = ?" : ""}
165171
+ ORDER BY window_index ASC`);
165172
+ map2.set(db, stmt);
165173
+ }
165174
+ return stmt;
165175
+ }
165176
+ function getDeleteByCompartmentStatement(db) {
165177
+ let stmt = deleteByCompartmentStatements.get(db);
165178
+ if (!stmt) {
165179
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
165180
+ deleteByCompartmentStatements.set(db, stmt);
165181
+ }
165182
+ return stmt;
165183
+ }
165184
+ function getInsertEmbeddingStatement(db) {
165185
+ let stmt = insertEmbeddingStatements.get(db);
165186
+ if (!stmt) {
165187
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
165188
+ compartment_id, session_id, project_path, harness, window_index,
165189
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
165190
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
165191
+ insertEmbeddingStatements.set(db, stmt);
165192
+ }
165193
+ return stmt;
165194
+ }
165195
+ function getDistinctModelStatement(db) {
165196
+ let stmt = distinctModelStatements.get(db);
165197
+ if (!stmt) {
165198
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
165199
+ FROM compartment_chunk_embeddings
165200
+ WHERE project_path = ?`);
165201
+ distinctModelStatements.set(db, stmt);
165202
+ }
165203
+ return stmt;
165204
+ }
165205
+ function getClearProjectStatement(db) {
165206
+ let stmt = clearProjectStatements.get(db);
165207
+ if (!stmt) {
165208
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
165209
+ clearProjectStatements.set(db, stmt);
165210
+ }
165211
+ return stmt;
165212
+ }
165213
+ function getClearProjectModelStatement(db) {
165214
+ let stmt = clearProjectModelStatements.get(db);
165215
+ if (!stmt) {
165216
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
165217
+ clearProjectModelStatements.set(db, stmt);
165218
+ }
165219
+ return stmt;
165220
+ }
165221
+ function getSearchRowsStatement(db, withModel) {
165222
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
165223
+ let stmt = map2.get(db);
165224
+ if (!stmt) {
165225
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
165226
+ e.session_id AS sessionId,
165227
+ c.title AS title,
165228
+ c.start_message AS compartmentStart,
165229
+ c.end_message AS compartmentEnd,
165230
+ e.window_index AS windowIndex,
165231
+ e.start_ordinal AS windowStart,
165232
+ e.end_ordinal AS windowEnd,
165233
+ e.chunk_hash AS chunkHash,
165234
+ e.model_id AS modelId,
165235
+ e.dims AS dims,
165236
+ e.vector AS vector
165237
+ FROM compartment_chunk_embeddings e
165238
+ JOIN compartments c ON c.id = e.compartment_id
165239
+ WHERE e.session_id = ?
165240
+ AND e.project_path = ?
165241
+ ${withModel ? "AND e.model_id = ?" : ""}
165242
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
165243
+ map2.set(db, stmt);
165244
+ }
165245
+ return stmt;
165246
+ }
165247
+ function isFinitePositiveInteger(value) {
165248
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
165249
+ }
165250
+ function normalizeCompartmentChunkMaxInputTokens(value) {
165251
+ if (!isFinitePositiveInteger(value)) {
165252
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
165253
+ }
165254
+ return Math.max(1, Math.floor(value));
165255
+ }
165256
+ function normalizeContent(text) {
165257
+ return text.replace(/\s+/g, " ").trim();
165258
+ }
165259
+ function formatOrdinalRange(start, end) {
165260
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
165261
+ }
165262
+ function rolePrefix(role) {
165263
+ if (role === "user")
165264
+ return "U";
165265
+ if (role === "assistant")
165266
+ return "A";
165267
+ return null;
165268
+ }
165269
+ function parseOrdinal(value) {
165270
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
165271
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
165272
+ }
165273
+ function parseCanonicalLineRange(line) {
165274
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
165275
+ if (!match)
165276
+ return null;
165277
+ const start = Number.parseInt(match[1], 10);
165278
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
165279
+ if (!Number.isFinite(start) || !Number.isFinite(end))
165280
+ return null;
165281
+ return { start, end };
165282
+ }
165283
+ function hashChunkText(text) {
165284
+ return createHash8("sha256").update(text).digest("hex");
165285
+ }
165286
+ function vectorBlob(vector) {
165287
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
165288
+ }
165289
+ function toFloat32Array2(blob) {
165290
+ if (blob instanceof Uint8Array) {
165291
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
165292
+ return new Float32Array(buffer2);
165293
+ }
165294
+ return new Float32Array(blob.slice(0));
165295
+ }
165296
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
165297
+ if (endOrdinal < startOrdinal)
165298
+ return "";
165299
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
165300
+ const lines = [];
165301
+ let current = null;
165302
+ const flush2 = () => {
165303
+ if (!current || current.parts.length === 0)
165304
+ return;
165305
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
165306
+ current = null;
165307
+ };
165308
+ for (const row of rows) {
165309
+ const ordinal = parseOrdinal(row.messageOrdinal);
165310
+ const prefix = rolePrefix(row.role);
165311
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
165312
+ if (ordinal === null || prefix === null || content.length === 0)
165313
+ continue;
165314
+ if (current && current.role === prefix) {
165315
+ current.end = ordinal;
165316
+ current.parts.push(content);
165317
+ continue;
165318
+ }
165319
+ flush2();
165320
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
165321
+ }
165322
+ flush2();
165323
+ return lines.join(`
165324
+ `);
165325
+ }
165326
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
165327
+ const lines = [];
165328
+ for (const rawLine of chunkText.split(/\r?\n/)) {
165329
+ const line = rawLine.trim();
165330
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
165331
+ if (!match)
165332
+ continue;
165333
+ const lineStart = Number.parseInt(match[2], 10);
165334
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
165335
+ if (startOrdinal != null && lineEnd < startOrdinal)
165336
+ continue;
165337
+ if (endOrdinal != null && lineStart > endOrdinal)
165338
+ continue;
165339
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
165340
+ const ordinalSpan = lineEnd - lineStart + 1;
165341
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
165342
+ if (ordinalSpan === rawParts.length) {
165343
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
165344
+ if (part.startsWith("TC:"))
165345
+ return false;
165346
+ if (startOrdinal != null && ordinal < startOrdinal)
165347
+ return false;
165348
+ if (endOrdinal != null && ordinal > endOrdinal)
165349
+ return false;
165350
+ return true;
165351
+ });
165352
+ if (retained.length === 0)
165353
+ continue;
165354
+ const retainedStart = retained[0].ordinal;
165355
+ const retainedEnd = retained[retained.length - 1].ordinal;
165356
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
165357
+ continue;
165358
+ }
165359
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
165360
+ if (parts.length === 0)
165361
+ continue;
165362
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
165363
+ }
165364
+ return lines.join(`
165365
+ `);
165366
+ }
165367
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
165368
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
165369
+ if (lines.length === 0 || endOrdinal < startOrdinal)
165370
+ return [];
165371
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
165372
+ const fullText = lines.join(`
165373
+ `);
165374
+ if (estimateTokens(fullText) <= normalizedMax) {
165375
+ return [
165376
+ {
165377
+ windowIndex: 0,
165378
+ startOrdinal,
165379
+ endOrdinal,
165380
+ text: fullText,
165381
+ chunkHash: hashChunkText(fullText)
165382
+ }
165383
+ ];
165384
+ }
165385
+ const windows = [];
165386
+ let currentLines = [];
165387
+ let currentStart = null;
165388
+ let currentEnd = null;
165389
+ let currentTokens = 0;
165390
+ const flush2 = () => {
165391
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
165392
+ return;
165393
+ const text = currentLines.join(`
165394
+ `);
165395
+ windows.push({
165396
+ windowIndex: windows.length + 1,
165397
+ startOrdinal: currentStart,
165398
+ endOrdinal: currentEnd,
165399
+ text,
165400
+ chunkHash: hashChunkText(text)
165401
+ });
165402
+ currentLines = [];
165403
+ currentStart = null;
165404
+ currentEnd = null;
165405
+ currentTokens = 0;
165406
+ };
165407
+ for (const line of lines) {
165408
+ const range = parseCanonicalLineRange(line);
165409
+ const lineStart = range?.start ?? startOrdinal;
165410
+ const lineEnd = range?.end ?? lineStart;
165411
+ const lineTokens = estimateTokens(line);
165412
+ if (currentLines.length > 0 && currentTokens + lineTokens > normalizedMax) {
165413
+ flush2();
165414
+ }
165415
+ if (currentLines.length === 0) {
165416
+ currentStart = lineStart;
165417
+ }
165418
+ currentLines.push(line);
165419
+ currentEnd = lineEnd;
165420
+ currentTokens += lineTokens;
165421
+ }
165422
+ flush2();
165423
+ return windows;
165424
+ }
165425
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
165426
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
165427
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
165428
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
165429
+ }
165430
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
165431
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
165432
+ if (existing.size !== windows.length)
165433
+ return false;
165434
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
165435
+ }
165436
+ function replaceCompartmentChunkEmbeddings(db, rows) {
165437
+ if (rows.length === 0)
165438
+ return;
165439
+ const compartmentId = rows[0].compartmentId;
165440
+ const now = Date.now();
165441
+ db.transaction(() => {
165442
+ getDeleteByCompartmentStatement(db).run(compartmentId);
165443
+ const insert = getInsertEmbeddingStatement(db);
165444
+ for (const row of rows) {
165445
+ insert.run(row.compartmentId, row.sessionId, row.projectPath, getHarness(), row.window.windowIndex, row.window.startOrdinal, row.window.endOrdinal, row.window.chunkHash, row.modelId, row.vector.length, vectorBlob(row.vector), row.createdAt ?? now);
165446
+ }
165447
+ })();
165448
+ }
165449
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
165450
+ const rows = getDistinctModelStatement(db).all(projectPath);
165451
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165452
+ }
165453
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
165454
+ if (modelId) {
165455
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
165456
+ }
165457
+ return getClearProjectStatement(db).run(projectPath).changes;
165458
+ }
165459
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
165460
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
165461
+ return rows.filter((row) => typeof row.compartmentId === "number" && typeof row.sessionId === "string" && typeof row.title === "string" && typeof row.compartmentStart === "number" && typeof row.compartmentEnd === "number" && typeof row.windowIndex === "number" && typeof row.windowStart === "number" && typeof row.windowEnd === "number" && typeof row.chunkHash === "string" && typeof row.modelId === "string" && typeof row.dims === "number" && (row.vector instanceof Uint8Array || row.vector instanceof ArrayBuffer)).map((row) => ({
165462
+ compartmentId: row.compartmentId,
165463
+ sessionId: row.sessionId,
165464
+ title: row.title,
165465
+ startOrdinal: row.compartmentStart,
165466
+ endOrdinal: row.compartmentEnd,
165467
+ windowIndex: row.windowIndex,
165468
+ windowStartOrdinal: row.windowStart,
165469
+ windowEndOrdinal: row.windowEnd,
165470
+ chunkHash: row.chunkHash,
165471
+ modelId: row.modelId,
165472
+ dims: row.dims,
165473
+ vector: toFloat32Array2(row.vector)
165474
+ }));
165475
+ }
165476
+ function mapBackfillCandidateRows(rows) {
165477
+ return rows.filter((row) => {
165478
+ if (row === null || typeof row !== "object")
165479
+ return false;
165480
+ const candidate = row;
165481
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
165482
+ }).map((row) => ({
165483
+ id: row.id,
165484
+ sessionId: row.sessionId,
165485
+ startMessage: row.startMessage,
165486
+ endMessage: row.endMessage,
165487
+ title: row.title
165488
+ }));
165489
+ }
165490
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
165491
+ if (excludeIds && excludeIds.length > 0) {
165492
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
165493
+ const stmt2 = db.prepare(`SELECT c.id AS id,
165494
+ c.session_id AS sessionId,
165495
+ c.start_message AS startMessage,
165496
+ c.end_message AS endMessage,
165497
+ c.title AS title
165498
+ FROM compartments c
165499
+ JOIN session_projects sp
165500
+ ON sp.session_id = c.session_id
165501
+ AND sp.harness = c.harness
165502
+ AND sp.project_path = ?
165503
+ WHERE c.session_id = ?
165504
+ AND c.start_message IS NOT NULL
165505
+ AND c.end_message IS NOT NULL
165506
+ AND c.id NOT IN (${placeholders3})
165507
+ AND NOT EXISTS (
165508
+ SELECT 1
165509
+ FROM compartment_chunk_embeddings current
165510
+ WHERE current.compartment_id = c.id
165511
+ AND current.project_path = ?
165512
+ AND current.model_id = ?
165513
+ )
165514
+ ORDER BY c.start_message ASC, c.id ASC
165515
+ LIMIT ?`);
165516
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
165517
+ return mapBackfillCandidateRows(rows2);
165518
+ }
165519
+ let stmt = sessionBackfillCandidateStatements.get(db);
165520
+ if (!stmt) {
165521
+ stmt = db.prepare(`SELECT c.id AS id,
165522
+ c.session_id AS sessionId,
165523
+ c.start_message AS startMessage,
165524
+ c.end_message AS endMessage,
165525
+ c.title AS title
165526
+ FROM compartments c
165527
+ JOIN session_projects sp
165528
+ ON sp.session_id = c.session_id
165529
+ AND sp.harness = c.harness
165530
+ AND sp.project_path = ?
165531
+ WHERE c.session_id = ?
165532
+ AND c.start_message IS NOT NULL
165533
+ AND c.end_message IS NOT NULL
165534
+ AND NOT EXISTS (
165535
+ SELECT 1
165536
+ FROM compartment_chunk_embeddings current
165537
+ WHERE current.compartment_id = c.id
165538
+ AND current.project_path = ?
165539
+ AND current.model_id = ?
165540
+ )
165541
+ ORDER BY c.start_message ASC, c.id ASC
165542
+ LIMIT ?`);
165543
+ sessionBackfillCandidateStatements.set(db, stmt);
165544
+ }
165545
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
165546
+ return mapBackfillCandidateRows(rows);
165547
+ }
165548
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
165549
+ const row = db.prepare(`SELECT COUNT(*) AS n
165550
+ FROM compartments c
165551
+ JOIN session_projects sp
165552
+ ON sp.session_id = c.session_id
165553
+ AND sp.harness = c.harness
165554
+ AND sp.project_path = ?
165555
+ WHERE c.session_id = ?
165556
+ AND c.start_message IS NOT NULL
165557
+ AND c.end_message IS NOT NULL
165558
+ AND NOT EXISTS (
165559
+ SELECT 1
165560
+ FROM compartment_chunk_embeddings current
165561
+ WHERE current.compartment_id = c.id
165562
+ AND current.project_path = ?
165563
+ AND current.model_id = ?
165564
+ )`).get(projectPath, sessionId, projectPath, modelId);
165565
+ return typeof row?.n === "number" ? row.n : 0;
165566
+ }
165567
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
165568
+ var init_compartment_chunk_embedding = __esm(() => {
165569
+ init_read_session_formatting();
165570
+ loadFtsRowsStatements = new WeakMap;
165571
+ existingHashStatements = new WeakMap;
165572
+ existingHashByProjectStatements = new WeakMap;
165573
+ deleteByCompartmentStatements = new WeakMap;
165574
+ insertEmbeddingStatements = new WeakMap;
165575
+ distinctModelStatements = new WeakMap;
165576
+ clearProjectStatements = new WeakMap;
165577
+ clearProjectModelStatements = new WeakMap;
165578
+ searchRowsStatements = new WeakMap;
165579
+ searchRowsByModelStatements = new WeakMap;
165580
+ backfillCandidateStatements = new WeakMap;
165581
+ sessionBackfillCandidateStatements = new WeakMap;
165582
+ });
165583
+
164478
165584
  // src/features/magic-context/memory/cosine-similarity.ts
164479
165585
  function cosineSimilarity(a, b) {
164480
165586
  if (a.length !== b.length) {
@@ -164632,19 +165738,19 @@ function isArrayLikeNumber(value) {
164632
165738
  }
164633
165739
  return arr.length === 0 || typeof arr[0] === "number";
164634
165740
  }
164635
- function toFloat32Array2(values) {
165741
+ function toFloat32Array3(values) {
164636
165742
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
164637
165743
  }
164638
165744
  function extractBatchEmbeddings(result, expectedCount) {
164639
165745
  const { data } = result;
164640
165746
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
164641
- return data.map((entry) => toFloat32Array2(entry));
165747
+ return data.map((entry) => toFloat32Array3(entry));
164642
165748
  }
164643
165749
  if (!isArrayLikeNumber(data)) {
164644
165750
  log("[magic-context] embedding batch returned unexpected data shape");
164645
165751
  return Array.from({ length: expectedCount }, () => null);
164646
165752
  }
164647
- const flatData = toFloat32Array2(data);
165753
+ const flatData = toFloat32Array3(data);
164648
165754
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
164649
165755
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
164650
165756
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -164659,6 +165765,7 @@ function extractBatchEmbeddings(result, expectedCount) {
164659
165765
 
164660
165766
  class LocalEmbeddingProvider {
164661
165767
  modelId;
165768
+ maxInputTokens;
164662
165769
  model;
164663
165770
  pipeline = null;
164664
165771
  initPromise = null;
@@ -164666,8 +165773,9 @@ class LocalEmbeddingProvider {
164666
165773
  disposing = false;
164667
165774
  disposePromise = null;
164668
165775
  inFlightWaiters = [];
164669
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
165776
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
164670
165777
  this.model = model;
165778
+ this.maxInputTokens = maxInputTokens;
164671
165779
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
164672
165780
  }
164673
165781
  async initialize() {
@@ -164927,6 +166035,7 @@ function normalizeEndpoint3(endpoint) {
164927
166035
 
164928
166036
  class OpenAICompatibleEmbeddingProvider {
164929
166037
  modelId;
166038
+ maxInputTokens;
164930
166039
  endpoint;
164931
166040
  model;
164932
166041
  apiKey;
@@ -164943,11 +166052,13 @@ class OpenAICompatibleEmbeddingProvider {
164943
166052
  this.apiKey = options.apiKey?.trim() ?? "";
164944
166053
  this.inputType = options.inputType?.trim() ?? "";
164945
166054
  this.truncate = options.truncate?.trim() ?? "";
166055
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
164946
166056
  this.modelId = getEmbeddingProviderIdentity({
164947
166057
  provider: "openai-compatible",
164948
166058
  endpoint: this.endpoint,
164949
166059
  model: this.model,
164950
- ...this.apiKey ? { api_key: this.apiKey } : {}
166060
+ ...this.apiKey ? { api_key: this.apiKey } : {},
166061
+ ...this.inputType ? { input_type: this.inputType } : {}
164951
166062
  });
164952
166063
  }
164953
166064
  async initialize() {
@@ -165191,12 +166302,12 @@ function getCountEmbeddedStatement(db) {
165191
166302
  }
165192
166303
  return stmt;
165193
166304
  }
165194
- function getClearProjectStatement(db) {
165195
- let stmt = clearProjectStatements.get(db);
166305
+ function getClearProjectStatement2(db) {
166306
+ let stmt = clearProjectStatements2.get(db);
165196
166307
  if (!stmt) {
165197
166308
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
165198
166309
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
165199
- clearProjectStatements.set(db, stmt);
166310
+ clearProjectStatements2.set(db, stmt);
165200
166311
  }
165201
166312
  return stmt;
165202
166313
  }
@@ -165232,19 +166343,19 @@ function countEmbeddedCommits(db, projectPath) {
165232
166343
  return row?.count ?? 0;
165233
166344
  }
165234
166345
  function clearProjectCommitEmbeddings(db, projectPath) {
165235
- return getClearProjectStatement(db).run(projectPath).changes;
166346
+ return getClearProjectStatement2(db).run(projectPath).changes;
165236
166347
  }
165237
166348
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
165238
166349
  const rows = getDistinctModelIdStatement(db).all(projectPath);
165239
166350
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165240
166351
  }
165241
- var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements, distinctModelIdStatements;
166352
+ var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements2, distinctModelIdStatements;
165242
166353
  var init_storage_git_commit_embeddings = __esm(() => {
165243
166354
  saveStatements = new WeakMap;
165244
166355
  loadProjectStatements = new WeakMap;
165245
166356
  loadUnembeddedStatements = new WeakMap;
165246
166357
  countEmbeddedStatements = new WeakMap;
165247
- clearProjectStatements = new WeakMap;
166358
+ clearProjectStatements2 = new WeakMap;
165248
166359
  distinctModelIdStatements = new WeakMap;
165249
166360
  });
165250
166361
 
@@ -165370,13 +166481,90 @@ var init_sweep_coordinator = __esm(() => {
165370
166481
  GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
165371
166482
  });
165372
166483
 
166484
+ // src/features/magic-context/session-project-storage.ts
166485
+ function getUpsertSessionProjectStatement(db) {
166486
+ let stmt = upsertSessionProjectStatements.get(db);
166487
+ if (!stmt) {
166488
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
166489
+ VALUES (?, ?, ?, ?)
166490
+ ON CONFLICT(session_id, harness) DO UPDATE SET
166491
+ project_path = excluded.project_path,
166492
+ updated_at = excluded.updated_at
166493
+ WHERE session_projects.project_path <> excluded.project_path`);
166494
+ upsertSessionProjectStatements.set(db, stmt);
166495
+ }
166496
+ return stmt;
166497
+ }
166498
+ function getRepairSessionChunkProjectStatement(db) {
166499
+ let stmt = repairSessionChunkProjectStatements.get(db);
166500
+ if (!stmt) {
166501
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166502
+ SET project_path = ?
166503
+ WHERE session_id = ?
166504
+ AND harness = ?
166505
+ AND project_path <> ?`);
166506
+ repairSessionChunkProjectStatements.set(db, stmt);
166507
+ }
166508
+ return stmt;
166509
+ }
166510
+ function getRepairProjectChunkProjectStatement(db) {
166511
+ let stmt = repairProjectChunkProjectStatements.get(db);
166512
+ if (!stmt) {
166513
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166514
+ SET project_path = (
166515
+ SELECT sp.project_path
166516
+ FROM session_projects sp
166517
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166518
+ AND sp.harness = compartment_chunk_embeddings.harness
166519
+ LIMIT 1
166520
+ )
166521
+ WHERE EXISTS (
166522
+ SELECT 1
166523
+ FROM session_projects sp
166524
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166525
+ AND sp.harness = compartment_chunk_embeddings.harness
166526
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
166527
+ AND (
166528
+ sp.project_path = ?
166529
+ OR compartment_chunk_embeddings.project_path = ?
166530
+ )
166531
+ )`);
166532
+ repairProjectChunkProjectStatements.set(db, stmt);
166533
+ }
166534
+ return stmt;
166535
+ }
166536
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
166537
+ if (!sessionId || !projectPath)
166538
+ return;
166539
+ const harness = getHarness();
166540
+ const now = Date.now();
166541
+ db.transaction(() => {
166542
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
166543
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
166544
+ })();
166545
+ }
166546
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
166547
+ if (!projectPath)
166548
+ return 0;
166549
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
166550
+ }
166551
+ var upsertSessionProjectStatements, repairSessionChunkProjectStatements, repairProjectChunkProjectStatements;
166552
+ var init_session_project_storage = __esm(() => {
166553
+ upsertSessionProjectStatements = new WeakMap;
166554
+ repairSessionChunkProjectStatements = new WeakMap;
166555
+ repairProjectChunkProjectStatements = new WeakMap;
166556
+ });
166557
+
165373
166558
  // src/features/magic-context/project-embedding-registry.ts
165374
- import { createHash as createHash7, randomUUID } from "node:crypto";
166559
+ import { createHash as createHash9, randomUUID } from "node:crypto";
165375
166560
  function resolveEmbeddingConfig(config2) {
165376
166561
  if (!config2 || config2.provider === "local") {
165377
166562
  return {
165378
166563
  provider: "local",
165379
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
166564
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
166565
+ ...config2?.max_input_tokens ? {
166566
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166567
+ } : {}
165380
166568
  };
165381
166569
  }
165382
166570
  if (config2.provider === "openai-compatible") {
@@ -165389,7 +166577,10 @@ function resolveEmbeddingConfig(config2) {
165389
166577
  endpoint: config2.endpoint.trim(),
165390
166578
  ...apiKey ? { api_key: apiKey } : {},
165391
166579
  ...inputType ? { input_type: inputType } : {},
165392
- ...truncate ? { truncate } : {}
166580
+ ...truncate ? { truncate } : {},
166581
+ ...config2.max_input_tokens ? {
166582
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166583
+ } : {}
165393
166584
  };
165394
166585
  }
165395
166586
  return { provider: "off" };
@@ -165407,10 +166598,11 @@ function createProvider(config2) {
165407
166598
  model: config2.model,
165408
166599
  apiKey: config2.api_key,
165409
166600
  inputType: config2.input_type,
165410
- truncate: config2.truncate
166601
+ truncate: config2.truncate,
166602
+ maxInputTokens: config2.max_input_tokens
165411
166603
  });
165412
166604
  }
165413
- return new LocalEmbeddingProvider(config2.model);
166605
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165414
166606
  }
165415
166607
  function stableStringify2(value) {
165416
166608
  if (Array.isArray(value)) {
@@ -165423,7 +166615,7 @@ function stableStringify2(value) {
165423
166615
  return JSON.stringify(value);
165424
166616
  }
165425
166617
  function sha256Prefix(value, length = 16) {
165426
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
166618
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
165427
166619
  }
165428
166620
  function getRuntimeFingerprint(config2) {
165429
166621
  if (config2.provider === "off") {
@@ -165431,6 +166623,18 @@ function getRuntimeFingerprint(config2) {
165431
166623
  }
165432
166624
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
165433
166625
  }
166626
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
166627
+ if (config2.provider === "off") {
166628
+ return OFF_PROVIDER_IDENTITY;
166629
+ }
166630
+ const chunkIdentity = {
166631
+ providerIdentity,
166632
+ chunkerVersion: 1,
166633
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
166634
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
166635
+ };
166636
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
166637
+ }
165434
166638
  function sameFeatures(a, b) {
165435
166639
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
165436
166640
  }
@@ -165447,7 +166651,8 @@ function snapshotFor(registration) {
165447
166651
  features: { ...registration.features },
165448
166652
  enabled,
165449
166653
  gitCommitEnabled,
165450
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
166654
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
166655
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
165451
166656
  };
165452
166657
  }
165453
166658
  function disposeProvider(provider) {
@@ -165467,7 +166672,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
165467
166672
  }
165468
166673
  return false;
165469
166674
  }
165470
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
166675
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
165471
166676
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
165472
166677
  return false;
165473
166678
  }
@@ -165488,6 +166693,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
165488
166693
  wiped = true;
165489
166694
  }
165490
166695
  }
166696
+ if (features.memoryEnabled) {
166697
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
166698
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
166699
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
166700
+ clearChunkEmbeddingsForProject(db, projectIdentity);
166701
+ wiped = true;
166702
+ }
166703
+ }
165491
166704
  })();
165492
166705
  return wiped;
165493
166706
  }
@@ -165495,10 +166708,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165495
166708
  const resolvedConfig = resolveEmbeddingConfig(config2);
165496
166709
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
165497
166710
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
166711
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
165498
166712
  const prior = projectRegistrations.get(projectIdentity);
165499
166713
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
165500
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
165501
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
166714
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
166715
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
165502
166716
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
165503
166717
  const registration = {
165504
166718
  projectIdentity,
@@ -165510,6 +166724,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165510
166724
  generation,
165511
166725
  features: { ...features },
165512
166726
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
166727
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
165513
166728
  observationMode: false
165514
166729
  };
165515
166730
  projectRegistrations.set(projectIdentity, registration);
@@ -165532,6 +166747,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
165532
166747
  generation,
165533
166748
  features: { memoryEnabled: false, gitCommitEnabled: false },
165534
166749
  modelId: "off",
166750
+ chunkModelId: "off",
165535
166751
  observationMode: true
165536
166752
  };
165537
166753
  projectRegistrations.set(projectIdentity, registration);
@@ -165542,6 +166758,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
165542
166758
  const registration = projectRegistrations.get(projectIdentity);
165543
166759
  return registration ? snapshotFor(registration) : null;
165544
166760
  }
166761
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
166762
+ const registration = projectRegistrations.get(projectIdentity);
166763
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
166764
+ }
166765
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
166766
+ const registration = projectRegistrations.get(projectIdentity);
166767
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
166768
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
166769
+ }
165545
166770
  function getOrCreateProjectProvider(registration) {
165546
166771
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
165547
166772
  return null;
@@ -165636,10 +166861,136 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
165636
166861
  return 0;
165637
166862
  }
165638
166863
  }
165639
- var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
166864
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
166865
+ const noWork = [];
166866
+ if (candidates.length === 0)
166867
+ return { embedded: 0, noWork };
166868
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
166869
+ const prepared = [];
166870
+ for (const candidate of candidates) {
166871
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
166872
+ if (canonicalText.length === 0) {
166873
+ noWork.push(candidate.id);
166874
+ continue;
166875
+ }
166876
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
166877
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
166878
+ noWork.push(candidate.id);
166879
+ continue;
166880
+ }
166881
+ prepared.push({ candidate, windows });
166882
+ }
166883
+ if (prepared.length === 0)
166884
+ return { embedded: 0, noWork };
166885
+ let embedded = 0;
166886
+ let i = 0;
166887
+ while (i < prepared.length) {
166888
+ if (signal?.aborted)
166889
+ break;
166890
+ const slice = [];
166891
+ let windowCount = 0;
166892
+ do {
166893
+ const item = prepared[i];
166894
+ slice.push(item);
166895
+ windowCount += item.windows.length;
166896
+ i += 1;
166897
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
166898
+ const texts = [];
166899
+ for (const item of slice)
166900
+ texts.push(...item.windows.map((w) => w.text));
166901
+ try {
166902
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
166903
+ if (!result)
166904
+ continue;
166905
+ if (signal?.aborted)
166906
+ break;
166907
+ let offset = 0;
166908
+ for (const item of slice) {
166909
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
166910
+ offset += item.windows.length;
166911
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
166912
+ continue;
166913
+ }
166914
+ const rows = item.windows.map((window, index) => ({
166915
+ compartmentId: item.candidate.id,
166916
+ sessionId: item.candidate.sessionId,
166917
+ projectPath: projectIdentity,
166918
+ window,
166919
+ modelId,
166920
+ vector: vectors[index]
166921
+ }));
166922
+ replaceCompartmentChunkEmbeddings(db, rows);
166923
+ embedded += 1;
166924
+ }
166925
+ } catch (error51) {
166926
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
166927
+ }
166928
+ }
166929
+ return { embedded, noWork };
166930
+ }
166931
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
166932
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
166933
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
166934
+ return { status: "disabled", embedded: 0, total: 0 };
166935
+ }
166936
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
166937
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
166938
+ if (total === 0)
166939
+ return { status: "nothing", embedded: 0, total: 0 };
166940
+ const holderId = `session-embed-${randomUUID()}`;
166941
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
166942
+ if (!lease2.acquired)
166943
+ return { status: "busy", embedded: 0, total };
166944
+ const renewal = setInterval(() => {
166945
+ try {
166946
+ renewGitSweepLease(db, projectIdentity, holderId);
166947
+ } catch {}
166948
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
166949
+ renewal.unref?.();
166950
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
166951
+ const skipIds = [];
166952
+ let embedded = 0;
166953
+ let aborted2 = false;
166954
+ let providerStalled = false;
166955
+ try {
166956
+ options?.onProgress?.({ embedded, total });
166957
+ for (;; ) {
166958
+ if (options?.signal?.aborted) {
166959
+ aborted2 = true;
166960
+ break;
166961
+ }
166962
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
166963
+ if (candidates.length === 0)
166964
+ break;
166965
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
166966
+ for (const id of noWork)
166967
+ skipIds.push(id);
166968
+ if (n === 0 && noWork.length === 0) {
166969
+ providerStalled = true;
166970
+ break;
166971
+ }
166972
+ embedded += n;
166973
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
166974
+ await new Promise((resolve7) => setTimeout(resolve7, 0));
166975
+ }
166976
+ } finally {
166977
+ clearInterval(renewal);
166978
+ releaseGitSweepLease(db, projectIdentity, holderId);
166979
+ }
166980
+ if (aborted2)
166981
+ return { status: "aborted", embedded, total };
166982
+ if (providerStalled) {
166983
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
166984
+ if (remaining > 0)
166985
+ return { status: "stalled", embedded, total, remaining };
166986
+ }
166987
+ return { status: "done", embedded, total };
166988
+ }
166989
+ var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, CHUNK_DRAIN_BATCH_SIZE = 8, MAX_WINDOWS_PER_EMBED_CALL = 16, SESSION_EMBED_LEASE_RENEWAL_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
165640
166990
  var init_project_embedding_registry = __esm(() => {
165641
166991
  init_magic_context();
165642
166992
  init_logger();
166993
+ init_compartment_chunk_embedding();
165643
166994
  init_storage_git_commit_embeddings();
165644
166995
  init_sweep_coordinator();
165645
166996
  init_embedding_cache();
@@ -165647,7 +166998,9 @@ var init_project_embedding_registry = __esm(() => {
165647
166998
  init_embedding_local();
165648
166999
  init_embedding_openai();
165649
167000
  init_storage_memory_embeddings();
167001
+ init_session_project_storage();
165650
167002
  SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167003
+ SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
165651
167004
  projectRegistrations = new Map;
165652
167005
  loadUnembeddedMemoriesStatements = new WeakMap;
165653
167006
  });
@@ -165663,10 +167016,11 @@ function createProvider2(config2) {
165663
167016
  model: config2.model,
165664
167017
  apiKey: config2.api_key,
165665
167018
  inputType: config2.input_type,
165666
- truncate: config2.truncate
167019
+ truncate: config2.truncate,
167020
+ maxInputTokens: config2.max_input_tokens
165667
167021
  });
165668
167022
  }
165669
- return new LocalEmbeddingProvider(config2.model);
167023
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165670
167024
  }
165671
167025
  function getOrCreateProvider() {
165672
167026
  if (provider) {
@@ -165692,6 +167046,7 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
165692
167046
  var init_embedding = __esm(() => {
165693
167047
  init_magic_context();
165694
167048
  init_logger();
167049
+ init_compartment_chunk_embedding();
165695
167050
  init_embedding_identity();
165696
167051
  init_embedding_local();
165697
167052
  init_embedding_openai();
@@ -165715,6 +167070,23 @@ function getSearchStatement(db) {
165715
167070
  }
165716
167071
  return stmt;
165717
167072
  }
167073
+ function getUnionSearchStatement(db, arity) {
167074
+ let statements = unionSearchStatements.get(arity);
167075
+ if (!statements) {
167076
+ statements = new WeakMap;
167077
+ unionSearchStatements.set(arity, statements);
167078
+ }
167079
+ let stmt = statements.get(db);
167080
+ if (!stmt) {
167081
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
167082
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${placeholders3}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
167083
+ statements.set(db, stmt);
167084
+ }
167085
+ return stmt;
167086
+ }
167087
+ function uniqueProjectPaths2(projectPaths) {
167088
+ return [...new Set(projectPaths.filter((path6) => path6.length > 0))];
167089
+ }
165718
167090
  function sanitizeFtsQuery(query) {
165719
167091
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
165720
167092
  if (tokens.length === 0)
@@ -165733,10 +167105,33 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
165733
167105
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
165734
167106
  return rows.map(toMemory);
165735
167107
  }
165736
- var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
167108
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
167109
+ const identities = uniqueProjectPaths2(projectPaths);
167110
+ if (identities.length === 0)
167111
+ return [];
167112
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
167113
+ identities,
167114
+ ownIdentities,
167115
+ shareCategories,
167116
+ tableName: "memories"
167117
+ });
167118
+ if (identities.length === 1 && !sharingFilter.active) {
167119
+ return searchMemoriesFTS(db, identities[0], query, limit);
167120
+ }
167121
+ const trimmedQuery = query.trim();
167122
+ if (trimmedQuery.length === 0 || limit <= 0)
167123
+ return [];
167124
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
167125
+ if (sanitized.length === 0)
167126
+ return [];
167127
+ const rows = sharingFilter.active ? db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${identities.map(() => "?").join(", ")}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ?${sharingFilter.clause} ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`).all(...identities, Date.now(), sanitized, ...sharingFilter.params, limit).filter(isMemoryRow) : getUnionSearchStatement(db, identities.length).all(...identities, Date.now(), sanitized, limit).filter(isMemoryRow);
167128
+ return rows.map(toMemory);
167129
+ }
167130
+ var DEFAULT_SEARCH_LIMIT = 10, searchStatements, unionSearchStatements;
165737
167131
  var init_storage_memory_fts = __esm(() => {
165738
167132
  init_storage_memory();
165739
167133
  searchStatements = new WeakMap;
167134
+ unionSearchStatements = new Map;
165740
167135
  });
165741
167136
 
165742
167137
  // src/shared/models-dev-cache.ts
@@ -165930,26 +167325,54 @@ var init_rpc_notifications = __esm(() => {
165930
167325
  });
165931
167326
 
165932
167327
  // src/features/magic-context/compartment-embedding.ts
165933
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
167328
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
165934
167329
  if (compartments.length === 0)
165935
167330
  return;
165936
- const update = db.prepare("UPDATE compartments SET p1_embedding = ?, p1_embedding_model_id = ? WHERE id = ?");
165937
- for (const c of compartments) {
165938
- if (!c.p1 || c.p1.length === 0)
165939
- continue;
167331
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
167332
+ for (const compartment of compartments) {
165940
167333
  try {
165941
- const result = await embedTextForProject(projectPath, c.p1);
165942
- if (result) {
165943
- const blob = Buffer.from(result.vector.buffer, result.vector.byteOffset, result.vector.byteLength);
165944
- update.run(blob, result.modelId, c.id);
167334
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
167335
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
167336
+ if (canonicalText.length === 0)
167337
+ continue;
167338
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
167339
+ if (windows.length === 0)
167340
+ continue;
167341
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
167342
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167343
+ continue;
167344
+ }
167345
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
167346
+ if (!result)
167347
+ continue;
167348
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167349
+ continue;
167350
+ }
167351
+ const rows = [];
167352
+ for (const [index, window] of windows.entries()) {
167353
+ const vector = result.vectors[index];
167354
+ if (!vector)
167355
+ continue;
167356
+ rows.push({
167357
+ compartmentId: compartment.id,
167358
+ sessionId,
167359
+ projectPath,
167360
+ window,
167361
+ modelId: currentModelId,
167362
+ vector
167363
+ });
167364
+ }
167365
+ if (rows.length === windows.length) {
167366
+ replaceCompartmentChunkEmbeddings(db, rows);
165945
167367
  }
165946
167368
  } catch (error51) {
165947
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
167369
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
165948
167370
  }
165949
167371
  }
165950
167372
  }
165951
167373
  var init_compartment_embedding = __esm(() => {
165952
167374
  init_logger();
167375
+ init_compartment_chunk_embedding();
165953
167376
  init_project_embedding_registry();
165954
167377
  });
165955
167378
 
@@ -167063,55 +168486,6 @@ var init_historian_state_file = __esm(() => {
167063
168486
  init_data_path();
167064
168487
  });
167065
168488
 
167066
- // src/features/magic-context/memory/constants.ts
167067
- var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
167068
- var init_constants = __esm(() => {
167069
- V2_MEMORY_CATEGORIES = [
167070
- "PROJECT_RULES",
167071
- "ARCHITECTURE",
167072
- "CONSTRAINTS",
167073
- "CONFIG_VALUES",
167074
- "NAMING"
167075
- ];
167076
- PROMOTABLE_CATEGORIES = [
167077
- "PROJECT_RULES",
167078
- "ARCHITECTURE",
167079
- "CONSTRAINTS",
167080
- "CONFIG_VALUES",
167081
- "NAMING",
167082
- "ARCHITECTURE_DECISIONS",
167083
- "CONFIG_DEFAULTS",
167084
- "USER_PREFERENCES",
167085
- "USER_DIRECTIVES",
167086
- "ENVIRONMENT",
167087
- "WORKFLOW_RULES",
167088
- "KNOWN_ISSUES"
167089
- ];
167090
- CATEGORY_PRIORITY = [
167091
- "PROJECT_RULES",
167092
- "ARCHITECTURE",
167093
- "CONSTRAINTS",
167094
- "CONFIG_VALUES",
167095
- "NAMING",
167096
- "USER_DIRECTIVES",
167097
- "USER_PREFERENCES",
167098
- "CONFIG_DEFAULTS",
167099
- "ARCHITECTURE_DECISIONS",
167100
- "ENVIRONMENT",
167101
- "WORKFLOW_RULES",
167102
- "KNOWN_ISSUES"
167103
- ];
167104
- MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
167105
- acc[category] = index;
167106
- return acc;
167107
- }, {});
167108
- MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
167109
- CATEGORY_DEFAULT_TTL = {
167110
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
167111
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
167112
- };
167113
- });
167114
-
167115
168489
  // src/features/magic-context/memory/embedding-backfill.ts
167116
168490
  async function ensureMemoryEmbeddings(args) {
167117
168491
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167135,7 +168509,7 @@ async function ensureMemoryEmbeddings(args) {
167135
168509
  continue;
167136
168510
  }
167137
168511
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
167138
- staged.set(memory.id, embedding);
168512
+ staged.set(memory.id, { embedding, modelId: result.modelId });
167139
168513
  }
167140
168514
  })();
167141
168515
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167932,6 +169306,71 @@ function lastCompartmentBoundaryId(compartments) {
167932
169306
  const last = compartments.at(-1);
167933
169307
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
167934
169308
  }
169309
+ function resolveWorkspaceRenderContext(args) {
169310
+ if (!args.projectPath) {
169311
+ return {
169312
+ identities: [],
169313
+ expandedIdentities: [],
169314
+ ownIdentities: [],
169315
+ shareCategories: null,
169316
+ namesByIdentity: new Map,
169317
+ canonicalIdentityByStoredPath: new Map,
169318
+ isWorkspaced: false
169319
+ };
169320
+ }
169321
+ const identitySet = args.workspaceIdentitySet ?? resolveWorkspaceIdentitySet(args.db, args.projectPath);
169322
+ const isWorkspaced = identitySet.identities.length > 1;
169323
+ const expanded = expandWorkspaceIdentitySetWithAliases(args.db, identitySet.identities);
169324
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
169325
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
169326
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === args.projectPath);
169327
+ if (ownIdentities.length === 0 && expandedIdentities.includes(args.projectPath)) {
169328
+ ownIdentities = [args.projectPath];
169329
+ }
169330
+ return {
169331
+ identities: identitySet.identities,
169332
+ expandedIdentities,
169333
+ ownIdentities,
169334
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(args.db, args.projectPath) : null,
169335
+ namesByIdentity: identitySet.namesByIdentity,
169336
+ canonicalIdentityByStoredPath,
169337
+ isWorkspaced
169338
+ };
169339
+ }
169340
+ function sourceNamesForMemories(args) {
169341
+ if (!args.projectPath || !args.workspace.isWorkspaced)
169342
+ return;
169343
+ const names = new Map;
169344
+ for (const memory of args.memories) {
169345
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
169346
+ if (source)
169347
+ names.set(memory.id, source);
169348
+ }
169349
+ return names.size > 0 ? names : undefined;
169350
+ }
169351
+ function memoryCanonicalIdentity(memory, workspace) {
169352
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
169353
+ }
169354
+ function memorySelectionOrder(left, right) {
169355
+ if (left.status === "permanent" && right.status !== "permanent")
169356
+ return -1;
169357
+ if (right.status === "permanent" && left.status !== "permanent")
169358
+ return 1;
169359
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
169360
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
169361
+ const importanceDiff = rightImportance - leftImportance;
169362
+ if (importanceDiff !== 0)
169363
+ return importanceDiff;
169364
+ return left.id - right.id;
169365
+ }
169366
+ function memoryRenderOrder(left, right) {
169367
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169368
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169369
+ const categoryDiff = aPriority - bPriority;
169370
+ if (categoryDiff !== 0)
169371
+ return categoryDiff;
169372
+ return left.id - right.id;
169373
+ }
167935
169374
  function cachedStatement(cache, db, sql) {
167936
169375
  let stmt = cache.get(db);
167937
169376
  if (!stmt) {
@@ -167974,19 +169413,24 @@ function getGlobalUserProfileVersion(db) {
167974
169413
  function readCurrentM0SnapshotMarkers(args) {
167975
169414
  const projectDirectory = args.projectDirectory ?? args.projectPath ?? "";
167976
169415
  const hard = args.hardSignals ?? EMPTY_HARD_SIGNALS;
169416
+ const workspace = resolveWorkspaceRenderContext({
169417
+ db: args.db,
169418
+ projectPath: args.projectPath,
169419
+ workspaceIdentitySet: args.workspaceIdentitySet
169420
+ });
167977
169421
  return {
167978
169422
  projectMemoryEpoch: getProjectMemoryEpoch(args.db, args.projectPath),
169423
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(args.db, workspace.identities) : null,
167979
169424
  projectUserProfileVersion: getGlobalUserProfileVersion(args.db),
167980
169425
  maxCompartmentSeq: getMaxCompartmentSeq(args.db, args.sessionId),
167981
- maxMemoryId: getMaxMemoryId(args.db, args.projectPath),
169426
+ maxMemoryId: workspace.isWorkspaced ? getMaxMemoryIdForProjects(args.db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryId(args.db, args.projectPath),
167982
169427
  maxMutationId: getMaxM0MutationId(args.db, args.sessionId) ?? 0,
167983
- maxMemoryMutationId: args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
169428
+ maxMemoryMutationId: workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(args.db, workspace.expandedIdentities) ?? 0 : args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
167984
169429
  projectDocsHash: projectDirectory ? computeProjectDocsHash(projectDirectory) : "",
167985
169430
  materializedAt: Date.now(),
167986
169431
  sessionFactsVersion: getSessionFactsVersion(args.db, args.sessionId),
167987
169432
  upgradeState: getUpgradeState(args.db, args.sessionId),
167988
169433
  systemHash: hard.systemHash,
167989
- toolSetHash: hard.toolSetHash,
167990
169434
  modelKey: hard.modelKey
167991
169435
  };
167992
169436
  }
@@ -168009,6 +169453,7 @@ function snapshotMarkersFromCachedM0(state) {
168009
169453
  return null;
168010
169454
  return {
168011
169455
  projectMemoryEpoch: state.cachedM0ProjectMemoryEpoch,
169456
+ workspaceFingerprint: state.cachedM0WorkspaceFingerprint,
168012
169457
  projectUserProfileVersion: state.cachedM0ProjectUserProfileVersion,
168013
169458
  maxCompartmentSeq: state.cachedM0MaxCompartmentSeq,
168014
169459
  maxMemoryId: state.cachedM0MaxMemoryId,
@@ -168019,7 +169464,6 @@ function snapshotMarkersFromCachedM0(state) {
168019
169464
  sessionFactsVersion: state.cachedM0SessionFactsVersion,
168020
169465
  upgradeState: state.cachedM0UpgradeState,
168021
169466
  systemHash: state.cachedM0SystemHash ?? "",
168022
- toolSetHash: state.cachedM0ToolSetHash ?? "",
168023
169467
  modelKey: state.cachedM0ModelKey ?? ""
168024
169468
  };
168025
169469
  }
@@ -168036,41 +169480,30 @@ function mustMaterialize(args) {
168036
169480
  if (hard.systemHash !== "" && hard.systemHash !== (args.state.cachedM0SystemHash ?? "")) {
168037
169481
  return { value: true, reason: "system_hash" };
168038
169482
  }
168039
- if (hard.toolSetHash !== "" && hard.toolSetHash !== (args.state.cachedM0ToolSetHash ?? "")) {
168040
- return { value: true, reason: "tool_set_hash" };
168041
- }
168042
169483
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (args.state.cachedM0MaterializedAt ?? 0)) {
168043
169484
  return { value: true, reason: "ttl_idle" };
168044
169485
  }
168045
- if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
169486
+ if (current.workspaceFingerprint !== null || (args.state.cachedM0WorkspaceFingerprint ?? null) !== null) {
169487
+ if ((args.state.cachedM0WorkspaceFingerprint ?? null) !== current.workspaceFingerprint) {
169488
+ return { value: true, reason: "project_memory_epoch" };
169489
+ }
169490
+ } else if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
168046
169491
  return { value: true, reason: "project_memory_epoch" };
168047
169492
  }
168048
169493
  if (args.state.cachedM0MaxMutationId !== current.maxMutationId) {
168049
169494
  return { value: true, reason: "max_mutation_id" };
168050
169495
  }
168051
- if ((args.state.cachedM0ProjectDocsHash ?? "") !== current.projectDocsHash) {
168052
- return { value: true, reason: "project_docs_hash" };
168053
- }
168054
169496
  if ((args.state.cachedM0UpgradeState ?? null) !== current.upgradeState) {
168055
169497
  return { value: true, reason: "upgrade_state" };
168056
169498
  }
168057
169499
  return { value: false, reason: null };
168058
169500
  }
168059
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168060
- const selectionOrder = [...memories].sort((a, b) => {
168061
- if (a.status === "permanent" && b.status !== "permanent")
168062
- return -1;
168063
- if (b.status === "permanent" && a.status !== "permanent")
168064
- return 1;
168065
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
168066
- if (importanceDiff !== 0)
168067
- return importanceDiff;
168068
- return a.id - b.id;
168069
- });
169501
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
169502
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
168070
169503
  const selected = [];
168071
169504
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
168072
169505
  for (const memory of selectionOrder) {
168073
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
169506
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168074
169507
  if (usedTokens + memoryTokens > budgetTokens)
168075
169508
  continue;
168076
169509
  selected.push(memory);
@@ -168079,16 +169512,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168079
169512
  if (selected.length < memories.length) {
168080
169513
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
168081
169514
  }
168082
- const renderOrder = [...selected].sort((a, b) => {
168083
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168084
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168085
- const categoryDiff = aPriority - bPriority;
168086
- if (categoryDiff !== 0)
168087
- return categoryDiff;
168088
- return a.id - b.id;
168089
- });
169515
+ const renderOrder = [...selected].sort(memoryRenderOrder);
168090
169516
  return { selected, renderOrder };
168091
169517
  }
169518
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
169519
+ if (!workspace.isWorkspaced) {
169520
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
169521
+ }
169522
+ const selected = [];
169523
+ const selectedIds = new Set;
169524
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
169525
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
169526
+ const trySelect = (memory) => {
169527
+ if (selectedIds.has(memory.id))
169528
+ return false;
169529
+ const tokens = tokenCost(memory);
169530
+ if (usedTokens + tokens > budgetTokens)
169531
+ return false;
169532
+ selected.push(memory);
169533
+ selectedIds.add(memory.id);
169534
+ usedTokens += tokens;
169535
+ return true;
169536
+ };
169537
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
169538
+ trySelect(memory);
169539
+ }
169540
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
169541
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
169542
+ const byIdentity = new Map;
169543
+ for (const memory of memories) {
169544
+ if (memory.status === "permanent")
169545
+ continue;
169546
+ const identity = memoryCanonicalIdentity(memory, workspace);
169547
+ if (!identity)
169548
+ continue;
169549
+ const list = byIdentity.get(identity) ?? [];
169550
+ list.push(memory);
169551
+ byIdentity.set(identity, list);
169552
+ }
169553
+ for (const identity of workspace.identities) {
169554
+ let memberTokens = 0;
169555
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
169556
+ for (const memory of candidates) {
169557
+ if (selectedIds.has(memory.id))
169558
+ continue;
169559
+ const tokens = tokenCost(memory);
169560
+ if (memberTokens + tokens > floorTokens)
169561
+ continue;
169562
+ if (usedTokens + tokens > budgetTokens)
169563
+ continue;
169564
+ selected.push(memory);
169565
+ selectedIds.add(memory.id);
169566
+ usedTokens += tokens;
169567
+ memberTokens += tokens;
169568
+ }
169569
+ }
169570
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
169571
+ for (const memory of remaining) {
169572
+ trySelect(memory);
169573
+ }
169574
+ if (selected.length < memories.length) {
169575
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
169576
+ }
169577
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
169578
+ }
168092
169579
  function safeGetActiveUserMemories(db) {
168093
169580
  try {
168094
169581
  return getActiveUserMemories(db);
@@ -168164,15 +169651,16 @@ function readNewMemoriesForM1(db, projectPath, afterId, expiryCutoff) {
168164
169651
  ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(projectPath, afterId, expiryCutoff).filter(isMemoryRow);
168165
169652
  return rows.map((row) => ({ ...row }));
168166
169653
  }
168167
- function renderMemoryLineV2(memory) {
168168
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
169654
+ function renderMemoryLineV2(memory, sourceName) {
169655
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
169656
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
168169
169657
  }
168170
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
169658
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
168171
169659
  if (memories.length === 0)
168172
169660
  return "";
168173
169661
  const lines = [`<${wrapper}>`];
168174
169662
  for (const memory of memories) {
168175
- lines.push(renderMemoryLineV2(memory));
169663
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168176
169664
  }
168177
169665
  lines.push(`</${wrapper}>`);
168178
169666
  return lines.join(`
@@ -168211,7 +169699,7 @@ function renderM0(args) {
168211
169699
  sections.push(sessionHistory.length > 0 ? `<session-history>
168212
169700
  ${sessionHistory}
168213
169701
  </session-history>` : M0_EMPTY_BODY);
168214
- const memoriesBlock = renderMemoryBlockV2(args.memories);
169702
+ const memoriesBlock = renderMemoryBlockV2(args.memories, "project-memory", args.memoryRenderOptions);
168215
169703
  if (memoriesBlock)
168216
169704
  sections.push(memoriesBlock);
168217
169705
  return sections.join(`
@@ -168223,6 +169711,7 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
168223
169711
  if (m1Bytes)
168224
169712
  state.cachedM1Bytes = m1Bytes;
168225
169713
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
169714
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168226
169715
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168227
169716
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168228
169717
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168233,7 +169722,6 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
168233
169722
  state.cachedM0SessionFactsVersion = markers.sessionFactsVersion;
168234
169723
  state.cachedM0UpgradeState = markers.upgradeState;
168235
169724
  state.cachedM0SystemHash = markers.systemHash;
168236
- state.cachedM0ToolSetHash = markers.toolSetHash;
168237
169725
  state.cachedM0ModelKey = markers.modelKey;
168238
169726
  state.snapshotMarkers = markers;
168239
169727
  }
@@ -168249,24 +169737,38 @@ function materializeM0(options) {
168249
169737
  let facts = [];
168250
169738
  let memories = [];
168251
169739
  let userMemories = [];
169740
+ let workspace = resolveWorkspaceRenderContext({
169741
+ db: options.db,
169742
+ projectPath,
169743
+ workspaceIdentitySet: options.workspaceIdentitySet
169744
+ });
168252
169745
  let docs = {
168253
169746
  renderedBlock: "",
168254
169747
  canonicalHash: ""
168255
169748
  };
168256
169749
  options.db.exec("BEGIN");
168257
169750
  try {
169751
+ workspace = resolveWorkspaceRenderContext({
169752
+ db: options.db,
169753
+ projectPath,
169754
+ workspaceIdentitySet: options.workspaceIdentitySet
169755
+ });
168258
169756
  snapshotMarkers = readCurrentM0SnapshotMarkers({
168259
169757
  db: options.db,
168260
169758
  sessionId: options.sessionId,
168261
169759
  projectPath,
168262
169760
  projectDirectory,
168263
- hardSignals: options.hardSignals
169761
+ hardSignals: options.hardSignals,
169762
+ workspaceIdentitySet: {
169763
+ identities: workspace.identities,
169764
+ namesByIdentity: workspace.namesByIdentity
169765
+ }
168264
169766
  });
168265
169767
  docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168266
169768
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168267
169769
  compartments = readM0Compartments(options.db, options.sessionId);
168268
169770
  facts = [];
168269
- memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
169771
+ memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
168270
169772
  userMemories = safeGetActiveUserMemories(options.db);
168271
169773
  options.db.exec("COMMIT");
168272
169774
  } catch (error51) {
@@ -168276,7 +169778,14 @@ function materializeM0(options) {
168276
169778
  throw error51;
168277
169779
  }
168278
169780
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168279
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
169781
+ const memoryRenderOptions = {
169782
+ sourceNameByMemoryId: sourceNamesForMemories({
169783
+ memories,
169784
+ projectPath,
169785
+ workspace
169786
+ })
169787
+ };
169788
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168280
169789
  let decayPressureMultiplier = 1;
168281
169790
  let m0Text = renderM0({
168282
169791
  projectDocs: docs.renderedBlock,
@@ -168284,6 +169793,7 @@ function materializeM0(options) {
168284
169793
  compartments,
168285
169794
  memories: trimmed.renderOrder,
168286
169795
  facts,
169796
+ memoryRenderOptions,
168287
169797
  historyBudgetTokens: options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS,
168288
169798
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168289
169799
  decayPressureMultiplier
@@ -168298,6 +169808,7 @@ function materializeM0(options) {
168298
169808
  compartments,
168299
169809
  memories: trimmed.renderOrder,
168300
169810
  facts,
169811
+ memoryRenderOptions,
168301
169812
  historyBudgetTokens: budget,
168302
169813
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168303
169814
  decayPressureMultiplier
@@ -168316,32 +169827,46 @@ function materializeM0(options) {
168316
169827
  let m1Bytes = Buffer4.from(m1Text, "utf8");
168317
169828
  options.db.exec("BEGIN IMMEDIATE");
168318
169829
  try {
169830
+ const currentWorkspace = resolveWorkspaceRenderContext({
169831
+ db: options.db,
169832
+ projectPath,
169833
+ workspaceIdentitySet: options.workspaceIdentitySet
169834
+ });
168319
169835
  const current = {
168320
169836
  projectMemoryEpoch: getProjectMemoryEpoch(options.db, projectPath),
169837
+ workspaceFingerprint: currentWorkspace.isWorkspaced ? computeWorkspaceEpochFingerprint(options.db, currentWorkspace.identities) : null,
168321
169838
  projectUserProfileVersion: getGlobalUserProfileVersion(options.db),
168322
169839
  maxCompartmentSeq: getMaxCompartmentSeq(options.db, options.sessionId),
168323
- maxMemoryId: getMaxMemoryId(options.db, projectPath),
169840
+ maxMemoryId: currentWorkspace.isWorkspaced ? getMaxMemoryIdForProjects(options.db, currentWorkspace.expandedIdentities, currentWorkspace.ownIdentities, currentWorkspace.shareCategories) : getMaxMemoryId(options.db, projectPath),
168324
169841
  maxMutationId: getMaxM0MutationId(options.db, options.sessionId) ?? 0,
168325
- maxMemoryMutationId: projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
169842
+ maxMemoryMutationId: currentWorkspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(options.db, currentWorkspace.expandedIdentities) ?? 0 : projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
168326
169843
  projectDocsHash: phase3ProjectDocsHash,
168327
169844
  materializedAt: Date.now(),
168328
169845
  sessionFactsVersion: getSessionFactsVersion(options.db, options.sessionId),
168329
169846
  upgradeState: getUpgradeState(options.db, options.sessionId),
168330
169847
  systemHash: snapshotMarkers.systemHash,
168331
- toolSetHash: snapshotMarkers.toolSetHash,
168332
169848
  modelKey: snapshotMarkers.modelKey
168333
169849
  };
168334
- const stale = current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.projectDocsHash !== snapshotMarkers.projectDocsHash || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
169850
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
169851
+ const stale = memoryEpochStale || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
168335
169852
  if (stale) {
168336
169853
  options.db.exec("ROLLBACK");
168337
169854
  throw new MaterializeContentionError({ reason: "snapshot changed before Phase 3" });
168338
169855
  }
168339
- const m1Render = renderM1WithMetadata({ ...options, preRenderedKeyFilesBlock }, snapshotMarkers, renderedMemoryIds);
169856
+ const m1Render = renderM1WithMetadata({
169857
+ ...options,
169858
+ preRenderedKeyFilesBlock,
169859
+ workspaceIdentitySet: {
169860
+ identities: workspace.identities,
169861
+ namesByIdentity: workspace.namesByIdentity
169862
+ }
169863
+ }, snapshotMarkers, renderedMemoryIds);
168340
169864
  m1Text = m1Render.text;
168341
169865
  m1Bytes = Buffer4.from(m1Text, "utf8");
168342
169866
  persistCachedM0(options.db, options.sessionId, {
168343
169867
  m0Bytes,
168344
169868
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
169869
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
168345
169870
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
168346
169871
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
168347
169872
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -168353,7 +169878,6 @@ function materializeM0(options) {
168353
169878
  sessionFactsVersion: snapshotMarkers.sessionFactsVersion,
168354
169879
  upgradeState: snapshotMarkers.upgradeState,
168355
169880
  systemHash: snapshotMarkers.systemHash,
168356
- toolSetHash: snapshotMarkers.toolSetHash,
168357
169881
  modelKey: snapshotMarkers.modelKey
168358
169882
  });
168359
169883
  options.db.prepare("UPDATE session_meta SET memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(renderedMemoryIds.length, JSON.stringify(renderedMemoryIds), options.sessionId);
@@ -168405,7 +169929,7 @@ function renderMemoryUpdatesBlock(args) {
168405
169929
  return { block: "", count: 0 };
168406
169930
  }
168407
169931
  const renderedIds = new Set(args.renderedMemoryIds);
168408
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
169932
+ const mutations = args.workspace.isWorkspaced ? getMemoryMutationsForRenderByProjects(args.db, args.workspace.expandedIdentities, args.afterId, args.renderedMemoryIds) : getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
168409
169933
  if (mutations.length === 0)
168410
169934
  return { block: "", count: 0 };
168411
169935
  const lines = ["These memories changed since the snapshot below — trust these:"];
@@ -168437,12 +169961,18 @@ function renderM1WithMetadata(options, markers, renderedMemoryIds) {
168437
169961
  throw new RenderM1InvalidMarkersError(options.sessionId);
168438
169962
  }
168439
169963
  const blocks = [];
169964
+ const workspace = resolveWorkspaceRenderContext({
169965
+ db: options.db,
169966
+ projectPath: options.projectPath,
169967
+ workspaceIdentitySet: options.workspaceIdentitySet
169968
+ });
168440
169969
  const keyFiles = renderedKeyFilesBlock(options);
168441
169970
  if (keyFiles)
168442
169971
  blocks.push(keyFiles);
168443
169972
  const memoryUpdates = renderMemoryUpdatesBlock({
168444
169973
  db: options.db,
168445
169974
  projectPath: options.projectPath,
169975
+ workspace,
168446
169976
  afterId: markers.maxMemoryMutationId,
168447
169977
  renderedMemoryIds
168448
169978
  });
@@ -168456,9 +169986,16 @@ ${newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).
168456
169986
  `)}
168457
169987
  </new-compartments>`);
168458
169988
  }
168459
- const newMemories = readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
168460
- const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25))).renderOrder;
168461
- const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories");
169989
+ const newMemories = workspace.isWorkspaced ? readNewMemoriesForM1Union(options.db, workspace.expandedIdentities, markers.maxMemoryId, markers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
169990
+ const newMemoryRenderOptions = {
169991
+ sourceNameByMemoryId: sourceNamesForMemories({
169992
+ memories: newMemories,
169993
+ projectPath: options.projectPath,
169994
+ workspace
169995
+ })
169996
+ };
169997
+ const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25)), newMemoryRenderOptions).renderOrder;
169998
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", newMemoryRenderOptions);
168462
169999
  if (newMemoriesBlock)
168463
170000
  blocks.push(newMemoriesBlock);
168464
170001
  const currentUserProfileVersion = getGlobalUserProfileVersion(options.db);
@@ -168506,6 +170043,7 @@ function parseMemoryBlockIds(raw) {
168506
170043
  function readCachedM0M1Row(db, sessionId) {
168507
170044
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
168508
170045
  cached_m0_project_memory_epoch,
170046
+ cached_m0_workspace_fingerprint,
168509
170047
  cached_m0_project_user_profile_version,
168510
170048
  cached_m0_max_compartment_seq,
168511
170049
  cached_m0_max_memory_id,
@@ -168516,7 +170054,6 @@ function readCachedM0M1Row(db, sessionId) {
168516
170054
  cached_m0_session_facts_version,
168517
170055
  cached_m0_upgrade_state,
168518
170056
  cached_m0_system_hash,
168519
- cached_m0_tool_set_hash,
168520
170057
  cached_m0_model_key,
168521
170058
  memory_block_ids
168522
170059
  FROM session_meta
@@ -168541,6 +170078,7 @@ function markersFromCachedRow(row) {
168541
170078
  return null;
168542
170079
  return {
168543
170080
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
170081
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
168544
170082
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
168545
170083
  maxCompartmentSeq: row.cached_m0_max_compartment_seq,
168546
170084
  maxMemoryId: row.cached_m0_max_memory_id,
@@ -168551,12 +170089,11 @@ function markersFromCachedRow(row) {
168551
170089
  sessionFactsVersion: row.cached_m0_session_facts_version,
168552
170090
  upgradeState: row.cached_m0_upgrade_state,
168553
170091
  systemHash: row.cached_m0_system_hash ?? "",
168554
- toolSetHash: row.cached_m0_tool_set_hash ?? "",
168555
170092
  modelKey: row.cached_m0_model_key ?? ""
168556
170093
  };
168557
170094
  }
168558
170095
  function cachedRowMatchesState(row, state) {
168559
- 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_tool_set_hash ?? "") === (state.cachedM0ToolSetHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
170096
+ return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && (row.cached_m0_workspace_fingerprint ?? null) === (state.cachedM0WorkspaceFingerprint ?? null) && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
168560
170097
  }
168561
170098
  function applyCachedRowToState(state, row) {
168562
170099
  const markers = markersFromCachedRow(row);
@@ -168566,6 +170103,7 @@ function applyCachedRowToState(state, row) {
168566
170103
  state.cachedM0Bytes = toBuffer(row.cached_m0_bytes);
168567
170104
  state.cachedM1Bytes = toBuffer(row.cached_m1_bytes);
168568
170105
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
170106
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168569
170107
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168570
170108
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168571
170109
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168576,7 +170114,6 @@ function applyCachedRowToState(state, row) {
168576
170114
  state.cachedM0SessionFactsVersion = markers.sessionFactsVersion;
168577
170115
  state.cachedM0UpgradeState = markers.upgradeState;
168578
170116
  state.cachedM0SystemHash = markers.systemHash;
168579
- state.cachedM0ToolSetHash = markers.toolSetHash;
168580
170117
  state.cachedM0ModelKey = markers.modelKey;
168581
170118
  state.snapshotMarkers = markers;
168582
170119
  }
@@ -168630,20 +170167,36 @@ function prependM0M1Messages(sessionId, messages, m0Text, m1Text) {
168630
170167
  function renderFreshM0NonPersisted(options) {
168631
170168
  const projectPath = options.projectPath;
168632
170169
  const projectDirectory = options.projectDirectory;
170170
+ const workspace = resolveWorkspaceRenderContext({
170171
+ db: options.db,
170172
+ projectPath,
170173
+ workspaceIdentitySet: options.workspaceIdentitySet
170174
+ });
168633
170175
  const snapshotMarkers = readCurrentM0SnapshotMarkers({
168634
170176
  db: options.db,
168635
170177
  sessionId: options.sessionId,
168636
170178
  projectPath,
168637
- projectDirectory
170179
+ projectDirectory,
170180
+ workspaceIdentitySet: {
170181
+ identities: workspace.identities,
170182
+ namesByIdentity: workspace.namesByIdentity
170183
+ }
168638
170184
  });
168639
170185
  const docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168640
170186
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168641
170187
  snapshotMarkers.materializedAt = options.state.cachedM0MaterializedAt ?? 0;
168642
170188
  const compartments = readM0Compartments(options.db, options.sessionId);
168643
- const memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
170189
+ const memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], snapshotMarkers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
168644
170190
  const userMemories = safeGetActiveUserMemories(options.db);
168645
170191
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168646
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
170192
+ const memoryRenderOptions = {
170193
+ sourceNameByMemoryId: sourceNamesForMemories({
170194
+ memories,
170195
+ projectPath,
170196
+ workspace
170197
+ })
170198
+ };
170199
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168647
170200
  const budget = options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
168648
170201
  let decayPressureMultiplier = 1;
168649
170202
  let m0Text = renderM0({
@@ -168652,6 +170205,7 @@ function renderFreshM0NonPersisted(options) {
168652
170205
  compartments,
168653
170206
  memories: trimmed.renderOrder,
168654
170207
  facts: [],
170208
+ memoryRenderOptions,
168655
170209
  historyBudgetTokens: budget,
168656
170210
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168657
170211
  decayPressureMultiplier
@@ -168665,6 +170219,7 @@ function renderFreshM0NonPersisted(options) {
168665
170219
  compartments,
168666
170220
  memories: trimmed.renderOrder,
168667
170221
  facts: [],
170222
+ memoryRenderOptions,
168668
170223
  historyBudgetTokens: budget,
168669
170224
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168670
170225
  decayPressureMultiplier
@@ -168680,6 +170235,12 @@ function renderFreshM0NonPersisted(options) {
168680
170235
  };
168681
170236
  }
168682
170237
  function injectM0M1(options) {
170238
+ if (!options.workspaceIdentitySet && options.projectPath) {
170239
+ options = {
170240
+ ...options,
170241
+ workspaceIdentitySet: resolveWorkspaceIdentitySet(options.db, options.projectPath)
170242
+ };
170243
+ }
168683
170244
  const skipped = {
168684
170245
  injected: false,
168685
170246
  m0RematerializedThisPass: false,
@@ -168696,7 +170257,8 @@ function injectM0M1(options) {
168696
170257
  state: options.state,
168697
170258
  projectPath: options.projectPath,
168698
170259
  projectDirectory: options.projectDirectory,
168699
- hardSignals: options.hardSignals
170260
+ hardSignals: options.hardSignals,
170261
+ workspaceIdentitySet: options.workspaceIdentitySet
168700
170262
  });
168701
170263
  let rematerialized = false;
168702
170264
  let contentionExhausted = false;
@@ -168790,6 +170352,7 @@ var init_inject_compartments = __esm(async () => {
168790
170352
  init_compartment_storage();
168791
170353
  init_constants();
168792
170354
  init_storage_memory();
170355
+ init_workspaces();
168793
170356
  init_logger();
168794
170357
  init_decay_render();
168795
170358
  init_key_files_block();
@@ -168803,7 +170366,6 @@ var init_inject_compartments = __esm(async () => {
168803
170366
  CONSTRAINT_KEYWORDS = /\b(must|never|always|cannot|should not|must not)\b/i;
168804
170367
  EMPTY_HARD_SIGNALS = {
168805
170368
  systemHash: "",
168806
- toolSetHash: "",
168807
170369
  modelKey: "",
168808
170370
  cacheExpired: false,
168809
170371
  lastResponseTime: 0
@@ -171930,18 +173492,38 @@ async function runCompartmentAgent(deps) {
171930
173492
  return;
171931
173493
  }
171932
173494
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
171933
- const boundarySnapshot = deps.boundarySnapshot ?? null;
173495
+ let boundarySnapshot = deps.boundarySnapshot ?? null;
171934
173496
  if (!boundarySnapshot) {
171935
173497
  telemetry.failureReason = "missing protected-tail boundary snapshot";
171936
173498
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from trigger decision");
171937
173499
  rollbackDrainReservation();
171938
173500
  return;
171939
173501
  }
171940
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
173502
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
171941
173503
  db,
171942
173504
  snapshot: boundarySnapshot,
171943
173505
  currentContextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit
171944
173506
  }) : { ok: true };
173507
+ if (!validation.ok && validation.reason === "stale_snapshot") {
173508
+ const refreshed = resolveOpenCodeProtectedTailBoundary({
173509
+ db,
173510
+ sessionId,
173511
+ mode: "incremental-runner",
173512
+ contextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit,
173513
+ executeThresholdPercentage: boundarySnapshot.executeThresholdPercentage,
173514
+ usage: {
173515
+ percentage: boundarySnapshot.usagePercentage,
173516
+ inputTokens: boundarySnapshot.usageInputTokens
173517
+ },
173518
+ usageSource: boundarySnapshot.usageSource,
173519
+ emergencyTailScale: boundarySnapshot.emergencyTailScale
173520
+ });
173521
+ if (hasRunnableCompartmentWindow(refreshed)) {
173522
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
173523
+ boundarySnapshot = refreshed;
173524
+ validation = { ok: true };
173525
+ }
173526
+ }
171945
173527
  if (!validation.ok) {
171946
173528
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
171947
173529
  telemetry.status = "noop";
@@ -172010,6 +173592,7 @@ async function runCompartmentAgent(deps) {
172010
173592
  rollbackDrainReservation();
172011
173593
  return;
172012
173594
  }
173595
+ deps.onHistorianRunStarted?.();
172013
173596
  const projectPath = resolveProjectIdentity(directory ?? process.cwd());
172014
173597
  const memories = getMemoriesByProject(db, projectPath, ["active", "permanent"]);
172015
173598
  const projectMemory = renderMemoryBlock(memories) ?? "";
@@ -172142,8 +173725,13 @@ ${chunkText}`,
172142
173725
  }
172143
173726
  if (embeddingActive) {
172144
173727
  const projectIdentity = resolveProjectIdentity(promotionDirectory);
172145
- const toEmbed = persistedCompartments.map((c, i) => ({ id: persistedIds[i], p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172146
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173728
+ const chunksToEmbed = persistedCompartments.map((c, i) => ({
173729
+ id: persistedIds[i],
173730
+ startMessage: c.startMessage,
173731
+ endMessage: c.endMessage,
173732
+ sourceChunkText: chunk.text
173733
+ })).filter((c) => typeof c.id === "number");
173734
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172147
173735
  }
172148
173736
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
172149
173737
  deps.onCompartmentStatePublished?.(sessionId);
@@ -172374,8 +173962,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
172374
173962
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172375
173963
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172376
173964
  const liveCompartments = getCompartments(db, sessionId);
172377
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172378
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173965
+ const chunksToEmbed = liveCompartments.map((c) => ({
173966
+ id: c.id,
173967
+ startMessage: c.startMessage,
173968
+ endMessage: c.endMessage
173969
+ }));
173970
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172379
173971
  }
172380
173972
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
172381
173973
  if (lastCompartmentEnd2 > 0) {
@@ -172588,8 +174180,12 @@ Another process acquired the compartment-state lease before recomp could publish
172588
174180
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172589
174181
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172590
174182
  const liveCompartments = getCompartments(db, sessionId);
172591
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172592
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
174183
+ const chunksToEmbed = liveCompartments.map((c) => ({
174184
+ id: c.id,
174185
+ startMessage: c.startMessage,
174186
+ endMessage: c.endMessage
174187
+ }));
174188
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172593
174189
  }
172594
174190
  if (lastCompartmentEnd > 0) {
172595
174191
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -172770,8 +174366,12 @@ Could not acquire the compartment-state lease for this session.`;
172770
174366
  if (deps.memoryEnabled !== false) {
172771
174367
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172772
174368
  const liveCompartments = getCompartments(db, sessionId);
172773
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172774
- Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed));
174369
+ const chunksToEmbed = liveCompartments.map((c) => ({
174370
+ id: c.id,
174371
+ startMessage: c.startMessage,
174372
+ endMessage: c.endMessage
174373
+ }));
174374
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
172775
174375
  }
172776
174376
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
172777
174377
  if (lastEnd > 0) {
@@ -175021,7 +176621,14 @@ function startCompartmentAgent(deps) {
175021
176621
  return;
175022
176622
  }
175023
176623
  const renewal = startLeaseRenewal(deps, holderId);
175024
- const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
176624
+ let realRunStarted = false;
176625
+ const runnerDeps = withPublishedCallback({
176626
+ ...deps,
176627
+ compartmentLeaseHolderId: holderId,
176628
+ onHistorianRunStarted: () => {
176629
+ realRunStarted = true;
176630
+ }
176631
+ });
175025
176632
  const promise2 = runCompartmentAgent(runnerDeps).catch((err) => {
175026
176633
  sessionLog(deps.sessionId, "compartment agent: unhandled rejection:", err);
175027
176634
  try {
@@ -175035,6 +176642,9 @@ function startCompartmentAgent(deps) {
175035
176642
  }
175036
176643
  });
175037
176644
  activeRuns.set(deps.sessionId, { promise: promise2, published: false });
176645
+ if (!realRunStarted && activeRuns.get(deps.sessionId)?.promise === promise2) {
176646
+ activeRuns.delete(deps.sessionId);
176647
+ }
175038
176648
  }
175039
176649
  async function executeContextRecompWithResult(deps, options = {}) {
175040
176650
  const { sessionId } = deps;
@@ -175169,7 +176779,7 @@ function applyMemoryMigration(db, projectPath, result) {
175169
176779
  inserted++;
175170
176780
  }
175171
176781
  if (removed > 0 || inserted > 0) {
175172
- bumpProjectMemoryEpoch(db, projectPath);
176782
+ bumpEpochsForWorkspaceMembers(db, projectPath);
175173
176783
  }
175174
176784
  })();
175175
176785
  return { removed, inserted };
@@ -175627,15 +177237,15 @@ function shouldShowAnnouncement() {
175627
177237
  }
175628
177238
  return state.version !== ANNOUNCEMENT_VERSION;
175629
177239
  }
175630
- var ANNOUNCEMENT_VERSION = "0.23.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
177240
+ var ANNOUNCEMENT_VERSION = "0.24.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
175631
177241
  var init_announcement = __esm(() => {
175632
177242
  init_data_path();
175633
177243
  ANNOUNCEMENT_FEATURES = [
175634
- "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.",
175635
- "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.",
175636
- "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).",
175637
- "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).",
175638
- "Fixed: session titles no longer fail to generate in fresh directories (issue #129), plus 20+ correctness fixes from three audit rounds."
177244
+ "Searchable session history: ctx_search can now find older discussion by meaning, not just keywords. New history is embedded automatically to backfill an EXISTING session's older history, run /ctx-embed-history once (it works in the background).",
177245
+ "Cross-project workspaces: group related repos and share project memories across them, with per-category control over what's shared. Set them up in the dashboard's Workspaces panel.",
177246
+ "Pi: fixed sessions overflowing the model context while still showing moderate usagePi now sheds context before a tool-heavy turn overflows.",
177247
+ "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
177248
+ "Setup wizard now lists your actual models with type-ahead instead of fixed recommendations, and explains the historian/dreamer roles (issue #144). Plus a GitHub Copilot tool-pairing fix (#135)."
175639
177249
  ];
175640
177250
  });
175641
177251
 
@@ -176429,6 +178039,10 @@ function getMagicContextBuiltinCommands() {
176429
178039
  "ctx-dream": {
176430
178040
  template: "ctx-dream",
176431
178041
  description: "Run the hidden dreamer maintenance pass for this project now"
178042
+ },
178043
+ "ctx-embed-history": {
178044
+ template: "ctx-embed-history",
178045
+ description: "Embed all of this session's history compartments for semantic search, in one pass"
176432
178046
  }
176433
178047
  };
176434
178048
  }
@@ -176922,7 +178536,7 @@ await init_storage_db();
176922
178536
  // src/features/magic-context/v22-deferred-backfill.ts
176923
178537
  init_logger();
176924
178538
  init_project_identity();
176925
- import { createHash as createHash5 } from "node:crypto";
178539
+ import { createHash as createHash6 } from "node:crypto";
176926
178540
  import { realpathSync as realpathSync2 } from "node:fs";
176927
178541
  import path5 from "node:path";
176928
178542
  var BATCH_SIZE = 25;
@@ -176976,7 +178590,7 @@ function computeLegacyRustDirIdentity(rawProjectPath) {
176976
178590
  } catch {
176977
178591
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
176978
178592
  }
176979
- return `dir:${createHash5("sha256").update(canonical, "utf8").digest("hex")}`;
178593
+ return `dir:${createHash6("sha256").update(canonical, "utf8").digest("hex")}`;
176980
178594
  }
176981
178595
  function upsertRekeyMap(db, oldProjectPath, newProjectPath, rekeyedAt) {
176982
178596
  db.prepare(`INSERT INTO v22_identity_rekey_map (old_project_path, new_project_path, rekeyed_at)
@@ -180114,7 +181728,7 @@ init_project_identity();
180114
181728
  // src/plugin/embedding-bootstrap-helpers.ts
180115
181729
  init_embedding();
180116
181730
  init_logger();
180117
- import { createHash as createHash8 } from "node:crypto";
181731
+ import { createHash as createHash10 } from "node:crypto";
180118
181732
  var EMBEDDING_AFFECTING_KEYS = new Set([
180119
181733
  "embedding.api_key",
180120
181734
  "embedding.endpoint",
@@ -180135,7 +181749,7 @@ var EMBEDDING_WARNING_TERMS = [
180135
181749
  ];
180136
181750
  var loggedFailureSignatures = new Map;
180137
181751
  function sha256Prefix2(value, length = 16) {
180138
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
181752
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
180139
181753
  }
180140
181754
  function warningLooksEmbeddingRelated(message) {
180141
181755
  const lower = message.toLowerCase();
@@ -180772,7 +182386,7 @@ function createTagger() {
180772
182386
  // src/hooks/magic-context/hook.ts
180773
182387
  init_magic_context();
180774
182388
  init_project_identity();
180775
- init_tool_definition_tokens();
182389
+ init_project_embedding_registry();
180776
182390
  await init_storage();
180777
182391
  init_logger();
180778
182392
  init_resolve_fallbacks();
@@ -181366,6 +182980,7 @@ function createMagicContextCommandHandler(deps) {
181366
182980
  const isAugCommand = (command) => command === "ctx-aug";
181367
182981
  const isDreamCommand = (command) => command === "ctx-dream";
181368
182982
  const isSessionUpgradeCommand = (command) => command === "ctx-session-upgrade";
182983
+ const isEmbedHistoryCommand = (command) => command === "ctx-embed-history";
181369
182984
  return {
181370
182985
  "command.execute.before": async (input, _output, _params) => {
181371
182986
  const isStatus = isStatusCommand(input.command);
@@ -181374,7 +182989,8 @@ function createMagicContextCommandHandler(deps) {
181374
182989
  const isAug = isAugCommand(input.command);
181375
182990
  const isDream = isDreamCommand(input.command);
181376
182991
  const isSessionUpgrade = isSessionUpgradeCommand(input.command);
181377
- if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade) {
182992
+ const isEmbedHistory = isEmbedHistoryCommand(input.command);
182993
+ if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbedHistory) {
181378
182994
  return;
181379
182995
  }
181380
182996
  const sessionId = input.sessionID;
@@ -181387,6 +183003,11 @@ function createMagicContextCommandHandler(deps) {
181387
183003
  await executeDreaming(deps, sessionId);
181388
183004
  return;
181389
183005
  }
183006
+ if (isEmbedHistory) {
183007
+ const summary = deps.executeEmbedHistory ? await deps.executeEmbedHistory(sessionId) : "Semantic embedding is not configured for this project, so there is nothing to embed.";
183008
+ await deps.sendNotification(sessionId, summary, {});
183009
+ throwSentinel(input.command);
183010
+ }
181390
183011
  if (isFlush) {
181391
183012
  result = executeFlush(deps.db, sessionId);
181392
183013
  clearCachedM0M1(deps.db, sessionId);
@@ -182127,6 +183748,7 @@ await init_read_session_chunk();
182127
183748
  // src/hooks/magic-context/transform.ts
182128
183749
  init_project_identity();
182129
183750
  import * as crypto2 from "node:crypto";
183751
+ init_session_project_storage();
182130
183752
  init_storage_meta_persisted();
182131
183753
  init_logger();
182132
183754
  await init_storage();
@@ -182508,6 +184130,63 @@ function replayCavemanCompression(sessionId, db, targets, tags) {
182508
184130
 
182509
184131
  // src/hooks/magic-context/transform.ts
182510
184132
  await init_compartment_runner();
184133
+
184134
+ // src/hooks/magic-context/ctx-reduce-availability.ts
184135
+ init_logger();
184136
+ await init_read_session_db();
184137
+ var availabilityBySession = new BoundedSessionMap(500);
184138
+ function verdictFromToolsMap(tools) {
184139
+ if (tools === null || typeof tools !== "object" || Array.isArray(tools))
184140
+ return null;
184141
+ const map2 = tools;
184142
+ if (map2.ctx_reduce === true)
184143
+ return true;
184144
+ if (map2.ctx_reduce === false)
184145
+ return false;
184146
+ if (map2["*"] === false)
184147
+ return false;
184148
+ return null;
184149
+ }
184150
+ function resolveCtxReduceAvailabilityFromMessages(sessionId, messages) {
184151
+ const cached2 = availabilityBySession.get(sessionId);
184152
+ if (cached2 !== undefined)
184153
+ return cached2;
184154
+ for (const message of messages) {
184155
+ if (message.info?.role !== "user")
184156
+ continue;
184157
+ const verdict = verdictFromToolsMap(message.info.tools);
184158
+ if (verdict !== null) {
184159
+ availabilityBySession.set(sessionId, verdict);
184160
+ return verdict;
184161
+ }
184162
+ break;
184163
+ }
184164
+ availabilityBySession.set(sessionId, true);
184165
+ return true;
184166
+ }
184167
+ function resolveCtxReduceAvailability(sessionId) {
184168
+ const cached2 = availabilityBySession.get(sessionId);
184169
+ if (cached2 !== undefined)
184170
+ return cached2;
184171
+ if (!openCodeDbExists())
184172
+ return true;
184173
+ try {
184174
+ const row = withReadOnlySessionDb((db) => db.prepare(`SELECT json_extract(data, '$.tools') AS tools FROM message
184175
+ WHERE session_id = ? AND json_extract(data, '$.role') = 'user'
184176
+ ORDER BY time_created ASC LIMIT 1`).get(sessionId));
184177
+ if (!row)
184178
+ return true;
184179
+ const verdict = row.tools === null ? null : verdictFromToolsMap(JSON.parse(row.tools));
184180
+ const resolved = verdict ?? true;
184181
+ availabilityBySession.set(sessionId, resolved);
184182
+ return resolved;
184183
+ } catch (error51) {
184184
+ sessionLog(sessionId, "ctx_reduce availability read failed (fail-open):", error51);
184185
+ return true;
184186
+ }
184187
+ }
184188
+
184189
+ // src/hooks/magic-context/transform.ts
182511
184190
  init_derive_budgets();
182512
184191
 
182513
184192
  // src/hooks/magic-context/image-token-estimate.ts
@@ -182670,6 +184349,7 @@ await __promiseAll([
182670
184349
  init_read_session_chunk(),
182671
184350
  init_read_session_db()
182672
184351
  ]);
184352
+
182673
184353
  // src/hooks/magic-context/sentinel.ts
182674
184354
  var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
182675
184355
  function modelAcceptsEmptyContent(providerID) {
@@ -182727,7 +184407,6 @@ function replaySentinelByMessageIds(messages, ids, providerID) {
182727
184407
  missingIds.push(id);
182728
184408
  return { replayed, missingIds };
182729
184409
  }
182730
-
182731
184410
  // src/hooks/magic-context/strip-content.ts
182732
184411
  var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped §\d+§\]$/;
182733
184412
  var TAG_PREFIX_PATTERN = /^§\d+§\s*/;
@@ -183080,8 +184759,10 @@ function stripReasoningFromMergedAssistants(messages, providerID) {
183080
184759
  }
183081
184760
  return stripped;
183082
184761
  }
183083
- function stripProcessedImages(messages, watermark, messageTagNumbers) {
184762
+ function stripProcessedImages(messages, frozenIds, options) {
184763
+ const { detect, watermark, messageTagNumbers } = options;
183084
184764
  let stripped = 0;
184765
+ const newlyStrippedIds = [];
183085
184766
  let hasAssistantResponse = false;
183086
184767
  for (let i = messages.length - 1;i >= 0; i--) {
183087
184768
  const msg = messages[i];
@@ -183089,13 +184770,17 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
183089
184770
  hasAssistantResponse = true;
183090
184771
  continue;
183091
184772
  }
183092
- if (msg.info.role !== "user" || !hasAssistantResponse) {
184773
+ if (msg.info.role !== "user") {
183093
184774
  continue;
183094
184775
  }
184776
+ const id = typeof msg.info.id === "string" ? msg.info.id : undefined;
184777
+ const inFrozen = id !== undefined && frozenIds.has(id);
183095
184778
  const maxTag = messageTagNumbers.get(msg) ?? 0;
183096
- if (maxTag > watermark) {
184779
+ const isNewDetection = !inFrozen && detect && hasAssistantResponse && id !== undefined && maxTag <= watermark;
184780
+ if (!inFrozen && !isNewDetection) {
183097
184781
  continue;
183098
184782
  }
184783
+ let touchedThisMsg = false;
183099
184784
  for (let j = 0;j < msg.parts.length; j++) {
183100
184785
  const part = msg.parts[j];
183101
184786
  if (!isRecord(part) || part.type !== "file") {
@@ -183107,10 +184792,14 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
183107
184792
  if (typeof part.url === "string" && part.url.startsWith("data:") && part.url.length > 200) {
183108
184793
  msg.parts[j] = makeSentinel(part);
183109
184794
  stripped++;
184795
+ touchedThisMsg = true;
183110
184796
  }
183111
184797
  }
184798
+ if (touchedThisMsg && isNewDetection && id !== undefined) {
184799
+ newlyStrippedIds.push(id);
184800
+ }
183112
184801
  }
183113
- return stripped;
184802
+ return { stripped, newlyStrippedIds };
183114
184803
  }
183115
184804
 
183116
184805
  // src/hooks/magic-context/transform.ts
@@ -183426,6 +185115,7 @@ function appendReminderToUserMessage(message, reminder) {
183426
185115
  init_tag_part_guards();
183427
185116
  await init_storage();
183428
185117
  var USER_DROP_PREVIEW_CHARS = 250;
185118
+ var RECENT_TOOL_SKELETON_WINDOW = 20;
183429
185119
  function buildReplacementContent(tagId, target) {
183430
185120
  const role = target.message?.info.role;
183431
185121
  if (role !== "user") {
@@ -183451,6 +185141,7 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
183451
185141
  const tagTypeById = new Map(tags.map((tag) => [tag.tagNumber, tag.type]));
183452
185142
  const protectedTagIds = protectedTags > 0 ? new Set(tags.filter((tag) => tag.status === "active").map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, protectedTags)) : new Set;
183453
185143
  const pendingOps = preloadedPendingOps ?? getPendingOps(db, sessionId);
185144
+ const skeletonWindow = new Set(tags.filter((tag) => tag.type === "tool").map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, RECENT_TOOL_SKELETON_WINDOW));
183454
185145
  for (const pendingOp of pendingOps) {
183455
185146
  const tagStatus = tagStatusById.get(pendingOp.tagId);
183456
185147
  if (tagStatus === "compacted" || tagStatus === "dropped") {
@@ -183463,14 +185154,25 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
183463
185154
  const target = targets.get(pendingOp.tagId);
183464
185155
  const isToolTag = tagTypeById.get(pendingOp.tagId) === "tool";
183465
185156
  if (isToolTag) {
183466
- const dropResult = target?.drop?.() ?? "absent";
183467
- if (dropResult === "incomplete") {
183468
- continue;
183469
- }
183470
- if (dropResult === "removed") {
183471
- didMutateMessage = true;
185157
+ if (skeletonWindow.has(pendingOp.tagId)) {
185158
+ const truncResult = target?.truncate?.() ?? "absent";
185159
+ if (truncResult === "incomplete") {
185160
+ continue;
185161
+ }
185162
+ if (truncResult === "truncated") {
185163
+ didMutateMessage = true;
185164
+ }
185165
+ updateTagDropMode(db, sessionId, pendingOp.tagId, "truncated");
185166
+ } else {
185167
+ const dropResult = target?.drop?.() ?? "absent";
185168
+ if (dropResult === "incomplete") {
185169
+ continue;
185170
+ }
185171
+ if (dropResult === "removed") {
185172
+ didMutateMessage = true;
185173
+ }
185174
+ updateTagDropMode(db, sessionId, pendingOp.tagId, "full");
183472
185175
  }
183473
- updateTagDropMode(db, sessionId, pendingOp.tagId, "full");
183474
185176
  } else if (target) {
183475
185177
  const changed = target.setContent(buildReplacementContent(pendingOp.tagId, target));
183476
185178
  if (changed)
@@ -184040,6 +185742,7 @@ init_embedding();
184040
185742
 
184041
185743
  // src/features/magic-context/search.ts
184042
185744
  init_logger();
185745
+ init_compartment_chunk_embedding();
184043
185746
 
184044
185747
  // src/features/magic-context/literal-probes.ts
184045
185748
  var MAX_PROBES = 5;
@@ -184101,6 +185804,7 @@ function containsProbeVerbatim(text, probes) {
184101
185804
  init_memory();
184102
185805
  init_embedding();
184103
185806
  init_storage_memory_fts();
185807
+ init_workspaces();
184104
185808
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
184105
185809
  var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
184106
185810
  var SEMANTIC_WEIGHT = 0.7;
@@ -184130,6 +185834,37 @@ function previewText(text) {
184130
185834
  }
184131
185835
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
184132
185836
  }
185837
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
185838
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
185839
+ const isWorkspaced = resolved.identities.length > 1;
185840
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
185841
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
185842
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
185843
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
185844
+ return {
185845
+ identities: resolved.identities,
185846
+ expandedIdentities,
185847
+ ownIdentities,
185848
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
185849
+ namesByIdentity: resolved.namesByIdentity,
185850
+ canonicalIdentityByStoredPath,
185851
+ isWorkspaced
185852
+ };
185853
+ }
185854
+ function memoryWorkspaceIdentity(memory, workspace) {
185855
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
185856
+ }
185857
+ function sourceNamesForSearchMemories(args) {
185858
+ if (!args.workspace.isWorkspaced)
185859
+ return;
185860
+ const sourceNames = new Map;
185861
+ for (const memory of args.memories) {
185862
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
185863
+ if (source)
185864
+ sourceNames.set(memory.id, source);
185865
+ }
185866
+ return sourceNames.size > 0 ? sourceNames : undefined;
185867
+ }
184133
185868
  function getMessageSearchStatement(db) {
184134
185869
  let stmt = messageSearchStatements.get(db);
184135
185870
  if (!stmt) {
@@ -184138,6 +185873,30 @@ function getMessageSearchStatement(db) {
184138
185873
  }
184139
185874
  return stmt;
184140
185875
  }
185876
+ var ftsRowCountStatements = new WeakMap;
185877
+ var ftsMatchCountStatements = new WeakMap;
185878
+ function getSessionFtsRowCount(db, sessionId) {
185879
+ let stmt = ftsRowCountStatements.get(db);
185880
+ if (!stmt) {
185881
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
185882
+ ftsRowCountStatements.set(db, stmt);
185883
+ }
185884
+ const row = stmt.get(sessionId);
185885
+ return typeof row?.n === "number" ? row.n : 0;
185886
+ }
185887
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
185888
+ let stmt = ftsMatchCountStatements.get(db);
185889
+ if (!stmt) {
185890
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
185891
+ ftsMatchCountStatements.set(db, stmt);
185892
+ }
185893
+ try {
185894
+ const row = stmt.get(sessionId, ftsQuery);
185895
+ return typeof row?.n === "number" ? row.n : 0;
185896
+ } catch {
185897
+ return 0;
185898
+ }
185899
+ }
184141
185900
  function getMessageOrdinal(value) {
184142
185901
  if (typeof value === "number" && Number.isFinite(value)) {
184143
185902
  return value;
@@ -184153,25 +185912,63 @@ async function getSemanticScores(args) {
184153
185912
  if (!args.queryEmbedding || args.memories.length === 0) {
184154
185913
  return semanticScores;
184155
185914
  }
184156
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
184157
- const embeddings = await ensureMemoryEmbeddings({
184158
- db: args.db,
184159
- projectIdentity: args.projectPath,
184160
- memories: args.memories,
184161
- existingEmbeddings: cachedEmbeddings
184162
- });
185915
+ if (!args.workspace?.isWorkspaced) {
185916
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185917
+ const embeddings = await ensureMemoryEmbeddings({
185918
+ db: args.db,
185919
+ projectIdentity: args.projectPath,
185920
+ memories: args.memories,
185921
+ existingEmbeddings: cachedEmbeddings
185922
+ });
185923
+ for (const memory of args.memories) {
185924
+ const memoryEmbedding = embeddings.get(memory.id);
185925
+ if (!memoryEmbedding) {
185926
+ continue;
185927
+ }
185928
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
185929
+ }
185930
+ return semanticScores;
185931
+ }
185932
+ if (!args.queryModelId || args.queryModelId === "off") {
185933
+ return semanticScores;
185934
+ }
185935
+ const workspace = args.workspace;
185936
+ const memoriesByIdentity = new Map;
184163
185937
  for (const memory of args.memories) {
184164
- const memoryEmbedding = embeddings.get(memory.id);
184165
- if (!memoryEmbedding) {
185938
+ const identity = memoryWorkspaceIdentity(memory, workspace);
185939
+ if (!identity)
185940
+ continue;
185941
+ const list = memoriesByIdentity.get(identity) ?? [];
185942
+ list.push(memory);
185943
+ memoriesByIdentity.set(identity, list);
185944
+ }
185945
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
185946
+ if (ownMemories.length > 0) {
185947
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185948
+ await ensureMemoryEmbeddings({
185949
+ db: args.db,
185950
+ projectIdentity: args.projectPath,
185951
+ memories: ownMemories,
185952
+ existingEmbeddings: ownEmbeddings
185953
+ });
185954
+ }
185955
+ for (const identity of workspace.identities) {
185956
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
185957
+ if (memberMemories.length === 0)
184166
185958
  continue;
185959
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
185960
+ for (const memory of memberMemories) {
185961
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
185962
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
185963
+ continue;
185964
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
184167
185965
  }
184168
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
184169
185966
  }
184170
185967
  return semanticScores;
184171
185968
  }
184172
185969
  function getFtsMatches(args) {
184173
185970
  try {
184174
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
185971
+ return args.workspace?.isWorkspaced ? searchMemoriesFTSUnion(args.db, args.workspace.expandedIdentities, args.query, args.limit, args.workspace.ownIdentities, args.workspace.shareCategories) : searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
184175
185972
  } catch (error51) {
184176
185973
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
184177
185974
  return [];
@@ -184185,8 +185982,11 @@ function selectSemanticCandidates(args) {
184185
185982
  return args.memories;
184186
185983
  }
184187
185984
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
184188
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
184189
- if (cachedEmbeddings) {
185985
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
185986
+ for (const projectPath of embeddingProjects) {
185987
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
185988
+ if (!cachedEmbeddings)
185989
+ continue;
184190
185990
  for (const memoryId of cachedEmbeddings.keys()) {
184191
185991
  candidateIds.add(memoryId);
184192
185992
  }
@@ -184228,7 +186028,8 @@ function mergeMemoryResults(args) {
184228
186028
  score,
184229
186029
  memoryId: memory.id,
184230
186030
  category: memory.category,
184231
- matchType
186031
+ matchType,
186032
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
184232
186033
  });
184233
186034
  }
184234
186035
  return results.sort((left, right) => {
@@ -184242,7 +186043,7 @@ async function searchMemories(args) {
184242
186043
  if (!args.memoryEnabled) {
184243
186044
  return [];
184244
186045
  }
184245
- const memories = getMemoriesByProject(args.db, args.projectPath);
186046
+ const memories = args.workspace?.isWorkspaced ? getMemoriesByProjects(args.db, args.workspace.expandedIdentities, ["active", "permanent"], Date.now(), args.workspace.ownIdentities, args.workspace.shareCategories) : getMemoriesByProject(args.db, args.projectPath);
184246
186047
  if (memories.length === 0) {
184247
186048
  return [];
184248
186049
  }
@@ -184250,26 +186051,43 @@ async function searchMemories(args) {
184250
186051
  db: args.db,
184251
186052
  projectPath: args.projectPath,
184252
186053
  query: args.query,
184253
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
186054
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
186055
+ workspace: args.workspace
184254
186056
  });
184255
186057
  const ftsScores = getFtsScores(ftsMatches);
184256
186058
  const semanticCandidates = selectSemanticCandidates({
184257
186059
  memories,
184258
186060
  projectPath: args.projectPath,
184259
- ftsMatches
186061
+ ftsMatches,
186062
+ workspace: args.workspace
184260
186063
  });
184261
186064
  const semanticScores = await getSemanticScores({
184262
186065
  db: args.db,
184263
186066
  projectPath: args.projectPath,
184264
186067
  memories: semanticCandidates,
184265
- queryEmbedding: args.queryEmbedding
186068
+ queryEmbedding: args.queryEmbedding,
186069
+ queryModelId: args.queryModelId,
186070
+ workspace: args.workspace
184266
186071
  });
184267
186072
  return mergeMemoryResults({
184268
186073
  memories,
184269
186074
  semanticScores,
184270
186075
  ftsScores,
184271
186076
  limit: args.limit,
184272
- visibleMemoryIds: args.visibleMemoryIds
186077
+ visibleMemoryIds: args.visibleMemoryIds,
186078
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
186079
+ memories,
186080
+ projectPath: args.projectPath,
186081
+ workspace: args.workspace ?? {
186082
+ identities: [args.projectPath],
186083
+ expandedIdentities: [args.projectPath],
186084
+ namesByIdentity: new Map,
186085
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
186086
+ ownIdentities: [args.projectPath],
186087
+ shareCategories: null,
186088
+ isWorkspaced: false
186089
+ }
186090
+ })
184273
186091
  });
184274
186092
  }
184275
186093
  function linearDecayScore(rank, total) {
@@ -184300,7 +186118,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
184300
186118
  return result;
184301
186119
  }
184302
186120
  var RRF_K = 60;
184303
- var VERBATIM_PROBE_BONUS = 0.5;
186121
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
186122
+ var IDF_FALLOFF = 100;
186123
+ function probeDiscriminationWeight(df, corpusSize) {
186124
+ if (corpusSize <= 0 || df <= 0)
186125
+ return 1;
186126
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
186127
+ }
184304
186128
  function searchMessages(args) {
184305
186129
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
184306
186130
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -184317,20 +186141,31 @@ function searchMessages(args) {
184317
186141
  role: row.role
184318
186142
  }));
184319
186143
  }
186144
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
184320
186145
  const queryLists = [];
184321
186146
  if (baseQuery.length > 0) {
184322
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
186147
+ queryLists.push({
186148
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
186149
+ weight: 1
186150
+ });
184323
186151
  }
186152
+ const probeWeights = new Map;
184324
186153
  for (const probe of probes) {
184325
186154
  const probeQuery = sanitizeFtsQuery(probe);
184326
186155
  if (probeQuery.length === 0)
184327
186156
  continue;
184328
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
186157
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
186158
+ const weight = probeDiscriminationWeight(df, corpusSize);
186159
+ probeWeights.set(probe, weight);
186160
+ queryLists.push({
186161
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
186162
+ weight
186163
+ });
184329
186164
  }
184330
186165
  const fused = new Map;
184331
186166
  for (const list of queryLists) {
184332
- list.forEach((row, rank) => {
184333
- const rrf = 1 / (RRF_K + rank);
186167
+ list.rows.forEach((row, rank) => {
186168
+ const rrf = list.weight / (RRF_K + rank);
184334
186169
  const existing = fused.get(row.messageId);
184335
186170
  if (existing) {
184336
186171
  existing.score += rrf;
@@ -184340,26 +186175,107 @@ function searchMessages(args) {
184340
186175
  });
184341
186176
  }
184342
186177
  for (const entry of fused.values()) {
184343
- if (containsProbeVerbatim(entry.row.content, probes)) {
184344
- entry.score += VERBATIM_PROBE_BONUS;
186178
+ let best = 0;
186179
+ for (const probe of probes) {
186180
+ const weight = probeWeights.get(probe) ?? 0;
186181
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
186182
+ best = weight;
186183
+ }
186184
+ }
186185
+ if (best > 0) {
186186
+ entry.score += best * VERBATIM_RANK_BONUS;
184345
186187
  }
184346
186188
  }
184347
186189
  const ranked = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.row.messageOrdinal - b.row.messageOrdinal).slice(0, args.limit);
184348
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
184349
- return ranked.map((entry) => ({
186190
+ return ranked.map((entry, rank) => ({
184350
186191
  source: "message",
184351
186192
  content: previewText(entry.row.content),
184352
- score: maxScore > 0 ? entry.score / maxScore : 0,
186193
+ score: linearDecayScore(rank, ranked.length),
184353
186194
  messageOrdinal: entry.row.messageOrdinal,
184354
186195
  messageId: entry.row.messageId,
184355
186196
  role: entry.row.role
184356
186197
  }));
184357
186198
  }
186199
+ function searchCompartmentChunks(args) {
186200
+ if (!args.queryEmbedding || args.limit <= 0)
186201
+ return [];
186202
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
186203
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
186204
+ if (rows.length === 0)
186205
+ return [];
186206
+ const byCompartment = new Map;
186207
+ for (const row of rows) {
186208
+ if (cutoff !== null && row.endOrdinal > cutoff) {
186209
+ continue;
186210
+ }
186211
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
186212
+ if (score <= 0)
186213
+ continue;
186214
+ const existing = byCompartment.get(row.compartmentId);
186215
+ if (!existing || score > existing.score) {
186216
+ byCompartment.set(row.compartmentId, { row, score });
186217
+ }
186218
+ }
186219
+ return [...byCompartment.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.row.startOrdinal - right.row.startOrdinal).slice(0, args.limit).map(({ row, score }) => ({
186220
+ source: "compartment",
186221
+ content: previewText(row.title),
186222
+ score: score * SINGLE_SOURCE_PENALTY,
186223
+ compartmentId: row.compartmentId,
186224
+ sessionId: row.sessionId,
186225
+ title: row.title,
186226
+ startOrdinal: row.startOrdinal,
186227
+ endOrdinal: row.endOrdinal,
186228
+ matchType: "semantic"
186229
+ }));
186230
+ }
186231
+ function mergeMessageAndCompartmentResults(args) {
186232
+ if (args.compartments.length === 0)
186233
+ return args.messages;
186234
+ if (args.messages.length === 0)
186235
+ return args.compartments;
186236
+ const fused = new Map;
186237
+ const add = (key, result, score, tieOrdinal) => {
186238
+ const existing = fused.get(key);
186239
+ if (existing) {
186240
+ existing.score += score;
186241
+ return existing;
186242
+ }
186243
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
186244
+ fused.set(key, entry);
186245
+ return entry;
186246
+ };
186247
+ args.compartments.forEach((compartment, rank) => {
186248
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
186249
+ });
186250
+ for (const [rank, message] of args.messages.entries()) {
186251
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
186252
+ const contribution = 1 / (RRF_K + rank);
186253
+ if (!containing) {
186254
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
186255
+ continue;
186256
+ }
186257
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
186258
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
186259
+ entry.snippetScore = message.score;
186260
+ entry.result = {
186261
+ ...entry.result,
186262
+ matchType: "hybrid",
186263
+ snippet: message.content
186264
+ };
186265
+ }
186266
+ }
186267
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
186268
+ return ranked.map((entry, rank) => ({
186269
+ ...entry.result,
186270
+ score: linearDecayScore(rank, ranked.length)
186271
+ }));
186272
+ }
184358
186273
  function getSourceBoost(result) {
184359
186274
  switch (result.source) {
184360
186275
  case "memory":
184361
186276
  return MEMORY_SOURCE_BOOST;
184362
186277
  case "message":
186278
+ case "compartment":
184363
186279
  return MESSAGE_SOURCE_BOOST;
184364
186280
  case "git_commit":
184365
186281
  return GIT_COMMIT_SOURCE_BOOST;
@@ -184377,6 +186293,9 @@ function compareUnifiedResults(left, right) {
184377
186293
  if (left.source === "message" && right.source === "message") {
184378
186294
  return left.messageOrdinal - right.messageOrdinal;
184379
186295
  }
186296
+ if (left.source === "compartment" && right.source === "compartment") {
186297
+ return left.startOrdinal - right.startOrdinal;
186298
+ }
184380
186299
  if (left.source === "git_commit" && right.source === "git_commit") {
184381
186300
  return right.committedAtMs - left.committedAtMs;
184382
186301
  }
@@ -184427,10 +186346,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184427
186346
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
184428
186347
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
184429
186348
  const activeSources = resolveSources(options.sources);
184430
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
186349
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
186350
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
184431
186351
  const runMessages = activeSources.has("message");
184432
186352
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
184433
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
186353
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
186354
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
184434
186355
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
184435
186356
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
184436
186357
  return null;
@@ -184446,6 +186367,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184446
186367
  probes: messageProbes
184447
186368
  }) : [];
184448
186369
  const queryEmbedding = await queryEmbeddingPromise;
186370
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
186371
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
186372
+ const embeddingModelId = embeddingSnapshot?.modelId;
186373
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
186374
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
186375
+ db,
186376
+ sessionId,
186377
+ projectPath,
186378
+ queryEmbedding,
186379
+ limit: tierLimit,
186380
+ maxOrdinal: options.maxMessageOrdinal,
186381
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
186382
+ }) : [];
186383
+ const messageLikeResults = mergeMessageAndCompartmentResults({
186384
+ messages: messageResults,
186385
+ compartments: compartmentResults,
186386
+ limit: tierLimit
186387
+ });
184449
186388
  const [memoryResults, gitCommitResults] = await Promise.all([
184450
186389
  runMemory ? searchMemories({
184451
186390
  db,
@@ -184454,6 +186393,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184454
186393
  limit: tierLimit,
184455
186394
  memoryEnabled: true,
184456
186395
  queryEmbedding,
186396
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
186397
+ workspace,
184457
186398
  visibleMemoryIds: options.visibleMemoryIds
184458
186399
  }) : Promise.resolve([]),
184459
186400
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -184464,7 +186405,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
184464
186405
  queryEmbedding
184465
186406
  })) : Promise.resolve([])
184466
186407
  ]);
184467
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
186408
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
184468
186409
  const countRetrievals = options.countRetrievals ?? true;
184469
186410
  if (countRetrievals) {
184470
186411
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -184528,6 +186469,11 @@ function renderFragment(result, charCap) {
184528
186469
  const compressed = cavemanCompress(result.content, "ultra");
184529
186470
  return truncate(compressed, charCap);
184530
186471
  }
186472
+ case "compartment": {
186473
+ const source = result.snippet ?? result.title;
186474
+ const compressed = cavemanCompress(source, "ultra");
186475
+ return truncate(compressed, charCap);
186476
+ }
184531
186477
  }
184532
186478
  }
184533
186479
  function buildAutoSearchHint(results, options = {}) {
@@ -185168,7 +187114,7 @@ function isVisibleNoteReadPart(part) {
185168
187114
  }
185169
187115
 
185170
187116
  // src/hooks/magic-context/todo-view.ts
185171
- import { createHash as createHash9 } from "node:crypto";
187117
+ import { createHash as createHash11 } from "node:crypto";
185172
187118
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
185173
187119
  var TITLE_DONE_STATUSES = new Set(["completed"]);
185174
187120
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -185213,7 +187159,7 @@ function buildSyntheticTodoPart(stateJson) {
185213
187159
  };
185214
187160
  }
185215
187161
  function computeSyntheticCallId(stateJson) {
185216
- const hash2 = createHash9("sha256").update(stateJson).digest("hex").slice(0, 16);
187162
+ const hash2 = createHash11("sha256").update(stateJson).digest("hex").slice(0, 16);
185217
187163
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
185218
187164
  }
185219
187165
  function parseTodoState(stateJson) {
@@ -185264,15 +187210,25 @@ async function runPostTransformPhase(args) {
185264
187210
  const emergencyBypassCompartmentGate = forceMaterialization;
185265
187211
  const deferredMaterialize = args.canConsumeDeferredLate && deferredMaterializationWasPending;
185266
187212
  const materializationRequested = isExplicitFlush || deferredMaterialize;
185267
- const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
187213
+ const m0M1EnabledForFold = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
187214
+ const m0HardFoldThisPass = m0M1EnabledForFold && args.m0M1 ? mustMaterialize({
187215
+ db: args.db,
187216
+ sessionId: args.sessionId,
187217
+ state: args.sessionMeta,
187218
+ projectPath: args.m0M1.projectPath,
187219
+ projectDirectory: args.m0M1.projectDirectory,
187220
+ hardSignals: args.m0M1.hardSignals
187221
+ }).value : false;
187222
+ const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || m0HardFoldThisPass || compartmentRunning;
185268
187223
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
185269
187224
  const hasPendingUserOps = pendingOps.length > 0;
185270
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
185271
- const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
187225
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || m0HardFoldThisPass) && (!compartmentRunning || emergencyBypassCompartmentGate);
187226
+ const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || m0HardFoldThisPass || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
185272
187227
  const isCacheBustingPass = shouldApplyPendingOps || shouldRunHeuristics;
187228
+ const canUseEmptySentinels = modelAcceptsEmptyContent(args.resolvedProviderID);
185273
187229
  if (shouldRunHeuristics) {
185274
187230
  const subagentRerun = !args.fullFeatureMode && alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && !forceMaterialization;
185275
- const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
187231
+ const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : m0HardFoldThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into known m[0] bust, scheduler=${args.schedulerDecision})` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
185276
187232
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
185277
187233
  }
185278
187234
  if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !materializationRequested && args.fullFeatureMode) {
@@ -185315,7 +187271,9 @@ async function runPostTransformPhase(args) {
185315
187271
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags}`);
185316
187272
  const t7 = performance.now();
185317
187273
  const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
185318
- stripClearedReasoning(args.messages);
187274
+ if (canUseEmptySentinels) {
187275
+ stripClearedReasoning(args.messages);
187276
+ }
185319
187277
  const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
185320
187278
  if (clearedReasoning > 0 || strippedInline > 0) {
185321
187279
  let maxTag = 0;
@@ -185361,7 +187319,6 @@ async function runPostTransformPhase(args) {
185361
187319
  if (args.watermark > 0) {
185362
187320
  const tWatermarkCleanup = performance.now();
185363
187321
  truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
185364
- stripProcessedImages(args.messages, args.watermark, args.messageTagNumbers);
185365
187322
  logTransformTiming(args.sessionId, "watermarkCleanup", tWatermarkCleanup);
185366
187323
  }
185367
187324
  if (shouldApplyPendingOps) {
@@ -185371,21 +187328,40 @@ async function runPostTransformPhase(args) {
185371
187328
  sessionLog(args.sessionId, "transform failed applying pending operations:", error51);
185372
187329
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: getErrorMessage(error51) });
185373
187330
  }
185374
- try {
185375
- const t8 = performance.now();
185376
- const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
185377
- const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
185378
- detect: isCacheBustingPass,
185379
- protectedCount: args.protectedTags
185380
- });
185381
- if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
185382
- addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
187331
+ if (canUseEmptySentinels) {
187332
+ try {
187333
+ const t8 = performance.now();
187334
+ const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
187335
+ const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
187336
+ detect: isCacheBustingPass,
187337
+ protectedCount: args.protectedTags
187338
+ });
187339
+ if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
187340
+ addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
187341
+ }
187342
+ logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
187343
+ } catch (error51) {
187344
+ sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
185383
187345
  }
185384
- logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
185385
- } catch (error51) {
185386
- sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
185387
187346
  }
185388
- const m0M1Enabled = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
187347
+ if (canUseEmptySentinels && args.watermark > 0) {
187348
+ try {
187349
+ const tImg = performance.now();
187350
+ const frozenImageIds = getProcessedImageStrippedIds(args.db, args.sessionId);
187351
+ const imageResult = stripProcessedImages(args.messages, frozenImageIds, {
187352
+ detect: isCacheBustingPass,
187353
+ watermark: args.watermark,
187354
+ messageTagNumbers: args.messageTagNumbers
187355
+ });
187356
+ if (isCacheBustingPass && imageResult.newlyStrippedIds.length > 0) {
187357
+ addProcessedImageStrippedIds(args.db, args.sessionId, imageResult.newlyStrippedIds);
187358
+ }
187359
+ logTransformTiming(args.sessionId, "stripProcessedImages", tImg);
187360
+ } catch (error51) {
187361
+ sessionLog(args.sessionId, "transform failed stripping processed images:", error51);
187362
+ }
187363
+ }
187364
+ const m0M1Enabled = m0M1EnabledForFold;
185389
187365
  if (m0M1Enabled && args.m0M1) {
185390
187366
  const tInjectM0M1 = performance.now();
185391
187367
  try {
@@ -185432,7 +187408,7 @@ async function runPostTransformPhase(args) {
185432
187408
  const tPlaceholder = performance.now();
185433
187409
  const persistedIds = getStrippedPlaceholderIds(args.db, args.sessionId);
185434
187410
  if (persistedIds.size > 0) {
185435
- const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.liveProviderID);
187411
+ const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.resolvedProviderID);
185436
187412
  if (replayed > 0) {
185437
187413
  sessionLog(args.sessionId, `sentinel replay: neutralized ${replayed} previously-stripped messages`);
185438
187414
  }
@@ -185443,9 +187419,9 @@ async function runPostTransformPhase(args) {
185443
187419
  }
185444
187420
  }
185445
187421
  if (isCacheBustingPass) {
185446
- const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.liveProviderID);
187422
+ const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.resolvedProviderID);
185447
187423
  const protectedTailStart = Math.max(0, args.messages.length - args.protectedTags * 2);
185448
- const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.liveProviderID);
187424
+ const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.resolvedProviderID);
185449
187425
  const newlyNeutralized = droppedResult.sentineledIds.length + systemInjectedResult.sentineledIds.length;
185450
187426
  if (newlyNeutralized > 0) {
185451
187427
  const addedIds = [
@@ -185645,6 +187621,7 @@ function clearMessageTokensCache(sessionId, messageId) {
185645
187621
  if (cache)
185646
187622
  cache.delete(messageId);
185647
187623
  }
187624
+ var recordedSessionProjectIdentity = new BoundedSessionMap(MESSAGE_TOKENS_CACHE_MAX);
185648
187625
  function findLastAssistantModel2(messages) {
185649
187626
  for (let i = messages.length - 1;i >= 0; i--) {
185650
187627
  const info = messages[i].info;
@@ -185690,11 +187667,13 @@ function createTransform(deps) {
185690
187667
  }
185691
187668
  const reducedMode = sessionMeta.isSubagent;
185692
187669
  const fullFeatureMode = !reducedMode;
185693
- const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false;
187670
+ const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false && resolveCtxReduceAvailabilityFromMessages(sessionId, messages);
185694
187671
  let sessionDirectory = deps.directory ?? "";
187672
+ let sessionDirectoryResolvedFromHost = false;
185695
187673
  const cachedDirectory = deps.sessionDirectoryBySession?.get(sessionId);
185696
187674
  if (cachedDirectory && cachedDirectory.length > 0) {
185697
187675
  sessionDirectory = cachedDirectory;
187676
+ sessionDirectoryResolvedFromHost = true;
185698
187677
  } else if (deps.client !== undefined) {
185699
187678
  try {
185700
187679
  const sessionResponse = await deps.client.session.get({ path: { id: sessionId } }).catch(() => null);
@@ -185702,6 +187681,7 @@ function createTransform(deps) {
185702
187681
  if (sessionInfo && typeof sessionInfo.directory === "string" && sessionInfo.directory.length > 0) {
185703
187682
  sessionDirectory = sessionInfo.directory;
185704
187683
  deps.sessionDirectoryBySession?.set(sessionId, sessionDirectory);
187684
+ sessionDirectoryResolvedFromHost = true;
185705
187685
  }
185706
187686
  } catch {}
185707
187687
  }
@@ -185796,6 +187776,8 @@ function createTransform(deps) {
185796
187776
  deps.liveModelBySession?.set(sessionId, recovered);
185797
187777
  }
185798
187778
  }
187779
+ const resolvedProviderID = modelForBudget?.providerID;
187780
+ const canUseEmptySentinels = modelAcceptsEmptyContent(resolvedProviderID);
185799
187781
  const resolvedContextLimit = modelForBudget ? resolveTrustedContextLimit(modelForBudget.providerID, modelForBudget.modelID, {
185800
187782
  db,
185801
187783
  sessionID: sessionId
@@ -185960,6 +187942,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185960
187942
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
185961
187943
  const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(compartmentDirectory || process.cwd()) : undefined;
185962
187944
  const sessionProjectIdentity = projectIdentity ?? (sessionDirectory ? resolveProjectIdentity(sessionDirectory) : deps.projectPath);
187945
+ if (sessionProjectIdentity && sessionDirectoryResolvedFromHost && recordedSessionProjectIdentity.get(sessionId) !== sessionProjectIdentity) {
187946
+ recordSessionProjectIdentity(db, sessionId, sessionProjectIdentity);
187947
+ recordedSessionProjectIdentity.set(sessionId, sessionProjectIdentity);
187948
+ }
185963
187949
  let triggerBoundarySnapshot;
185964
187950
  if (fullFeatureMode && historianRunnable && !sessionMeta.compartmentInProgress) {
185965
187951
  const tTrigger = performance.now();
@@ -186054,7 +188040,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186054
188040
  }
186055
188041
  }
186056
188042
  const t3 = performance.now();
186057
- const strippedStructuralNoise = stripStructuralNoise(messages);
188043
+ const strippedStructuralNoise = canUseEmptySentinels ? stripStructuralNoise(messages) : 0;
186058
188044
  logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
186059
188045
  const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
186060
188046
  if (persistedReasoningWatermark > 0) {
@@ -186075,18 +188061,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186075
188061
  logTransformTiming(sessionId, "replayCavemanCompression", tCavemanReplay);
186076
188062
  }
186077
188063
  const t4 = performance.now();
186078
- const strippedClearedReasoning = stripClearedReasoning(messages);
188064
+ const strippedClearedReasoning = canUseEmptySentinels ? stripClearedReasoning(messages) : 0;
186079
188065
  logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
186080
188066
  const tMergeStrip = performance.now();
186081
- let liveProviderID = deps.liveModelBySession?.get(sessionId)?.providerID;
186082
- if (liveProviderID === undefined) {
186083
- const recovered = findLastAssistantModelFromOpenCodeDb(sessionId);
186084
- if (recovered) {
186085
- liveProviderID = recovered.providerID;
186086
- deps.liveModelBySession?.set(sessionId, recovered);
186087
- }
186088
- }
186089
- const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, liveProviderID);
188067
+ const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, resolvedProviderID);
186090
188068
  if (strippedMergedReasoning > 0) {
186091
188069
  sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
186092
188070
  }
@@ -186143,7 +188121,6 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186143
188121
  logTransformTiming(sessionId, "compartmentPhase", tCompartmentPhase);
186144
188122
  const hardModel = deps.liveModelBySession?.get(sessionId);
186145
188123
  const hardModelKey = hardModel ? `${hardModel.providerID}/${hardModel.modelID}` : "";
186146
- const hardToolSetHash = deps.getToolSetHash?.(sessionId) ?? "";
186147
188124
  const hardSystemHash = typeof sessionMeta.systemPromptHash === "string" ? sessionMeta.systemPromptHash : "";
186148
188125
  let hardTtlMs = 5 * 60 * 1000;
186149
188126
  try {
@@ -186152,7 +188129,6 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186152
188129
  const hardCacheExpired = sessionMeta.lastResponseTime > 0 && Date.now() - sessionMeta.lastResponseTime >= hardTtlMs;
186153
188130
  const m0HardSignals = {
186154
188131
  systemHash: hardSystemHash,
186155
- toolSetHash: hardToolSetHash,
186156
188132
  modelKey: hardModelKey,
186157
188133
  cacheExpired: hardCacheExpired,
186158
188134
  lastResponseTime: sessionMeta.lastResponseTime
@@ -186207,7 +188183,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
186207
188183
  sessionDirectory,
186208
188184
  autoSearch: deps.autoSearch,
186209
188185
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
186210
- liveProviderID,
188186
+ resolvedProviderID,
186211
188187
  historyRefreshSessions: deps.historyRefreshSessions,
186212
188188
  m0M1: {
186213
188189
  projectPath: projectIdentity,
@@ -187036,7 +189012,7 @@ function createToolExecuteAfterHook(args) {
187036
189012
  init_send_session_notification();
187037
189013
 
187038
189014
  // src/hooks/magic-context/system-prompt-hash.ts
187039
- import { createHash as createHash10 } from "node:crypto";
189015
+ import { createHash as createHash12 } from "node:crypto";
187040
189016
 
187041
189017
  // src/agents/magic-context-prompt.ts
187042
189018
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -187152,9 +189128,9 @@ Prefer many small targeted operations over one large blanket operation, and keep
187152
189128
 
187153
189129
  // src/hooks/magic-context/system-prompt-hash.ts
187154
189130
  init_logger();
189131
+ await init_storage();
187155
189132
  init_key_files_block();
187156
189133
  init_read_session_formatting();
187157
- await init_storage();
187158
189134
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
187159
189135
  function clearSystemPromptHashSession(sessionId, handleMaps) {
187160
189136
  clearKeyFilesCacheForSession(sessionId);
@@ -187200,9 +189176,10 @@ function createSystemPromptHashHandler(deps) {
187200
189176
  sessionLog(sessionId, "system-prompt-hash session meta load failed:", error51);
187201
189177
  }
187202
189178
  const isSubagentSession = sessionMetaEarly?.isSubagent === true;
187203
- const subagentReduceMode = isSubagentSession && deps.ctxReduceEnabled !== false;
187204
- const effectiveCtxReduceEnabled = isSubagentSession ? false : deps.ctxReduceEnabled;
187205
- const skipGuidanceForDisabledSubagent = isSubagentSession && deps.ctxReduceEnabled === false;
189179
+ const ctxReduceCallable = resolveCtxReduceAvailability(sessionId);
189180
+ const subagentReduceMode = isSubagentSession && deps.ctxReduceEnabled !== false && ctxReduceCallable;
189181
+ const effectiveCtxReduceEnabled = isSubagentSession ? false : deps.ctxReduceEnabled !== false && ctxReduceCallable;
189182
+ const skipGuidanceForDisabledSubagent = isSubagentSession && (deps.ctxReduceEnabled === false || !ctxReduceCallable);
187206
189183
  const fullPrompt = output.system.join(`
187207
189184
  `);
187208
189185
  if (fullPrompt.length > 0 && !fullPrompt.includes(MAGIC_CONTEXT_MARKER) && !skipGuidanceForDisabledSubagent) {
@@ -187235,7 +189212,7 @@ function createSystemPromptHashHandler(deps) {
187235
189212
  `);
187236
189213
  if (systemContent.length === 0)
187237
189214
  return;
187238
- const currentHash = createHash10("md5").update(systemContent).digest("hex");
189215
+ const currentHash = createHash12("md5").update(systemContent).digest("hex");
187239
189216
  if (!sessionMetaEarly) {
187240
189217
  return;
187241
189218
  }
@@ -187496,6 +189473,46 @@ function createMagicContextHook(deps) {
187496
189473
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
187497
189474
  getNotificationParams: (sid) => getLiveNotificationParams(sid, liveModelBySession, variantBySession, agentBySession)
187498
189475
  });
189476
+ const executeEmbedHistory = async (sessionId) => {
189477
+ if (deps.config.memory?.enabled === false) {
189478
+ return "Memory is disabled for this project, so there is no semantic embedding to backfill.";
189479
+ }
189480
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
189481
+ await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
189482
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
189483
+ setRecompStarting({ recompProgressBySession }, sessionId, "Embedding history…", "embed");
189484
+ const outcome = await embedSessionCompartmentChunks(db, sessionProjectIdentity, sessionId, {
189485
+ onProgress: ({ embedded, total }) => {
189486
+ const cur = recompProgressBySession.get(sessionId);
189487
+ if (!cur || cur.phase !== "recomp")
189488
+ return;
189489
+ recompProgressBySession.set(sessionId, {
189490
+ ...cur,
189491
+ processedMessages: embedded,
189492
+ totalMessages: total,
189493
+ updatedAt: Date.now()
189494
+ });
189495
+ }
189496
+ });
189497
+ const terminal = (phase, message) => {
189498
+ setRecompTerminal({ recompProgressBySession }, sessionId, phase, message);
189499
+ return message;
189500
+ };
189501
+ switch (outcome.status) {
189502
+ case "nothing":
189503
+ return terminal("done", "All of this session's history is already embedded.");
189504
+ case "disabled":
189505
+ return terminal("skipped", "No embedding provider is configured, so there is nothing to embed.");
189506
+ case "busy":
189507
+ return terminal("skipped", `Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`);
189508
+ case "aborted":
189509
+ return terminal("done", `Embedded ${outcome.embedded} of ${outcome.total} compartments before stopping.`);
189510
+ case "stalled":
189511
+ return terminal("skipped", `Embedded ${outcome.embedded} compartments; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed-history again to retry them.`);
189512
+ default:
189513
+ return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`);
189514
+ }
189515
+ };
187499
189516
  const sidekickRunnable = isSidekickRunnable(deps.config);
187500
189517
  const sidekickConfig = sidekickRunnable ? deps.config.sidekick : undefined;
187501
189518
  const transform2 = createTransform({
@@ -187534,12 +189551,6 @@ function createMagicContextHook(deps) {
187534
189551
  const model = liveModelBySession.get(sessionId);
187535
189552
  return resolveModelKey(model?.providerID, model?.modelID);
187536
189553
  },
187537
- getToolSetHash: (sessionId) => {
187538
- const model = liveModelBySession.get(sessionId);
187539
- if (!model)
187540
- return "";
187541
- return getCurrentToolSetHash(model.providerID, model.modelID, agentBySession.get(sessionId));
187542
- },
187543
189554
  getFallbackModelId: (sessionId) => {
187544
189555
  const model = liveModelBySession.get(sessionId);
187545
189556
  return model ? `${model.providerID}/${model.modelID}` : undefined;
@@ -187656,6 +189667,7 @@ function createMagicContextHook(deps) {
187656
189667
  },
187657
189668
  executeRecomp: historianRunnable ? async (sessionId, options) => runManagedRecomp(buildManagedRecompCtx(sessionId), sessionId, options) : undefined,
187658
189669
  runUpgrade: historianRunnable ? async (sessionId) => runManagedUpgrade(buildManagedRecompCtx(sessionId), sessionId) : undefined,
189670
+ executeEmbedHistory,
187659
189671
  sendNotification: async (sessionId, text, params) => {
187660
189672
  await sendIgnoredMessage(deps.client, sessionId, text, {
187661
189673
  ...getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
@@ -188793,6 +190805,7 @@ init_memory();
188793
190805
  init_embedding();
188794
190806
  init_embedding_cache();
188795
190807
  init_normalize_hash();
190808
+ init_workspaces();
188796
190809
  init_logger();
188797
190810
  await init_storage();
188798
190811
  import { tool as tool2 } from "@opencode-ai/plugin";
@@ -188952,6 +190965,23 @@ function createCtxMemoryTool(deps) {
188952
190965
  }
188953
190966
  const projectPath = deps.resolveProjectPath(toolContext.directory);
188954
190967
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
190968
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectPath);
190969
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
190970
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
190971
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? projectIdentityForStoredPath(rawProjectPath) : projectIdentityForStoredPath(rawProjectPath);
190972
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectPath) : null;
190973
+ const memoryVisibleToTool = (memory) => {
190974
+ if (workspaceIdentitySet.identities.length <= 1) {
190975
+ return memoryBelongsToProject(memory, projectPath);
190976
+ }
190977
+ if (!storedPathBelongsToWorkspace(memory.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
190978
+ return false;
190979
+ }
190980
+ const isOwn = targetIdentityForStoredPath(memory.projectPath) === projectPath;
190981
+ if (isOwn)
190982
+ return true;
190983
+ return toolShareCategories === null || toolShareCategories.includes(memory.category);
190984
+ };
188955
190985
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
188956
190986
  if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188957
190987
  return getDisabledMessage();
@@ -189008,18 +191038,18 @@ function createCtxMemoryTool(deps) {
189008
191038
  }
189009
191039
  const rawProjectPath = projectPathForMemoryId(deps.db, updateId);
189010
191040
  const memory = getMemoryById(deps.db, updateId);
189011
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
191041
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
189012
191042
  return `Error: Memory with ID ${updateId} was not found.`;
189013
191043
  }
189014
191044
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
189015
191045
  return inactiveMemoryError(updateId, "updating");
189016
191046
  }
189017
191047
  const normalizedHash = computeNormalizedHash(content);
189018
- const duplicate = getMemoryByHash(deps.db, projectPath, memory.category, normalizedHash);
191048
+ const duplicate = getMemoryByHash(deps.db, targetIdentityForStoredPath(rawProjectPath), memory.category, normalizedHash);
189019
191049
  if (duplicate && duplicate.id !== memory.id) {
189020
191050
  return `Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`;
189021
191051
  }
189022
- const projectIdentity = projectIdentityForStoredPath(rawProjectPath);
191052
+ const projectIdentity = targetIdentityForStoredPath(rawProjectPath);
189023
191053
  deps.db.transaction(() => {
189024
191054
  updateMemoryContentInCurrentTransaction(deps.db, memory, content, normalizedHash);
189025
191055
  queueMemoryMutation(deps.db, {
@@ -189033,7 +191063,7 @@ function createCtxMemoryTool(deps) {
189033
191063
  queueMemoryEmbedding({
189034
191064
  deps,
189035
191065
  sessionId: toolContext.sessionID,
189036
- projectPath,
191066
+ projectPath: projectIdentity,
189037
191067
  memoryId: memory.id,
189038
191068
  content
189039
191069
  });
@@ -189056,7 +191086,7 @@ function createCtxMemoryTool(deps) {
189056
191086
  return "Error: One or more source memories were not found.";
189057
191087
  }
189058
191088
  if (toolContext.agent !== DREAMER_AGENT) {
189059
- const foreign = sourceMemories.find((memory) => !memoryBelongsToProject(memory, projectPath));
191089
+ const foreign = sourceMemories.find((memory) => !memoryVisibleToTool(memory));
189060
191090
  if (foreign) {
189061
191091
  return `Error: Memory with ID ${foreign.id} was not found.`;
189062
191092
  }
@@ -189140,15 +191170,16 @@ function createCtxMemoryTool(deps) {
189140
191170
  return `Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`;
189141
191171
  }
189142
191172
  if (args.action === "archive") {
189143
- const archiveIds = args.ids;
189144
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
191173
+ const rawArchiveIds = args.ids;
191174
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
189145
191175
  return "Error: 'ids' must contain at least one integer memory ID when action is 'archive'.";
189146
191176
  }
191177
+ const archiveIds = [...new Set(rawArchiveIds)];
189147
191178
  const targets = [];
189148
191179
  for (const memoryId of archiveIds) {
189149
191180
  const rawProjectPath = projectPathForMemoryId(deps.db, memoryId);
189150
191181
  const memory = getMemoryById(deps.db, memoryId);
189151
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
191182
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
189152
191183
  return `Error: Memory with ID ${memoryId} was not found.`;
189153
191184
  }
189154
191185
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -189156,7 +191187,7 @@ function createCtxMemoryTool(deps) {
189156
191187
  }
189157
191188
  targets.push({
189158
191189
  memoryId,
189159
- projectIdentity: projectIdentityForStoredPath(rawProjectPath)
191190
+ projectIdentity: targetIdentityForStoredPath(rawProjectPath)
189160
191191
  });
189161
191192
  }
189162
191193
  deps.db.transaction(() => {
@@ -189630,8 +191661,9 @@ function formatAge2(committedAtMs) {
189630
191661
  }
189631
191662
  function formatResult(result, index) {
189632
191663
  if (result.source === "memory") {
191664
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
189633
191665
  return [
189634
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
191666
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
189635
191667
  result.content
189636
191668
  ].join(`
189637
191669
  `);
@@ -189641,6 +191673,13 @@ function formatResult(result, index) {
189641
191673
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
189642
191674
  result.content
189643
191675
  ].join(`
191676
+ `);
191677
+ }
191678
+ if (result.source === "compartment") {
191679
+ return [
191680
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
191681
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
191682
+ ].join(`
189644
191683
  `);
189645
191684
  }
189646
191685
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -189656,7 +191695,7 @@ function formatSearchResults(query, results) {
189656
191695
  return `No results found for "${query}" across memories, git commits, or message history.`;
189657
191696
  }
189658
191697
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
189659
- if (results.some((result) => result.source === "message")) {
191698
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
189660
191699
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
189661
191700
  }
189662
191701
  const body = bodyParts.join(`
@@ -189814,11 +191853,11 @@ import { createServer } from "node:http";
189814
191853
  import { dirname as dirname8 } from "node:path";
189815
191854
 
189816
191855
  // src/shared/rpc-utils.ts
189817
- import { createHash as createHash11 } from "node:crypto";
191856
+ import { createHash as createHash13 } from "node:crypto";
189818
191857
  import { join as join24 } from "node:path";
189819
191858
  function projectHash(directory) {
189820
191859
  const normalized = directory.replace(/\/+$/, "");
189821
- return createHash11("sha256").update(normalized).digest("hex").slice(0, 16);
191860
+ return createHash13("sha256").update(normalized).digest("hex").slice(0, 16);
189822
191861
  }
189823
191862
  function rpcPortDir(storageDir, directory) {
189824
191863
  return join24(storageDir, "rpc", projectHash(directory));