@wolfx/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 (96) hide show
  1. package/dist/config/schema/magic-context.d.ts +10 -3
  2. package/dist/config/schema/magic-context.d.ts.map +1 -1
  3. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  4. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +70 -0
  5. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -0
  6. package/dist/features/magic-context/compartment-embedding.d.ts +22 -26
  7. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  8. package/dist/features/magic-context/memory/embedding-backfill.d.ts +3 -2
  9. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  10. package/dist/features/magic-context/memory/embedding-cache.d.ts +3 -2
  11. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -1
  12. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -1
  13. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding-openai.d.ts +3 -0
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  16. package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -0
  17. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  18. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  19. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +5 -1
  21. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  22. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -7
  23. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  24. package/dist/features/magic-context/memory/storage-memory.d.ts +18 -12
  25. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  26. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  27. package/dist/features/magic-context/project-embedding-registry.d.ts +53 -0
  28. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  29. package/dist/features/magic-context/search.d.ts +14 -1
  30. package/dist/features/magic-context/search.d.ts.map +1 -1
  31. package/dist/features/magic-context/session-project-storage.d.ts +17 -0
  32. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -0
  33. package/dist/features/magic-context/storage-db.d.ts +1 -1
  34. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-memory-mutation-log.d.ts +2 -0
  36. package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -1
  37. package/dist/features/magic-context/storage-meta-persisted.d.ts +16 -0
  38. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  39. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-meta-shared.d.ts +3 -2
  41. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  42. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  43. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  44. package/dist/features/magic-context/storage.d.ts +3 -2
  45. package/dist/features/magic-context/storage.d.ts.map +1 -1
  46. package/dist/features/magic-context/tool-definition-tokens.d.ts +0 -21
  47. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  48. package/dist/features/magic-context/types.d.ts +1 -0
  49. package/dist/features/magic-context/types.d.ts.map +1 -1
  50. package/dist/features/magic-context/workspaces.d.ts +20 -0
  51. package/dist/features/magic-context/workspaces.d.ts.map +1 -0
  52. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/command-handler.d.ts +5 -0
  55. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/compartment-runner-types.d.ts +11 -1
  60. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +18 -0
  63. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -0
  64. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/inject-compartments.d.ts +23 -5
  66. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/issue-135-wire-fixtures.d.ts +8 -0
  68. package/dist/hooks/magic-context/issue-135-wire-fixtures.d.ts.map +1 -0
  69. package/dist/hooks/magic-context/openai-compat-adjacency.d.ts +38 -0
  70. package/dist/hooks/magic-context/openai-compat-adjacency.d.ts.map +1 -0
  71. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -1
  72. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  73. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/sentinel.d.ts +33 -28
  75. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  76. package/dist/hooks/magic-context/strip-content.d.ts +34 -17
  77. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  78. package/dist/hooks/magic-context/strip-structural-noise.d.ts +5 -7
  79. package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
  80. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  81. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +4 -5
  82. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  83. package/dist/hooks/magic-context/transform.d.ts +0 -8
  84. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  85. package/dist/index.js +2387 -348
  86. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  87. package/dist/shared/announcement.d.ts +1 -1
  88. package/dist/shared/rpc-types.d.ts +1 -1
  89. package/dist/shared/rpc-types.d.ts.map +1 -1
  90. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  91. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  92. package/package.json +1 -1
  93. package/src/shared/announcement.ts +6 -6
  94. package/src/shared/rpc-types.ts +1 -1
  95. package/src/tui/index.tsx +5 -3
  96. package/src/tui/slots/sidebar-content.tsx +24 -5
package/dist/index.js CHANGED
@@ -14887,7 +14887,8 @@ var init_magic_context = __esm(() => {
14887
14887
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
14888
14888
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
14889
14889
  input_type: exports_external.string().optional().describe("Optional input_type sent in the embedding request body. Required by some openai-compatible providers (e.g. NVIDIA NIM expects 'query' or 'passage'). Omitted from the request when unset."),
14890
- truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset.")
14890
+ truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset."),
14891
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
14891
14892
  }).superRefine((data, ctx) => {
14892
14893
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
14893
14894
  ctx.addIssue({
@@ -14908,7 +14909,8 @@ var init_magic_context = __esm(() => {
14908
14909
  if (data.provider === "local") {
14909
14910
  return {
14910
14911
  provider: "local",
14911
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
14912
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
14913
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14912
14914
  };
14913
14915
  }
14914
14916
  if (data.provider === "openai-compatible") {
@@ -14921,7 +14923,8 @@ var init_magic_context = __esm(() => {
14921
14923
  endpoint: data.endpoint?.trim() ?? "",
14922
14924
  ...apiKey ? { api_key: apiKey } : {},
14923
14925
  ...inputType ? { input_type: inputType } : {},
14924
- ...truncate ? { truncate } : {}
14926
+ ...truncate ? { truncate } : {},
14927
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
14925
14928
  };
14926
14929
  }
14927
14930
  return { provider: "off" };
@@ -15844,7 +15847,7 @@ function isSessionMetaRow(row) {
15844
15847
  if (row === null || typeof row !== "object")
15845
15848
  return false;
15846
15849
  const r = row;
15847
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15850
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15848
15851
  }
15849
15852
  function getDefaultSessionMeta(sessionId) {
15850
15853
  return {
@@ -15871,6 +15874,7 @@ function getDefaultSessionMeta(sessionId) {
15871
15874
  cachedM0Bytes: null,
15872
15875
  cachedM1Bytes: null,
15873
15876
  cachedM0ProjectMemoryEpoch: null,
15877
+ cachedM0WorkspaceFingerprint: null,
15874
15878
  cachedM0ProjectUserProfileVersion: null,
15875
15879
  cachedM0MaxCompartmentSeq: null,
15876
15880
  cachedM0MaxMemoryId: null,
@@ -15933,6 +15937,7 @@ function toSessionMeta(row) {
15933
15937
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
15934
15938
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
15935
15939
  cachedM0ProjectMemoryEpoch: numOrNull(row.cached_m0_project_memory_epoch),
15940
+ cachedM0WorkspaceFingerprint: stringOrNull(row.cached_m0_workspace_fingerprint),
15936
15941
  cachedM0ProjectUserProfileVersion: numOrNull(row.cached_m0_project_user_profile_version),
15937
15942
  cachedM0MaxCompartmentSeq: numOrNull(row.cached_m0_max_compartment_seq),
15938
15943
  cachedM0MaxMemoryId: numOrNull(row.cached_m0_max_memory_id),
@@ -15963,6 +15968,7 @@ function persistCachedM0(db, sessionId, payload) {
15963
15968
  db.prepare(`UPDATE session_meta SET
15964
15969
  cached_m0_bytes = ?,
15965
15970
  cached_m0_project_memory_epoch = ?,
15971
+ cached_m0_workspace_fingerprint = ?,
15966
15972
  cached_m0_project_user_profile_version = ?,
15967
15973
  cached_m0_max_compartment_seq = ?,
15968
15974
  cached_m0_max_memory_id = ?,
@@ -15974,9 +15980,8 @@ function persistCachedM0(db, sessionId, payload) {
15974
15980
  cached_m0_session_facts_version = ?,
15975
15981
  cached_m0_upgrade_state = ?,
15976
15982
  cached_m0_system_hash = ?,
15977
- cached_m0_tool_set_hash = ?,
15978
15983
  cached_m0_model_key = ?
15979
- 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);
15984
+ WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.workspaceFingerprint ?? null, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.modelKey ?? "", sessionId);
15980
15985
  }
15981
15986
  function clearCachedM0M1(db, sessionId) {
15982
15987
  ensureSessionMetaRow(db, sessionId);
@@ -15985,6 +15990,7 @@ function clearCachedM0M1(db, sessionId) {
15985
15990
  ["cached_m0_bytes", null],
15986
15991
  ["cached_m1_bytes", null],
15987
15992
  ["cached_m0_project_memory_epoch", null],
15993
+ ["cached_m0_workspace_fingerprint", null],
15988
15994
  ["cached_m0_project_user_profile_version", null],
15989
15995
  ["cached_m0_max_compartment_seq", null],
15990
15996
  ["cached_m0_max_memory_id", null],
@@ -16040,6 +16046,7 @@ var init_storage_meta_shared = __esm(() => {
16040
16046
  "cached_m0_bytes",
16041
16047
  "cached_m1_bytes",
16042
16048
  "cached_m0_project_memory_epoch",
16049
+ "cached_m0_workspace_fingerprint",
16043
16050
  "cached_m0_project_user_profile_version",
16044
16051
  "cached_m0_max_compartment_seq",
16045
16052
  "cached_m0_max_memory_id",
@@ -16087,6 +16094,7 @@ var init_storage_meta_shared = __esm(() => {
16087
16094
  cachedM0Bytes: "cached_m0_bytes",
16088
16095
  cachedM1Bytes: "cached_m1_bytes",
16089
16096
  cachedM0ProjectMemoryEpoch: "cached_m0_project_memory_epoch",
16097
+ cachedM0WorkspaceFingerprint: "cached_m0_workspace_fingerprint",
16090
16098
  cachedM0ProjectUserProfileVersion: "cached_m0_project_user_profile_version",
16091
16099
  cachedM0MaxCompartmentSeq: "cached_m0_max_compartment_seq",
16092
16100
  cachedM0MaxMemoryId: "cached_m0_max_memory_id",
@@ -16116,6 +16124,7 @@ var init_storage_meta_shared = __esm(() => {
16116
16124
  "cachedM0Bytes",
16117
16125
  "cachedM1Bytes",
16118
16126
  "cachedM0ProjectMemoryEpoch",
16127
+ "cachedM0WorkspaceFingerprint",
16119
16128
  "cachedM0ProjectUserProfileVersion",
16120
16129
  "cachedM0MaxCompartmentSeq",
16121
16130
  "cachedM0MaxMemoryId",
@@ -150320,18 +150329,6 @@ function keyFor(providerID, modelID, agentName) {
150320
150329
  function fingerprintFor(description, parameters) {
150321
150330
  return createHash3("sha256").update(description).update("\x00").update(stableStringify(parameters)).digest("hex");
150322
150331
  }
150323
- function getCurrentToolSetHash(providerID, modelID, agentName) {
150324
- const key = keyFor(providerID, modelID, agentName);
150325
- const inner = fingerprints.get(key);
150326
- if (!inner || inner.size === 0)
150327
- return "";
150328
- const parts = [];
150329
- for (const [toolID, fp] of inner)
150330
- parts.push(`${toolID}\x00${fp}`);
150331
- parts.sort();
150332
- return createHash3("sha256").update(parts.join(`
150333
- `)).digest("hex");
150334
- }
150335
150332
  function setDatabase(db) {
150336
150333
  persistenceDb = db;
150337
150334
  cachedInsertStmt = null;
@@ -150839,6 +150836,35 @@ function initializeDatabase(db) {
150839
150836
  );
150840
150837
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
150841
150838
 
150839
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
150840
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
150841
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
150842
+ session_id TEXT NOT NULL,
150843
+ project_path TEXT NOT NULL,
150844
+ harness TEXT NOT NULL DEFAULT 'opencode',
150845
+ window_index INTEGER NOT NULL DEFAULT 0,
150846
+ start_ordinal INTEGER NOT NULL,
150847
+ end_ordinal INTEGER NOT NULL,
150848
+ chunk_hash TEXT NOT NULL,
150849
+ model_id TEXT NOT NULL,
150850
+ dims INTEGER NOT NULL,
150851
+ vector BLOB NOT NULL,
150852
+ created_at INTEGER NOT NULL,
150853
+ UNIQUE(compartment_id, window_index)
150854
+ );
150855
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
150856
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
150857
+
150858
+ CREATE TABLE IF NOT EXISTS session_projects (
150859
+ session_id TEXT NOT NULL,
150860
+ harness TEXT NOT NULL DEFAULT 'opencode',
150861
+ project_path TEXT NOT NULL,
150862
+ updated_at INTEGER NOT NULL,
150863
+ PRIMARY KEY(session_id, harness)
150864
+ );
150865
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
150866
+ ON session_projects(project_path);
150867
+
150842
150868
  CREATE TABLE IF NOT EXISTS compartment_events (
150843
150869
  id INTEGER PRIMARY KEY AUTOINCREMENT,
150844
150870
  session_id TEXT NOT NULL,
@@ -151024,6 +151050,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151024
151050
  rekeyed_at INTEGER NOT NULL
151025
151051
  );
151026
151052
 
151053
+ CREATE TABLE IF NOT EXISTS workspaces (
151054
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151055
+ name TEXT NOT NULL UNIQUE,
151056
+ created_at INTEGER NOT NULL,
151057
+ updated_at INTEGER NOT NULL,
151058
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151059
+ );
151060
+
151061
+ CREATE TABLE IF NOT EXISTS workspace_members (
151062
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151063
+ project_path TEXT NOT NULL,
151064
+ display_name TEXT NOT NULL,
151065
+ display_path TEXT NOT NULL,
151066
+ added_at INTEGER NOT NULL,
151067
+ PRIMARY KEY (workspace_id, project_path)
151068
+ );
151069
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151070
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151071
+
151027
151072
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151028
151073
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151029
151074
  table_name TEXT NOT NULL,
@@ -151135,6 +151180,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151135
151180
  deferred_execute_state TEXT,
151136
151181
  cached_m0_bytes BLOB,
151137
151182
  cached_m0_project_memory_epoch INTEGER,
151183
+ cached_m0_workspace_fingerprint TEXT,
151138
151184
  cached_m0_project_user_profile_version INTEGER,
151139
151185
  cached_m0_max_compartment_seq INTEGER,
151140
151186
  cached_m0_max_memory_id INTEGER,
@@ -151293,6 +151339,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151293
151339
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151294
151340
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151295
151341
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151342
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
151296
151343
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
151297
151344
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
151298
151345
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -151346,6 +151393,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151346
151393
  ensureColumn(db, "memories", "importance", "INTEGER");
151347
151394
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
151348
151395
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
151396
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
151349
151397
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
151350
151398
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
151351
151399
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -151377,6 +151425,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151377
151425
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
151378
151426
  updated_at INTEGER NOT NULL DEFAULT 0
151379
151427
  );
151428
+ CREATE TABLE IF NOT EXISTS session_projects (
151429
+ session_id TEXT NOT NULL,
151430
+ harness TEXT NOT NULL DEFAULT 'opencode',
151431
+ project_path TEXT NOT NULL,
151432
+ updated_at INTEGER NOT NULL,
151433
+ PRIMARY KEY(session_id, harness)
151434
+ );
151435
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
151436
+ ON session_projects(project_path);
151380
151437
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
151381
151438
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151382
151439
  session_id TEXT NOT NULL,
@@ -151404,6 +151461,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151404
151461
  new_project_path TEXT NOT NULL,
151405
151462
  rekeyed_at INTEGER NOT NULL
151406
151463
  );
151464
+ CREATE TABLE IF NOT EXISTS workspaces (
151465
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151466
+ name TEXT NOT NULL UNIQUE,
151467
+ created_at INTEGER NOT NULL,
151468
+ updated_at INTEGER NOT NULL,
151469
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
151470
+ );
151471
+ CREATE TABLE IF NOT EXISTS workspace_members (
151472
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
151473
+ project_path TEXT NOT NULL,
151474
+ display_name TEXT NOT NULL,
151475
+ display_path TEXT NOT NULL,
151476
+ added_at INTEGER NOT NULL,
151477
+ PRIMARY KEY (workspace_id, project_path)
151478
+ );
151479
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
151480
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
151407
151481
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
151408
151482
  id INTEGER PRIMARY KEY AUTOINCREMENT,
151409
151483
  table_name TEXT NOT NULL,
@@ -151425,6 +151499,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151425
151499
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151426
151500
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151427
151501
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
151502
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
151428
151503
  }
151429
151504
  function healAllNullColumns(db) {
151430
151505
  healNullTextColumns(db);
@@ -151462,6 +151537,7 @@ function healNullTextColumns(db) {
151462
151537
  ["system_prompt_hash", ""],
151463
151538
  ["stripped_placeholder_ids", ""],
151464
151539
  ["stale_reduce_stripped_ids", ""],
151540
+ ["processed_image_stripped_ids", ""],
151465
151541
  ["memory_block_cache", ""],
151466
151542
  ["memory_block_ids", ""],
151467
151543
  ["compaction_marker_state", ""],
@@ -151506,7 +151582,7 @@ function healNullIntegerColumns(db) {
151506
151582
  }
151507
151583
  }
151508
151584
  function ensureColumn(db, table, column, definition) {
151509
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
151585
+ if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
151510
151586
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
151511
151587
  }
151512
151588
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -151592,7 +151668,7 @@ function getDatabasePersistenceError(db) {
151592
151668
  return null;
151593
151669
  return persistenceErrorByDatabase.get(db) ?? null;
151594
151670
  }
151595
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 32, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151671
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 36, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
151596
151672
  var init_storage_db = __esm(async () => {
151597
151673
  init_data_path();
151598
151674
  init_logger();
@@ -151612,10 +151688,302 @@ var init_storage_db = __esm(async () => {
151612
151688
  };
151613
151689
  });
151614
151690
 
151691
+ // src/features/magic-context/memory/constants.ts
151692
+ var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
151693
+ var init_constants = __esm(() => {
151694
+ V2_MEMORY_CATEGORIES = [
151695
+ "PROJECT_RULES",
151696
+ "ARCHITECTURE",
151697
+ "CONSTRAINTS",
151698
+ "CONFIG_VALUES",
151699
+ "NAMING"
151700
+ ];
151701
+ PROMOTABLE_CATEGORIES = [
151702
+ "PROJECT_RULES",
151703
+ "ARCHITECTURE",
151704
+ "CONSTRAINTS",
151705
+ "CONFIG_VALUES",
151706
+ "NAMING",
151707
+ "ARCHITECTURE_DECISIONS",
151708
+ "CONFIG_DEFAULTS",
151709
+ "USER_PREFERENCES",
151710
+ "USER_DIRECTIVES",
151711
+ "ENVIRONMENT",
151712
+ "WORKFLOW_RULES",
151713
+ "KNOWN_ISSUES"
151714
+ ];
151715
+ CATEGORY_PRIORITY = [
151716
+ "PROJECT_RULES",
151717
+ "ARCHITECTURE",
151718
+ "CONSTRAINTS",
151719
+ "CONFIG_VALUES",
151720
+ "NAMING",
151721
+ "USER_DIRECTIVES",
151722
+ "USER_PREFERENCES",
151723
+ "CONFIG_DEFAULTS",
151724
+ "ARCHITECTURE_DECISIONS",
151725
+ "ENVIRONMENT",
151726
+ "WORKFLOW_RULES",
151727
+ "KNOWN_ISSUES"
151728
+ ];
151729
+ MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
151730
+ acc[category] = index;
151731
+ return acc;
151732
+ }, {});
151733
+ MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
151734
+ CATEGORY_DEFAULT_TTL = {
151735
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
151736
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
151737
+ };
151738
+ });
151739
+
151740
+ // src/features/magic-context/project-identity.ts
151741
+ var init_project_identity2 = __esm(() => {
151742
+ init_project_identity();
151743
+ });
151744
+
151745
+ // src/features/magic-context/workspaces.ts
151746
+ import { createHash as createHash4 } from "node:crypto";
151747
+ function tableExists(db, tableName) {
151748
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
151749
+ return Boolean(row);
151750
+ }
151751
+ function columnExists(db, tableName, columnName) {
151752
+ const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
151753
+ return rows.some((row) => row.name === columnName);
151754
+ }
151755
+ function uniqueSorted(values) {
151756
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
151757
+ }
151758
+ function placeholders(values) {
151759
+ return values.map(() => "?").join(", ");
151760
+ }
151761
+ function normalizeShareCategories(raw) {
151762
+ if (raw === null || raw === undefined)
151763
+ return null;
151764
+ if (typeof raw !== "string")
151765
+ return null;
151766
+ let parsed;
151767
+ try {
151768
+ parsed = JSON.parse(raw);
151769
+ } catch {
151770
+ return null;
151771
+ }
151772
+ if (!Array.isArray(parsed))
151773
+ return null;
151774
+ const categories = [];
151775
+ for (const value of parsed) {
151776
+ if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
151777
+ return null;
151778
+ }
151779
+ if (!categories.includes(value))
151780
+ categories.push(value);
151781
+ }
151782
+ return categories.sort((left, right) => left.localeCompare(right));
151783
+ }
151784
+ function selectWorkspaceShareCategories(db, identities) {
151785
+ const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
151786
+ if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
151787
+ return null;
151788
+ }
151789
+ const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
151790
+ FROM workspace_members AS member
151791
+ JOIN workspaces AS workspace ON workspace.id = member.workspace_id
151792
+ WHERE member.project_path IN (${placeholders(candidates)})
151793
+ ORDER BY workspace.id ASC
151794
+ LIMIT 1`).get(...candidates);
151795
+ return normalizeShareCategories(row?.shareCategories ?? null);
151796
+ }
151797
+ function resolveWorkspaceShareCategories(db, projectIdentity) {
151798
+ return selectWorkspaceShareCategories(db, [projectIdentity]);
151799
+ }
151800
+ function resolveWorkspaceIdentitySet(db, projectIdentity) {
151801
+ if (!tableExists(db, "workspace_members")) {
151802
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151803
+ }
151804
+ const rows = db.prepare(`SELECT member.project_path AS identity, member.display_name AS displayName
151805
+ FROM workspace_members AS anchor
151806
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151807
+ WHERE anchor.project_path = ?
151808
+ ORDER BY member.display_name ASC, member.project_path ASC`).all(projectIdentity);
151809
+ if (rows.length === 0) {
151810
+ return { identities: [projectIdentity], namesByIdentity: new Map };
151811
+ }
151812
+ const namesByIdentity = new Map;
151813
+ const identities = [];
151814
+ for (const row of rows) {
151815
+ if (typeof row.identity !== "string" || row.identity.length === 0)
151816
+ continue;
151817
+ if (identities.includes(row.identity))
151818
+ continue;
151819
+ identities.push(row.identity);
151820
+ if (typeof row.displayName === "string" && row.displayName.length > 0) {
151821
+ namesByIdentity.set(row.identity, row.displayName);
151822
+ }
151823
+ }
151824
+ return identities.length > 0 ? { identities, namesByIdentity } : { identities: [projectIdentity], namesByIdentity: new Map };
151825
+ }
151826
+ function expandWorkspaceIdentitySetWithAliases(db, identities) {
151827
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151828
+ const expanded = new Set(canonical);
151829
+ const canonicalIdentityByStoredPath = new Map;
151830
+ for (const identity of canonical) {
151831
+ canonicalIdentityByStoredPath.set(identity, identity);
151832
+ }
151833
+ if (canonical.length === 0 || !tableExists(db, "v22_identity_rekey_map")) {
151834
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151835
+ }
151836
+ const rows = db.prepare(`SELECT old_project_path AS oldProjectPath, new_project_path AS newProjectPath
151837
+ FROM v22_identity_rekey_map
151838
+ WHERE new_project_path IN (${placeholders(canonical)})
151839
+ ORDER BY old_project_path ASC`).all(...canonical);
151840
+ for (const row of rows) {
151841
+ if (typeof row.oldProjectPath !== "string" || typeof row.newProjectPath !== "string") {
151842
+ continue;
151843
+ }
151844
+ if (!canonicalIdentityByStoredPath.has(row.newProjectPath))
151845
+ continue;
151846
+ expanded.add(row.oldProjectPath);
151847
+ canonicalIdentityByStoredPath.set(row.oldProjectPath, row.newProjectPath);
151848
+ }
151849
+ return { expandedIdentities: [...expanded], canonicalIdentityByStoredPath };
151850
+ }
151851
+ function resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) {
151852
+ const direct = canonicalIdentityByStoredPath.get(storedProjectPath);
151853
+ if (direct)
151854
+ return direct;
151855
+ const normalized = normalizeStoredProjectPath(storedProjectPath);
151856
+ const normalizedDirect = canonicalIdentityByStoredPath.get(normalized);
151857
+ if (normalizedDirect)
151858
+ return normalizedDirect;
151859
+ if (memberIdentities.includes(normalized))
151860
+ return normalized;
151861
+ for (const identity of memberIdentities) {
151862
+ if (storedPathBelongsToIdentity(storedProjectPath, identity)) {
151863
+ return identity;
151864
+ }
151865
+ }
151866
+ return null;
151867
+ }
151868
+ function storedPathBelongsToWorkspace(storedProjectPath, memberIdentities, expandedIdentities, canonicalIdentityByStoredPath) {
151869
+ if (expandedIdentities.includes(storedProjectPath))
151870
+ return true;
151871
+ return resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath) !== null;
151872
+ }
151873
+ function sourceNameForMemory(storedProjectPath, ownIdentity, memberIdentities, namesByIdentity, canonicalIdentityByStoredPath) {
151874
+ const canonicalIdentity = resolveStoredPathWorkspaceIdentity(storedProjectPath, memberIdentities, canonicalIdentityByStoredPath);
151875
+ if (!canonicalIdentity || canonicalIdentity === ownIdentity)
151876
+ return;
151877
+ return namesByIdentity.get(canonicalIdentity);
151878
+ }
151879
+ function getEpochMap(db, identities) {
151880
+ if (identities.length === 0)
151881
+ return new Map;
151882
+ const rows = db.prepare(`SELECT project_path AS projectPath, project_memory_epoch AS epoch
151883
+ FROM project_state
151884
+ WHERE project_path IN (${placeholders(identities)})`).all(...identities);
151885
+ const epochs = new Map;
151886
+ for (const row of rows) {
151887
+ if (typeof row.projectPath !== "string" || typeof row.epoch !== "number")
151888
+ continue;
151889
+ epochs.set(row.projectPath, row.epoch);
151890
+ }
151891
+ return epochs;
151892
+ }
151893
+ function computeWorkspaceEpochFingerprint(db, identities) {
151894
+ const canonical = uniqueSorted(identities.filter((identity) => identity.length > 0));
151895
+ const epochs = getEpochMap(db, canonical);
151896
+ const shareCategories = selectWorkspaceShareCategories(db, canonical);
151897
+ const hash2 = createHash4("sha256");
151898
+ hash2.update("share_categories", "utf8");
151899
+ hash2.update("\x00");
151900
+ hash2.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
151901
+ hash2.update(`
151902
+ `);
151903
+ for (const identity of canonical) {
151904
+ hash2.update(identity, "utf8");
151905
+ hash2.update("\x00");
151906
+ hash2.update(String(epochs.get(identity) ?? 0), "utf8");
151907
+ hash2.update(`
151908
+ `);
151909
+ }
151910
+ return hash2.digest("hex");
151911
+ }
151912
+ function isInTransaction(db) {
151913
+ const candidate = db;
151914
+ return candidate.inTransaction === true || candidate.isTransaction === true;
151915
+ }
151916
+ function workspaceMembersForIdentity(db, identity) {
151917
+ if (!tableExists(db, "workspace_members"))
151918
+ return [identity];
151919
+ const rows = db.prepare(`SELECT member.project_path AS identity
151920
+ FROM workspace_members AS anchor
151921
+ JOIN workspace_members AS member ON member.workspace_id = anchor.workspace_id
151922
+ WHERE anchor.project_path = ?
151923
+ ORDER BY member.project_path ASC`).all(identity);
151924
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((value) => value.length > 0);
151925
+ return identities.length > 0 ? uniqueSorted(identities) : [identity];
151926
+ }
151927
+ function bumpEpochRows(db, identities, now) {
151928
+ const stmt = db.prepare(`INSERT INTO project_state
151929
+ (project_path, project_memory_epoch, project_user_profile_version, updated_at)
151930
+ VALUES (?, 1, 0, ?)
151931
+ ON CONFLICT(project_path) DO UPDATE SET
151932
+ project_memory_epoch = project_memory_epoch + 1,
151933
+ updated_at = excluded.updated_at`);
151934
+ for (const identity of uniqueSorted(identities)) {
151935
+ stmt.run(identity, now);
151936
+ }
151937
+ }
151938
+ function bumpEpochsForWorkspaceMembers(db, identity, now = Date.now()) {
151939
+ const run = () => bumpEpochRows(db, workspaceMembersForIdentity(db, identity), now);
151940
+ if (isInTransaction(db)) {
151941
+ run();
151942
+ return;
151943
+ }
151944
+ db.exec("BEGIN IMMEDIATE");
151945
+ try {
151946
+ run();
151947
+ db.exec("COMMIT");
151948
+ } catch (error51) {
151949
+ try {
151950
+ db.exec("ROLLBACK");
151951
+ } catch {}
151952
+ throw error51;
151953
+ }
151954
+ }
151955
+ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
151956
+ const run = () => bumpEpochRows(db, identities, now);
151957
+ if (isInTransaction(db)) {
151958
+ run();
151959
+ return;
151960
+ }
151961
+ db.exec("BEGIN IMMEDIATE");
151962
+ try {
151963
+ run();
151964
+ db.exec("COMMIT");
151965
+ } catch (error51) {
151966
+ try {
151967
+ db.exec("ROLLBACK");
151968
+ } catch {}
151969
+ throw error51;
151970
+ }
151971
+ }
151972
+ var VALID_SHARE_CATEGORIES;
151973
+ var init_workspaces = __esm(() => {
151974
+ init_constants();
151975
+ init_project_identity2();
151976
+ VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
151977
+ });
151978
+
151615
151979
  // src/features/magic-context/migrations.ts
151616
- function tableExists(db, name2) {
151980
+ function tableExists2(db, name2) {
151617
151981
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
151618
151982
  }
151983
+ function columnExists2(db, table, column) {
151984
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
151985
+ return rows.some((row) => row.name === column);
151986
+ }
151619
151987
  function ensureMigrationsTable(db) {
151620
151988
  db.exec(`
151621
151989
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -151684,6 +152052,7 @@ function runMigrations(db) {
151684
152052
  var MIGRATIONS, LATEST_MIGRATION_VERSION;
151685
152053
  var init_migrations = __esm(async () => {
151686
152054
  init_logger();
152055
+ init_workspaces();
151687
152056
  await init_storage_db();
151688
152057
  MIGRATIONS = [
151689
152058
  {
@@ -152143,9 +152512,9 @@ var init_migrations = __esm(async () => {
152143
152512
  version: 22,
152144
152513
  description: "v2.0 cache architecture schema foundation",
152145
152514
  up: (db) => {
152146
- const hasSessionMetaTable = tableExists(db, "session_meta");
152147
- const hasCompartmentsTable = tableExists(db, "compartments");
152148
- const hasMemoriesTable = tableExists(db, "memories");
152515
+ const hasSessionMetaTable = tableExists2(db, "session_meta");
152516
+ const hasCompartmentsTable = tableExists2(db, "compartments");
152517
+ const hasMemoriesTable = tableExists2(db, "memories");
152149
152518
  if (hasSessionMetaTable) {
152150
152519
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
152151
152520
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
@@ -152170,7 +152539,7 @@ var init_migrations = __esm(async () => {
152170
152539
  ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
152171
152540
  ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
152172
152541
  }
152173
- const hasRecompCompartmentsTable = tableExists(db, "recomp_compartments");
152542
+ const hasRecompCompartmentsTable = tableExists2(db, "recomp_compartments");
152174
152543
  if (hasRecompCompartmentsTable) {
152175
152544
  ensureColumn(db, "recomp_compartments", "p1", "TEXT");
152176
152545
  ensureColumn(db, "recomp_compartments", "p2", "TEXT");
@@ -152481,13 +152850,163 @@ var init_migrations = __esm(async () => {
152481
152850
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
152482
152851
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
152483
152852
  }
152853
+ },
152854
+ {
152855
+ version: 33,
152856
+ description: "Compartment chunk embeddings for semantic message-history search",
152857
+ up: (db) => {
152858
+ db.exec(`
152859
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
152860
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152861
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
152862
+ session_id TEXT NOT NULL,
152863
+ project_path TEXT NOT NULL,
152864
+ harness TEXT NOT NULL DEFAULT 'opencode',
152865
+ window_index INTEGER NOT NULL DEFAULT 0,
152866
+ start_ordinal INTEGER NOT NULL,
152867
+ end_ordinal INTEGER NOT NULL,
152868
+ chunk_hash TEXT NOT NULL,
152869
+ model_id TEXT NOT NULL,
152870
+ dims INTEGER NOT NULL,
152871
+ vector BLOB NOT NULL,
152872
+ created_at INTEGER NOT NULL,
152873
+ UNIQUE(compartment_id, window_index)
152874
+ );
152875
+ CREATE INDEX IF NOT EXISTS idx_cce_session
152876
+ ON compartment_chunk_embeddings(session_id);
152877
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model
152878
+ ON compartment_chunk_embeddings(project_path, model_id);
152879
+ `);
152880
+ }
152881
+ },
152882
+ {
152883
+ version: 34,
152884
+ description: "workspace tables and m[0] workspace fingerprint cache reset",
152885
+ up: (db) => {
152886
+ db.exec(`
152887
+ CREATE TABLE IF NOT EXISTS workspaces (
152888
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152889
+ name TEXT NOT NULL UNIQUE,
152890
+ created_at INTEGER NOT NULL,
152891
+ updated_at INTEGER NOT NULL
152892
+ );
152893
+ CREATE TABLE IF NOT EXISTS workspace_members (
152894
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
152895
+ project_path TEXT NOT NULL,
152896
+ display_name TEXT NOT NULL,
152897
+ display_path TEXT NOT NULL,
152898
+ added_at INTEGER NOT NULL,
152899
+ PRIMARY KEY (workspace_id, project_path)
152900
+ );
152901
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique
152902
+ ON workspace_members(project_path);
152903
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name
152904
+ ON workspace_members(workspace_id, display_name);
152905
+ `);
152906
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
152907
+ if (!hasSessionMeta)
152908
+ return;
152909
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
152910
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
152911
+ const clears = [
152912
+ ["cached_m0_bytes", null],
152913
+ ["cached_m1_bytes", null],
152914
+ ["cached_m0_project_memory_epoch", null],
152915
+ ["cached_m0_workspace_fingerprint", null],
152916
+ ["cached_m0_project_user_profile_version", null],
152917
+ ["cached_m0_max_compartment_seq", null],
152918
+ ["cached_m0_max_memory_id", null],
152919
+ ["cached_m0_max_mutation_id", null],
152920
+ ["cached_m0_max_memory_mutation_id", null],
152921
+ ["cached_m0_project_docs_hash", null],
152922
+ ["cached_m0_materialized_at", null],
152923
+ ["cached_m0_session_facts_version", null],
152924
+ ["cached_m0_upgrade_state", null],
152925
+ ["cached_m0_system_hash", null],
152926
+ ["cached_m0_tool_set_hash", null],
152927
+ ["cached_m0_model_key", null],
152928
+ ["cached_m0_last_baseline_end_message_id", null],
152929
+ ["memory_block_cache", ""],
152930
+ ["memory_block_ids", ""],
152931
+ ["memory_block_count", 0]
152932
+ ];
152933
+ const setClauses = [];
152934
+ const values = [];
152935
+ for (const [column, value] of clears) {
152936
+ if (!columns.has(column))
152937
+ continue;
152938
+ setClauses.push(`${column} = ?`);
152939
+ values.push(value);
152940
+ }
152941
+ if (setClauses.length > 0) {
152942
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")}`).run(...values);
152943
+ }
152944
+ }
152945
+ },
152946
+ {
152947
+ version: 35,
152948
+ description: "workspace per-category share defaults and epoch refresh",
152949
+ up: (db) => {
152950
+ db.exec(`
152951
+ CREATE TABLE IF NOT EXISTS workspaces (
152952
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152953
+ name TEXT NOT NULL UNIQUE,
152954
+ created_at INTEGER NOT NULL,
152955
+ updated_at INTEGER NOT NULL,
152956
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
152957
+ );
152958
+ `);
152959
+ if (!columnExists2(db, "workspaces", "share_categories")) {
152960
+ db.exec(`ALTER TABLE workspaces ADD COLUMN share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
152961
+ }
152962
+ db.prepare(`UPDATE workspaces
152963
+ SET share_categories = '["CONSTRAINTS"]'
152964
+ WHERE share_categories IS NULL OR share_categories = ''`).run();
152965
+ if (!tableExists2(db, "workspace_members"))
152966
+ return;
152967
+ const rows = db.prepare(`SELECT DISTINCT project_path AS identity
152968
+ FROM workspace_members
152969
+ WHERE project_path IS NOT NULL AND project_path <> ''
152970
+ ORDER BY project_path ASC`).all();
152971
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((identity) => identity.length > 0);
152972
+ if (identities.length > 0) {
152973
+ bumpEpochsForWorkspaceMemberSet(db, identities, Date.now());
152974
+ }
152975
+ }
152976
+ },
152977
+ {
152978
+ version: 36,
152979
+ description: "session project ownership map for compartment chunk backfill scoping",
152980
+ up: (db) => {
152981
+ db.exec(`
152982
+ CREATE TABLE IF NOT EXISTS session_projects (
152983
+ session_id TEXT NOT NULL,
152984
+ harness TEXT NOT NULL DEFAULT 'opencode',
152985
+ project_path TEXT NOT NULL,
152986
+ updated_at INTEGER NOT NULL,
152987
+ PRIMARY KEY(session_id, harness)
152988
+ );
152989
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
152990
+ ON session_projects(project_path);
152991
+ `);
152992
+ const hasChunkTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartment_chunk_embeddings'").get();
152993
+ if (hasChunkTable) {
152994
+ db.exec(`
152995
+ INSERT OR IGNORE INTO session_projects (session_id, harness, project_path, updated_at)
152996
+ SELECT session_id, harness, MIN(project_path), 0
152997
+ FROM compartment_chunk_embeddings
152998
+ GROUP BY session_id, harness
152999
+ HAVING COUNT(DISTINCT project_path) = 1;
153000
+ `);
153001
+ }
153002
+ }
152484
153003
  }
152485
153004
  ];
152486
153005
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
152487
153006
  });
152488
153007
 
152489
153008
  // src/features/magic-context/project-docs-hash.ts
152490
- import { createHash as createHash4 } from "node:crypto";
153009
+ import { createHash as createHash5 } from "node:crypto";
152491
153010
  import { lstatSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152492
153011
  import path4 from "node:path";
152493
153012
  function canonicalizeDocContent(raw) {
@@ -152577,7 +153096,7 @@ function hashCanonicalPieces(hashPieces) {
152577
153096
  if (hashPieces.length === 0) {
152578
153097
  return "";
152579
153098
  }
152580
- return createHash4("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
153099
+ return createHash5("sha256").update(hashPieces.join(PROJECT_DOCS_DELIMITER), "utf8").digest("hex");
152581
153100
  }
152582
153101
  function readProjectDocsCanonical(projectDirectory) {
152583
153102
  const canonicalDirectory = path4.resolve(projectDirectory);
@@ -152615,11 +153134,6 @@ var init_project_docs_hash = __esm(() => {
152615
153134
  MAX_PROJECT_DOC_BYTES = 256 * 1024;
152616
153135
  docsCache = new Map;
152617
153136
  });
152618
-
152619
- // src/features/magic-context/project-identity.ts
152620
- var init_project_identity2 = __esm(() => {
152621
- init_project_identity();
152622
- });
152623
153137
  // src/features/magic-context/storage-m0-mutation-log.ts
152624
153138
  function assertMutationType(mutationType) {
152625
153139
  if (!M0_MUTATION_TYPES.has(mutationType)) {
@@ -152704,18 +153218,13 @@ function getMemoryMutation(db, id) {
152704
153218
  WHERE id = ?`).get(id);
152705
153219
  return row ? toMemoryMutation(row) : null;
152706
153220
  }
152707
- function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
152708
- if (renderedMemoryIds.length === 0)
152709
- return [];
152710
- const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
152711
- const placeholders = uniqueIds.map(() => "?").join(", ");
152712
- const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
152713
- superseded_by_id, category, new_content, queued_at
152714
- FROM memory_mutation_log
152715
- WHERE project_path = ?
152716
- AND id > ?
152717
- AND target_memory_id IN (${placeholders})
152718
- ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153221
+ function uniqueProjectPaths(projectPaths) {
153222
+ return [...new Set(projectPaths.filter((path5) => path5.length > 0))];
153223
+ }
153224
+ function placeholders2(values) {
153225
+ return values.map(() => "?").join(", ");
153226
+ }
153227
+ function coalesceMutations(rows) {
152719
153228
  const chosenByTarget = new Map;
152720
153229
  for (const dbRow of rows) {
152721
153230
  const candidate = toMemoryMutation(dbRow);
@@ -152736,10 +153245,54 @@ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds
152736
153245
  }
152737
153246
  return [...chosenByTarget.values()].sort((left, right) => left.id - right.id);
152738
153247
  }
153248
+ function getMemoryMutationsForRender(db, projectPath, afterId, renderedMemoryIds) {
153249
+ if (renderedMemoryIds.length === 0)
153250
+ return [];
153251
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153252
+ const placeholders3 = uniqueIds.map(() => "?").join(", ");
153253
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153254
+ superseded_by_id, category, new_content, queued_at
153255
+ FROM memory_mutation_log
153256
+ WHERE project_path = ?
153257
+ AND id > ?
153258
+ AND target_memory_id IN (${placeholders3})
153259
+ ORDER BY id ASC`).all(projectPath, afterId ?? 0, ...uniqueIds);
153260
+ return coalesceMutations(rows);
153261
+ }
153262
+ function getMemoryMutationsForRenderByProjects(db, projectPaths, afterId, renderedMemoryIds) {
153263
+ if (renderedMemoryIds.length === 0)
153264
+ return [];
153265
+ const identities = uniqueProjectPaths(projectPaths);
153266
+ if (identities.length === 0)
153267
+ return [];
153268
+ if (identities.length === 1) {
153269
+ return getMemoryMutationsForRender(db, identities[0], afterId, renderedMemoryIds);
153270
+ }
153271
+ const uniqueIds = [...new Set(renderedMemoryIds)].sort((left, right) => left - right);
153272
+ const rows = db.prepare(`SELECT id, project_path, mutation_type, target_memory_id,
153273
+ superseded_by_id, category, new_content, queued_at
153274
+ FROM memory_mutation_log
153275
+ WHERE project_path IN (${placeholders2(identities)})
153276
+ AND id > ?
153277
+ AND target_memory_id IN (${placeholders2(uniqueIds)})
153278
+ ORDER BY id ASC`).all(...identities, afterId ?? 0, ...uniqueIds);
153279
+ return coalesceMutations(rows);
153280
+ }
152739
153281
  function getMaxMemoryMutationId(db, projectPath) {
152740
153282
  const row = db.prepare("SELECT MAX(id) AS max_id FROM memory_mutation_log WHERE project_path = ?").get(projectPath);
152741
153283
  return row?.max_id ?? null;
152742
153284
  }
153285
+ function getMaxMemoryMutationIdForProjects(db, projectPaths) {
153286
+ const identities = uniqueProjectPaths(projectPaths);
153287
+ if (identities.length === 0)
153288
+ return null;
153289
+ if (identities.length === 1)
153290
+ return getMaxMemoryMutationId(db, identities[0]);
153291
+ const row = db.prepare(`SELECT MAX(id) AS max_id
153292
+ FROM memory_mutation_log
153293
+ WHERE project_path IN (${placeholders2(identities)})`).get(...identities);
153294
+ return row?.max_id ?? null;
153295
+ }
152743
153296
  var MEMORY_MUTATION_TYPES, TERMINAL_MUTATION_TYPES;
152744
153297
  var init_storage_memory_mutation_log = __esm(() => {
152745
153298
  MEMORY_MUTATION_TYPES = new Set(["archive", "delete", "update", "superseded"]);
@@ -153432,6 +153985,36 @@ function addStaleReduceStrippedIds(db, sessionId, ids) {
153432
153985
  sessionLog(sessionId, `stale_reduce_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
153433
153986
  return false;
153434
153987
  }
153988
+ function getProcessedImageStrippedIds(db, sessionId) {
153989
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153990
+ return new Set(parseStrippedBlob(row?.processed_image_stripped_ids));
153991
+ }
153992
+ function addProcessedImageStrippedIds(db, sessionId, ids) {
153993
+ const add = [...ids];
153994
+ if (add.length === 0)
153995
+ return true;
153996
+ ensureSessionMetaRow(db, sessionId);
153997
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
153998
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
153999
+ const rawStored = row ? row.processed_image_stripped_ids ?? null : null;
154000
+ const current = new Set(parseStrippedBlob(rawStored));
154001
+ let changed = false;
154002
+ for (const id of add) {
154003
+ if (!current.has(id)) {
154004
+ current.add(id);
154005
+ changed = true;
154006
+ }
154007
+ }
154008
+ if (!changed)
154009
+ return true;
154010
+ const nextBlob = JSON.stringify([...current]);
154011
+ const result = db.prepare("UPDATE session_meta SET processed_image_stripped_ids = ? WHERE session_id = ? AND processed_image_stripped_ids IS ?").run(nextBlob, sessionId, rawStored);
154012
+ if (result.changes > 0)
154013
+ return true;
154014
+ }
154015
+ sessionLog(sessionId, `processed_image_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
154016
+ return false;
154017
+ }
153435
154018
  function isPendingCompactionMarker(value) {
153436
154019
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
153437
154020
  }
@@ -153610,6 +154193,8 @@ function clearSession(db, sessionId) {
153610
154193
  db.prepare("DELETE FROM source_contents WHERE session_id = ?").run(sessionId);
153611
154194
  db.prepare("DELETE FROM tags WHERE session_id = ?").run(sessionId);
153612
154195
  db.prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
154196
+ db.prepare("DELETE FROM session_projects WHERE session_id = ?").run(sessionId);
154197
+ db.prepare("DELETE FROM compartment_chunk_embeddings WHERE session_id = ?").run(sessionId);
153613
154198
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
153614
154199
  clearCompressionDepth(db, sessionId);
153615
154200
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
@@ -153716,9 +154301,9 @@ function buildStatusClause(status) {
153716
154301
  if (statuses.length === 0) {
153717
154302
  return null;
153718
154303
  }
153719
- const placeholders = statuses.map(() => "?").join(", ");
154304
+ const placeholders3 = statuses.map(() => "?").join(", ");
153720
154305
  return {
153721
- sql: `status IN (${placeholders})`,
154306
+ sql: `status IN (${placeholders3})`,
153722
154307
  params: statuses
153723
154308
  };
153724
154309
  }
@@ -153924,19 +154509,6 @@ function getProjectState(db, projectPath) {
153924
154509
  WHERE project_path = ?`).get(projectPath);
153925
154510
  return row ? toProjectState(row) : null;
153926
154511
  }
153927
- function bumpProjectMemoryEpoch(db, projectPath, now = Date.now()) {
153928
- db.prepare(`INSERT INTO project_state
153929
- (project_path, project_memory_epoch, project_user_profile_version, updated_at)
153930
- VALUES (?, 1, 0, ?)
153931
- ON CONFLICT(project_path) DO UPDATE SET
153932
- project_memory_epoch = project_memory_epoch + 1,
153933
- updated_at = excluded.updated_at`).run(projectPath, now);
153934
- const state = getProjectState(db, projectPath);
153935
- if (!state) {
153936
- throw new Error(`Failed to bump project memory epoch for ${projectPath}`);
153937
- }
153938
- return state;
153939
- }
153940
154512
  function bumpProjectUserProfileVersion(db, projectPath = GLOBAL_USER_PROFILE_PROJECT_PATH, now = Date.now()) {
153941
154513
  db.prepare(`INSERT INTO project_state
153942
154514
  (project_path, project_memory_epoch, project_user_profile_version, updated_at)
@@ -153972,8 +154544,8 @@ function getSourceContents(db, sessionId, tagIds) {
153972
154544
  if (tagIds.length === 0) {
153973
154545
  return new Map;
153974
154546
  }
153975
- const placeholders = tagIds.map(() => "?").join(", ");
153976
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
154547
+ const placeholders3 = tagIds.map(() => "?").join(", ");
154548
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders3})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
153977
154549
  const sources = new Map;
153978
154550
  for (const row of rows) {
153979
154551
  sources.set(row.tag_id, row.content);
@@ -154283,8 +154855,8 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
154283
154855
  }
154284
154856
  return all;
154285
154857
  }
154286
- const placeholders = tagNumbers.map(() => "?").join(",");
154287
- const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
154858
+ const placeholders3 = tagNumbers.map(() => "?").join(",");
154859
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders3}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
154288
154860
  return rows.map(toTagEntry);
154289
154861
  }
154290
154862
  function getMaxDroppedTagNumber(db, sessionId) {
@@ -154437,6 +155009,7 @@ var init_storage = __esm(async () => {
154437
155009
  init_storage_source();
154438
155010
  init_storage_tags();
154439
155011
  init_storage_v22_backfill_failures();
155012
+ init_workspaces();
154440
155013
  await __promiseAll([
154441
155014
  init_message_index(),
154442
155015
  init_migrations(),
@@ -163888,7 +164461,7 @@ function isEmbeddingRow(row) {
163888
164461
  if (row === null || typeof row !== "object")
163889
164462
  return false;
163890
164463
  const candidate = row;
163891
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
164464
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding) && (candidate.modelId === null || typeof candidate.modelId === "string");
163892
164465
  }
163893
164466
  function toFloat32Array(blob) {
163894
164467
  if (blob instanceof Uint8Array) {
@@ -163908,7 +164481,7 @@ function getSaveEmbeddingStatement(db) {
163908
164481
  function getLoadAllEmbeddingsStatement(db) {
163909
164482
  let stmt = loadAllEmbeddingsStatements.get(db);
163910
164483
  if (!stmt) {
163911
- stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
164484
+ stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding, memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
163912
164485
  loadAllEmbeddingsStatements.set(db, stmt);
163913
164486
  }
163914
164487
  return stmt;
@@ -163937,7 +164510,10 @@ function loadAllEmbeddings(db, projectPath) {
163937
164510
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
163938
164511
  const embeddings = new Map;
163939
164512
  for (const row of rows) {
163940
- embeddings.set(row.memoryId, toFloat32Array(row.embedding));
164513
+ embeddings.set(row.memoryId, {
164514
+ embedding: toFloat32Array(row.embedding),
164515
+ modelId: row.modelId
164516
+ });
163941
164517
  }
163942
164518
  return embeddings;
163943
164519
  }
@@ -164000,13 +164576,13 @@ var init_embedding_cache = __esm(() => {
164000
164576
  });
164001
164577
 
164002
164578
  // src/features/magic-context/memory/normalize-hash.ts
164003
- import { createHash as createHash6 } from "node:crypto";
164579
+ import { createHash as createHash7 } from "node:crypto";
164004
164580
  function normalizeMemoryContent(content) {
164005
164581
  return content.toLowerCase().replace(/\s+/g, " ").trim();
164006
164582
  }
164007
164583
  function computeNormalizedHash(content) {
164008
164584
  const normalized = normalizeMemoryContent(content);
164009
- return createHash6("md5").update(normalized).digest("hex");
164585
+ return createHash7("md5").update(normalized).digest("hex");
164010
164586
  }
164011
164587
  var init_normalize_hash = () => {};
164012
164588
 
@@ -164120,8 +164696,8 @@ function getMemoriesByProjectStatement(db, statuses) {
164120
164696
  }
164121
164697
  let stmt = statements.get(db);
164122
164698
  if (!stmt) {
164123
- const placeholders = statuses.map(() => "?").join(", ");
164124
- stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
164699
+ const placeholders3 = statuses.map(() => "?").join(", ");
164700
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories WHERE project_path = ? AND status IN (${placeholders3}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
164125
164701
  statements.set(db, stmt);
164126
164702
  }
164127
164703
  return stmt;
@@ -164242,6 +164818,97 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
164242
164818
  const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, expiryCutoff).filter(isMemoryRow);
164243
164819
  return rows.map(toMemory);
164244
164820
  }
164821
+ function sqlPlaceholders(values) {
164822
+ return values.map(() => "?").join(", ");
164823
+ }
164824
+ function uniqueValues(values) {
164825
+ return [...new Set(values.filter((value) => value.length > 0))];
164826
+ }
164827
+ function buildWorkspaceMemorySqlFilter(args) {
164828
+ if (args.shareCategories === null || args.shareCategories === undefined) {
164829
+ return { clause: "", params: [], active: false };
164830
+ }
164831
+ const identities = uniqueValues(args.identities);
164832
+ const identitySet = new Set(identities);
164833
+ const ownSet = new Set(uniqueValues(args.ownIdentities ?? []).filter((identity) => identitySet.has(identity)));
164834
+ const foreignIdentities = identities.filter((identity) => !ownSet.has(identity));
164835
+ if (foreignIdentities.length === 0) {
164836
+ return { clause: "", params: [], active: false };
164837
+ }
164838
+ const ownIdentities = identities.filter((identity) => ownSet.has(identity));
164839
+ const shareCategories = uniqueValues([...args.shareCategories]);
164840
+ const qualifier = args.tableName ? `${args.tableName}.` : "";
164841
+ const predicates = [];
164842
+ const params = [];
164843
+ if (ownIdentities.length > 0) {
164844
+ predicates.push(`${qualifier}project_path IN (${sqlPlaceholders(ownIdentities)})`);
164845
+ params.push(...ownIdentities);
164846
+ }
164847
+ if (foreignIdentities.length > 0 && shareCategories.length > 0) {
164848
+ predicates.push(`(${qualifier}project_path IN (${sqlPlaceholders(foreignIdentities)}) AND ${qualifier}category IN (${sqlPlaceholders(shareCategories)}))`);
164849
+ params.push(...foreignIdentities, ...shareCategories);
164850
+ }
164851
+ if (predicates.length === 0) {
164852
+ return { clause: " AND 0 = 1", params: [], active: true };
164853
+ }
164854
+ return { clause: ` AND (${predicates.join(" OR ")})`, params, active: true };
164855
+ }
164856
+ function getMemoriesByProjects(db, projectPaths, statuses = ["active", "permanent"], expiryCutoff = Date.now(), ownIdentities, shareCategories) {
164857
+ const identities = uniqueValues(projectPaths);
164858
+ if (identities.length === 0 || statuses.length === 0)
164859
+ return [];
164860
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164861
+ identities,
164862
+ ownIdentities,
164863
+ shareCategories
164864
+ });
164865
+ if (identities.length === 1 && !sharingFilter.active) {
164866
+ return getMemoriesByProject(db, identities[0], statuses, expiryCutoff);
164867
+ }
164868
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164869
+ FROM memories
164870
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164871
+ AND status IN (${sqlPlaceholders(statuses)})
164872
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164873
+ ORDER BY category ASC, updated_at DESC, id ASC`).all(...identities, ...statuses, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164874
+ return rows.map(toMemory);
164875
+ }
164876
+ function getMaxMemoryIdForProjects(db, projectPaths, ownIdentities, shareCategories) {
164877
+ const identities = uniqueValues(projectPaths);
164878
+ if (identities.length === 0)
164879
+ return 0;
164880
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164881
+ identities,
164882
+ ownIdentities,
164883
+ shareCategories
164884
+ });
164885
+ if (identities.length === 1 && !sharingFilter.active) {
164886
+ const row2 = db.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM memories WHERE project_path = ?").get(identities[0]);
164887
+ return typeof row2?.max_id === "number" ? row2.max_id : 0;
164888
+ }
164889
+ const row = db.prepare(`SELECT COALESCE(MAX(id), 0) AS max_id
164890
+ FROM memories
164891
+ WHERE project_path IN (${sqlPlaceholders(identities)})${sharingFilter.clause}`).get(...identities, ...sharingFilter.params);
164892
+ return typeof row?.max_id === "number" ? row.max_id : 0;
164893
+ }
164894
+ function readNewMemoriesForM1Union(db, projectPaths, afterId, expiryCutoff, ownIdentities, shareCategories) {
164895
+ const identities = uniqueValues(projectPaths);
164896
+ if (identities.length === 0)
164897
+ return [];
164898
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
164899
+ identities,
164900
+ ownIdentities,
164901
+ shareCategories
164902
+ });
164903
+ const rows = db.prepare(`SELECT ${getMemorySelectColumns(db)}
164904
+ FROM memories
164905
+ WHERE project_path IN (${sqlPlaceholders(identities)})
164906
+ AND id > ?
164907
+ AND status IN ('active', 'permanent')
164908
+ AND (expires_at IS NULL OR expires_at > ?)${sharingFilter.clause}
164909
+ ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(...identities, afterId, expiryCutoff, ...sharingFilter.params).filter(isMemoryRow);
164910
+ return rows.map(toMemory);
164911
+ }
164245
164912
  function getAllActiveMemoriesForMigration(db, projectPath) {
164246
164913
  const rows = getActiveMemoriesNoExpiryStatement(db).all(projectPath).filter(isMemoryRow);
164247
164914
  return rows.map(toMemory);
@@ -164339,6 +165006,7 @@ function getMemoryCountsByStatus(db, projectPath) {
164339
165006
  }
164340
165007
  var COLUMN_MAP, MEMORY_CATEGORY_LOOKUP, MEMORY_STATUS_LOOKUP, MEMORY_SOURCE_TYPE_LOOKUP, VERIFICATION_STATUS_LOOKUP, insertMemoryStatements, getMemoryByHashStatements, getMemoryByIdStatements, activeMemoriesNoExpiryStatements, updateMemorySeenCountStatements, updateMemoryRetrievalCountStatements, updateMemoryStatusStatements, updateArchivedMemoryStatements, updateMemoryVerificationStatements, updateMemoryContentStatements, supersededMemoryStatements, mergeMemoryStatsStatements, deleteMemoryStatements, deleteMemoryEmbeddingStatements, deleteEmbeddingOnContentUpdateStatements, getMemoryCountStatements, getMemoryCountByProjectStatements, getMemoryCountsByStatusStatements, memoriesByProjectStatements, memoryImportanceColumnCache;
164341
165008
  var init_storage_memory = __esm(() => {
165009
+ init_constants();
164342
165010
  init_embedding_cache();
164343
165011
  init_normalize_hash();
164344
165012
  COLUMN_MAP = {
@@ -164444,8 +165112,8 @@ function getUserMemoryCandidates(db) {
164444
165112
  function deleteUserMemoryCandidates(db, ids) {
164445
165113
  if (ids.length === 0)
164446
165114
  return;
164447
- const placeholders = ids.map(() => "?").join(",");
164448
- db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
165115
+ const placeholders3 = ids.map(() => "?").join(",");
165116
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders3})`).run(...ids);
164449
165117
  }
164450
165118
  function insertUserMemory(db, content, sourceCandidateIds) {
164451
165119
  const now = Date.now();
@@ -164478,6 +165146,444 @@ function parseUserMemoryRow(row) {
164478
165146
  };
164479
165147
  }
164480
165148
 
165149
+ // src/features/magic-context/compartment-chunk-embedding.ts
165150
+ import { createHash as createHash8 } from "node:crypto";
165151
+ function getLoadFtsRowsStatement(db) {
165152
+ let stmt = loadFtsRowsStatements.get(db);
165153
+ if (!stmt) {
165154
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, role, content
165155
+ FROM message_history_fts
165156
+ WHERE session_id = ?
165157
+ AND message_ordinal >= ?
165158
+ AND message_ordinal <= ?
165159
+ AND role IN ('user', 'assistant')
165160
+ ORDER BY message_ordinal ASC`);
165161
+ loadFtsRowsStatements.set(db, stmt);
165162
+ }
165163
+ return stmt;
165164
+ }
165165
+ function getExistingHashStatement(db, scopedToProject) {
165166
+ const map2 = scopedToProject ? existingHashByProjectStatements : existingHashStatements;
165167
+ let stmt = map2.get(db);
165168
+ if (!stmt) {
165169
+ stmt = db.prepare(`SELECT window_index AS windowIndex, chunk_hash AS chunkHash
165170
+ FROM compartment_chunk_embeddings
165171
+ WHERE compartment_id = ?
165172
+ AND model_id = ?
165173
+ ${scopedToProject ? "AND project_path = ?" : ""}
165174
+ ORDER BY window_index ASC`);
165175
+ map2.set(db, stmt);
165176
+ }
165177
+ return stmt;
165178
+ }
165179
+ function getDeleteByCompartmentStatement(db) {
165180
+ let stmt = deleteByCompartmentStatements.get(db);
165181
+ if (!stmt) {
165182
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
165183
+ deleteByCompartmentStatements.set(db, stmt);
165184
+ }
165185
+ return stmt;
165186
+ }
165187
+ function getInsertEmbeddingStatement(db) {
165188
+ let stmt = insertEmbeddingStatements.get(db);
165189
+ if (!stmt) {
165190
+ stmt = db.prepare(`INSERT INTO compartment_chunk_embeddings (
165191
+ compartment_id, session_id, project_path, harness, window_index,
165192
+ start_ordinal, end_ordinal, chunk_hash, model_id, dims, vector, created_at
165193
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
165194
+ insertEmbeddingStatements.set(db, stmt);
165195
+ }
165196
+ return stmt;
165197
+ }
165198
+ function getDistinctModelStatement(db) {
165199
+ let stmt = distinctModelStatements.get(db);
165200
+ if (!stmt) {
165201
+ stmt = db.prepare(`SELECT DISTINCT model_id AS modelId
165202
+ FROM compartment_chunk_embeddings
165203
+ WHERE project_path = ?`);
165204
+ distinctModelStatements.set(db, stmt);
165205
+ }
165206
+ return stmt;
165207
+ }
165208
+ function getClearProjectStatement(db) {
165209
+ let stmt = clearProjectStatements.get(db);
165210
+ if (!stmt) {
165211
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ?");
165212
+ clearProjectStatements.set(db, stmt);
165213
+ }
165214
+ return stmt;
165215
+ }
165216
+ function getClearProjectModelStatement(db) {
165217
+ let stmt = clearProjectModelStatements.get(db);
165218
+ if (!stmt) {
165219
+ stmt = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE project_path = ? AND model_id = ?");
165220
+ clearProjectModelStatements.set(db, stmt);
165221
+ }
165222
+ return stmt;
165223
+ }
165224
+ function getSearchRowsStatement(db, withModel) {
165225
+ const map2 = withModel ? searchRowsByModelStatements : searchRowsStatements;
165226
+ let stmt = map2.get(db);
165227
+ if (!stmt) {
165228
+ stmt = db.prepare(`SELECT e.compartment_id AS compartmentId,
165229
+ e.session_id AS sessionId,
165230
+ c.title AS title,
165231
+ c.start_message AS compartmentStart,
165232
+ c.end_message AS compartmentEnd,
165233
+ e.window_index AS windowIndex,
165234
+ e.start_ordinal AS windowStart,
165235
+ e.end_ordinal AS windowEnd,
165236
+ e.chunk_hash AS chunkHash,
165237
+ e.model_id AS modelId,
165238
+ e.dims AS dims,
165239
+ e.vector AS vector
165240
+ FROM compartment_chunk_embeddings e
165241
+ JOIN compartments c ON c.id = e.compartment_id
165242
+ WHERE e.session_id = ?
165243
+ AND e.project_path = ?
165244
+ ${withModel ? "AND e.model_id = ?" : ""}
165245
+ ORDER BY e.compartment_id ASC, e.window_index ASC`);
165246
+ map2.set(db, stmt);
165247
+ }
165248
+ return stmt;
165249
+ }
165250
+ function isFinitePositiveInteger(value) {
165251
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
165252
+ }
165253
+ function normalizeCompartmentChunkMaxInputTokens(value) {
165254
+ if (!isFinitePositiveInteger(value)) {
165255
+ return DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
165256
+ }
165257
+ return Math.max(1, Math.floor(value));
165258
+ }
165259
+ function normalizeContent(text) {
165260
+ return text.replace(/\s+/g, " ").trim();
165261
+ }
165262
+ function formatOrdinalRange(start, end) {
165263
+ return start === end ? `[${start}]` : `[${start}-${end}]`;
165264
+ }
165265
+ function rolePrefix(role) {
165266
+ if (role === "user")
165267
+ return "U";
165268
+ if (role === "assistant")
165269
+ return "A";
165270
+ return null;
165271
+ }
165272
+ function parseOrdinal(value) {
165273
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
165274
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
165275
+ }
165276
+ function parseCanonicalLineRange(line) {
165277
+ const match = /^\[(\d+)(?:-(\d+))?\]\s+[UA]:/.exec(line.trim());
165278
+ if (!match)
165279
+ return null;
165280
+ const start = Number.parseInt(match[1], 10);
165281
+ const end = match[2] ? Number.parseInt(match[2], 10) : start;
165282
+ if (!Number.isFinite(start) || !Number.isFinite(end))
165283
+ return null;
165284
+ return { start, end };
165285
+ }
165286
+ function hashChunkText(text) {
165287
+ return createHash8("sha256").update(text).digest("hex");
165288
+ }
165289
+ function vectorBlob(vector) {
165290
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
165291
+ }
165292
+ function toFloat32Array2(blob) {
165293
+ if (blob instanceof Uint8Array) {
165294
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
165295
+ return new Float32Array(buffer2);
165296
+ }
165297
+ return new Float32Array(blob.slice(0));
165298
+ }
165299
+ function buildCanonicalChunkTextFromFts(db, sessionId, startOrdinal, endOrdinal) {
165300
+ if (endOrdinal < startOrdinal)
165301
+ return "";
165302
+ const rows = getLoadFtsRowsStatement(db).all(sessionId, startOrdinal, endOrdinal).map((row) => row);
165303
+ const lines = [];
165304
+ let current = null;
165305
+ const flush2 = () => {
165306
+ if (!current || current.parts.length === 0)
165307
+ return;
165308
+ lines.push(`${formatOrdinalRange(current.start, current.end)} ${current.role}: ${current.parts.join(" / ")}`);
165309
+ current = null;
165310
+ };
165311
+ for (const row of rows) {
165312
+ const ordinal = parseOrdinal(row.messageOrdinal);
165313
+ const prefix = rolePrefix(row.role);
165314
+ const content = typeof row.content === "string" ? normalizeContent(row.content) : "";
165315
+ if (ordinal === null || prefix === null || content.length === 0)
165316
+ continue;
165317
+ if (current && current.role === prefix) {
165318
+ current.end = ordinal;
165319
+ current.parts.push(content);
165320
+ continue;
165321
+ }
165322
+ flush2();
165323
+ current = { role: prefix, start: ordinal, end: ordinal, parts: [content] };
165324
+ }
165325
+ flush2();
165326
+ return lines.join(`
165327
+ `);
165328
+ }
165329
+ function canonicalizeInMemoryChunkTextForEmbedding(chunkText, startOrdinal, endOrdinal) {
165330
+ const lines = [];
165331
+ for (const rawLine of chunkText.split(/\r?\n/)) {
165332
+ const line = rawLine.trim();
165333
+ const match = /^(\[(\d+)(?:-(\d+))?\]\s+[UA]:)\s*(.*)$/.exec(line);
165334
+ if (!match)
165335
+ continue;
165336
+ const lineStart = Number.parseInt(match[2], 10);
165337
+ const lineEnd = match[3] ? Number.parseInt(match[3], 10) : lineStart;
165338
+ if (startOrdinal != null && lineEnd < startOrdinal)
165339
+ continue;
165340
+ if (endOrdinal != null && lineStart > endOrdinal)
165341
+ continue;
165342
+ const rawParts = match[4].split(" / ").map((part) => normalizeContent(part)).filter((part) => part.length > 0);
165343
+ const ordinalSpan = lineEnd - lineStart + 1;
165344
+ const roleLabel = match[1].slice(match[1].indexOf("]") + 2);
165345
+ if (ordinalSpan === rawParts.length) {
165346
+ const retained = rawParts.map((part, index) => ({ ordinal: lineStart + index, part })).filter(({ ordinal, part }) => {
165347
+ if (part.startsWith("TC:"))
165348
+ return false;
165349
+ if (startOrdinal != null && ordinal < startOrdinal)
165350
+ return false;
165351
+ if (endOrdinal != null && ordinal > endOrdinal)
165352
+ return false;
165353
+ return true;
165354
+ });
165355
+ if (retained.length === 0)
165356
+ continue;
165357
+ const retainedStart = retained[0].ordinal;
165358
+ const retainedEnd = retained[retained.length - 1].ordinal;
165359
+ lines.push(`${formatOrdinalRange(retainedStart, retainedEnd)} ${roleLabel} ${retained.map(({ part }) => part).join(" / ")}`);
165360
+ continue;
165361
+ }
165362
+ const parts = rawParts.filter((part) => !part.startsWith("TC:"));
165363
+ if (parts.length === 0)
165364
+ continue;
165365
+ lines.push(`${match[1]} ${parts.join(" / ")}`);
165366
+ }
165367
+ return lines.join(`
165368
+ `);
165369
+ }
165370
+ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTokens) {
165371
+ const lines = canonicalText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
165372
+ if (lines.length === 0 || endOrdinal < startOrdinal)
165373
+ return [];
165374
+ const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
165375
+ const fullText = lines.join(`
165376
+ `);
165377
+ if (estimateTokens(fullText) <= normalizedMax) {
165378
+ return [
165379
+ {
165380
+ windowIndex: 0,
165381
+ startOrdinal,
165382
+ endOrdinal,
165383
+ text: fullText,
165384
+ chunkHash: hashChunkText(fullText)
165385
+ }
165386
+ ];
165387
+ }
165388
+ const windows = [];
165389
+ let currentLines = [];
165390
+ let currentStart = null;
165391
+ let currentEnd = null;
165392
+ let currentTokens = 0;
165393
+ const flush2 = () => {
165394
+ if (currentLines.length === 0 || currentStart === null || currentEnd === null)
165395
+ return;
165396
+ const text = currentLines.join(`
165397
+ `);
165398
+ windows.push({
165399
+ windowIndex: windows.length + 1,
165400
+ startOrdinal: currentStart,
165401
+ endOrdinal: currentEnd,
165402
+ text,
165403
+ chunkHash: hashChunkText(text)
165404
+ });
165405
+ currentLines = [];
165406
+ currentStart = null;
165407
+ currentEnd = null;
165408
+ currentTokens = 0;
165409
+ };
165410
+ for (const line of lines) {
165411
+ const range = parseCanonicalLineRange(line);
165412
+ const lineStart = range?.start ?? startOrdinal;
165413
+ const lineEnd = range?.end ?? lineStart;
165414
+ const lineTokens = estimateTokens(line);
165415
+ if (currentLines.length > 0 && currentTokens + lineTokens > normalizedMax) {
165416
+ flush2();
165417
+ }
165418
+ if (currentLines.length === 0) {
165419
+ currentStart = lineStart;
165420
+ }
165421
+ currentLines.push(line);
165422
+ currentEnd = lineEnd;
165423
+ currentTokens += lineTokens;
165424
+ }
165425
+ flush2();
165426
+ return windows;
165427
+ }
165428
+ function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
165429
+ const scoped = typeof projectPath === "string" && projectPath.length > 0;
165430
+ const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
165431
+ return new Map(rows.filter((row) => typeof row.windowIndex === "number" && typeof row.chunkHash === "string").map((row) => [row.windowIndex, row.chunkHash]));
165432
+ }
165433
+ function chunkEmbeddingWindowsAreCurrent(db, compartmentId, modelId, windows, projectPath) {
165434
+ const existing = getExistingChunkHashes(db, compartmentId, modelId, projectPath);
165435
+ if (existing.size !== windows.length)
165436
+ return false;
165437
+ return windows.every((window) => existing.get(window.windowIndex) === window.chunkHash);
165438
+ }
165439
+ function replaceCompartmentChunkEmbeddings(db, rows) {
165440
+ if (rows.length === 0)
165441
+ return;
165442
+ const compartmentId = rows[0].compartmentId;
165443
+ const now = Date.now();
165444
+ db.transaction(() => {
165445
+ getDeleteByCompartmentStatement(db).run(compartmentId);
165446
+ const insert = getInsertEmbeddingStatement(db);
165447
+ for (const row of rows) {
165448
+ insert.run(row.compartmentId, row.sessionId, row.projectPath, getHarness(), row.window.windowIndex, row.window.startOrdinal, row.window.endOrdinal, row.window.chunkHash, row.modelId, row.vector.length, vectorBlob(row.vector), row.createdAt ?? now);
165449
+ }
165450
+ })();
165451
+ }
165452
+ function getDistinctChunkEmbeddingModelIds(db, projectPath) {
165453
+ const rows = getDistinctModelStatement(db).all(projectPath);
165454
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165455
+ }
165456
+ function clearChunkEmbeddingsForProject(db, projectPath, modelId) {
165457
+ if (modelId) {
165458
+ return getClearProjectModelStatement(db).run(projectPath, modelId).changes;
165459
+ }
165460
+ return getClearProjectStatement(db).run(projectPath).changes;
165461
+ }
165462
+ function loadCompartmentChunkEmbeddingsForSearch(db, sessionId, projectPath, modelId) {
165463
+ const rows = modelId ? getSearchRowsStatement(db, true).all(sessionId, projectPath, modelId) : getSearchRowsStatement(db, false).all(sessionId, projectPath);
165464
+ return rows.filter((row) => typeof row.compartmentId === "number" && typeof row.sessionId === "string" && typeof row.title === "string" && typeof row.compartmentStart === "number" && typeof row.compartmentEnd === "number" && typeof row.windowIndex === "number" && typeof row.windowStart === "number" && typeof row.windowEnd === "number" && typeof row.chunkHash === "string" && typeof row.modelId === "string" && typeof row.dims === "number" && (row.vector instanceof Uint8Array || row.vector instanceof ArrayBuffer)).map((row) => ({
165465
+ compartmentId: row.compartmentId,
165466
+ sessionId: row.sessionId,
165467
+ title: row.title,
165468
+ startOrdinal: row.compartmentStart,
165469
+ endOrdinal: row.compartmentEnd,
165470
+ windowIndex: row.windowIndex,
165471
+ windowStartOrdinal: row.windowStart,
165472
+ windowEndOrdinal: row.windowEnd,
165473
+ chunkHash: row.chunkHash,
165474
+ modelId: row.modelId,
165475
+ dims: row.dims,
165476
+ vector: toFloat32Array2(row.vector)
165477
+ }));
165478
+ }
165479
+ function mapBackfillCandidateRows(rows) {
165480
+ return rows.filter((row) => {
165481
+ if (row === null || typeof row !== "object")
165482
+ return false;
165483
+ const candidate = row;
165484
+ return typeof candidate.id === "number" && typeof candidate.sessionId === "string" && typeof candidate.startMessage === "number" && typeof candidate.endMessage === "number" && typeof candidate.title === "string";
165485
+ }).map((row) => ({
165486
+ id: row.id,
165487
+ sessionId: row.sessionId,
165488
+ startMessage: row.startMessage,
165489
+ endMessage: row.endMessage,
165490
+ title: row.title
165491
+ }));
165492
+ }
165493
+ function loadUnembeddedSessionChunkCandidates(db, projectPath, sessionId, modelId, limit, excludeIds) {
165494
+ if (excludeIds && excludeIds.length > 0) {
165495
+ const placeholders3 = excludeIds.map(() => "?").join(", ");
165496
+ const stmt2 = db.prepare(`SELECT c.id AS id,
165497
+ c.session_id AS sessionId,
165498
+ c.start_message AS startMessage,
165499
+ c.end_message AS endMessage,
165500
+ c.title AS title
165501
+ FROM compartments c
165502
+ JOIN session_projects sp
165503
+ ON sp.session_id = c.session_id
165504
+ AND sp.harness = c.harness
165505
+ AND sp.project_path = ?
165506
+ WHERE c.session_id = ?
165507
+ AND c.start_message IS NOT NULL
165508
+ AND c.end_message IS NOT NULL
165509
+ AND c.id NOT IN (${placeholders3})
165510
+ AND NOT EXISTS (
165511
+ SELECT 1
165512
+ FROM compartment_chunk_embeddings current
165513
+ WHERE current.compartment_id = c.id
165514
+ AND current.project_path = ?
165515
+ AND current.model_id = ?
165516
+ )
165517
+ ORDER BY c.start_message ASC, c.id ASC
165518
+ LIMIT ?`);
165519
+ const rows2 = stmt2.all(projectPath, sessionId, ...excludeIds, projectPath, modelId, Math.max(1, limit));
165520
+ return mapBackfillCandidateRows(rows2);
165521
+ }
165522
+ let stmt = sessionBackfillCandidateStatements.get(db);
165523
+ if (!stmt) {
165524
+ stmt = db.prepare(`SELECT c.id AS id,
165525
+ c.session_id AS sessionId,
165526
+ c.start_message AS startMessage,
165527
+ c.end_message AS endMessage,
165528
+ c.title AS title
165529
+ FROM compartments c
165530
+ JOIN session_projects sp
165531
+ ON sp.session_id = c.session_id
165532
+ AND sp.harness = c.harness
165533
+ AND sp.project_path = ?
165534
+ WHERE c.session_id = ?
165535
+ AND c.start_message IS NOT NULL
165536
+ AND c.end_message IS NOT NULL
165537
+ AND NOT EXISTS (
165538
+ SELECT 1
165539
+ FROM compartment_chunk_embeddings current
165540
+ WHERE current.compartment_id = c.id
165541
+ AND current.project_path = ?
165542
+ AND current.model_id = ?
165543
+ )
165544
+ ORDER BY c.start_message ASC, c.id ASC
165545
+ LIMIT ?`);
165546
+ sessionBackfillCandidateStatements.set(db, stmt);
165547
+ }
165548
+ const rows = stmt.all(projectPath, sessionId, projectPath, modelId, Math.max(1, limit));
165549
+ return mapBackfillCandidateRows(rows);
165550
+ }
165551
+ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId) {
165552
+ const row = db.prepare(`SELECT COUNT(*) AS n
165553
+ FROM compartments c
165554
+ JOIN session_projects sp
165555
+ ON sp.session_id = c.session_id
165556
+ AND sp.harness = c.harness
165557
+ AND sp.project_path = ?
165558
+ WHERE c.session_id = ?
165559
+ AND c.start_message IS NOT NULL
165560
+ AND c.end_message IS NOT NULL
165561
+ AND NOT EXISTS (
165562
+ SELECT 1
165563
+ FROM compartment_chunk_embeddings current
165564
+ WHERE current.compartment_id = c.id
165565
+ AND current.project_path = ?
165566
+ AND current.model_id = ?
165567
+ )`).get(projectPath, sessionId, projectPath, modelId);
165568
+ return typeof row?.n === "number" ? row.n : 0;
165569
+ }
165570
+ var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
165571
+ var init_compartment_chunk_embedding = __esm(() => {
165572
+ init_read_session_formatting();
165573
+ loadFtsRowsStatements = new WeakMap;
165574
+ existingHashStatements = new WeakMap;
165575
+ existingHashByProjectStatements = new WeakMap;
165576
+ deleteByCompartmentStatements = new WeakMap;
165577
+ insertEmbeddingStatements = new WeakMap;
165578
+ distinctModelStatements = new WeakMap;
165579
+ clearProjectStatements = new WeakMap;
165580
+ clearProjectModelStatements = new WeakMap;
165581
+ searchRowsStatements = new WeakMap;
165582
+ searchRowsByModelStatements = new WeakMap;
165583
+ backfillCandidateStatements = new WeakMap;
165584
+ sessionBackfillCandidateStatements = new WeakMap;
165585
+ });
165586
+
164481
165587
  // src/features/magic-context/memory/cosine-similarity.ts
164482
165588
  function cosineSimilarity(a, b) {
164483
165589
  if (a.length !== b.length) {
@@ -164635,19 +165741,19 @@ function isArrayLikeNumber(value) {
164635
165741
  }
164636
165742
  return arr.length === 0 || typeof arr[0] === "number";
164637
165743
  }
164638
- function toFloat32Array2(values) {
165744
+ function toFloat32Array3(values) {
164639
165745
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
164640
165746
  }
164641
165747
  function extractBatchEmbeddings(result, expectedCount) {
164642
165748
  const { data } = result;
164643
165749
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
164644
- return data.map((entry) => toFloat32Array2(entry));
165750
+ return data.map((entry) => toFloat32Array3(entry));
164645
165751
  }
164646
165752
  if (!isArrayLikeNumber(data)) {
164647
165753
  log("[magic-context] embedding batch returned unexpected data shape");
164648
165754
  return Array.from({ length: expectedCount }, () => null);
164649
165755
  }
164650
- const flatData = toFloat32Array2(data);
165756
+ const flatData = toFloat32Array3(data);
164651
165757
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
164652
165758
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
164653
165759
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -164662,6 +165768,7 @@ function extractBatchEmbeddings(result, expectedCount) {
164662
165768
 
164663
165769
  class LocalEmbeddingProvider {
164664
165770
  modelId;
165771
+ maxInputTokens;
164665
165772
  model;
164666
165773
  pipeline = null;
164667
165774
  initPromise = null;
@@ -164669,8 +165776,9 @@ class LocalEmbeddingProvider {
164669
165776
  disposing = false;
164670
165777
  disposePromise = null;
164671
165778
  inFlightWaiters = [];
164672
- constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
165779
+ constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL, maxInputTokens = 512) {
164673
165780
  this.model = model;
165781
+ this.maxInputTokens = maxInputTokens;
164674
165782
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
164675
165783
  }
164676
165784
  async initialize() {
@@ -164930,6 +166038,7 @@ function normalizeEndpoint3(endpoint) {
164930
166038
 
164931
166039
  class OpenAICompatibleEmbeddingProvider {
164932
166040
  modelId;
166041
+ maxInputTokens;
164933
166042
  endpoint;
164934
166043
  model;
164935
166044
  apiKey;
@@ -164946,11 +166055,13 @@ class OpenAICompatibleEmbeddingProvider {
164946
166055
  this.apiKey = options.apiKey?.trim() ?? "";
164947
166056
  this.inputType = options.inputType?.trim() ?? "";
164948
166057
  this.truncate = options.truncate?.trim() ?? "";
166058
+ this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
164949
166059
  this.modelId = getEmbeddingProviderIdentity({
164950
166060
  provider: "openai-compatible",
164951
166061
  endpoint: this.endpoint,
164952
166062
  model: this.model,
164953
- ...this.apiKey ? { api_key: this.apiKey } : {}
166063
+ ...this.apiKey ? { api_key: this.apiKey } : {},
166064
+ ...this.inputType ? { input_type: this.inputType } : {}
164954
166065
  });
164955
166066
  }
164956
166067
  async initialize() {
@@ -165194,12 +166305,12 @@ function getCountEmbeddedStatement(db) {
165194
166305
  }
165195
166306
  return stmt;
165196
166307
  }
165197
- function getClearProjectStatement(db) {
165198
- let stmt = clearProjectStatements.get(db);
166308
+ function getClearProjectStatement2(db) {
166309
+ let stmt = clearProjectStatements2.get(db);
165199
166310
  if (!stmt) {
165200
166311
  stmt = db.prepare(`DELETE FROM git_commit_embeddings
165201
166312
  WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
165202
- clearProjectStatements.set(db, stmt);
166313
+ clearProjectStatements2.set(db, stmt);
165203
166314
  }
165204
166315
  return stmt;
165205
166316
  }
@@ -165235,19 +166346,19 @@ function countEmbeddedCommits(db, projectPath) {
165235
166346
  return row?.count ?? 0;
165236
166347
  }
165237
166348
  function clearProjectCommitEmbeddings(db, projectPath) {
165238
- return getClearProjectStatement(db).run(projectPath).changes;
166349
+ return getClearProjectStatement2(db).run(projectPath).changes;
165239
166350
  }
165240
166351
  function getDistinctCommitEmbeddingModelIds(db, projectPath) {
165241
166352
  const rows = getDistinctModelIdStatement(db).all(projectPath);
165242
166353
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
165243
166354
  }
165244
- var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements, distinctModelIdStatements;
166355
+ var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements2, distinctModelIdStatements;
165245
166356
  var init_storage_git_commit_embeddings = __esm(() => {
165246
166357
  saveStatements = new WeakMap;
165247
166358
  loadProjectStatements = new WeakMap;
165248
166359
  loadUnembeddedStatements = new WeakMap;
165249
166360
  countEmbeddedStatements = new WeakMap;
165250
- clearProjectStatements = new WeakMap;
166361
+ clearProjectStatements2 = new WeakMap;
165251
166362
  distinctModelIdStatements = new WeakMap;
165252
166363
  });
165253
166364
 
@@ -165373,13 +166484,90 @@ var init_sweep_coordinator = __esm(() => {
165373
166484
  GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
165374
166485
  });
165375
166486
 
166487
+ // src/features/magic-context/session-project-storage.ts
166488
+ function getUpsertSessionProjectStatement(db) {
166489
+ let stmt = upsertSessionProjectStatements.get(db);
166490
+ if (!stmt) {
166491
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
166492
+ VALUES (?, ?, ?, ?)
166493
+ ON CONFLICT(session_id, harness) DO UPDATE SET
166494
+ project_path = excluded.project_path,
166495
+ updated_at = excluded.updated_at
166496
+ WHERE session_projects.project_path <> excluded.project_path`);
166497
+ upsertSessionProjectStatements.set(db, stmt);
166498
+ }
166499
+ return stmt;
166500
+ }
166501
+ function getRepairSessionChunkProjectStatement(db) {
166502
+ let stmt = repairSessionChunkProjectStatements.get(db);
166503
+ if (!stmt) {
166504
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166505
+ SET project_path = ?
166506
+ WHERE session_id = ?
166507
+ AND harness = ?
166508
+ AND project_path <> ?`);
166509
+ repairSessionChunkProjectStatements.set(db, stmt);
166510
+ }
166511
+ return stmt;
166512
+ }
166513
+ function getRepairProjectChunkProjectStatement(db) {
166514
+ let stmt = repairProjectChunkProjectStatements.get(db);
166515
+ if (!stmt) {
166516
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
166517
+ SET project_path = (
166518
+ SELECT sp.project_path
166519
+ FROM session_projects sp
166520
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166521
+ AND sp.harness = compartment_chunk_embeddings.harness
166522
+ LIMIT 1
166523
+ )
166524
+ WHERE EXISTS (
166525
+ SELECT 1
166526
+ FROM session_projects sp
166527
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
166528
+ AND sp.harness = compartment_chunk_embeddings.harness
166529
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
166530
+ AND (
166531
+ sp.project_path = ?
166532
+ OR compartment_chunk_embeddings.project_path = ?
166533
+ )
166534
+ )`);
166535
+ repairProjectChunkProjectStatements.set(db, stmt);
166536
+ }
166537
+ return stmt;
166538
+ }
166539
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
166540
+ if (!sessionId || !projectPath)
166541
+ return;
166542
+ const harness = getHarness();
166543
+ const now = Date.now();
166544
+ db.transaction(() => {
166545
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
166546
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
166547
+ })();
166548
+ }
166549
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
166550
+ if (!projectPath)
166551
+ return 0;
166552
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
166553
+ }
166554
+ var upsertSessionProjectStatements, repairSessionChunkProjectStatements, repairProjectChunkProjectStatements;
166555
+ var init_session_project_storage = __esm(() => {
166556
+ upsertSessionProjectStatements = new WeakMap;
166557
+ repairSessionChunkProjectStatements = new WeakMap;
166558
+ repairProjectChunkProjectStatements = new WeakMap;
166559
+ });
166560
+
165376
166561
  // src/features/magic-context/project-embedding-registry.ts
165377
- import { createHash as createHash7, randomUUID } from "node:crypto";
166562
+ import { createHash as createHash9, randomUUID } from "node:crypto";
165378
166563
  function resolveEmbeddingConfig(config2) {
165379
166564
  if (!config2 || config2.provider === "local") {
165380
166565
  return {
165381
166566
  provider: "local",
165382
- model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
166567
+ model: config2?.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
166568
+ ...config2?.max_input_tokens ? {
166569
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166570
+ } : {}
165383
166571
  };
165384
166572
  }
165385
166573
  if (config2.provider === "openai-compatible") {
@@ -165392,7 +166580,10 @@ function resolveEmbeddingConfig(config2) {
165392
166580
  endpoint: config2.endpoint.trim(),
165393
166581
  ...apiKey ? { api_key: apiKey } : {},
165394
166582
  ...inputType ? { input_type: inputType } : {},
165395
- ...truncate ? { truncate } : {}
166583
+ ...truncate ? { truncate } : {},
166584
+ ...config2.max_input_tokens ? {
166585
+ max_input_tokens: normalizeCompartmentChunkMaxInputTokens(config2.max_input_tokens)
166586
+ } : {}
165396
166587
  };
165397
166588
  }
165398
166589
  return { provider: "off" };
@@ -165410,10 +166601,11 @@ function createProvider(config2) {
165410
166601
  model: config2.model,
165411
166602
  apiKey: config2.api_key,
165412
166603
  inputType: config2.input_type,
165413
- truncate: config2.truncate
166604
+ truncate: config2.truncate,
166605
+ maxInputTokens: config2.max_input_tokens
165414
166606
  });
165415
166607
  }
165416
- return new LocalEmbeddingProvider(config2.model);
166608
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165417
166609
  }
165418
166610
  function stableStringify2(value) {
165419
166611
  if (Array.isArray(value)) {
@@ -165426,7 +166618,7 @@ function stableStringify2(value) {
165426
166618
  return JSON.stringify(value);
165427
166619
  }
165428
166620
  function sha256Prefix(value, length = 16) {
165429
- return createHash7("sha256").update(value).digest("hex").slice(0, length);
166621
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
165430
166622
  }
165431
166623
  function getRuntimeFingerprint(config2) {
165432
166624
  if (config2.provider === "off") {
@@ -165434,6 +166626,18 @@ function getRuntimeFingerprint(config2) {
165434
166626
  }
165435
166627
  return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
165436
166628
  }
166629
+ function getChunkEmbeddingModelId(config2, providerIdentity) {
166630
+ if (config2.provider === "off") {
166631
+ return OFF_PROVIDER_IDENTITY;
166632
+ }
166633
+ const chunkIdentity = {
166634
+ providerIdentity,
166635
+ chunkerVersion: 1,
166636
+ maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
166637
+ truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
166638
+ };
166639
+ return `${providerIdentity}:chunk:${sha256Prefix(stableStringify2(chunkIdentity))}`;
166640
+ }
165437
166641
  function sameFeatures(a, b) {
165438
166642
  return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
165439
166643
  }
@@ -165450,7 +166654,8 @@ function snapshotFor(registration) {
165450
166654
  features: { ...registration.features },
165451
166655
  enabled,
165452
166656
  gitCommitEnabled,
165453
- modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
166657
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
166658
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
165454
166659
  };
165455
166660
  }
165456
166661
  function disposeProvider(provider) {
@@ -165470,7 +166675,7 @@ function anyStoredModelIdIsStale(storedIds, currentId) {
165470
166675
  }
165471
166676
  return false;
165472
166677
  }
165473
- function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
166678
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, currentChunkIdentity, features) {
165474
166679
  if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
165475
166680
  return false;
165476
166681
  }
@@ -165491,6 +166696,14 @@ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity,
165491
166696
  wiped = true;
165492
166697
  }
165493
166698
  }
166699
+ if (features.memoryEnabled) {
166700
+ repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
166701
+ const chunkIds = getDistinctChunkEmbeddingModelIds(db, projectIdentity);
166702
+ if (anyStoredModelIdIsStale(chunkIds, currentChunkIdentity)) {
166703
+ clearChunkEmbeddingsForProject(db, projectIdentity);
166704
+ wiped = true;
166705
+ }
166706
+ }
165494
166707
  })();
165495
166708
  return wiped;
165496
166709
  }
@@ -165498,10 +166711,11 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165498
166711
  const resolvedConfig = resolveEmbeddingConfig(config2);
165499
166712
  const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
165500
166713
  const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
166714
+ const chunkModelId = getChunkEmbeddingModelId(resolvedConfig, providerIdentity);
165501
166715
  const prior = projectRegistrations.get(projectIdentity);
165502
166716
  const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
165503
- const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
165504
- const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
166717
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, chunkModelId, features);
166718
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || prior.chunkModelId !== chunkModelId || !sameFeatures(prior.features, features) || wiped;
165505
166719
  const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
165506
166720
  const registration = {
165507
166721
  projectIdentity,
@@ -165513,6 +166727,7 @@ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, feat
165513
166727
  generation,
165514
166728
  features: { ...features },
165515
166729
  modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
166730
+ chunkModelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : chunkModelId,
165516
166731
  observationMode: false
165517
166732
  };
165518
166733
  projectRegistrations.set(projectIdentity, registration);
@@ -165535,6 +166750,7 @@ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory,
165535
166750
  generation,
165536
166751
  features: { memoryEnabled: false, gitCommitEnabled: false },
165537
166752
  modelId: "off",
166753
+ chunkModelId: "off",
165538
166754
  observationMode: true
165539
166755
  };
165540
166756
  projectRegistrations.set(projectIdentity, registration);
@@ -165545,6 +166761,15 @@ function getProjectEmbeddingSnapshot(projectIdentity) {
165545
166761
  const registration = projectRegistrations.get(projectIdentity);
165546
166762
  return registration ? snapshotFor(registration) : null;
165547
166763
  }
166764
+ function getProjectChunkEmbeddingModelId(projectIdentity) {
166765
+ const registration = projectRegistrations.get(projectIdentity);
166766
+ return registration && !registration.observationMode ? registration.chunkModelId : "off";
166767
+ }
166768
+ function getProjectEmbeddingMaxInputTokens(projectIdentity) {
166769
+ const registration = projectRegistrations.get(projectIdentity);
166770
+ const configMax = registration?.config && "max_input_tokens" in registration.config ? registration.config.max_input_tokens : undefined;
166771
+ return normalizeCompartmentChunkMaxInputTokens(registration?.provider?.maxInputTokens ?? configMax);
166772
+ }
165548
166773
  function getOrCreateProjectProvider(registration) {
165549
166774
  if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
165550
166775
  return null;
@@ -165639,10 +166864,136 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
165639
166864
  return 0;
165640
166865
  }
165641
166866
  }
165642
- var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
166867
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
166868
+ const noWork = [];
166869
+ if (candidates.length === 0)
166870
+ return { embedded: 0, noWork };
166871
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
166872
+ const prepared = [];
166873
+ for (const candidate of candidates) {
166874
+ const canonicalText = buildCanonicalChunkTextFromFts(db, candidate.sessionId, candidate.startMessage, candidate.endMessage);
166875
+ if (canonicalText.length === 0) {
166876
+ noWork.push(candidate.id);
166877
+ continue;
166878
+ }
166879
+ const windows = chunkCanonicalText(canonicalText, candidate.startMessage, candidate.endMessage, maxInputTokens);
166880
+ if (windows.length === 0 || chunkEmbeddingWindowsAreCurrent(db, candidate.id, modelId, windows, projectIdentity)) {
166881
+ noWork.push(candidate.id);
166882
+ continue;
166883
+ }
166884
+ prepared.push({ candidate, windows });
166885
+ }
166886
+ if (prepared.length === 0)
166887
+ return { embedded: 0, noWork };
166888
+ let embedded = 0;
166889
+ let i = 0;
166890
+ while (i < prepared.length) {
166891
+ if (signal?.aborted)
166892
+ break;
166893
+ const slice = [];
166894
+ let windowCount = 0;
166895
+ do {
166896
+ const item = prepared[i];
166897
+ slice.push(item);
166898
+ windowCount += item.windows.length;
166899
+ i += 1;
166900
+ } while (i < prepared.length && windowCount + prepared[i].windows.length <= MAX_WINDOWS_PER_EMBED_CALL);
166901
+ const texts = [];
166902
+ for (const item of slice)
166903
+ texts.push(...item.windows.map((w) => w.text));
166904
+ try {
166905
+ const result = await embedBatchForProject(projectIdentity, texts, signal);
166906
+ if (!result)
166907
+ continue;
166908
+ if (signal?.aborted)
166909
+ break;
166910
+ let offset = 0;
166911
+ for (const item of slice) {
166912
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
166913
+ offset += item.windows.length;
166914
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
166915
+ continue;
166916
+ }
166917
+ const rows = item.windows.map((window, index) => ({
166918
+ compartmentId: item.candidate.id,
166919
+ sessionId: item.candidate.sessionId,
166920
+ projectPath: projectIdentity,
166921
+ window,
166922
+ modelId,
166923
+ vector: vectors[index]
166924
+ }));
166925
+ replaceCompartmentChunkEmbeddings(db, rows);
166926
+ embedded += 1;
166927
+ }
166928
+ } catch (error51) {
166929
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
166930
+ }
166931
+ }
166932
+ return { embedded, noWork };
166933
+ }
166934
+ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
166935
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
166936
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
166937
+ return { status: "disabled", embedded: 0, total: 0 };
166938
+ }
166939
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
166940
+ const total = countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId);
166941
+ if (total === 0)
166942
+ return { status: "nothing", embedded: 0, total: 0 };
166943
+ const holderId = `session-embed-${randomUUID()}`;
166944
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
166945
+ if (!lease2.acquired)
166946
+ return { status: "busy", embedded: 0, total };
166947
+ const renewal = setInterval(() => {
166948
+ try {
166949
+ renewGitSweepLease(db, projectIdentity, holderId);
166950
+ } catch {}
166951
+ }, SESSION_EMBED_LEASE_RENEWAL_MS);
166952
+ renewal.unref?.();
166953
+ const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
166954
+ const skipIds = [];
166955
+ let embedded = 0;
166956
+ let aborted2 = false;
166957
+ let providerStalled = false;
166958
+ try {
166959
+ options?.onProgress?.({ embedded, total });
166960
+ for (;; ) {
166961
+ if (options?.signal?.aborted) {
166962
+ aborted2 = true;
166963
+ break;
166964
+ }
166965
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
166966
+ if (candidates.length === 0)
166967
+ break;
166968
+ const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
166969
+ for (const id of noWork)
166970
+ skipIds.push(id);
166971
+ if (n === 0 && noWork.length === 0) {
166972
+ providerStalled = true;
166973
+ break;
166974
+ }
166975
+ embedded += n;
166976
+ options?.onProgress?.({ embedded: Math.min(embedded, total), total });
166977
+ await new Promise((resolve6) => setTimeout(resolve6, 0));
166978
+ }
166979
+ } finally {
166980
+ clearInterval(renewal);
166981
+ releaseGitSweepLease(db, projectIdentity, holderId);
166982
+ }
166983
+ if (aborted2)
166984
+ return { status: "aborted", embedded, total };
166985
+ if (providerStalled) {
166986
+ const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
166987
+ if (remaining > 0)
166988
+ return { status: "stalled", embedded, total, remaining };
166989
+ }
166990
+ return { status: "done", embedded, total };
166991
+ }
166992
+ var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, CHUNK_DRAIN_BATCH_SIZE = 8, MAX_WINDOWS_PER_EMBED_CALL = 16, SESSION_EMBED_LEASE_RENEWAL_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
165643
166993
  var init_project_embedding_registry = __esm(() => {
165644
166994
  init_magic_context();
165645
166995
  init_logger();
166996
+ init_compartment_chunk_embedding();
165646
166997
  init_storage_git_commit_embeddings();
165647
166998
  init_sweep_coordinator();
165648
166999
  init_embedding_cache();
@@ -165650,7 +167001,9 @@ var init_project_embedding_registry = __esm(() => {
165650
167001
  init_embedding_local();
165651
167002
  init_embedding_openai();
165652
167003
  init_storage_memory_embeddings();
167004
+ init_session_project_storage();
165653
167005
  SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167006
+ SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
165654
167007
  projectRegistrations = new Map;
165655
167008
  loadUnembeddedMemoriesStatements = new WeakMap;
165656
167009
  });
@@ -165666,10 +167019,11 @@ function createProvider2(config2) {
165666
167019
  model: config2.model,
165667
167020
  apiKey: config2.api_key,
165668
167021
  inputType: config2.input_type,
165669
- truncate: config2.truncate
167022
+ truncate: config2.truncate,
167023
+ maxInputTokens: config2.max_input_tokens
165670
167024
  });
165671
167025
  }
165672
- return new LocalEmbeddingProvider(config2.model);
167026
+ return new LocalEmbeddingProvider(config2.model, config2.max_input_tokens);
165673
167027
  }
165674
167028
  function getOrCreateProvider() {
165675
167029
  if (provider) {
@@ -165695,6 +167049,7 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
165695
167049
  var init_embedding = __esm(() => {
165696
167050
  init_magic_context();
165697
167051
  init_logger();
167052
+ init_compartment_chunk_embedding();
165698
167053
  init_embedding_identity();
165699
167054
  init_embedding_local();
165700
167055
  init_embedding_openai();
@@ -165718,6 +167073,23 @@ function getSearchStatement(db) {
165718
167073
  }
165719
167074
  return stmt;
165720
167075
  }
167076
+ function getUnionSearchStatement(db, arity) {
167077
+ let statements = unionSearchStatements.get(arity);
167078
+ if (!statements) {
167079
+ statements = new WeakMap;
167080
+ unionSearchStatements.set(arity, statements);
167081
+ }
167082
+ let stmt = statements.get(db);
167083
+ if (!stmt) {
167084
+ const placeholders3 = Array.from({ length: arity }, () => "?").join(", ");
167085
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${placeholders3}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
167086
+ statements.set(db, stmt);
167087
+ }
167088
+ return stmt;
167089
+ }
167090
+ function uniqueProjectPaths2(projectPaths) {
167091
+ return [...new Set(projectPaths.filter((path6) => path6.length > 0))];
167092
+ }
165721
167093
  function sanitizeFtsQuery(query) {
165722
167094
  const tokens = query.split(/\s+/).filter((token) => token.length > 0);
165723
167095
  if (tokens.length === 0)
@@ -165736,10 +167108,33 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
165736
167108
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
165737
167109
  return rows.map(toMemory);
165738
167110
  }
165739
- var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
167111
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories) {
167112
+ const identities = uniqueProjectPaths2(projectPaths);
167113
+ if (identities.length === 0)
167114
+ return [];
167115
+ const sharingFilter = buildWorkspaceMemorySqlFilter({
167116
+ identities,
167117
+ ownIdentities,
167118
+ shareCategories,
167119
+ tableName: "memories"
167120
+ });
167121
+ if (identities.length === 1 && !sharingFilter.active) {
167122
+ return searchMemoriesFTS(db, identities[0], query, limit);
167123
+ }
167124
+ const trimmedQuery = query.trim();
167125
+ if (trimmedQuery.length === 0 || limit <= 0)
167126
+ return [];
167127
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
167128
+ if (sanitized.length === 0)
167129
+ return [];
167130
+ const rows = sharingFilter.active ? db.prepare(`SELECT ${getMemorySelectColumns(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${identities.map(() => "?").join(", ")}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ?${sharingFilter.clause} ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`).all(...identities, Date.now(), sanitized, ...sharingFilter.params, limit).filter(isMemoryRow) : getUnionSearchStatement(db, identities.length).all(...identities, Date.now(), sanitized, limit).filter(isMemoryRow);
167131
+ return rows.map(toMemory);
167132
+ }
167133
+ var DEFAULT_SEARCH_LIMIT = 10, searchStatements, unionSearchStatements;
165740
167134
  var init_storage_memory_fts = __esm(() => {
165741
167135
  init_storage_memory();
165742
167136
  searchStatements = new WeakMap;
167137
+ unionSearchStatements = new Map;
165743
167138
  });
165744
167139
 
165745
167140
  // src/shared/models-dev-cache.ts
@@ -165933,26 +167328,54 @@ var init_rpc_notifications = __esm(() => {
165933
167328
  });
165934
167329
 
165935
167330
  // src/features/magic-context/compartment-embedding.ts
165936
- async function embedAndStoreCompartments(db, sessionId, projectPath, compartments) {
167331
+ async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
165937
167332
  if (compartments.length === 0)
165938
167333
  return;
165939
- const update = db.prepare("UPDATE compartments SET p1_embedding = ?, p1_embedding_model_id = ? WHERE id = ?");
165940
- for (const c of compartments) {
165941
- if (!c.p1 || c.p1.length === 0)
165942
- continue;
167334
+ const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
167335
+ for (const compartment of compartments) {
165943
167336
  try {
165944
- const result = await embedTextForProject(projectPath, c.p1);
165945
- if (result) {
165946
- const blob = Buffer.from(result.vector.buffer, result.vector.byteOffset, result.vector.byteLength);
165947
- update.run(blob, result.modelId, c.id);
167337
+ const fromMemory = compartment.sourceChunkText ? canonicalizeInMemoryChunkTextForEmbedding(compartment.sourceChunkText, compartment.startMessage, compartment.endMessage) : "";
167338
+ const canonicalText = fromMemory || buildCanonicalChunkTextFromFts(db, sessionId, compartment.startMessage, compartment.endMessage);
167339
+ if (canonicalText.length === 0)
167340
+ continue;
167341
+ const windows = chunkCanonicalText(canonicalText, compartment.startMessage, compartment.endMessage, maxInputTokens);
167342
+ if (windows.length === 0)
167343
+ continue;
167344
+ const currentModelId = getProjectChunkEmbeddingModelId(projectPath);
167345
+ if (currentModelId !== "off" && chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167346
+ continue;
167347
+ }
167348
+ const result = await embedBatchForProject(projectPath, windows.map((window) => window.text));
167349
+ if (!result)
167350
+ continue;
167351
+ if (chunkEmbeddingWindowsAreCurrent(db, compartment.id, currentModelId, windows, projectPath)) {
167352
+ continue;
167353
+ }
167354
+ const rows = [];
167355
+ for (const [index, window] of windows.entries()) {
167356
+ const vector = result.vectors[index];
167357
+ if (!vector)
167358
+ continue;
167359
+ rows.push({
167360
+ compartmentId: compartment.id,
167361
+ sessionId,
167362
+ projectPath,
167363
+ window,
167364
+ modelId: currentModelId,
167365
+ vector
167366
+ });
167367
+ }
167368
+ if (rows.length === windows.length) {
167369
+ replaceCompartmentChunkEmbeddings(db, rows);
165948
167370
  }
165949
167371
  } catch (error51) {
165950
- sessionLog(sessionId, `compartment embedding failed for compartment ${c.id}:`, error51);
167372
+ sessionLog(sessionId, `compartment chunk embedding failed for compartment ${compartment.id}:`, error51);
165951
167373
  }
165952
167374
  }
165953
167375
  }
165954
167376
  var init_compartment_embedding = __esm(() => {
165955
167377
  init_logger();
167378
+ init_compartment_chunk_embedding();
165956
167379
  init_project_embedding_registry();
165957
167380
  });
165958
167381
 
@@ -167066,55 +168489,6 @@ var init_historian_state_file = __esm(() => {
167066
168489
  init_data_path();
167067
168490
  });
167068
168491
 
167069
- // src/features/magic-context/memory/constants.ts
167070
- var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
167071
- var init_constants = __esm(() => {
167072
- V2_MEMORY_CATEGORIES = [
167073
- "PROJECT_RULES",
167074
- "ARCHITECTURE",
167075
- "CONSTRAINTS",
167076
- "CONFIG_VALUES",
167077
- "NAMING"
167078
- ];
167079
- PROMOTABLE_CATEGORIES = [
167080
- "PROJECT_RULES",
167081
- "ARCHITECTURE",
167082
- "CONSTRAINTS",
167083
- "CONFIG_VALUES",
167084
- "NAMING",
167085
- "ARCHITECTURE_DECISIONS",
167086
- "CONFIG_DEFAULTS",
167087
- "USER_PREFERENCES",
167088
- "USER_DIRECTIVES",
167089
- "ENVIRONMENT",
167090
- "WORKFLOW_RULES",
167091
- "KNOWN_ISSUES"
167092
- ];
167093
- CATEGORY_PRIORITY = [
167094
- "PROJECT_RULES",
167095
- "ARCHITECTURE",
167096
- "CONSTRAINTS",
167097
- "CONFIG_VALUES",
167098
- "NAMING",
167099
- "USER_DIRECTIVES",
167100
- "USER_PREFERENCES",
167101
- "CONFIG_DEFAULTS",
167102
- "ARCHITECTURE_DECISIONS",
167103
- "ENVIRONMENT",
167104
- "WORKFLOW_RULES",
167105
- "KNOWN_ISSUES"
167106
- ];
167107
- MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
167108
- acc[category] = index;
167109
- return acc;
167110
- }, {});
167111
- MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
167112
- CATEGORY_DEFAULT_TTL = {
167113
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
167114
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
167115
- };
167116
- });
167117
-
167118
168492
  // src/features/magic-context/memory/embedding-backfill.ts
167119
168493
  async function ensureMemoryEmbeddings(args) {
167120
168494
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167138,7 +168512,7 @@ async function ensureMemoryEmbeddings(args) {
167138
168512
  continue;
167139
168513
  }
167140
168514
  saveEmbedding(args.db, memory.id, embedding, result.modelId);
167141
- staged.set(memory.id, embedding);
168515
+ staged.set(memory.id, { embedding, modelId: result.modelId });
167142
168516
  }
167143
168517
  })();
167144
168518
  const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
@@ -167935,6 +169309,71 @@ function lastCompartmentBoundaryId(compartments) {
167935
169309
  const last = compartments.at(-1);
167936
169310
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
167937
169311
  }
169312
+ function resolveWorkspaceRenderContext(args) {
169313
+ if (!args.projectPath) {
169314
+ return {
169315
+ identities: [],
169316
+ expandedIdentities: [],
169317
+ ownIdentities: [],
169318
+ shareCategories: null,
169319
+ namesByIdentity: new Map,
169320
+ canonicalIdentityByStoredPath: new Map,
169321
+ isWorkspaced: false
169322
+ };
169323
+ }
169324
+ const identitySet = args.workspaceIdentitySet ?? resolveWorkspaceIdentitySet(args.db, args.projectPath);
169325
+ const isWorkspaced = identitySet.identities.length > 1;
169326
+ const expanded = expandWorkspaceIdentitySetWithAliases(args.db, identitySet.identities);
169327
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : identitySet.identities;
169328
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(identitySet.identities.map((identity) => [identity, identity]));
169329
+ let ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === args.projectPath);
169330
+ if (ownIdentities.length === 0 && expandedIdentities.includes(args.projectPath)) {
169331
+ ownIdentities = [args.projectPath];
169332
+ }
169333
+ return {
169334
+ identities: identitySet.identities,
169335
+ expandedIdentities,
169336
+ ownIdentities,
169337
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(args.db, args.projectPath) : null,
169338
+ namesByIdentity: identitySet.namesByIdentity,
169339
+ canonicalIdentityByStoredPath,
169340
+ isWorkspaced
169341
+ };
169342
+ }
169343
+ function sourceNamesForMemories(args) {
169344
+ if (!args.projectPath || !args.workspace.isWorkspaced)
169345
+ return;
169346
+ const names = new Map;
169347
+ for (const memory of args.memories) {
169348
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
169349
+ if (source)
169350
+ names.set(memory.id, source);
169351
+ }
169352
+ return names.size > 0 ? names : undefined;
169353
+ }
169354
+ function memoryCanonicalIdentity(memory, workspace) {
169355
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
169356
+ }
169357
+ function memorySelectionOrder(left, right) {
169358
+ if (left.status === "permanent" && right.status !== "permanent")
169359
+ return -1;
169360
+ if (right.status === "permanent" && left.status !== "permanent")
169361
+ return 1;
169362
+ const leftImportance = left.importance ?? Number.NEGATIVE_INFINITY;
169363
+ const rightImportance = right.importance ?? Number.NEGATIVE_INFINITY;
169364
+ const importanceDiff = rightImportance - leftImportance;
169365
+ if (importanceDiff !== 0)
169366
+ return importanceDiff;
169367
+ return left.id - right.id;
169368
+ }
169369
+ function memoryRenderOrder(left, right) {
169370
+ const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169371
+ const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
169372
+ const categoryDiff = aPriority - bPriority;
169373
+ if (categoryDiff !== 0)
169374
+ return categoryDiff;
169375
+ return left.id - right.id;
169376
+ }
167938
169377
  function cachedStatement(cache, db, sql) {
167939
169378
  let stmt = cache.get(db);
167940
169379
  if (!stmt) {
@@ -167977,19 +169416,24 @@ function getGlobalUserProfileVersion(db) {
167977
169416
  function readCurrentM0SnapshotMarkers(args) {
167978
169417
  const projectDirectory = args.projectDirectory ?? args.projectPath ?? "";
167979
169418
  const hard = args.hardSignals ?? EMPTY_HARD_SIGNALS;
169419
+ const workspace = resolveWorkspaceRenderContext({
169420
+ db: args.db,
169421
+ projectPath: args.projectPath,
169422
+ workspaceIdentitySet: args.workspaceIdentitySet
169423
+ });
167980
169424
  return {
167981
169425
  projectMemoryEpoch: getProjectMemoryEpoch(args.db, args.projectPath),
169426
+ workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(args.db, workspace.identities) : null,
167982
169427
  projectUserProfileVersion: getGlobalUserProfileVersion(args.db),
167983
169428
  maxCompartmentSeq: getMaxCompartmentSeq(args.db, args.sessionId),
167984
- maxMemoryId: getMaxMemoryId(args.db, args.projectPath),
169429
+ maxMemoryId: workspace.isWorkspaced ? getMaxMemoryIdForProjects(args.db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryId(args.db, args.projectPath),
167985
169430
  maxMutationId: getMaxM0MutationId(args.db, args.sessionId) ?? 0,
167986
- maxMemoryMutationId: args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
169431
+ maxMemoryMutationId: workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(args.db, workspace.expandedIdentities) ?? 0 : args.projectPath ? getMaxMemoryMutationId(args.db, args.projectPath) ?? 0 : 0,
167987
169432
  projectDocsHash: projectDirectory ? computeProjectDocsHash(projectDirectory) : "",
167988
169433
  materializedAt: Date.now(),
167989
169434
  sessionFactsVersion: getSessionFactsVersion(args.db, args.sessionId),
167990
169435
  upgradeState: getUpgradeState(args.db, args.sessionId),
167991
169436
  systemHash: hard.systemHash,
167992
- toolSetHash: hard.toolSetHash,
167993
169437
  modelKey: hard.modelKey
167994
169438
  };
167995
169439
  }
@@ -168012,6 +169456,7 @@ function snapshotMarkersFromCachedM0(state) {
168012
169456
  return null;
168013
169457
  return {
168014
169458
  projectMemoryEpoch: state.cachedM0ProjectMemoryEpoch,
169459
+ workspaceFingerprint: state.cachedM0WorkspaceFingerprint,
168015
169460
  projectUserProfileVersion: state.cachedM0ProjectUserProfileVersion,
168016
169461
  maxCompartmentSeq: state.cachedM0MaxCompartmentSeq,
168017
169462
  maxMemoryId: state.cachedM0MaxMemoryId,
@@ -168022,7 +169467,6 @@ function snapshotMarkersFromCachedM0(state) {
168022
169467
  sessionFactsVersion: state.cachedM0SessionFactsVersion,
168023
169468
  upgradeState: state.cachedM0UpgradeState,
168024
169469
  systemHash: state.cachedM0SystemHash ?? "",
168025
- toolSetHash: state.cachedM0ToolSetHash ?? "",
168026
169470
  modelKey: state.cachedM0ModelKey ?? ""
168027
169471
  };
168028
169472
  }
@@ -168039,41 +169483,30 @@ function mustMaterialize(args) {
168039
169483
  if (hard.systemHash !== "" && hard.systemHash !== (args.state.cachedM0SystemHash ?? "")) {
168040
169484
  return { value: true, reason: "system_hash" };
168041
169485
  }
168042
- if (hard.toolSetHash !== "" && hard.toolSetHash !== (args.state.cachedM0ToolSetHash ?? "")) {
168043
- return { value: true, reason: "tool_set_hash" };
168044
- }
168045
169486
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (args.state.cachedM0MaterializedAt ?? 0)) {
168046
169487
  return { value: true, reason: "ttl_idle" };
168047
169488
  }
168048
- if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
169489
+ if (current.workspaceFingerprint !== null || (args.state.cachedM0WorkspaceFingerprint ?? null) !== null) {
169490
+ if ((args.state.cachedM0WorkspaceFingerprint ?? null) !== current.workspaceFingerprint) {
169491
+ return { value: true, reason: "project_memory_epoch" };
169492
+ }
169493
+ } else if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
168049
169494
  return { value: true, reason: "project_memory_epoch" };
168050
169495
  }
168051
169496
  if (args.state.cachedM0MaxMutationId !== current.maxMutationId) {
168052
169497
  return { value: true, reason: "max_mutation_id" };
168053
169498
  }
168054
- if ((args.state.cachedM0ProjectDocsHash ?? "") !== current.projectDocsHash) {
168055
- return { value: true, reason: "project_docs_hash" };
168056
- }
168057
169499
  if ((args.state.cachedM0UpgradeState ?? null) !== current.upgradeState) {
168058
169500
  return { value: true, reason: "upgrade_state" };
168059
169501
  }
168060
169502
  return { value: false, reason: null };
168061
169503
  }
168062
- function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168063
- const selectionOrder = [...memories].sort((a, b) => {
168064
- if (a.status === "permanent" && b.status !== "permanent")
168065
- return -1;
168066
- if (b.status === "permanent" && a.status !== "permanent")
168067
- return 1;
168068
- const importanceDiff = (b.importance ?? 50) - (a.importance ?? 50);
168069
- if (importanceDiff !== 0)
168070
- return importanceDiff;
168071
- return a.id - b.id;
168072
- });
169504
+ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
169505
+ const selectionOrder = [...memories].sort(memorySelectionOrder);
168073
169506
  const selected = [];
168074
169507
  let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
168075
169508
  for (const memory of selectionOrder) {
168076
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory));
169509
+ const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168077
169510
  if (usedTokens + memoryTokens > budgetTokens)
168078
169511
  continue;
168079
169512
  selected.push(memory);
@@ -168082,16 +169515,70 @@ function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens) {
168082
169515
  if (selected.length < memories.length) {
168083
169516
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
168084
169517
  }
168085
- const renderOrder = [...selected].sort((a, b) => {
168086
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[a.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168087
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[b.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
168088
- const categoryDiff = aPriority - bPriority;
168089
- if (categoryDiff !== 0)
168090
- return categoryDiff;
168091
- return a.id - b.id;
168092
- });
169518
+ const renderOrder = [...selected].sort(memoryRenderOrder);
168093
169519
  return { selected, renderOrder };
168094
169520
  }
169521
+ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, workspace, renderOptions = {}) {
169522
+ if (!workspace.isWorkspaced) {
169523
+ return trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions);
169524
+ }
169525
+ const selected = [];
169526
+ const selectedIds = new Set;
169527
+ let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
169528
+ const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
169529
+ const trySelect = (memory) => {
169530
+ if (selectedIds.has(memory.id))
169531
+ return false;
169532
+ const tokens = tokenCost(memory);
169533
+ if (usedTokens + tokens > budgetTokens)
169534
+ return false;
169535
+ selected.push(memory);
169536
+ selectedIds.add(memory.id);
169537
+ usedTokens += tokens;
169538
+ return true;
169539
+ };
169540
+ for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
169541
+ trySelect(memory);
169542
+ }
169543
+ const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
169544
+ const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
169545
+ const byIdentity = new Map;
169546
+ for (const memory of memories) {
169547
+ if (memory.status === "permanent")
169548
+ continue;
169549
+ const identity = memoryCanonicalIdentity(memory, workspace);
169550
+ if (!identity)
169551
+ continue;
169552
+ const list = byIdentity.get(identity) ?? [];
169553
+ list.push(memory);
169554
+ byIdentity.set(identity, list);
169555
+ }
169556
+ for (const identity of workspace.identities) {
169557
+ let memberTokens = 0;
169558
+ const candidates = (byIdentity.get(identity) ?? []).sort(memorySelectionOrder);
169559
+ for (const memory of candidates) {
169560
+ if (selectedIds.has(memory.id))
169561
+ continue;
169562
+ const tokens = tokenCost(memory);
169563
+ if (memberTokens + tokens > floorTokens)
169564
+ continue;
169565
+ if (usedTokens + tokens > budgetTokens)
169566
+ continue;
169567
+ selected.push(memory);
169568
+ selectedIds.add(memory.id);
169569
+ usedTokens += tokens;
169570
+ memberTokens += tokens;
169571
+ }
169572
+ }
169573
+ const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
169574
+ for (const memory of remaining) {
169575
+ trySelect(memory);
169576
+ }
169577
+ if (selected.length < memories.length) {
169578
+ sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
169579
+ }
169580
+ return { selected, renderOrder: [...selected].sort(memoryRenderOrder) };
169581
+ }
168095
169582
  function safeGetActiveUserMemories(db) {
168096
169583
  try {
168097
169584
  return getActiveUserMemories(db);
@@ -168167,15 +169654,16 @@ function readNewMemoriesForM1(db, projectPath, afterId, expiryCutoff) {
168167
169654
  ORDER BY ${MEMORY_CATEGORY_ORDER_SQL}, id ASC`).all(projectPath, afterId, expiryCutoff).filter(isMemoryRow);
168168
169655
  return rows.map((row) => ({ ...row }));
168169
169656
  }
168170
- function renderMemoryLineV2(memory) {
168171
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}" importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
169657
+ function renderMemoryLineV2(memory, sourceName) {
169658
+ const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
169659
+ return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
168172
169660
  }
168173
- function renderMemoryBlockV2(memories, wrapper = "project-memory") {
169661
+ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
168174
169662
  if (memories.length === 0)
168175
169663
  return "";
168176
169664
  const lines = [`<${wrapper}>`];
168177
169665
  for (const memory of memories) {
168178
- lines.push(renderMemoryLineV2(memory));
169666
+ lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
168179
169667
  }
168180
169668
  lines.push(`</${wrapper}>`);
168181
169669
  return lines.join(`
@@ -168214,7 +169702,7 @@ function renderM0(args) {
168214
169702
  sections.push(sessionHistory.length > 0 ? `<session-history>
168215
169703
  ${sessionHistory}
168216
169704
  </session-history>` : M0_EMPTY_BODY);
168217
- const memoriesBlock = renderMemoryBlockV2(args.memories);
169705
+ const memoriesBlock = renderMemoryBlockV2(args.memories, "project-memory", args.memoryRenderOptions);
168218
169706
  if (memoriesBlock)
168219
169707
  sections.push(memoriesBlock);
168220
169708
  return sections.join(`
@@ -168226,6 +169714,7 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
168226
169714
  if (m1Bytes)
168227
169715
  state.cachedM1Bytes = m1Bytes;
168228
169716
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
169717
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168229
169718
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168230
169719
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168231
169720
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168236,7 +169725,6 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
168236
169725
  state.cachedM0SessionFactsVersion = markers.sessionFactsVersion;
168237
169726
  state.cachedM0UpgradeState = markers.upgradeState;
168238
169727
  state.cachedM0SystemHash = markers.systemHash;
168239
- state.cachedM0ToolSetHash = markers.toolSetHash;
168240
169728
  state.cachedM0ModelKey = markers.modelKey;
168241
169729
  state.snapshotMarkers = markers;
168242
169730
  }
@@ -168252,24 +169740,38 @@ function materializeM0(options) {
168252
169740
  let facts = [];
168253
169741
  let memories = [];
168254
169742
  let userMemories = [];
169743
+ let workspace = resolveWorkspaceRenderContext({
169744
+ db: options.db,
169745
+ projectPath,
169746
+ workspaceIdentitySet: options.workspaceIdentitySet
169747
+ });
168255
169748
  let docs = {
168256
169749
  renderedBlock: "",
168257
169750
  canonicalHash: ""
168258
169751
  };
168259
169752
  options.db.exec("BEGIN");
168260
169753
  try {
169754
+ workspace = resolveWorkspaceRenderContext({
169755
+ db: options.db,
169756
+ projectPath,
169757
+ workspaceIdentitySet: options.workspaceIdentitySet
169758
+ });
168261
169759
  snapshotMarkers = readCurrentM0SnapshotMarkers({
168262
169760
  db: options.db,
168263
169761
  sessionId: options.sessionId,
168264
169762
  projectPath,
168265
169763
  projectDirectory,
168266
- hardSignals: options.hardSignals
169764
+ hardSignals: options.hardSignals,
169765
+ workspaceIdentitySet: {
169766
+ identities: workspace.identities,
169767
+ namesByIdentity: workspace.namesByIdentity
169768
+ }
168267
169769
  });
168268
169770
  docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168269
169771
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168270
169772
  compartments = readM0Compartments(options.db, options.sessionId);
168271
169773
  facts = [];
168272
- memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
169774
+ memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"]) : [];
168273
169775
  userMemories = safeGetActiveUserMemories(options.db);
168274
169776
  options.db.exec("COMMIT");
168275
169777
  } catch (error51) {
@@ -168279,7 +169781,14 @@ function materializeM0(options) {
168279
169781
  throw error51;
168280
169782
  }
168281
169783
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168282
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
169784
+ const memoryRenderOptions = {
169785
+ sourceNameByMemoryId: sourceNamesForMemories({
169786
+ memories,
169787
+ projectPath,
169788
+ workspace
169789
+ })
169790
+ };
169791
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168283
169792
  let decayPressureMultiplier = 1;
168284
169793
  let m0Text = renderM0({
168285
169794
  projectDocs: docs.renderedBlock,
@@ -168287,6 +169796,7 @@ function materializeM0(options) {
168287
169796
  compartments,
168288
169797
  memories: trimmed.renderOrder,
168289
169798
  facts,
169799
+ memoryRenderOptions,
168290
169800
  historyBudgetTokens: options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS,
168291
169801
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168292
169802
  decayPressureMultiplier
@@ -168301,6 +169811,7 @@ function materializeM0(options) {
168301
169811
  compartments,
168302
169812
  memories: trimmed.renderOrder,
168303
169813
  facts,
169814
+ memoryRenderOptions,
168304
169815
  historyBudgetTokens: budget,
168305
169816
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168306
169817
  decayPressureMultiplier
@@ -168319,32 +169830,46 @@ function materializeM0(options) {
168319
169830
  let m1Bytes = Buffer4.from(m1Text, "utf8");
168320
169831
  options.db.exec("BEGIN IMMEDIATE");
168321
169832
  try {
169833
+ const currentWorkspace = resolveWorkspaceRenderContext({
169834
+ db: options.db,
169835
+ projectPath,
169836
+ workspaceIdentitySet: options.workspaceIdentitySet
169837
+ });
168322
169838
  const current = {
168323
169839
  projectMemoryEpoch: getProjectMemoryEpoch(options.db, projectPath),
169840
+ workspaceFingerprint: currentWorkspace.isWorkspaced ? computeWorkspaceEpochFingerprint(options.db, currentWorkspace.identities) : null,
168324
169841
  projectUserProfileVersion: getGlobalUserProfileVersion(options.db),
168325
169842
  maxCompartmentSeq: getMaxCompartmentSeq(options.db, options.sessionId),
168326
- maxMemoryId: getMaxMemoryId(options.db, projectPath),
169843
+ maxMemoryId: currentWorkspace.isWorkspaced ? getMaxMemoryIdForProjects(options.db, currentWorkspace.expandedIdentities, currentWorkspace.ownIdentities, currentWorkspace.shareCategories) : getMaxMemoryId(options.db, projectPath),
168327
169844
  maxMutationId: getMaxM0MutationId(options.db, options.sessionId) ?? 0,
168328
- maxMemoryMutationId: projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
169845
+ maxMemoryMutationId: currentWorkspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(options.db, currentWorkspace.expandedIdentities) ?? 0 : projectPath ? getMaxMemoryMutationId(options.db, projectPath) ?? 0 : 0,
168329
169846
  projectDocsHash: phase3ProjectDocsHash,
168330
169847
  materializedAt: Date.now(),
168331
169848
  sessionFactsVersion: getSessionFactsVersion(options.db, options.sessionId),
168332
169849
  upgradeState: getUpgradeState(options.db, options.sessionId),
168333
169850
  systemHash: snapshotMarkers.systemHash,
168334
- toolSetHash: snapshotMarkers.toolSetHash,
168335
169851
  modelKey: snapshotMarkers.modelKey
168336
169852
  };
168337
- const stale = current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.projectDocsHash !== snapshotMarkers.projectDocsHash || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
169853
+ const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
169854
+ const stale = memoryEpochStale || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
168338
169855
  if (stale) {
168339
169856
  options.db.exec("ROLLBACK");
168340
169857
  throw new MaterializeContentionError({ reason: "snapshot changed before Phase 3" });
168341
169858
  }
168342
- const m1Render = renderM1WithMetadata({ ...options, preRenderedKeyFilesBlock }, snapshotMarkers, renderedMemoryIds);
169859
+ const m1Render = renderM1WithMetadata({
169860
+ ...options,
169861
+ preRenderedKeyFilesBlock,
169862
+ workspaceIdentitySet: {
169863
+ identities: workspace.identities,
169864
+ namesByIdentity: workspace.namesByIdentity
169865
+ }
169866
+ }, snapshotMarkers, renderedMemoryIds);
168343
169867
  m1Text = m1Render.text;
168344
169868
  m1Bytes = Buffer4.from(m1Text, "utf8");
168345
169869
  persistCachedM0(options.db, options.sessionId, {
168346
169870
  m0Bytes,
168347
169871
  projectMemoryEpoch: snapshotMarkers.projectMemoryEpoch,
169872
+ workspaceFingerprint: snapshotMarkers.workspaceFingerprint,
168348
169873
  projectUserProfileVersion: snapshotMarkers.projectUserProfileVersion,
168349
169874
  maxCompartmentSeq: snapshotMarkers.maxCompartmentSeq,
168350
169875
  maxMemoryId: snapshotMarkers.maxMemoryId,
@@ -168356,7 +169881,6 @@ function materializeM0(options) {
168356
169881
  sessionFactsVersion: snapshotMarkers.sessionFactsVersion,
168357
169882
  upgradeState: snapshotMarkers.upgradeState,
168358
169883
  systemHash: snapshotMarkers.systemHash,
168359
- toolSetHash: snapshotMarkers.toolSetHash,
168360
169884
  modelKey: snapshotMarkers.modelKey
168361
169885
  });
168362
169886
  options.db.prepare("UPDATE session_meta SET memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(renderedMemoryIds.length, JSON.stringify(renderedMemoryIds), options.sessionId);
@@ -168408,7 +169932,7 @@ function renderMemoryUpdatesBlock(args) {
168408
169932
  return { block: "", count: 0 };
168409
169933
  }
168410
169934
  const renderedIds = new Set(args.renderedMemoryIds);
168411
- const mutations = getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
169935
+ const mutations = args.workspace.isWorkspaced ? getMemoryMutationsForRenderByProjects(args.db, args.workspace.expandedIdentities, args.afterId, args.renderedMemoryIds) : getMemoryMutationsForRender(args.db, args.projectPath, args.afterId, args.renderedMemoryIds);
168412
169936
  if (mutations.length === 0)
168413
169937
  return { block: "", count: 0 };
168414
169938
  const lines = ["These memories changed since the snapshot below — trust these:"];
@@ -168440,12 +169964,18 @@ function renderM1WithMetadata(options, markers, renderedMemoryIds) {
168440
169964
  throw new RenderM1InvalidMarkersError(options.sessionId);
168441
169965
  }
168442
169966
  const blocks = [];
169967
+ const workspace = resolveWorkspaceRenderContext({
169968
+ db: options.db,
169969
+ projectPath: options.projectPath,
169970
+ workspaceIdentitySet: options.workspaceIdentitySet
169971
+ });
168443
169972
  const keyFiles = renderedKeyFilesBlock(options);
168444
169973
  if (keyFiles)
168445
169974
  blocks.push(keyFiles);
168446
169975
  const memoryUpdates = renderMemoryUpdatesBlock({
168447
169976
  db: options.db,
168448
169977
  projectPath: options.projectPath,
169978
+ workspace,
168449
169979
  afterId: markers.maxMemoryMutationId,
168450
169980
  renderedMemoryIds
168451
169981
  });
@@ -168459,9 +169989,16 @@ ${newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).
168459
169989
  `)}
168460
169990
  </new-compartments>`);
168461
169991
  }
168462
- const newMemories = readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
168463
- const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25))).renderOrder;
168464
- const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories");
169992
+ const newMemories = workspace.isWorkspaced ? readNewMemoriesForM1Union(options.db, workspace.expandedIdentities, markers.maxMemoryId, markers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : readNewMemoriesForM1(options.db, options.projectPath, markers.maxMemoryId, markers.materializedAt);
169993
+ const newMemoryRenderOptions = {
169994
+ sourceNameByMemoryId: sourceNamesForMemories({
169995
+ memories: newMemories,
169996
+ projectPath: options.projectPath,
169997
+ workspace
169998
+ })
169999
+ };
170000
+ const trimmedNewMemories = trimMemoriesToBudgetV2(options.sessionId, newMemories, Math.max(1, Math.floor((options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS) * 0.25)), newMemoryRenderOptions).renderOrder;
170001
+ const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", newMemoryRenderOptions);
168465
170002
  if (newMemoriesBlock)
168466
170003
  blocks.push(newMemoriesBlock);
168467
170004
  const currentUserProfileVersion = getGlobalUserProfileVersion(options.db);
@@ -168509,6 +170046,7 @@ function parseMemoryBlockIds(raw) {
168509
170046
  function readCachedM0M1Row(db, sessionId) {
168510
170047
  return db.prepare(`SELECT cached_m0_bytes, cached_m1_bytes,
168511
170048
  cached_m0_project_memory_epoch,
170049
+ cached_m0_workspace_fingerprint,
168512
170050
  cached_m0_project_user_profile_version,
168513
170051
  cached_m0_max_compartment_seq,
168514
170052
  cached_m0_max_memory_id,
@@ -168519,7 +170057,6 @@ function readCachedM0M1Row(db, sessionId) {
168519
170057
  cached_m0_session_facts_version,
168520
170058
  cached_m0_upgrade_state,
168521
170059
  cached_m0_system_hash,
168522
- cached_m0_tool_set_hash,
168523
170060
  cached_m0_model_key,
168524
170061
  memory_block_ids
168525
170062
  FROM session_meta
@@ -168544,6 +170081,7 @@ function markersFromCachedRow(row) {
168544
170081
  return null;
168545
170082
  return {
168546
170083
  projectMemoryEpoch: row.cached_m0_project_memory_epoch,
170084
+ workspaceFingerprint: row.cached_m0_workspace_fingerprint,
168547
170085
  projectUserProfileVersion: row.cached_m0_project_user_profile_version,
168548
170086
  maxCompartmentSeq: row.cached_m0_max_compartment_seq,
168549
170087
  maxMemoryId: row.cached_m0_max_memory_id,
@@ -168554,12 +170092,11 @@ function markersFromCachedRow(row) {
168554
170092
  sessionFactsVersion: row.cached_m0_session_facts_version,
168555
170093
  upgradeState: row.cached_m0_upgrade_state,
168556
170094
  systemHash: row.cached_m0_system_hash ?? "",
168557
- toolSetHash: row.cached_m0_tool_set_hash ?? "",
168558
170095
  modelKey: row.cached_m0_model_key ?? ""
168559
170096
  };
168560
170097
  }
168561
170098
  function cachedRowMatchesState(row, state) {
168562
- 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 ?? "");
170099
+ return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && (row.cached_m0_workspace_fingerprint ?? null) === (state.cachedM0WorkspaceFingerprint ?? null) && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
168563
170100
  }
168564
170101
  function applyCachedRowToState(state, row) {
168565
170102
  const markers = markersFromCachedRow(row);
@@ -168569,6 +170106,7 @@ function applyCachedRowToState(state, row) {
168569
170106
  state.cachedM0Bytes = toBuffer(row.cached_m0_bytes);
168570
170107
  state.cachedM1Bytes = toBuffer(row.cached_m1_bytes);
168571
170108
  state.cachedM0ProjectMemoryEpoch = markers.projectMemoryEpoch;
170109
+ state.cachedM0WorkspaceFingerprint = markers.workspaceFingerprint;
168572
170110
  state.cachedM0ProjectUserProfileVersion = markers.projectUserProfileVersion;
168573
170111
  state.cachedM0MaxCompartmentSeq = markers.maxCompartmentSeq;
168574
170112
  state.cachedM0MaxMemoryId = markers.maxMemoryId;
@@ -168579,7 +170117,6 @@ function applyCachedRowToState(state, row) {
168579
170117
  state.cachedM0SessionFactsVersion = markers.sessionFactsVersion;
168580
170118
  state.cachedM0UpgradeState = markers.upgradeState;
168581
170119
  state.cachedM0SystemHash = markers.systemHash;
168582
- state.cachedM0ToolSetHash = markers.toolSetHash;
168583
170120
  state.cachedM0ModelKey = markers.modelKey;
168584
170121
  state.snapshotMarkers = markers;
168585
170122
  }
@@ -168633,20 +170170,36 @@ function prependM0M1Messages(sessionId, messages, m0Text, m1Text) {
168633
170170
  function renderFreshM0NonPersisted(options) {
168634
170171
  const projectPath = options.projectPath;
168635
170172
  const projectDirectory = options.projectDirectory;
170173
+ const workspace = resolveWorkspaceRenderContext({
170174
+ db: options.db,
170175
+ projectPath,
170176
+ workspaceIdentitySet: options.workspaceIdentitySet
170177
+ });
168636
170178
  const snapshotMarkers = readCurrentM0SnapshotMarkers({
168637
170179
  db: options.db,
168638
170180
  sessionId: options.sessionId,
168639
170181
  projectPath,
168640
- projectDirectory
170182
+ projectDirectory,
170183
+ workspaceIdentitySet: {
170184
+ identities: workspace.identities,
170185
+ namesByIdentity: workspace.namesByIdentity
170186
+ }
168641
170187
  });
168642
170188
  const docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
168643
170189
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
168644
170190
  snapshotMarkers.materializedAt = options.state.cachedM0MaterializedAt ?? 0;
168645
170191
  const compartments = readM0Compartments(options.db, options.sessionId);
168646
- const memories = projectPath ? getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
170192
+ const memories = projectPath ? workspace.isWorkspaced ? getMemoriesByProjects(options.db, workspace.expandedIdentities, ["active", "permanent"], snapshotMarkers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(options.db, projectPath, ["active", "permanent"], snapshotMarkers.materializedAt) : [];
168647
170193
  const userMemories = safeGetActiveUserMemories(options.db);
168648
170194
  const memoryBudget = options.memoryInjectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
168649
- const trimmed = trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
170195
+ const memoryRenderOptions = {
170196
+ sourceNameByMemoryId: sourceNamesForMemories({
170197
+ memories,
170198
+ projectPath,
170199
+ workspace
170200
+ })
170201
+ };
170202
+ const trimmed = workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(options.sessionId, memories, memoryBudget, workspace, memoryRenderOptions) : trimMemoriesToBudgetV2(options.sessionId, memories, memoryBudget);
168650
170203
  const budget = options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
168651
170204
  let decayPressureMultiplier = 1;
168652
170205
  let m0Text = renderM0({
@@ -168655,6 +170208,7 @@ function renderFreshM0NonPersisted(options) {
168655
170208
  compartments,
168656
170209
  memories: trimmed.renderOrder,
168657
170210
  facts: [],
170211
+ memoryRenderOptions,
168658
170212
  historyBudgetTokens: budget,
168659
170213
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168660
170214
  decayPressureMultiplier
@@ -168668,6 +170222,7 @@ function renderFreshM0NonPersisted(options) {
168668
170222
  compartments,
168669
170223
  memories: trimmed.renderOrder,
168670
170224
  facts: [],
170225
+ memoryRenderOptions,
168671
170226
  historyBudgetTokens: budget,
168672
170227
  userProfileBudgetTokens: options.userProfileBudgetTokens,
168673
170228
  decayPressureMultiplier
@@ -168683,6 +170238,12 @@ function renderFreshM0NonPersisted(options) {
168683
170238
  };
168684
170239
  }
168685
170240
  function injectM0M1(options) {
170241
+ if (!options.workspaceIdentitySet && options.projectPath) {
170242
+ options = {
170243
+ ...options,
170244
+ workspaceIdentitySet: resolveWorkspaceIdentitySet(options.db, options.projectPath)
170245
+ };
170246
+ }
168686
170247
  const skipped = {
168687
170248
  injected: false,
168688
170249
  m0RematerializedThisPass: false,
@@ -168699,7 +170260,8 @@ function injectM0M1(options) {
168699
170260
  state: options.state,
168700
170261
  projectPath: options.projectPath,
168701
170262
  projectDirectory: options.projectDirectory,
168702
- hardSignals: options.hardSignals
170263
+ hardSignals: options.hardSignals,
170264
+ workspaceIdentitySet: options.workspaceIdentitySet
168703
170265
  });
168704
170266
  let rematerialized = false;
168705
170267
  let contentionExhausted = false;
@@ -168793,6 +170355,7 @@ var init_inject_compartments = __esm(async () => {
168793
170355
  init_compartment_storage();
168794
170356
  init_constants();
168795
170357
  init_storage_memory();
170358
+ init_workspaces();
168796
170359
  init_logger();
168797
170360
  init_decay_render();
168798
170361
  init_key_files_block();
@@ -168806,7 +170369,6 @@ var init_inject_compartments = __esm(async () => {
168806
170369
  CONSTRAINT_KEYWORDS = /\b(must|never|always|cannot|should not|must not)\b/i;
168807
170370
  EMPTY_HARD_SIGNALS = {
168808
170371
  systemHash: "",
168809
- toolSetHash: "",
168810
170372
  modelKey: "",
168811
170373
  cacheExpired: false,
168812
170374
  lastResponseTime: 0
@@ -171933,18 +173495,38 @@ async function runCompartmentAgent(deps) {
171933
173495
  return;
171934
173496
  }
171935
173497
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
171936
- const boundarySnapshot = deps.boundarySnapshot ?? null;
173498
+ let boundarySnapshot = deps.boundarySnapshot ?? null;
171937
173499
  if (!boundarySnapshot) {
171938
173500
  telemetry.failureReason = "missing protected-tail boundary snapshot";
171939
173501
  sessionLog(sessionId, "historian no-op: missing protected-tail boundary snapshot from trigger decision");
171940
173502
  rollbackDrainReservation();
171941
173503
  return;
171942
173504
  }
171943
- const validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
173505
+ let validation = boundarySnapshot.rawRangeFingerprint.length > 0 ? validateBoundarySnapshot({
171944
173506
  db,
171945
173507
  snapshot: boundarySnapshot,
171946
173508
  currentContextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit
171947
173509
  }) : { ok: true };
173510
+ if (!validation.ok && validation.reason === "stale_snapshot") {
173511
+ const refreshed = resolveOpenCodeProtectedTailBoundary({
173512
+ db,
173513
+ sessionId,
173514
+ mode: "incremental-runner",
173515
+ contextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit,
173516
+ executeThresholdPercentage: boundarySnapshot.executeThresholdPercentage,
173517
+ usage: {
173518
+ percentage: boundarySnapshot.usagePercentage,
173519
+ inputTokens: boundarySnapshot.usageInputTokens
173520
+ },
173521
+ usageSource: boundarySnapshot.usageSource,
173522
+ emergencyTailScale: boundarySnapshot.emergencyTailScale
173523
+ });
173524
+ if (hasRunnableCompartmentWindow(refreshed)) {
173525
+ sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
173526
+ boundarySnapshot = refreshed;
173527
+ validation = { ok: true };
173528
+ }
173529
+ }
171948
173530
  if (!validation.ok) {
171949
173531
  sessionLog(sessionId, `historian no-op: stale protected-tail snapshot (${validation.detail ?? validation.reason ?? "unknown"})`);
171950
173532
  telemetry.status = "noop";
@@ -172013,6 +173595,7 @@ async function runCompartmentAgent(deps) {
172013
173595
  rollbackDrainReservation();
172014
173596
  return;
172015
173597
  }
173598
+ deps.onHistorianRunStarted?.();
172016
173599
  const projectPath = resolveProjectIdentity(directory ?? process.cwd());
172017
173600
  const memories = getMemoriesByProject(db, projectPath, ["active", "permanent"]);
172018
173601
  const projectMemory = renderMemoryBlock(memories) ?? "";
@@ -172145,8 +173728,13 @@ ${chunkText}`,
172145
173728
  }
172146
173729
  if (embeddingActive) {
172147
173730
  const projectIdentity = resolveProjectIdentity(promotionDirectory);
172148
- const toEmbed = persistedCompartments.map((c, i) => ({ id: persistedIds[i], p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172149
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173731
+ const chunksToEmbed = persistedCompartments.map((c, i) => ({
173732
+ id: persistedIds[i],
173733
+ startMessage: c.startMessage,
173734
+ endMessage: c.endMessage,
173735
+ sourceChunkText: chunk.text
173736
+ })).filter((c) => typeof c.id === "number");
173737
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172150
173738
  }
172151
173739
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
172152
173740
  deps.onCompartmentStatePublished?.(sessionId);
@@ -172377,8 +173965,12 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
172377
173965
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172378
173966
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172379
173967
  const liveCompartments = getCompartments(db, sessionId);
172380
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172381
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
173968
+ const chunksToEmbed = liveCompartments.map((c) => ({
173969
+ id: c.id,
173970
+ startMessage: c.startMessage,
173971
+ endMessage: c.endMessage
173972
+ }));
173973
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172382
173974
  }
172383
173975
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
172384
173976
  if (lastCompartmentEnd2 > 0) {
@@ -172591,8 +174183,12 @@ Another process acquired the compartment-state lease before recomp could publish
172591
174183
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172592
174184
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
172593
174185
  const liveCompartments = getCompartments(db, sessionId);
172594
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172595
- embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
174186
+ const chunksToEmbed = liveCompartments.map((c) => ({
174187
+ id: c.id,
174188
+ startMessage: c.startMessage,
174189
+ endMessage: c.endMessage
174190
+ }));
174191
+ embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed);
172596
174192
  }
172597
174193
  if (lastCompartmentEnd > 0) {
172598
174194
  const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
@@ -172773,8 +174369,12 @@ Could not acquire the compartment-state lease for this session.`;
172773
174369
  if (deps.memoryEnabled !== false) {
172774
174370
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
172775
174371
  const liveCompartments = getCompartments(db, sessionId);
172776
- const toEmbed = liveCompartments.map((c) => ({ id: c.id, p1: c.p1 ?? c.content })).filter((c) => typeof c.id === "number" && c.p1.length > 0);
172777
- Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed));
174372
+ const chunksToEmbed = liveCompartments.map((c) => ({
174373
+ id: c.id,
174374
+ startMessage: c.startMessage,
174375
+ endMessage: c.endMessage
174376
+ }));
174377
+ Promise.resolve(deps.ensureProjectRegistered?.(sessionDirectory, db)).then(() => embedAndStoreCompartmentChunks(db, sessionId, projectIdentity, chunksToEmbed));
172778
174378
  }
172779
174379
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
172780
174380
  if (lastEnd > 0) {
@@ -174996,7 +176596,14 @@ function startCompartmentAgent(deps) {
174996
176596
  return;
174997
176597
  }
174998
176598
  const renewal = startLeaseRenewal(deps, holderId);
174999
- const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
176599
+ let realRunStarted = false;
176600
+ const runnerDeps = withPublishedCallback({
176601
+ ...deps,
176602
+ compartmentLeaseHolderId: holderId,
176603
+ onHistorianRunStarted: () => {
176604
+ realRunStarted = true;
176605
+ }
176606
+ });
175000
176607
  const promise2 = runCompartmentAgent(runnerDeps).catch((err) => {
175001
176608
  sessionLog(deps.sessionId, "compartment agent: unhandled rejection:", err);
175002
176609
  try {
@@ -175010,6 +176617,9 @@ function startCompartmentAgent(deps) {
175010
176617
  }
175011
176618
  });
175012
176619
  activeRuns.set(deps.sessionId, { promise: promise2, published: false });
176620
+ if (!realRunStarted && activeRuns.get(deps.sessionId)?.promise === promise2) {
176621
+ activeRuns.delete(deps.sessionId);
176622
+ }
175013
176623
  }
175014
176624
  async function executeContextRecompWithResult(deps, options = {}) {
175015
176625
  const { sessionId } = deps;
@@ -175144,7 +176754,7 @@ function applyMemoryMigration(db, projectPath, result) {
175144
176754
  inserted++;
175145
176755
  }
175146
176756
  if (removed > 0 || inserted > 0) {
175147
- bumpProjectMemoryEpoch(db, projectPath);
176757
+ bumpEpochsForWorkspaceMembers(db, projectPath);
175148
176758
  }
175149
176759
  })();
175150
176760
  return { removed, inserted };
@@ -175602,15 +177212,15 @@ function shouldShowAnnouncement() {
175602
177212
  }
175603
177213
  return state.version !== ANNOUNCEMENT_VERSION;
175604
177214
  }
175605
- var ANNOUNCEMENT_VERSION = "0.23.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
177215
+ var ANNOUNCEMENT_VERSION = "0.24.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
175606
177216
  var init_announcement = __esm(() => {
175607
177217
  init_data_path();
175608
177218
  ANNOUNCEMENT_FEATURES = [
175609
- "Smarter context nudges: gentle <system-reminder> notes on tool outputs replace the old chat nudgesquieter, cache-safe, and they now also help subagents manage their own context.",
175610
- "The agent can now maintain its own project memories: update, archive (batch), and merge the memories it sees not just write new ones. Memory categories are schema-enforced.",
175611
- "Big-session performance: historian trigger and token math moved off the database hot pathmulti-second stalls on large sessions are gone (measured 250ms → 2.4ms per pass).",
175612
- "History compaction unstuck for sparse sessions: the protected-tail boundary is now size-based, so sessions with few user turns compact reliably instead of growing forever (issue #132).",
175613
- "Fixed: session titles no longer fail to generate in fresh directories (issue #129), plus 20+ correctness fixes from three audit rounds."
177219
+ "Searchable session history: ctx_search can now find older discussion by meaning, not just keywords. New history is embedded automatically to backfill an EXISTING session's older history, run /ctx-embed-history once (it works in the background).",
177220
+ "Cross-project workspaces: group related repos and share project memories across them, with per-category control over what's shared. Set them up in the dashboard's Workspaces panel.",
177221
+ "Pi: fixed sessions overflowing the model context while still showing moderate usagePi now sheds context before a tool-heavy turn overflows.",
177222
+ "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
177223
+ "Setup wizard now lists your actual models with type-ahead instead of fixed recommendations, and explains the historian/dreamer roles (issue #144). Plus a GitHub Copilot tool-pairing fix (#135)."
175614
177224
  ];
175615
177225
  });
175616
177226
  // src/agents/permissions.ts
@@ -176338,6 +177948,10 @@ function getMagicContextBuiltinCommands() {
176338
177948
  "ctx-dream": {
176339
177949
  template: "ctx-dream",
176340
177950
  description: "Run the hidden dreamer maintenance pass for this project now"
177951
+ },
177952
+ "ctx-embed-history": {
177953
+ template: "ctx-embed-history",
177954
+ description: "Embed all of this session's history compartments for semantic search, in one pass"
176341
177955
  }
176342
177956
  };
176343
177957
  }
@@ -176831,7 +178445,7 @@ await init_storage_db();
176831
178445
  // src/features/magic-context/v22-deferred-backfill.ts
176832
178446
  init_logger();
176833
178447
  init_project_identity();
176834
- import { createHash as createHash5 } from "node:crypto";
178448
+ import { createHash as createHash6 } from "node:crypto";
176835
178449
  import { realpathSync as realpathSync2 } from "node:fs";
176836
178450
  import path5 from "node:path";
176837
178451
  var BATCH_SIZE = 25;
@@ -176885,7 +178499,7 @@ function computeLegacyRustDirIdentity(rawProjectPath) {
176885
178499
  } catch {
176886
178500
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
176887
178501
  }
176888
- return `dir:${createHash5("sha256").update(canonical, "utf8").digest("hex")}`;
178502
+ return `dir:${createHash6("sha256").update(canonical, "utf8").digest("hex")}`;
176889
178503
  }
176890
178504
  function upsertRekeyMap(db, oldProjectPath, newProjectPath, rekeyedAt) {
176891
178505
  db.prepare(`INSERT INTO v22_identity_rekey_map (old_project_path, new_project_path, rekeyed_at)
@@ -179426,7 +181040,7 @@ init_project_identity();
179426
181040
  // src/plugin/embedding-bootstrap-helpers.ts
179427
181041
  init_embedding();
179428
181042
  init_logger();
179429
- import { createHash as createHash8 } from "node:crypto";
181043
+ import { createHash as createHash10 } from "node:crypto";
179430
181044
  var EMBEDDING_AFFECTING_KEYS = new Set([
179431
181045
  "embedding.api_key",
179432
181046
  "embedding.endpoint",
@@ -179447,7 +181061,7 @@ var EMBEDDING_WARNING_TERMS = [
179447
181061
  ];
179448
181062
  var loggedFailureSignatures = new Map;
179449
181063
  function sha256Prefix2(value, length = 16) {
179450
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
181064
+ return createHash10("sha256").update(value).digest("hex").slice(0, length);
179451
181065
  }
179452
181066
  function warningLooksEmbeddingRelated(message) {
179453
181067
  const lower = message.toLowerCase();
@@ -180083,7 +181697,7 @@ function createTagger() {
180083
181697
  // src/hooks/magic-context/hook.ts
180084
181698
  init_magic_context();
180085
181699
  init_project_identity();
180086
- init_tool_definition_tokens();
181700
+ init_project_embedding_registry();
180087
181701
  await init_storage();
180088
181702
  init_logger();
180089
181703
  init_resolve_fallbacks();
@@ -180677,6 +182291,7 @@ function createMagicContextCommandHandler(deps) {
180677
182291
  const isAugCommand = (command) => command === "ctx-aug";
180678
182292
  const isDreamCommand = (command) => command === "ctx-dream";
180679
182293
  const isSessionUpgradeCommand = (command) => command === "ctx-session-upgrade";
182294
+ const isEmbedHistoryCommand = (command) => command === "ctx-embed-history";
180680
182295
  return {
180681
182296
  "command.execute.before": async (input, _output, _params) => {
180682
182297
  const isStatus = isStatusCommand(input.command);
@@ -180685,7 +182300,8 @@ function createMagicContextCommandHandler(deps) {
180685
182300
  const isAug = isAugCommand(input.command);
180686
182301
  const isDream = isDreamCommand(input.command);
180687
182302
  const isSessionUpgrade = isSessionUpgradeCommand(input.command);
180688
- if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade) {
182303
+ const isEmbedHistory = isEmbedHistoryCommand(input.command);
182304
+ if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbedHistory) {
180689
182305
  return;
180690
182306
  }
180691
182307
  const sessionId = input.sessionID;
@@ -180698,6 +182314,11 @@ function createMagicContextCommandHandler(deps) {
180698
182314
  await executeDreaming(deps, sessionId);
180699
182315
  return;
180700
182316
  }
182317
+ if (isEmbedHistory) {
182318
+ const summary = deps.executeEmbedHistory ? await deps.executeEmbedHistory(sessionId) : "Semantic embedding is not configured for this project, so there is nothing to embed.";
182319
+ await deps.sendNotification(sessionId, summary, {});
182320
+ throwSentinel(input.command);
182321
+ }
180701
182322
  if (isFlush) {
180702
182323
  result = executeFlush(deps.db, sessionId);
180703
182324
  clearCachedM0M1(deps.db, sessionId);
@@ -181438,6 +183059,7 @@ await init_read_session_chunk();
181438
183059
  // src/hooks/magic-context/transform.ts
181439
183060
  init_project_identity();
181440
183061
  import * as crypto2 from "node:crypto";
183062
+ init_session_project_storage();
181441
183063
  init_storage_meta_persisted();
181442
183064
  init_logger();
181443
183065
  await init_storage();
@@ -181819,6 +183441,63 @@ function replayCavemanCompression(sessionId, db, targets, tags) {
181819
183441
 
181820
183442
  // src/hooks/magic-context/transform.ts
181821
183443
  await init_compartment_runner();
183444
+
183445
+ // src/hooks/magic-context/ctx-reduce-availability.ts
183446
+ init_logger();
183447
+ await init_read_session_db();
183448
+ var availabilityBySession = new BoundedSessionMap(500);
183449
+ function verdictFromToolsMap(tools) {
183450
+ if (tools === null || typeof tools !== "object" || Array.isArray(tools))
183451
+ return null;
183452
+ const map2 = tools;
183453
+ if (map2.ctx_reduce === true)
183454
+ return true;
183455
+ if (map2.ctx_reduce === false)
183456
+ return false;
183457
+ if (map2["*"] === false)
183458
+ return false;
183459
+ return null;
183460
+ }
183461
+ function resolveCtxReduceAvailabilityFromMessages(sessionId, messages) {
183462
+ const cached2 = availabilityBySession.get(sessionId);
183463
+ if (cached2 !== undefined)
183464
+ return cached2;
183465
+ for (const message of messages) {
183466
+ if (message.info?.role !== "user")
183467
+ continue;
183468
+ const verdict = verdictFromToolsMap(message.info.tools);
183469
+ if (verdict !== null) {
183470
+ availabilityBySession.set(sessionId, verdict);
183471
+ return verdict;
183472
+ }
183473
+ break;
183474
+ }
183475
+ availabilityBySession.set(sessionId, true);
183476
+ return true;
183477
+ }
183478
+ function resolveCtxReduceAvailability(sessionId) {
183479
+ const cached2 = availabilityBySession.get(sessionId);
183480
+ if (cached2 !== undefined)
183481
+ return cached2;
183482
+ if (!openCodeDbExists())
183483
+ return true;
183484
+ try {
183485
+ const row = withReadOnlySessionDb((db) => db.prepare(`SELECT json_extract(data, '$.tools') AS tools FROM message
183486
+ WHERE session_id = ? AND json_extract(data, '$.role') = 'user'
183487
+ ORDER BY time_created ASC LIMIT 1`).get(sessionId));
183488
+ if (!row)
183489
+ return true;
183490
+ const verdict = row.tools === null ? null : verdictFromToolsMap(JSON.parse(row.tools));
183491
+ const resolved = verdict ?? true;
183492
+ availabilityBySession.set(sessionId, resolved);
183493
+ return resolved;
183494
+ } catch (error51) {
183495
+ sessionLog(sessionId, "ctx_reduce availability read failed (fail-open):", error51);
183496
+ return true;
183497
+ }
183498
+ }
183499
+
183500
+ // src/hooks/magic-context/transform.ts
181822
183501
  init_derive_budgets();
181823
183502
 
181824
183503
  // src/hooks/magic-context/image-token-estimate.ts
@@ -181981,6 +183660,7 @@ await __promiseAll([
181981
183660
  init_read_session_chunk(),
181982
183661
  init_read_session_db()
181983
183662
  ]);
183663
+
181984
183664
  // src/hooks/magic-context/sentinel.ts
181985
183665
  var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
181986
183666
  function modelAcceptsEmptyContent(providerID) {
@@ -182038,7 +183718,6 @@ function replaySentinelByMessageIds(messages, ids, providerID) {
182038
183718
  missingIds.push(id);
182039
183719
  return { replayed, missingIds };
182040
183720
  }
182041
-
182042
183721
  // src/hooks/magic-context/strip-content.ts
182043
183722
  var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped §\d+§\]$/;
182044
183723
  var TAG_PREFIX_PATTERN = /^§\d+§\s*/;
@@ -182391,8 +184070,10 @@ function stripReasoningFromMergedAssistants(messages, providerID) {
182391
184070
  }
182392
184071
  return stripped;
182393
184072
  }
182394
- function stripProcessedImages(messages, watermark, messageTagNumbers) {
184073
+ function stripProcessedImages(messages, frozenIds, options) {
184074
+ const { detect, watermark, messageTagNumbers } = options;
182395
184075
  let stripped = 0;
184076
+ const newlyStrippedIds = [];
182396
184077
  let hasAssistantResponse = false;
182397
184078
  for (let i = messages.length - 1;i >= 0; i--) {
182398
184079
  const msg = messages[i];
@@ -182400,13 +184081,17 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
182400
184081
  hasAssistantResponse = true;
182401
184082
  continue;
182402
184083
  }
182403
- if (msg.info.role !== "user" || !hasAssistantResponse) {
184084
+ if (msg.info.role !== "user") {
182404
184085
  continue;
182405
184086
  }
184087
+ const id = typeof msg.info.id === "string" ? msg.info.id : undefined;
184088
+ const inFrozen = id !== undefined && frozenIds.has(id);
182406
184089
  const maxTag = messageTagNumbers.get(msg) ?? 0;
182407
- if (maxTag > watermark) {
184090
+ const isNewDetection = !inFrozen && detect && hasAssistantResponse && id !== undefined && maxTag <= watermark;
184091
+ if (!inFrozen && !isNewDetection) {
182408
184092
  continue;
182409
184093
  }
184094
+ let touchedThisMsg = false;
182410
184095
  for (let j = 0;j < msg.parts.length; j++) {
182411
184096
  const part = msg.parts[j];
182412
184097
  if (!isRecord(part) || part.type !== "file") {
@@ -182418,10 +184103,14 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
182418
184103
  if (typeof part.url === "string" && part.url.startsWith("data:") && part.url.length > 200) {
182419
184104
  msg.parts[j] = makeSentinel(part);
182420
184105
  stripped++;
184106
+ touchedThisMsg = true;
182421
184107
  }
182422
184108
  }
184109
+ if (touchedThisMsg && isNewDetection && id !== undefined) {
184110
+ newlyStrippedIds.push(id);
184111
+ }
182423
184112
  }
182424
- return stripped;
184113
+ return { stripped, newlyStrippedIds };
182425
184114
  }
182426
184115
 
182427
184116
  // src/hooks/magic-context/transform.ts
@@ -182737,6 +184426,7 @@ function appendReminderToUserMessage(message, reminder) {
182737
184426
  init_tag_part_guards();
182738
184427
  await init_storage();
182739
184428
  var USER_DROP_PREVIEW_CHARS = 250;
184429
+ var RECENT_TOOL_SKELETON_WINDOW = 20;
182740
184430
  function buildReplacementContent(tagId, target) {
182741
184431
  const role = target.message?.info.role;
182742
184432
  if (role !== "user") {
@@ -182762,6 +184452,7 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
182762
184452
  const tagTypeById = new Map(tags.map((tag) => [tag.tagNumber, tag.type]));
182763
184453
  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;
182764
184454
  const pendingOps = preloadedPendingOps ?? getPendingOps(db, sessionId);
184455
+ 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));
182765
184456
  for (const pendingOp of pendingOps) {
182766
184457
  const tagStatus = tagStatusById.get(pendingOp.tagId);
182767
184458
  if (tagStatus === "compacted" || tagStatus === "dropped") {
@@ -182774,14 +184465,25 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
182774
184465
  const target = targets.get(pendingOp.tagId);
182775
184466
  const isToolTag = tagTypeById.get(pendingOp.tagId) === "tool";
182776
184467
  if (isToolTag) {
182777
- const dropResult = target?.drop?.() ?? "absent";
182778
- if (dropResult === "incomplete") {
182779
- continue;
182780
- }
182781
- if (dropResult === "removed") {
182782
- didMutateMessage = true;
184468
+ if (skeletonWindow.has(pendingOp.tagId)) {
184469
+ const truncResult = target?.truncate?.() ?? "absent";
184470
+ if (truncResult === "incomplete") {
184471
+ continue;
184472
+ }
184473
+ if (truncResult === "truncated") {
184474
+ didMutateMessage = true;
184475
+ }
184476
+ updateTagDropMode(db, sessionId, pendingOp.tagId, "truncated");
184477
+ } else {
184478
+ const dropResult = target?.drop?.() ?? "absent";
184479
+ if (dropResult === "incomplete") {
184480
+ continue;
184481
+ }
184482
+ if (dropResult === "removed") {
184483
+ didMutateMessage = true;
184484
+ }
184485
+ updateTagDropMode(db, sessionId, pendingOp.tagId, "full");
182783
184486
  }
182784
- updateTagDropMode(db, sessionId, pendingOp.tagId, "full");
182785
184487
  } else if (target) {
182786
184488
  const changed = target.setContent(buildReplacementContent(pendingOp.tagId, target));
182787
184489
  if (changed)
@@ -183351,6 +185053,7 @@ init_embedding();
183351
185053
 
183352
185054
  // src/features/magic-context/search.ts
183353
185055
  init_logger();
185056
+ init_compartment_chunk_embedding();
183354
185057
 
183355
185058
  // src/features/magic-context/literal-probes.ts
183356
185059
  var MAX_PROBES = 5;
@@ -183412,6 +185115,7 @@ function containsProbeVerbatim(text, probes) {
183412
185115
  init_memory();
183413
185116
  init_embedding();
183414
185117
  init_storage_memory_fts();
185118
+ init_workspaces();
183415
185119
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
183416
185120
  var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
183417
185121
  var SEMANTIC_WEIGHT = 0.7;
@@ -183441,6 +185145,37 @@ function previewText(text) {
183441
185145
  }
183442
185146
  return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
183443
185147
  }
185148
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
185149
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
185150
+ const isWorkspaced = resolved.identities.length > 1;
185151
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
185152
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
185153
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
185154
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
185155
+ return {
185156
+ identities: resolved.identities,
185157
+ expandedIdentities,
185158
+ ownIdentities,
185159
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
185160
+ namesByIdentity: resolved.namesByIdentity,
185161
+ canonicalIdentityByStoredPath,
185162
+ isWorkspaced
185163
+ };
185164
+ }
185165
+ function memoryWorkspaceIdentity(memory, workspace) {
185166
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
185167
+ }
185168
+ function sourceNamesForSearchMemories(args) {
185169
+ if (!args.workspace.isWorkspaced)
185170
+ return;
185171
+ const sourceNames = new Map;
185172
+ for (const memory of args.memories) {
185173
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
185174
+ if (source)
185175
+ sourceNames.set(memory.id, source);
185176
+ }
185177
+ return sourceNames.size > 0 ? sourceNames : undefined;
185178
+ }
183444
185179
  function getMessageSearchStatement(db) {
183445
185180
  let stmt = messageSearchStatements.get(db);
183446
185181
  if (!stmt) {
@@ -183449,6 +185184,30 @@ function getMessageSearchStatement(db) {
183449
185184
  }
183450
185185
  return stmt;
183451
185186
  }
185187
+ var ftsRowCountStatements = new WeakMap;
185188
+ var ftsMatchCountStatements = new WeakMap;
185189
+ function getSessionFtsRowCount(db, sessionId) {
185190
+ let stmt = ftsRowCountStatements.get(db);
185191
+ if (!stmt) {
185192
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
185193
+ ftsRowCountStatements.set(db, stmt);
185194
+ }
185195
+ const row = stmt.get(sessionId);
185196
+ return typeof row?.n === "number" ? row.n : 0;
185197
+ }
185198
+ function countSessionFtsMatches(db, sessionId, ftsQuery) {
185199
+ let stmt = ftsMatchCountStatements.get(db);
185200
+ if (!stmt) {
185201
+ stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
185202
+ ftsMatchCountStatements.set(db, stmt);
185203
+ }
185204
+ try {
185205
+ const row = stmt.get(sessionId, ftsQuery);
185206
+ return typeof row?.n === "number" ? row.n : 0;
185207
+ } catch {
185208
+ return 0;
185209
+ }
185210
+ }
183452
185211
  function getMessageOrdinal(value) {
183453
185212
  if (typeof value === "number" && Number.isFinite(value)) {
183454
185213
  return value;
@@ -183464,25 +185223,63 @@ async function getSemanticScores(args) {
183464
185223
  if (!args.queryEmbedding || args.memories.length === 0) {
183465
185224
  return semanticScores;
183466
185225
  }
183467
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
183468
- const embeddings = await ensureMemoryEmbeddings({
183469
- db: args.db,
183470
- projectIdentity: args.projectPath,
183471
- memories: args.memories,
183472
- existingEmbeddings: cachedEmbeddings
183473
- });
185226
+ if (!args.workspace?.isWorkspaced) {
185227
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185228
+ const embeddings = await ensureMemoryEmbeddings({
185229
+ db: args.db,
185230
+ projectIdentity: args.projectPath,
185231
+ memories: args.memories,
185232
+ existingEmbeddings: cachedEmbeddings
185233
+ });
185234
+ for (const memory of args.memories) {
185235
+ const memoryEmbedding = embeddings.get(memory.id);
185236
+ if (!memoryEmbedding) {
185237
+ continue;
185238
+ }
185239
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
185240
+ }
185241
+ return semanticScores;
185242
+ }
185243
+ if (!args.queryModelId || args.queryModelId === "off") {
185244
+ return semanticScores;
185245
+ }
185246
+ const workspace = args.workspace;
185247
+ const memoriesByIdentity = new Map;
183474
185248
  for (const memory of args.memories) {
183475
- const memoryEmbedding = embeddings.get(memory.id);
183476
- if (!memoryEmbedding) {
185249
+ const identity = memoryWorkspaceIdentity(memory, workspace);
185250
+ if (!identity)
183477
185251
  continue;
185252
+ const list = memoriesByIdentity.get(identity) ?? [];
185253
+ list.push(memory);
185254
+ memoriesByIdentity.set(identity, list);
185255
+ }
185256
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
185257
+ if (ownMemories.length > 0) {
185258
+ const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
185259
+ await ensureMemoryEmbeddings({
185260
+ db: args.db,
185261
+ projectIdentity: args.projectPath,
185262
+ memories: ownMemories,
185263
+ existingEmbeddings: ownEmbeddings
185264
+ });
185265
+ }
185266
+ for (const identity of workspace.identities) {
185267
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
185268
+ if (memberMemories.length === 0)
185269
+ continue;
185270
+ const cachedEmbeddings = getProjectEmbeddings(args.db, identity);
185271
+ for (const memory of memberMemories) {
185272
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
185273
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
185274
+ continue;
185275
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
183478
185276
  }
183479
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
183480
185277
  }
183481
185278
  return semanticScores;
183482
185279
  }
183483
185280
  function getFtsMatches(args) {
183484
185281
  try {
183485
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
185282
+ return args.workspace?.isWorkspaced ? searchMemoriesFTSUnion(args.db, args.workspace.expandedIdentities, args.query, args.limit, args.workspace.ownIdentities, args.workspace.shareCategories) : searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
183486
185283
  } catch (error51) {
183487
185284
  log(`[search] FTS query failed for "${args.query}": ${error51 instanceof Error ? error51.message : String(error51)}`);
183488
185285
  return [];
@@ -183496,8 +185293,11 @@ function selectSemanticCandidates(args) {
183496
185293
  return args.memories;
183497
185294
  }
183498
185295
  const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
183499
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
183500
- if (cachedEmbeddings) {
185296
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
185297
+ for (const projectPath of embeddingProjects) {
185298
+ const cachedEmbeddings = peekProjectEmbeddings(projectPath);
185299
+ if (!cachedEmbeddings)
185300
+ continue;
183501
185301
  for (const memoryId of cachedEmbeddings.keys()) {
183502
185302
  candidateIds.add(memoryId);
183503
185303
  }
@@ -183539,7 +185339,8 @@ function mergeMemoryResults(args) {
183539
185339
  score,
183540
185340
  memoryId: memory.id,
183541
185341
  category: memory.category,
183542
- matchType
185342
+ matchType,
185343
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
183543
185344
  });
183544
185345
  }
183545
185346
  return results.sort((left, right) => {
@@ -183553,7 +185354,7 @@ async function searchMemories(args) {
183553
185354
  if (!args.memoryEnabled) {
183554
185355
  return [];
183555
185356
  }
183556
- const memories = getMemoriesByProject(args.db, args.projectPath);
185357
+ const memories = args.workspace?.isWorkspaced ? getMemoriesByProjects(args.db, args.workspace.expandedIdentities, ["active", "permanent"], Date.now(), args.workspace.ownIdentities, args.workspace.shareCategories) : getMemoriesByProject(args.db, args.projectPath);
183557
185358
  if (memories.length === 0) {
183558
185359
  return [];
183559
185360
  }
@@ -183561,26 +185362,43 @@ async function searchMemories(args) {
183561
185362
  db: args.db,
183562
185363
  projectPath: args.projectPath,
183563
185364
  query: args.query,
183564
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
185365
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
185366
+ workspace: args.workspace
183565
185367
  });
183566
185368
  const ftsScores = getFtsScores(ftsMatches);
183567
185369
  const semanticCandidates = selectSemanticCandidates({
183568
185370
  memories,
183569
185371
  projectPath: args.projectPath,
183570
- ftsMatches
185372
+ ftsMatches,
185373
+ workspace: args.workspace
183571
185374
  });
183572
185375
  const semanticScores = await getSemanticScores({
183573
185376
  db: args.db,
183574
185377
  projectPath: args.projectPath,
183575
185378
  memories: semanticCandidates,
183576
- queryEmbedding: args.queryEmbedding
185379
+ queryEmbedding: args.queryEmbedding,
185380
+ queryModelId: args.queryModelId,
185381
+ workspace: args.workspace
183577
185382
  });
183578
185383
  return mergeMemoryResults({
183579
185384
  memories,
183580
185385
  semanticScores,
183581
185386
  ftsScores,
183582
185387
  limit: args.limit,
183583
- visibleMemoryIds: args.visibleMemoryIds
185388
+ visibleMemoryIds: args.visibleMemoryIds,
185389
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
185390
+ memories,
185391
+ projectPath: args.projectPath,
185392
+ workspace: args.workspace ?? {
185393
+ identities: [args.projectPath],
185394
+ expandedIdentities: [args.projectPath],
185395
+ namesByIdentity: new Map,
185396
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
185397
+ ownIdentities: [args.projectPath],
185398
+ shareCategories: null,
185399
+ isWorkspaced: false
185400
+ }
185401
+ })
183584
185402
  });
183585
185403
  }
183586
185404
  function linearDecayScore(rank, total) {
@@ -183611,7 +185429,13 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
183611
185429
  return result;
183612
185430
  }
183613
185431
  var RRF_K = 60;
183614
- var VERBATIM_PROBE_BONUS = 0.5;
185432
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
185433
+ var IDF_FALLOFF = 100;
185434
+ function probeDiscriminationWeight(df, corpusSize) {
185435
+ if (corpusSize <= 0 || df <= 0)
185436
+ return 1;
185437
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
185438
+ }
183615
185439
  function searchMessages(args) {
183616
185440
  const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
183617
185441
  const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
@@ -183628,20 +185452,31 @@ function searchMessages(args) {
183628
185452
  role: row.role
183629
185453
  }));
183630
185454
  }
185455
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
183631
185456
  const queryLists = [];
183632
185457
  if (baseQuery.length > 0) {
183633
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
185458
+ queryLists.push({
185459
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
185460
+ weight: 1
185461
+ });
183634
185462
  }
185463
+ const probeWeights = new Map;
183635
185464
  for (const probe of probes) {
183636
185465
  const probeQuery = sanitizeFtsQuery(probe);
183637
185466
  if (probeQuery.length === 0)
183638
185467
  continue;
183639
- queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
185468
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
185469
+ const weight = probeDiscriminationWeight(df, corpusSize);
185470
+ probeWeights.set(probe, weight);
185471
+ queryLists.push({
185472
+ rows: runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff),
185473
+ weight
185474
+ });
183640
185475
  }
183641
185476
  const fused = new Map;
183642
185477
  for (const list of queryLists) {
183643
- list.forEach((row, rank) => {
183644
- const rrf = 1 / (RRF_K + rank);
185478
+ list.rows.forEach((row, rank) => {
185479
+ const rrf = list.weight / (RRF_K + rank);
183645
185480
  const existing = fused.get(row.messageId);
183646
185481
  if (existing) {
183647
185482
  existing.score += rrf;
@@ -183651,26 +185486,107 @@ function searchMessages(args) {
183651
185486
  });
183652
185487
  }
183653
185488
  for (const entry of fused.values()) {
183654
- if (containsProbeVerbatim(entry.row.content, probes)) {
183655
- entry.score += VERBATIM_PROBE_BONUS;
185489
+ let best = 0;
185490
+ for (const probe of probes) {
185491
+ const weight = probeWeights.get(probe) ?? 0;
185492
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
185493
+ best = weight;
185494
+ }
185495
+ }
185496
+ if (best > 0) {
185497
+ entry.score += best * VERBATIM_RANK_BONUS;
183656
185498
  }
183657
185499
  }
183658
185500
  const ranked = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.row.messageOrdinal - b.row.messageOrdinal).slice(0, args.limit);
183659
- const maxScore = ranked.length > 0 ? ranked[0].score : 1;
183660
- return ranked.map((entry) => ({
185501
+ return ranked.map((entry, rank) => ({
183661
185502
  source: "message",
183662
185503
  content: previewText(entry.row.content),
183663
- score: maxScore > 0 ? entry.score / maxScore : 0,
185504
+ score: linearDecayScore(rank, ranked.length),
183664
185505
  messageOrdinal: entry.row.messageOrdinal,
183665
185506
  messageId: entry.row.messageId,
183666
185507
  role: entry.row.role
183667
185508
  }));
183668
185509
  }
185510
+ function searchCompartmentChunks(args) {
185511
+ if (!args.queryEmbedding || args.limit <= 0)
185512
+ return [];
185513
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
185514
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId);
185515
+ if (rows.length === 0)
185516
+ return [];
185517
+ const byCompartment = new Map;
185518
+ for (const row of rows) {
185519
+ if (cutoff !== null && row.endOrdinal > cutoff) {
185520
+ continue;
185521
+ }
185522
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
185523
+ if (score <= 0)
185524
+ continue;
185525
+ const existing = byCompartment.get(row.compartmentId);
185526
+ if (!existing || score > existing.score) {
185527
+ byCompartment.set(row.compartmentId, { row, score });
185528
+ }
185529
+ }
185530
+ return [...byCompartment.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.row.startOrdinal - right.row.startOrdinal).slice(0, args.limit).map(({ row, score }) => ({
185531
+ source: "compartment",
185532
+ content: previewText(row.title),
185533
+ score: score * SINGLE_SOURCE_PENALTY,
185534
+ compartmentId: row.compartmentId,
185535
+ sessionId: row.sessionId,
185536
+ title: row.title,
185537
+ startOrdinal: row.startOrdinal,
185538
+ endOrdinal: row.endOrdinal,
185539
+ matchType: "semantic"
185540
+ }));
185541
+ }
185542
+ function mergeMessageAndCompartmentResults(args) {
185543
+ if (args.compartments.length === 0)
185544
+ return args.messages;
185545
+ if (args.messages.length === 0)
185546
+ return args.compartments;
185547
+ const fused = new Map;
185548
+ const add = (key, result, score, tieOrdinal) => {
185549
+ const existing = fused.get(key);
185550
+ if (existing) {
185551
+ existing.score += score;
185552
+ return existing;
185553
+ }
185554
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
185555
+ fused.set(key, entry);
185556
+ return entry;
185557
+ };
185558
+ args.compartments.forEach((compartment, rank) => {
185559
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
185560
+ });
185561
+ for (const [rank, message] of args.messages.entries()) {
185562
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
185563
+ const contribution = 1 / (RRF_K + rank);
185564
+ if (!containing) {
185565
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
185566
+ continue;
185567
+ }
185568
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
185569
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
185570
+ entry.snippetScore = message.score;
185571
+ entry.result = {
185572
+ ...entry.result,
185573
+ matchType: "hybrid",
185574
+ snippet: message.content
185575
+ };
185576
+ }
185577
+ }
185578
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
185579
+ return ranked.map((entry, rank) => ({
185580
+ ...entry.result,
185581
+ score: linearDecayScore(rank, ranked.length)
185582
+ }));
185583
+ }
183669
185584
  function getSourceBoost(result) {
183670
185585
  switch (result.source) {
183671
185586
  case "memory":
183672
185587
  return MEMORY_SOURCE_BOOST;
183673
185588
  case "message":
185589
+ case "compartment":
183674
185590
  return MESSAGE_SOURCE_BOOST;
183675
185591
  case "git_commit":
183676
185592
  return GIT_COMMIT_SOURCE_BOOST;
@@ -183688,6 +185604,9 @@ function compareUnifiedResults(left, right) {
183688
185604
  if (left.source === "message" && right.source === "message") {
183689
185605
  return left.messageOrdinal - right.messageOrdinal;
183690
185606
  }
185607
+ if (left.source === "compartment" && right.source === "compartment") {
185608
+ return left.startOrdinal - right.startOrdinal;
185609
+ }
183691
185610
  if (left.source === "git_commit" && right.source === "git_commit") {
183692
185611
  return right.committedAtMs - left.committedAtMs;
183693
185612
  }
@@ -183738,10 +185657,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183738
185657
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
183739
185658
  const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
183740
185659
  const activeSources = resolveSources(options.sources);
183741
- const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
185660
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
185661
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
183742
185662
  const runMessages = activeSources.has("message");
183743
185663
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
183744
- const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
185664
+ const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
185665
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks) && embeddingEnabled && isEmbeddingRuntimeEnabled();
183745
185666
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
183746
185667
  log(`[search] query embedding failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
183747
185668
  return null;
@@ -183757,6 +185678,24 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183757
185678
  probes: messageProbes
183758
185679
  }) : [];
183759
185680
  const queryEmbedding = await queryEmbeddingPromise;
185681
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
185682
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
185683
+ const embeddingModelId = embeddingSnapshot?.modelId;
185684
+ const chunkModelId = embeddingSnapshot?.chunkModelId;
185685
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
185686
+ db,
185687
+ sessionId,
185688
+ projectPath,
185689
+ queryEmbedding,
185690
+ limit: tierLimit,
185691
+ maxOrdinal: options.maxMessageOrdinal,
185692
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null
185693
+ }) : [];
185694
+ const messageLikeResults = mergeMessageAndCompartmentResults({
185695
+ messages: messageResults,
185696
+ compartments: compartmentResults,
185697
+ limit: tierLimit
185698
+ });
183760
185699
  const [memoryResults, gitCommitResults] = await Promise.all([
183761
185700
  runMemory ? searchMemories({
183762
185701
  db,
@@ -183765,6 +185704,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183765
185704
  limit: tierLimit,
183766
185705
  memoryEnabled: true,
183767
185706
  queryEmbedding,
185707
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
185708
+ workspace,
183768
185709
  visibleMemoryIds: options.visibleMemoryIds
183769
185710
  }) : Promise.resolve([]),
183770
185711
  runGitCommits ? Promise.resolve(searchGitCommits({
@@ -183775,7 +185716,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
183775
185716
  queryEmbedding
183776
185717
  })) : Promise.resolve([])
183777
185718
  ]);
183778
- const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
185719
+ const results = [...memoryResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
183779
185720
  const countRetrievals = options.countRetrievals ?? true;
183780
185721
  if (countRetrievals) {
183781
185722
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -183839,6 +185780,11 @@ function renderFragment(result, charCap) {
183839
185780
  const compressed = cavemanCompress(result.content, "ultra");
183840
185781
  return truncate(compressed, charCap);
183841
185782
  }
185783
+ case "compartment": {
185784
+ const source = result.snippet ?? result.title;
185785
+ const compressed = cavemanCompress(source, "ultra");
185786
+ return truncate(compressed, charCap);
185787
+ }
183842
185788
  }
183843
185789
  }
183844
185790
  function buildAutoSearchHint(results, options = {}) {
@@ -184479,7 +186425,7 @@ function isVisibleNoteReadPart(part) {
184479
186425
  }
184480
186426
 
184481
186427
  // src/hooks/magic-context/todo-view.ts
184482
- import { createHash as createHash9 } from "node:crypto";
186428
+ import { createHash as createHash11 } from "node:crypto";
184483
186429
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
184484
186430
  var TITLE_DONE_STATUSES = new Set(["completed"]);
184485
186431
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -184524,7 +186470,7 @@ function buildSyntheticTodoPart(stateJson) {
184524
186470
  };
184525
186471
  }
184526
186472
  function computeSyntheticCallId(stateJson) {
184527
- const hash2 = createHash9("sha256").update(stateJson).digest("hex").slice(0, 16);
186473
+ const hash2 = createHash11("sha256").update(stateJson).digest("hex").slice(0, 16);
184528
186474
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
184529
186475
  }
184530
186476
  function parseTodoState(stateJson) {
@@ -184575,15 +186521,25 @@ async function runPostTransformPhase(args) {
184575
186521
  const emergencyBypassCompartmentGate = forceMaterialization;
184576
186522
  const deferredMaterialize = args.canConsumeDeferredLate && deferredMaterializationWasPending;
184577
186523
  const materializationRequested = isExplicitFlush || deferredMaterialize;
184578
- const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
186524
+ const m0M1EnabledForFold = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
186525
+ const m0HardFoldThisPass = m0M1EnabledForFold && args.m0M1 ? mustMaterialize({
186526
+ db: args.db,
186527
+ sessionId: args.sessionId,
186528
+ state: args.sessionMeta,
186529
+ projectPath: args.m0M1.projectPath,
186530
+ projectDirectory: args.m0M1.projectDirectory,
186531
+ hardSignals: args.m0M1.hardSignals
186532
+ }).value : false;
186533
+ const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || m0HardFoldThisPass || compartmentRunning;
184579
186534
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
184580
186535
  const hasPendingUserOps = pendingOps.length > 0;
184581
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
184582
- const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
186536
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || m0HardFoldThisPass) && (!compartmentRunning || emergencyBypassCompartmentGate);
186537
+ const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || m0HardFoldThisPass || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
184583
186538
  const isCacheBustingPass = shouldApplyPendingOps || shouldRunHeuristics;
186539
+ const canUseEmptySentinels = modelAcceptsEmptyContent(args.resolvedProviderID);
184584
186540
  if (shouldRunHeuristics) {
184585
186541
  const subagentRerun = !args.fullFeatureMode && alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && !forceMaterialization;
184586
- const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
186542
+ const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : m0HardFoldThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into known m[0] bust, scheduler=${args.schedulerDecision})` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
184587
186543
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
184588
186544
  }
184589
186545
  if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !materializationRequested && args.fullFeatureMode) {
@@ -184626,7 +186582,9 @@ async function runPostTransformPhase(args) {
184626
186582
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags}`);
184627
186583
  const t7 = performance.now();
184628
186584
  const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
184629
- stripClearedReasoning(args.messages);
186585
+ if (canUseEmptySentinels) {
186586
+ stripClearedReasoning(args.messages);
186587
+ }
184630
186588
  const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
184631
186589
  if (clearedReasoning > 0 || strippedInline > 0) {
184632
186590
  let maxTag = 0;
@@ -184672,7 +186630,6 @@ async function runPostTransformPhase(args) {
184672
186630
  if (args.watermark > 0) {
184673
186631
  const tWatermarkCleanup = performance.now();
184674
186632
  truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
184675
- stripProcessedImages(args.messages, args.watermark, args.messageTagNumbers);
184676
186633
  logTransformTiming(args.sessionId, "watermarkCleanup", tWatermarkCleanup);
184677
186634
  }
184678
186635
  if (shouldApplyPendingOps) {
@@ -184682,21 +186639,40 @@ async function runPostTransformPhase(args) {
184682
186639
  sessionLog(args.sessionId, "transform failed applying pending operations:", error51);
184683
186640
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: getErrorMessage(error51) });
184684
186641
  }
184685
- try {
184686
- const t8 = performance.now();
184687
- const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
184688
- const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
184689
- detect: isCacheBustingPass,
184690
- protectedCount: args.protectedTags
184691
- });
184692
- if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
184693
- addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
186642
+ if (canUseEmptySentinels) {
186643
+ try {
186644
+ const t8 = performance.now();
186645
+ const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
186646
+ const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
186647
+ detect: isCacheBustingPass,
186648
+ protectedCount: args.protectedTags
186649
+ });
186650
+ if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
186651
+ addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
186652
+ }
186653
+ logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
186654
+ } catch (error51) {
186655
+ sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
184694
186656
  }
184695
- logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
184696
- } catch (error51) {
184697
- sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
184698
186657
  }
184699
- const m0M1Enabled = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
186658
+ if (canUseEmptySentinels && args.watermark > 0) {
186659
+ try {
186660
+ const tImg = performance.now();
186661
+ const frozenImageIds = getProcessedImageStrippedIds(args.db, args.sessionId);
186662
+ const imageResult = stripProcessedImages(args.messages, frozenImageIds, {
186663
+ detect: isCacheBustingPass,
186664
+ watermark: args.watermark,
186665
+ messageTagNumbers: args.messageTagNumbers
186666
+ });
186667
+ if (isCacheBustingPass && imageResult.newlyStrippedIds.length > 0) {
186668
+ addProcessedImageStrippedIds(args.db, args.sessionId, imageResult.newlyStrippedIds);
186669
+ }
186670
+ logTransformTiming(args.sessionId, "stripProcessedImages", tImg);
186671
+ } catch (error51) {
186672
+ sessionLog(args.sessionId, "transform failed stripping processed images:", error51);
186673
+ }
186674
+ }
186675
+ const m0M1Enabled = m0M1EnabledForFold;
184700
186676
  if (m0M1Enabled && args.m0M1) {
184701
186677
  const tInjectM0M1 = performance.now();
184702
186678
  try {
@@ -184743,7 +186719,7 @@ async function runPostTransformPhase(args) {
184743
186719
  const tPlaceholder = performance.now();
184744
186720
  const persistedIds = getStrippedPlaceholderIds(args.db, args.sessionId);
184745
186721
  if (persistedIds.size > 0) {
184746
- const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.liveProviderID);
186722
+ const { replayed, missingIds } = replaySentinelByMessageIds(args.messages, persistedIds, args.resolvedProviderID);
184747
186723
  if (replayed > 0) {
184748
186724
  sessionLog(args.sessionId, `sentinel replay: neutralized ${replayed} previously-stripped messages`);
184749
186725
  }
@@ -184754,9 +186730,9 @@ async function runPostTransformPhase(args) {
184754
186730
  }
184755
186731
  }
184756
186732
  if (isCacheBustingPass) {
184757
- const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.liveProviderID);
186733
+ const droppedResult = stripDroppedPlaceholderMessages(args.messages, args.resolvedProviderID);
184758
186734
  const protectedTailStart = Math.max(0, args.messages.length - args.protectedTags * 2);
184759
- const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.liveProviderID);
186735
+ const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.resolvedProviderID);
184760
186736
  const newlyNeutralized = droppedResult.sentineledIds.length + systemInjectedResult.sentineledIds.length;
184761
186737
  if (newlyNeutralized > 0) {
184762
186738
  const addedIds = [
@@ -184956,6 +186932,7 @@ function clearMessageTokensCache(sessionId, messageId) {
184956
186932
  if (cache)
184957
186933
  cache.delete(messageId);
184958
186934
  }
186935
+ var recordedSessionProjectIdentity = new BoundedSessionMap(MESSAGE_TOKENS_CACHE_MAX);
184959
186936
  function findLastAssistantModel2(messages) {
184960
186937
  for (let i = messages.length - 1;i >= 0; i--) {
184961
186938
  const info = messages[i].info;
@@ -185001,11 +186978,13 @@ function createTransform(deps) {
185001
186978
  }
185002
186979
  const reducedMode = sessionMeta.isSubagent;
185003
186980
  const fullFeatureMode = !reducedMode;
185004
- const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false;
186981
+ const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false && resolveCtxReduceAvailabilityFromMessages(sessionId, messages);
185005
186982
  let sessionDirectory = deps.directory ?? "";
186983
+ let sessionDirectoryResolvedFromHost = false;
185006
186984
  const cachedDirectory = deps.sessionDirectoryBySession?.get(sessionId);
185007
186985
  if (cachedDirectory && cachedDirectory.length > 0) {
185008
186986
  sessionDirectory = cachedDirectory;
186987
+ sessionDirectoryResolvedFromHost = true;
185009
186988
  } else if (deps.client !== undefined) {
185010
186989
  try {
185011
186990
  const sessionResponse = await deps.client.session.get({ path: { id: sessionId } }).catch(() => null);
@@ -185013,6 +186992,7 @@ function createTransform(deps) {
185013
186992
  if (sessionInfo && typeof sessionInfo.directory === "string" && sessionInfo.directory.length > 0) {
185014
186993
  sessionDirectory = sessionInfo.directory;
185015
186994
  deps.sessionDirectoryBySession?.set(sessionId, sessionDirectory);
186995
+ sessionDirectoryResolvedFromHost = true;
185016
186996
  }
185017
186997
  } catch {}
185018
186998
  }
@@ -185107,6 +187087,8 @@ function createTransform(deps) {
185107
187087
  deps.liveModelBySession?.set(sessionId, recovered);
185108
187088
  }
185109
187089
  }
187090
+ const resolvedProviderID = modelForBudget?.providerID;
187091
+ const canUseEmptySentinels = modelAcceptsEmptyContent(resolvedProviderID);
185110
187092
  const resolvedContextLimit = modelForBudget ? resolveTrustedContextLimit(modelForBudget.providerID, modelForBudget.modelID, {
185111
187093
  db,
185112
187094
  sessionID: sessionId
@@ -185271,6 +187253,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185271
187253
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
185272
187254
  const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(compartmentDirectory || process.cwd()) : undefined;
185273
187255
  const sessionProjectIdentity = projectIdentity ?? (sessionDirectory ? resolveProjectIdentity(sessionDirectory) : deps.projectPath);
187256
+ if (sessionProjectIdentity && sessionDirectoryResolvedFromHost && recordedSessionProjectIdentity.get(sessionId) !== sessionProjectIdentity) {
187257
+ recordSessionProjectIdentity(db, sessionId, sessionProjectIdentity);
187258
+ recordedSessionProjectIdentity.set(sessionId, sessionProjectIdentity);
187259
+ }
185274
187260
  let triggerBoundarySnapshot;
185275
187261
  if (fullFeatureMode && historianRunnable && !sessionMeta.compartmentInProgress) {
185276
187262
  const tTrigger = performance.now();
@@ -185365,7 +187351,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185365
187351
  }
185366
187352
  }
185367
187353
  const t3 = performance.now();
185368
- const strippedStructuralNoise = stripStructuralNoise(messages);
187354
+ const strippedStructuralNoise = canUseEmptySentinels ? stripStructuralNoise(messages) : 0;
185369
187355
  logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
185370
187356
  const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
185371
187357
  if (persistedReasoningWatermark > 0) {
@@ -185386,18 +187372,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185386
187372
  logTransformTiming(sessionId, "replayCavemanCompression", tCavemanReplay);
185387
187373
  }
185388
187374
  const t4 = performance.now();
185389
- const strippedClearedReasoning = stripClearedReasoning(messages);
187375
+ const strippedClearedReasoning = canUseEmptySentinels ? stripClearedReasoning(messages) : 0;
185390
187376
  logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
185391
187377
  const tMergeStrip = performance.now();
185392
- let liveProviderID = deps.liveModelBySession?.get(sessionId)?.providerID;
185393
- if (liveProviderID === undefined) {
185394
- const recovered = findLastAssistantModelFromOpenCodeDb(sessionId);
185395
- if (recovered) {
185396
- liveProviderID = recovered.providerID;
185397
- deps.liveModelBySession?.set(sessionId, recovered);
185398
- }
185399
- }
185400
- const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, liveProviderID);
187378
+ const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages, resolvedProviderID);
185401
187379
  if (strippedMergedReasoning > 0) {
185402
187380
  sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
185403
187381
  }
@@ -185454,7 +187432,6 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185454
187432
  logTransformTiming(sessionId, "compartmentPhase", tCompartmentPhase);
185455
187433
  const hardModel = deps.liveModelBySession?.get(sessionId);
185456
187434
  const hardModelKey = hardModel ? `${hardModel.providerID}/${hardModel.modelID}` : "";
185457
- const hardToolSetHash = deps.getToolSetHash?.(sessionId) ?? "";
185458
187435
  const hardSystemHash = typeof sessionMeta.systemPromptHash === "string" ? sessionMeta.systemPromptHash : "";
185459
187436
  let hardTtlMs = 5 * 60 * 1000;
185460
187437
  try {
@@ -185463,7 +187440,6 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185463
187440
  const hardCacheExpired = sessionMeta.lastResponseTime > 0 && Date.now() - sessionMeta.lastResponseTime >= hardTtlMs;
185464
187441
  const m0HardSignals = {
185465
187442
  systemHash: hardSystemHash,
185466
- toolSetHash: hardToolSetHash,
185467
187443
  modelKey: hardModelKey,
185468
187444
  cacheExpired: hardCacheExpired,
185469
187445
  lastResponseTime: sessionMeta.lastResponseTime
@@ -185518,7 +187494,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
185518
187494
  sessionDirectory,
185519
187495
  autoSearch: deps.autoSearch,
185520
187496
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
185521
- liveProviderID,
187497
+ resolvedProviderID,
185522
187498
  historyRefreshSessions: deps.historyRefreshSessions,
185523
187499
  m0M1: {
185524
187500
  projectPath: projectIdentity,
@@ -186371,7 +188347,7 @@ function createToolExecuteAfterHook(args) {
186371
188347
  init_send_session_notification();
186372
188348
 
186373
188349
  // src/hooks/magic-context/system-prompt-hash.ts
186374
- import { createHash as createHash10 } from "node:crypto";
188350
+ import { createHash as createHash12 } from "node:crypto";
186375
188351
 
186376
188352
  // src/agents/magic-context-prompt.ts
186377
188353
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -186487,9 +188463,9 @@ Prefer many small targeted operations over one large blanket operation, and keep
186487
188463
 
186488
188464
  // src/hooks/magic-context/system-prompt-hash.ts
186489
188465
  init_logger();
188466
+ await init_storage();
186490
188467
  init_key_files_block();
186491
188468
  init_read_session_formatting();
186492
- await init_storage();
186493
188469
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
186494
188470
  function clearSystemPromptHashSession(sessionId, handleMaps) {
186495
188471
  clearKeyFilesCacheForSession(sessionId);
@@ -186535,9 +188511,10 @@ function createSystemPromptHashHandler(deps) {
186535
188511
  sessionLog(sessionId, "system-prompt-hash session meta load failed:", error51);
186536
188512
  }
186537
188513
  const isSubagentSession = sessionMetaEarly?.isSubagent === true;
186538
- const subagentReduceMode = isSubagentSession && deps.ctxReduceEnabled !== false;
186539
- const effectiveCtxReduceEnabled = isSubagentSession ? false : deps.ctxReduceEnabled;
186540
- const skipGuidanceForDisabledSubagent = isSubagentSession && deps.ctxReduceEnabled === false;
188514
+ const ctxReduceCallable = resolveCtxReduceAvailability(sessionId);
188515
+ const subagentReduceMode = isSubagentSession && deps.ctxReduceEnabled !== false && ctxReduceCallable;
188516
+ const effectiveCtxReduceEnabled = isSubagentSession ? false : deps.ctxReduceEnabled !== false && ctxReduceCallable;
188517
+ const skipGuidanceForDisabledSubagent = isSubagentSession && (deps.ctxReduceEnabled === false || !ctxReduceCallable);
186541
188518
  const fullPrompt = output.system.join(`
186542
188519
  `);
186543
188520
  if (fullPrompt.length > 0 && !fullPrompt.includes(MAGIC_CONTEXT_MARKER) && !skipGuidanceForDisabledSubagent) {
@@ -186570,7 +188547,7 @@ function createSystemPromptHashHandler(deps) {
186570
188547
  `);
186571
188548
  if (systemContent.length === 0)
186572
188549
  return;
186573
- const currentHash = createHash10("md5").update(systemContent).digest("hex");
188550
+ const currentHash = createHash12("md5").update(systemContent).digest("hex");
186574
188551
  if (!sessionMetaEarly) {
186575
188552
  return;
186576
188553
  }
@@ -186831,6 +188808,46 @@ function createMagicContextHook(deps) {
186831
188808
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
186832
188809
  getNotificationParams: (sid) => getLiveNotificationParams(sid, liveModelBySession, variantBySession, agentBySession)
186833
188810
  });
188811
+ const executeEmbedHistory = async (sessionId) => {
188812
+ if (deps.config.memory?.enabled === false) {
188813
+ return "Memory is disabled for this project, so there is no semantic embedding to backfill.";
188814
+ }
188815
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
188816
+ await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
188817
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
188818
+ setRecompStarting({ recompProgressBySession }, sessionId, "Embedding history…", "embed");
188819
+ const outcome = await embedSessionCompartmentChunks(db, sessionProjectIdentity, sessionId, {
188820
+ onProgress: ({ embedded, total }) => {
188821
+ const cur = recompProgressBySession.get(sessionId);
188822
+ if (!cur || cur.phase !== "recomp")
188823
+ return;
188824
+ recompProgressBySession.set(sessionId, {
188825
+ ...cur,
188826
+ processedMessages: embedded,
188827
+ totalMessages: total,
188828
+ updatedAt: Date.now()
188829
+ });
188830
+ }
188831
+ });
188832
+ const terminal = (phase, message) => {
188833
+ setRecompTerminal({ recompProgressBySession }, sessionId, phase, message);
188834
+ return message;
188835
+ };
188836
+ switch (outcome.status) {
188837
+ case "nothing":
188838
+ return terminal("done", "All of this session's history is already embedded.");
188839
+ case "disabled":
188840
+ return terminal("skipped", "No embedding provider is configured, so there is nothing to embed.");
188841
+ case "busy":
188842
+ return terminal("skipped", `Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`);
188843
+ case "aborted":
188844
+ return terminal("done", `Embedded ${outcome.embedded} of ${outcome.total} compartments before stopping.`);
188845
+ case "stalled":
188846
+ return terminal("skipped", `Embedded ${outcome.embedded} compartments; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed-history again to retry them.`);
188847
+ default:
188848
+ return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`);
188849
+ }
188850
+ };
186834
188851
  const sidekickRunnable = isSidekickRunnable(deps.config);
186835
188852
  const sidekickConfig = sidekickRunnable ? deps.config.sidekick : undefined;
186836
188853
  const transform2 = createTransform({
@@ -186869,12 +188886,6 @@ function createMagicContextHook(deps) {
186869
188886
  const model = liveModelBySession.get(sessionId);
186870
188887
  return resolveModelKey(model?.providerID, model?.modelID);
186871
188888
  },
186872
- getToolSetHash: (sessionId) => {
186873
- const model = liveModelBySession.get(sessionId);
186874
- if (!model)
186875
- return "";
186876
- return getCurrentToolSetHash(model.providerID, model.modelID, agentBySession.get(sessionId));
186877
- },
186878
188889
  getFallbackModelId: (sessionId) => {
186879
188890
  const model = liveModelBySession.get(sessionId);
186880
188891
  return model ? `${model.providerID}/${model.modelID}` : undefined;
@@ -186991,6 +189002,7 @@ function createMagicContextHook(deps) {
186991
189002
  },
186992
189003
  executeRecomp: historianRunnable ? async (sessionId, options) => runManagedRecomp(buildManagedRecompCtx(sessionId), sessionId, options) : undefined,
186993
189004
  runUpgrade: historianRunnable ? async (sessionId) => runManagedUpgrade(buildManagedRecompCtx(sessionId), sessionId) : undefined,
189005
+ executeEmbedHistory,
186994
189006
  sendNotification: async (sessionId, text, params) => {
186995
189007
  await sendIgnoredMessage(deps.client, sessionId, text, {
186996
189008
  ...getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
@@ -188129,6 +190141,7 @@ init_memory();
188129
190141
  init_embedding();
188130
190142
  init_embedding_cache();
188131
190143
  init_normalize_hash();
190144
+ init_workspaces();
188132
190145
  init_logger();
188133
190146
  await init_storage();
188134
190147
  import { tool as tool2 } from "@opencode-ai/plugin";
@@ -188288,6 +190301,23 @@ function createCtxMemoryTool(deps) {
188288
190301
  }
188289
190302
  const projectPath = deps.resolveProjectPath(toolContext.directory);
188290
190303
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
190304
+ const workspaceIdentitySet = resolveWorkspaceIdentitySet(deps.db, projectPath);
190305
+ const expandedWorkspace = expandWorkspaceIdentitySetWithAliases(deps.db, workspaceIdentitySet.identities);
190306
+ const workspaceVisibleIdentities = workspaceIdentitySet.identities.length > 1 ? expandedWorkspace.expandedIdentities : workspaceIdentitySet.identities;
190307
+ const targetIdentityForStoredPath = (rawProjectPath) => workspaceIdentitySet.identities.length > 1 ? resolveStoredPathWorkspaceIdentity(rawProjectPath, workspaceIdentitySet.identities, expandedWorkspace.canonicalIdentityByStoredPath) ?? projectIdentityForStoredPath(rawProjectPath) : projectIdentityForStoredPath(rawProjectPath);
190308
+ const toolShareCategories = workspaceIdentitySet.identities.length > 1 ? resolveWorkspaceShareCategories(deps.db, projectPath) : null;
190309
+ const memoryVisibleToTool = (memory) => {
190310
+ if (workspaceIdentitySet.identities.length <= 1) {
190311
+ return memoryBelongsToProject(memory, projectPath);
190312
+ }
190313
+ if (!storedPathBelongsToWorkspace(memory.projectPath, workspaceIdentitySet.identities, workspaceVisibleIdentities, expandedWorkspace.canonicalIdentityByStoredPath)) {
190314
+ return false;
190315
+ }
190316
+ const isOwn = targetIdentityForStoredPath(memory.projectPath) === projectPath;
190317
+ if (isOwn)
190318
+ return true;
190319
+ return toolShareCategories === null || toolShareCategories.includes(memory.category);
190320
+ };
188291
190321
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
188292
190322
  if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
188293
190323
  return getDisabledMessage();
@@ -188344,18 +190374,18 @@ function createCtxMemoryTool(deps) {
188344
190374
  }
188345
190375
  const rawProjectPath = projectPathForMemoryId(deps.db, updateId);
188346
190376
  const memory = getMemoryById(deps.db, updateId);
188347
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
190377
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
188348
190378
  return `Error: Memory with ID ${updateId} was not found.`;
188349
190379
  }
188350
190380
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
188351
190381
  return inactiveMemoryError(updateId, "updating");
188352
190382
  }
188353
190383
  const normalizedHash = computeNormalizedHash(content);
188354
- const duplicate = getMemoryByHash(deps.db, projectPath, memory.category, normalizedHash);
190384
+ const duplicate = getMemoryByHash(deps.db, targetIdentityForStoredPath(rawProjectPath), memory.category, normalizedHash);
188355
190385
  if (duplicate && duplicate.id !== memory.id) {
188356
190386
  return `Error: Memory content already exists as ID ${duplicate.id}; merge or archive duplicates instead.`;
188357
190387
  }
188358
- const projectIdentity = projectIdentityForStoredPath(rawProjectPath);
190388
+ const projectIdentity = targetIdentityForStoredPath(rawProjectPath);
188359
190389
  deps.db.transaction(() => {
188360
190390
  updateMemoryContentInCurrentTransaction(deps.db, memory, content, normalizedHash);
188361
190391
  queueMemoryMutation(deps.db, {
@@ -188369,7 +190399,7 @@ function createCtxMemoryTool(deps) {
188369
190399
  queueMemoryEmbedding({
188370
190400
  deps,
188371
190401
  sessionId: toolContext.sessionID,
188372
- projectPath,
190402
+ projectPath: projectIdentity,
188373
190403
  memoryId: memory.id,
188374
190404
  content
188375
190405
  });
@@ -188392,7 +190422,7 @@ function createCtxMemoryTool(deps) {
188392
190422
  return "Error: One or more source memories were not found.";
188393
190423
  }
188394
190424
  if (toolContext.agent !== DREAMER_AGENT) {
188395
- const foreign = sourceMemories.find((memory) => !memoryBelongsToProject(memory, projectPath));
190425
+ const foreign = sourceMemories.find((memory) => !memoryVisibleToTool(memory));
188396
190426
  if (foreign) {
188397
190427
  return `Error: Memory with ID ${foreign.id} was not found.`;
188398
190428
  }
@@ -188476,15 +190506,16 @@ function createCtxMemoryTool(deps) {
188476
190506
  return `Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`;
188477
190507
  }
188478
190508
  if (args.action === "archive") {
188479
- const archiveIds = args.ids;
188480
- if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
190509
+ const rawArchiveIds = args.ids;
190510
+ if (!rawArchiveIds || rawArchiveIds.length === 0 || !rawArchiveIds.every(Number.isInteger)) {
188481
190511
  return "Error: 'ids' must contain at least one integer memory ID when action is 'archive'.";
188482
190512
  }
190513
+ const archiveIds = [...new Set(rawArchiveIds)];
188483
190514
  const targets = [];
188484
190515
  for (const memoryId of archiveIds) {
188485
190516
  const rawProjectPath = projectPathForMemoryId(deps.db, memoryId);
188486
190517
  const memory = getMemoryById(deps.db, memoryId);
188487
- if (!memory || !rawProjectPath || !memoryBelongsToProject(memory, projectPath)) {
190518
+ if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
188488
190519
  return `Error: Memory with ID ${memoryId} was not found.`;
188489
190520
  }
188490
190521
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -188492,7 +190523,7 @@ function createCtxMemoryTool(deps) {
188492
190523
  }
188493
190524
  targets.push({
188494
190525
  memoryId,
188495
- projectIdentity: projectIdentityForStoredPath(rawProjectPath)
190526
+ projectIdentity: targetIdentityForStoredPath(rawProjectPath)
188496
190527
  });
188497
190528
  }
188498
190529
  deps.db.transaction(() => {
@@ -188966,8 +190997,9 @@ function formatAge2(committedAtMs) {
188966
190997
  }
188967
190998
  function formatResult(result, index) {
188968
190999
  if (result.source === "memory") {
191000
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
188969
191001
  return [
188970
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category} match=${result.matchType}`,
191002
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
188971
191003
  result.content
188972
191004
  ].join(`
188973
191005
  `);
@@ -188977,6 +191009,13 @@ function formatResult(result, index) {
188977
191009
  `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
188978
191010
  result.content
188979
191011
  ].join(`
191012
+ `);
191013
+ }
191014
+ if (result.source === "compartment") {
191015
+ return [
191016
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
191017
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
191018
+ ].join(`
188980
191019
  `);
188981
191020
  }
188982
191021
  const expandStart = Math.max(1, result.messageOrdinal - 3);
@@ -188992,7 +191031,7 @@ function formatSearchResults(query, results) {
188992
191031
  return `No results found for "${query}" across memories, git commits, or message history.`;
188993
191032
  }
188994
191033
  const bodyParts = results.map((result, index) => formatResult(result, index + 1));
188995
- if (results.some((result) => result.source === "message")) {
191034
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
188996
191035
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
188997
191036
  }
188998
191037
  const body = bodyParts.join(`
@@ -189150,11 +191189,11 @@ import { createServer } from "node:http";
189150
191189
  import { dirname as dirname5 } from "node:path";
189151
191190
 
189152
191191
  // src/shared/rpc-utils.ts
189153
- import { createHash as createHash11 } from "node:crypto";
191192
+ import { createHash as createHash13 } from "node:crypto";
189154
191193
  import { join as join20 } from "node:path";
189155
191194
  function projectHash(directory) {
189156
191195
  const normalized = directory.replace(/\/+$/, "");
189157
- return createHash11("sha256").update(normalized).digest("hex").slice(0, 16);
191196
+ return createHash13("sha256").update(normalized).digest("hex").slice(0, 16);
189158
191197
  }
189159
191198
  function rpcPortDir(storageDir, directory) {
189160
191199
  return join20(storageDir, "rpc", projectHash(directory));