@cortexkit/opencode-magic-context 0.22.3 → 0.22.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/magic-context-prompt.d.ts.map +1 -1
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/project-security.d.ts +25 -0
  5. package/dist/config/project-security.d.ts.map +1 -0
  6. package/dist/config/prune-config-leaf.d.ts +27 -0
  7. package/dist/config/prune-config-leaf.d.ts.map +1 -0
  8. package/dist/config/schema/magic-context.d.ts +7 -0
  9. package/dist/config/schema/magic-context.d.ts.map +1 -1
  10. package/dist/config/variable.d.ts.map +1 -1
  11. package/dist/features/magic-context/compartment-storage.d.ts +9 -6
  12. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  13. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  14. package/dist/features/magic-context/dreamer/scheduler.d.ts.map +1 -1
  15. package/dist/features/magic-context/key-files/aft-availability.d.ts.map +1 -1
  16. package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -1
  17. package/dist/features/magic-context/key-files/read-stats.d.ts +0 -2
  18. package/dist/features/magic-context/key-files/read-stats.d.ts.map +1 -1
  19. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/embedding-ssrf.d.ts +29 -0
  21. package/dist/features/magic-context/memory/embedding-ssrf.d.ts.map +1 -0
  22. package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -1
  23. package/dist/features/magic-context/memory/project-identity.d.ts +10 -0
  24. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  25. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  26. package/dist/features/magic-context/project-docs-hash.d.ts.map +1 -1
  27. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  28. package/dist/features/magic-context/range-parser.d.ts +6 -0
  29. package/dist/features/magic-context/range-parser.d.ts.map +1 -1
  30. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  31. package/dist/features/magic-context/storage-db.d.ts +1 -1
  32. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  33. package/dist/features/magic-context/storage-meta-persisted.d.ts +41 -1
  34. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  36. package/dist/features/magic-context/storage-meta-shared.d.ts +7 -1
  37. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  39. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage.d.ts +2 -2
  41. package/dist/features/magic-context/storage.d.ts.map +1 -1
  42. package/dist/features/magic-context/tool-definition-tokens.d.ts +21 -0
  43. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  44. package/dist/features/magic-context/types.d.ts +4 -0
  45. package/dist/features/magic-context/types.d.ts.map +1 -1
  46. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  47. package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
  48. package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
  49. package/dist/hooks/auto-update-checker/semver.d.ts +17 -0
  50. package/dist/hooks/auto-update-checker/semver.d.ts.map +1 -0
  51. package/dist/hooks/magic-context/command-handler.d.ts +1 -6
  52. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +1 -1
  54. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/compartment-runner-historian.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-validation.d.ts +25 -0
  60. package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/decay-render.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts +36 -1
  63. package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/inject-compartments.d.ts +41 -0
  67. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +2 -1
  70. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/transform.d.ts +8 -0
  72. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +818 -259
  75. package/dist/plugin/embedding-bootstrap-helpers.d.ts.map +1 -1
  76. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  77. package/dist/shared/keep-subagents.d.ts +7 -0
  78. package/dist/shared/keep-subagents.d.ts.map +1 -0
  79. package/dist/shared/rpc-server.d.ts.map +1 -1
  80. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  81. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/shared/keep-subagents.test.ts +39 -0
  84. package/src/shared/keep-subagents.ts +33 -0
  85. package/src/shared/rpc-server.ts +18 -2
  86. package/src/tui/index.tsx +9 -5
package/dist/index.js CHANGED
@@ -287,7 +287,7 @@ __export(exports_util, {
287
287
  jsonStringifyReplacer: () => jsonStringifyReplacer,
288
288
  joinValues: () => joinValues,
289
289
  issue: () => issue,
290
- isPlainObject: () => isPlainObject,
290
+ isPlainObject: () => isPlainObject3,
291
291
  isObject: () => isObject,
292
292
  hexToUint8Array: () => hexToUint8Array,
293
293
  getSizableOrigin: () => getSizableOrigin,
@@ -452,7 +452,7 @@ function slugify(input) {
452
452
  function isObject(data) {
453
453
  return typeof data === "object" && data !== null && !Array.isArray(data);
454
454
  }
455
- function isPlainObject(o) {
455
+ function isPlainObject3(o) {
456
456
  if (isObject(o) === false)
457
457
  return false;
458
458
  const ctor = o.constructor;
@@ -469,7 +469,7 @@ function isPlainObject(o) {
469
469
  return true;
470
470
  }
471
471
  function shallowClone(o) {
472
- if (isPlainObject(o))
472
+ if (isPlainObject3(o))
473
473
  return { ...o };
474
474
  if (Array.isArray(o))
475
475
  return [...o];
@@ -609,7 +609,7 @@ function omit(schema, mask) {
609
609
  return clone(schema, def);
610
610
  }
611
611
  function extend(schema, shape) {
612
- if (!isPlainObject(shape)) {
612
+ if (!isPlainObject3(shape)) {
613
613
  throw new Error("Invalid input to extend: expected a plain object");
614
614
  }
615
615
  const checks = schema._zod.def.checks;
@@ -632,7 +632,7 @@ function extend(schema, shape) {
632
632
  return clone(schema, def);
633
633
  }
634
634
  function safeExtend(schema, shape) {
635
- if (!isPlainObject(shape)) {
635
+ if (!isPlainObject3(shape)) {
636
636
  throw new Error("Invalid input to safeExtend: expected a plain object");
637
637
  }
638
638
  const def = mergeDefs(schema._zod.def, {
@@ -2115,7 +2115,7 @@ function mergeValues(a, b) {
2115
2115
  if (a instanceof Date && b instanceof Date && +a === +b) {
2116
2116
  return { valid: true, data: a };
2117
2117
  }
2118
- if (isPlainObject(a) && isPlainObject(b)) {
2118
+ if (isPlainObject3(a) && isPlainObject3(b)) {
2119
2119
  const bKeys = Object.keys(b);
2120
2120
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
2121
2121
  const newObj = { ...a, ...b };
@@ -3372,7 +3372,7 @@ var init_schemas = __esm(() => {
3372
3372
  $ZodType.init(inst, def);
3373
3373
  inst._zod.parse = (payload, ctx) => {
3374
3374
  const input = payload.value;
3375
- if (!isPlainObject(input)) {
3375
+ if (!isPlainObject3(input)) {
3376
3376
  payload.issues.push({
3377
3377
  expected: "record",
3378
3378
  code: "invalid_type",
@@ -14968,6 +14968,7 @@ var init_magic_context = __esm(() => {
14968
14968
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
14969
14969
  }).describe("Embedding provider configuration"),
14970
14970
  temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
14971
+ keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
14971
14972
  caveman_text_compression: exports_external.object({
14972
14973
  enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Only active when ctx_reduce_enabled=false. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
14973
14974
  min_chars: exports_external.number().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
@@ -15161,6 +15162,9 @@ function normalizeStoredProjectPath(rawOrStored) {
15161
15162
  return directoryFallback(rawOrStored);
15162
15163
  }
15163
15164
  }
15165
+ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
15166
+ return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
15167
+ }
15164
15168
  var GIT_TIMEOUT_MS = 5000, identityCache, directoryFallbackCache, ProjectIdentityError;
15165
15169
  var init_project_identity = __esm(() => {
15166
15170
  identityCache = new Map;
@@ -15746,6 +15750,15 @@ function extractLatestAssistantText(messages) {
15746
15750
  }
15747
15751
  var init_assistant_message_extractor = () => {};
15748
15752
 
15753
+ // src/shared/keep-subagents.ts
15754
+ function setKeepSubagents(value) {
15755
+ keepSubagents = value === true;
15756
+ }
15757
+ function shouldKeepSubagents() {
15758
+ return keepSubagents;
15759
+ }
15760
+ var keepSubagents = false;
15761
+
15749
15762
  // src/features/magic-context/compartment-lease.ts
15750
15763
  function acquireCompartmentLease(db, sessionId, holderId) {
15751
15764
  const acquiredAt = Date.now();
@@ -15832,7 +15845,7 @@ function isSessionMetaRow(row) {
15832
15845
  if (row === null || typeof row !== "object")
15833
15846
  return false;
15834
15847
  const r = row;
15835
- 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.last_observed_model_key) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15848
+ 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.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15836
15849
  }
15837
15850
  function getDefaultSessionMeta(sessionId) {
15838
15851
  return {
@@ -15868,6 +15881,9 @@ function getDefaultSessionMeta(sessionId) {
15868
15881
  cachedM0MaterializedAt: null,
15869
15882
  cachedM0SessionFactsVersion: null,
15870
15883
  cachedM0UpgradeState: null,
15884
+ cachedM0SystemHash: null,
15885
+ cachedM0ToolSetHash: null,
15886
+ cachedM0ModelKey: null,
15871
15887
  lastObservedModelKey: null,
15872
15888
  upgradeRemindedAt: null,
15873
15889
  piStableIdScheme: null
@@ -15919,6 +15935,9 @@ function toSessionMeta(row) {
15919
15935
  cachedM0MaterializedAt: numOrNull(row.cached_m0_materialized_at),
15920
15936
  cachedM0SessionFactsVersion: numOrNull(row.cached_m0_session_facts_version),
15921
15937
  cachedM0UpgradeState: stringOrNull(row.cached_m0_upgrade_state),
15938
+ cachedM0SystemHash: stringOrNull(row.cached_m0_system_hash),
15939
+ cachedM0ToolSetHash: stringOrNull(row.cached_m0_tool_set_hash),
15940
+ cachedM0ModelKey: stringOrNull(row.cached_m0_model_key),
15922
15941
  lastObservedModelKey: stringOrNull(row.last_observed_model_key),
15923
15942
  upgradeRemindedAt: numOrNull(row.upgrade_reminded_at),
15924
15943
  piStableIdScheme: numOrNull(row.pi_stable_id_scheme)
@@ -15938,8 +15957,11 @@ function persistCachedM0(db, sessionId, payload) {
15938
15957
  cached_m0_project_docs_hash = ?,
15939
15958
  cached_m0_materialized_at = ?,
15940
15959
  cached_m0_session_facts_version = ?,
15941
- cached_m0_upgrade_state = ?
15942
- 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, sessionId);
15960
+ cached_m0_upgrade_state = ?,
15961
+ cached_m0_system_hash = ?,
15962
+ cached_m0_tool_set_hash = ?,
15963
+ cached_m0_model_key = ?
15964
+ 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);
15943
15965
  }
15944
15966
  function clearCachedM0M1(db, sessionId) {
15945
15967
  ensureSessionMetaRow(db, sessionId);
@@ -15957,6 +15979,9 @@ function clearCachedM0M1(db, sessionId) {
15957
15979
  ["cached_m0_materialized_at", null],
15958
15980
  ["cached_m0_session_facts_version", null],
15959
15981
  ["cached_m0_upgrade_state", null],
15982
+ ["cached_m0_system_hash", null],
15983
+ ["cached_m0_tool_set_hash", null],
15984
+ ["cached_m0_model_key", null],
15960
15985
  ["cached_m0_last_baseline_end_message_id", null],
15961
15986
  ["memory_block_cache", ""],
15962
15987
  ["memory_block_count", 0],
@@ -16009,6 +16034,9 @@ var init_storage_meta_shared = __esm(() => {
16009
16034
  "cached_m0_materialized_at",
16010
16035
  "cached_m0_session_facts_version",
16011
16036
  "cached_m0_upgrade_state",
16037
+ "cached_m0_system_hash",
16038
+ "cached_m0_tool_set_hash",
16039
+ "cached_m0_model_key",
16012
16040
  "last_observed_model_key",
16013
16041
  "upgrade_reminded_at",
16014
16042
  "pi_stable_id_scheme"
@@ -16045,6 +16073,9 @@ var init_storage_meta_shared = __esm(() => {
16045
16073
  cachedM0MaterializedAt: "cached_m0_materialized_at",
16046
16074
  cachedM0SessionFactsVersion: "cached_m0_session_facts_version",
16047
16075
  cachedM0UpgradeState: "cached_m0_upgrade_state",
16076
+ cachedM0SystemHash: "cached_m0_system_hash",
16077
+ cachedM0ToolSetHash: "cached_m0_tool_set_hash",
16078
+ cachedM0ModelKey: "cached_m0_model_key",
16048
16079
  lastObservedModelKey: "last_observed_model_key",
16049
16080
  upgradeRemindedAt: "upgrade_reminded_at",
16050
16081
  piStableIdScheme: "pi_stable_id_scheme"
@@ -16090,12 +16121,6 @@ function isCompartmentRow(row) {
16090
16121
  const candidate = row;
16091
16122
  return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.sequence === "number" && typeof candidate.start_message === "number" && typeof candidate.end_message === "number" && typeof candidate.start_message_id === "string" && typeof candidate.end_message_id === "string" && typeof candidate.title === "string" && typeof candidate.content === "string" && isStringOrNullish(candidate.p1) && isStringOrNullish(candidate.p2) && isStringOrNullish(candidate.p3) && isStringOrNullish(candidate.p4) && isNumberOrNullish(candidate.importance) && isStringOrNullish(candidate.episode_type) && isNumberOrNullish(candidate.legacy) && typeof candidate.created_at === "number";
16092
16123
  }
16093
- function isSessionFactRow(row) {
16094
- if (row === null || typeof row !== "object")
16095
- return false;
16096
- const candidate = row;
16097
- return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.category === "string" && typeof candidate.content === "string" && typeof candidate.created_at === "number" && typeof candidate.updated_at === "number";
16098
- }
16099
16124
  function insertCompartmentRows(db, sessionId, compartments, now) {
16100
16125
  const stmt = getInsertCompartmentStatement(db);
16101
16126
  for (const compartment of compartments) {
@@ -16124,16 +16149,6 @@ function toCompartment(row) {
16124
16149
  createdAt: row.created_at
16125
16150
  };
16126
16151
  }
16127
- function toSessionFact(row) {
16128
- return {
16129
- id: row.id,
16130
- sessionId: row.session_id,
16131
- category: row.category,
16132
- content: row.content,
16133
- createdAt: row.created_at,
16134
- updatedAt: row.updated_at
16135
- };
16136
- }
16137
16152
  function getCompartments(db, sessionId) {
16138
16153
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? ORDER BY sequence ASC").all(sessionId).filter(isCompartmentRow);
16139
16154
  return rows.map(toCompartment);
@@ -16142,6 +16157,11 @@ function getLastCompartmentEndMessage(db, sessionId) {
16142
16157
  const row = db.prepare("SELECT MAX(end_message) as max_end FROM compartments WHERE session_id = ?").get(sessionId);
16143
16158
  return row?.max_end ?? -1;
16144
16159
  }
16160
+ function getLastCompartmentEndMessageId(db, sessionId) {
16161
+ const row = db.prepare("SELECT end_message_id FROM compartments WHERE session_id = ? ORDER BY sequence DESC LIMIT 1").get(sessionId);
16162
+ const id = row?.end_message_id;
16163
+ return id && id.length > 0 ? id : null;
16164
+ }
16145
16165
  function getCompartmentsByEndMessageId(db, sessionId, endMessageId) {
16146
16166
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? AND end_message_id = ? ORDER BY sequence ASC").all(sessionId, endMessageId).filter(isCompartmentRow);
16147
16167
  return rows.map(toCompartment);
@@ -16154,10 +16174,6 @@ function appendCompartments(db, sessionId, compartments) {
16154
16174
  insertCompartmentRows(db, sessionId, compartments, now);
16155
16175
  })();
16156
16176
  }
16157
- function getSessionFacts(db, sessionId) {
16158
- const rows = db.prepare("SELECT * FROM session_facts WHERE session_id = ? ORDER BY category ASC, id ASC").all(sessionId).filter(isSessionFactRow);
16159
- return rows.map(toSessionFact);
16160
- }
16161
16177
  function buildCompartmentBlock(compartments, facts, memoryBlock, dateRanges) {
16162
16178
  const lines = [];
16163
16179
  if (memoryBlock) {
@@ -150113,6 +150129,18 @@ function keyFor(providerID, modelID, agentName) {
150113
150129
  function fingerprintFor(description, parameters) {
150114
150130
  return createHash3("sha256").update(description).update("\x00").update(stableStringify(parameters)).digest("hex");
150115
150131
  }
150132
+ function getCurrentToolSetHash(providerID, modelID, agentName) {
150133
+ const key = keyFor(providerID, modelID, agentName);
150134
+ const inner = fingerprints.get(key);
150135
+ if (!inner || inner.size === 0)
150136
+ return "";
150137
+ const parts = [];
150138
+ for (const [toolID, fp] of inner)
150139
+ parts.push(`${toolID}\x00${fp}`);
150140
+ parts.sort();
150141
+ return createHash3("sha256").update(parts.join(`
150142
+ `)).digest("hex");
150143
+ }
150116
150144
  function setDatabase(db) {
150117
150145
  persistenceDb = db;
150118
150146
  cachedInsertStmt = null;
@@ -150906,6 +150934,9 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150906
150934
  cached_m0_materialized_at INTEGER,
150907
150935
  cached_m0_session_facts_version INTEGER,
150908
150936
  cached_m0_upgrade_state TEXT,
150937
+ cached_m0_system_hash TEXT,
150938
+ cached_m0_tool_set_hash TEXT,
150939
+ cached_m0_model_key TEXT,
150909
150940
  cached_m0_last_baseline_end_message_id TEXT,
150910
150941
  upgrade_reminded_at INTEGER,
150911
150942
  pi_stable_id_scheme INTEGER
@@ -151036,6 +151067,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151036
151067
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
151037
151068
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151038
151069
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151070
+ ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151039
151071
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
151040
151072
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
151041
151073
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -151097,6 +151129,9 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151097
151129
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
151098
151130
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
151099
151131
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
151132
+ ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
151133
+ ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
151134
+ ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
151100
151135
  ensureColumn(db, "session_meta", "cached_m0_last_baseline_end_message_id", "TEXT");
151101
151136
  ensureColumn(db, "session_meta", "upgrade_reminded_at", "INTEGER");
151102
151137
  db.exec(`
@@ -151183,6 +151218,7 @@ function healNullTextColumns(db) {
151183
151218
  ["todo_synthetic_state_json", ""],
151184
151219
  ["system_prompt_hash", ""],
151185
151220
  ["stripped_placeholder_ids", ""],
151221
+ ["stale_reduce_stripped_ids", ""],
151186
151222
  ["memory_block_cache", ""],
151187
151223
  ["memory_block_ids", ""],
151188
151224
  ["compaction_marker_state", ""],
@@ -151301,7 +151337,7 @@ function getDatabasePersistenceError(db) {
151301
151337
  return null;
151302
151338
  return persistenceErrorByDatabase.get(db) ?? null;
151303
151339
  }
151304
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 29, sqlitePragmaConfig;
151340
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 30, sqlitePragmaConfig;
151305
151341
  var init_storage_db = __esm(async () => {
151306
151342
  init_data_path();
151307
151343
  init_logger();
@@ -152124,6 +152160,28 @@ var init_migrations = __esm(async () => {
152124
152160
  db.exec("ALTER TABLE notes ADD COLUMN anchor_ordinal INTEGER");
152125
152161
  }
152126
152162
  }
152163
+ },
152164
+ {
152165
+ version: 30,
152166
+ description: "HARD-bust m[0] markers: cached system/tool-set/model identity",
152167
+ up: (db) => {
152168
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
152169
+ if (!hasSessionMeta)
152170
+ return;
152171
+ ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
152172
+ ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
152173
+ ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
152174
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
152175
+ if (columns.has("cached_m0_bytes")) {
152176
+ db.prepare(`UPDATE session_meta SET
152177
+ cached_m0_bytes = NULL,
152178
+ cached_m1_bytes = NULL,
152179
+ cached_m0_materialized_at = NULL,
152180
+ cached_m0_system_hash = NULL,
152181
+ cached_m0_tool_set_hash = NULL,
152182
+ cached_m0_model_key = NULL`).run();
152183
+ }
152184
+ }
152127
152185
  }
152128
152186
  ];
152129
152187
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -152131,7 +152189,7 @@ var init_migrations = __esm(async () => {
152131
152189
 
152132
152190
  // src/features/magic-context/project-docs-hash.ts
152133
152191
  import { createHash as createHash4 } from "node:crypto";
152134
- import { readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152192
+ import { lstatSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152135
152193
  import path4 from "node:path";
152136
152194
  function canonicalizeDocContent(raw) {
152137
152195
  return raw.replace(/^\uFEFF/, "").replace(/\r\n/g, `
@@ -152141,9 +152199,10 @@ function canonicalizeDocContent(raw) {
152141
152199
  }
152142
152200
  function fingerprintFile(filePath) {
152143
152201
  try {
152144
- const stat = statSync2(filePath);
152202
+ const stat = lstatSync(filePath);
152203
+ const isReadableDoc = stat.isFile() && stat.size <= MAX_PROJECT_DOC_BYTES;
152145
152204
  return {
152146
- exists: stat.isFile(),
152205
+ exists: isReadableDoc,
152147
152206
  mtimeMs: stat.mtimeMs,
152148
152207
  size: stat.size
152149
152208
  };
@@ -152186,6 +152245,16 @@ function readCanonicalPieces(projectDirectory, files) {
152186
152245
  if (!fingerprint?.exists) {
152187
152246
  continue;
152188
152247
  }
152248
+ let safeToRead = false;
152249
+ try {
152250
+ const st = lstatSync(filePath);
152251
+ safeToRead = st.isFile() && st.size <= MAX_PROJECT_DOC_BYTES;
152252
+ } catch {
152253
+ safeToRead = false;
152254
+ }
152255
+ if (!safeToRead) {
152256
+ continue;
152257
+ }
152189
152258
  const canonicalContent = canonicalizeDocContent(readFileSync5(filePath, "utf8"));
152190
152259
  hashPieces.push(`file:${filename}
152191
152260
  ${canonicalContent}`);
@@ -152240,10 +152309,11 @@ var PROJECT_DOC_FILES, PROJECT_DOCS_DELIMITER = `
152240
152309
 
152241
152310
  ---
152242
152311
 
152243
- `, docsCache;
152312
+ `, MAX_PROJECT_DOC_BYTES, docsCache;
152244
152313
  var init_project_docs_hash = __esm(() => {
152245
152314
  init_compartment_storage();
152246
152315
  PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
152316
+ MAX_PROJECT_DOC_BYTES = 256 * 1024;
152247
152317
  docsCache = new Map;
152248
152318
  });
152249
152319
 
@@ -152580,7 +152650,8 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
152580
152650
  }
152581
152651
  for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
152582
152652
  const row = db.prepare(`SELECT ${column} FROM session_meta WHERE session_id = ?`).get(sessionId);
152583
- const currentBlob = row?.[column] ?? "[]";
152653
+ const rawCurrent = row?.[column] ?? null;
152654
+ const currentBlob = rawCurrent ?? "[]";
152584
152655
  const current = parseJsonArray(currentBlob, validator);
152585
152656
  const next = mutate(current);
152586
152657
  if (next === null)
@@ -152588,7 +152659,7 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
152588
152659
  const nextBlob = stableStringify(next);
152589
152660
  if (nextBlob === currentBlob)
152590
152661
  return true;
152591
- const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} = ?`).run(nextBlob, sessionId, currentBlob);
152662
+ const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} IS ?`).run(nextBlob, sessionId, rawCurrent);
152592
152663
  if (result.changes > 0)
152593
152664
  return true;
152594
152665
  }
@@ -152735,14 +152806,16 @@ function getHistorianFailureState(db, sessionId) {
152735
152806
  };
152736
152807
  }
152737
152808
  function incrementHistorianFailure(db, sessionId, error51) {
152809
+ let nextCount = 1;
152738
152810
  db.transaction(() => {
152739
152811
  ensureSessionMetaRow(db, sessionId);
152740
152812
  const current = getHistorianFailureState(db, sessionId);
152741
- const nextCount = current.failureCount + 1;
152813
+ nextCount = current.failureCount + 1;
152742
152814
  db.prepare("UPDATE session_meta SET historian_failure_count = ?, historian_last_error = ?, historian_last_failure_at = ? WHERE session_id = ?").run(nextCount, error51, Date.now(), sessionId);
152743
152815
  const reason = error51.replace(/\s+/g, " ").trim().slice(0, 300);
152744
152816
  sessionLog(sessionId, `historian failure recorded: count=${nextCount} reason="${reason}"`);
152745
152817
  })();
152818
+ return nextCount;
152746
152819
  }
152747
152820
  function clearHistorianFailureState(db, sessionId) {
152748
152821
  db.transaction(() => {
@@ -152819,19 +152892,78 @@ function getStrippedPlaceholderIds(db, sessionId) {
152819
152892
  } catch {}
152820
152893
  return new Set;
152821
152894
  }
152822
- function setStrippedPlaceholderIds(db, sessionId, ids) {
152895
+ function applyStrippedPlaceholderDelta(db, sessionId, delta) {
152896
+ const add = delta.add ? [...delta.add] : [];
152897
+ const remove = delta.remove ? [...delta.remove] : [];
152898
+ if (add.length === 0 && remove.length === 0)
152899
+ return true;
152823
152900
  ensureSessionMetaRow(db, sessionId);
152824
- const json2 = ids.size > 0 ? JSON.stringify([...ids]) : "";
152825
- db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ?").run(json2, sessionId);
152901
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
152902
+ const row = db.prepare("SELECT stripped_placeholder_ids FROM session_meta WHERE session_id = ?").get(sessionId);
152903
+ const rawStored = row ? row.stripped_placeholder_ids ?? null : null;
152904
+ const current = new Set(parseStrippedBlob(rawStored));
152905
+ for (const id of add)
152906
+ current.add(id);
152907
+ for (const id of remove)
152908
+ current.delete(id);
152909
+ const nextBlob = current.size > 0 ? JSON.stringify([...current]) : "";
152910
+ if (nextBlob === (rawStored ?? ""))
152911
+ return true;
152912
+ const result = db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ? AND stripped_placeholder_ids IS ?").run(nextBlob, sessionId, rawStored);
152913
+ if (result.changes > 0)
152914
+ return true;
152915
+ }
152916
+ sessionLog(sessionId, `stripped_placeholder_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
152917
+ return false;
152918
+ }
152919
+ function parseStrippedBlob(raw) {
152920
+ if (!raw || raw.length === 0)
152921
+ return [];
152922
+ try {
152923
+ const parsed = JSON.parse(raw);
152924
+ if (Array.isArray(parsed))
152925
+ return parsed.filter((v) => typeof v === "string");
152926
+ } catch {}
152927
+ return [];
152826
152928
  }
152827
152929
  function removeStrippedPlaceholderId(db, sessionId, messageId) {
152828
- const ids = getStrippedPlaceholderIds(db, sessionId);
152829
- if (!ids.delete(messageId)) {
152930
+ const before = getStrippedPlaceholderIds(db, sessionId);
152931
+ if (!before.has(messageId)) {
152830
152932
  return false;
152831
152933
  }
152832
- setStrippedPlaceholderIds(db, sessionId, ids);
152934
+ applyStrippedPlaceholderDelta(db, sessionId, { remove: [messageId] });
152833
152935
  return true;
152834
152936
  }
152937
+ function getStaleReduceStrippedIds(db, sessionId) {
152938
+ const row = db.prepare("SELECT stale_reduce_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
152939
+ return new Set(parseStrippedBlob(row?.stale_reduce_stripped_ids));
152940
+ }
152941
+ function addStaleReduceStrippedIds(db, sessionId, ids) {
152942
+ const add = [...ids];
152943
+ if (add.length === 0)
152944
+ return true;
152945
+ ensureSessionMetaRow(db, sessionId);
152946
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
152947
+ const row = db.prepare("SELECT stale_reduce_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
152948
+ const rawStored = row ? row.stale_reduce_stripped_ids ?? null : null;
152949
+ const current = new Set(parseStrippedBlob(rawStored));
152950
+ let changed = false;
152951
+ for (const id of add) {
152952
+ if (!current.has(id)) {
152953
+ current.add(id);
152954
+ changed = true;
152955
+ }
152956
+ }
152957
+ if (!changed)
152958
+ return true;
152959
+ const nextBlob = JSON.stringify([...current]);
152960
+ const result = db.prepare("UPDATE session_meta SET stale_reduce_stripped_ids = ? WHERE session_id = ? AND stale_reduce_stripped_ids IS ?").run(nextBlob, sessionId, rawStored);
152961
+ if (result.changes > 0)
152962
+ return true;
152963
+ }
152964
+ sessionLog(sessionId, `stale_reduce_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
152965
+ return false;
152966
+ }
152835
152967
  function isPendingCompactionMarker(value) {
152836
152968
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
152837
152969
  }
@@ -153042,6 +153174,9 @@ var init_storage_meta_session = __esm(async () => {
153042
153174
  cached_m0_materialized_at: "NULL AS cached_m0_materialized_at",
153043
153175
  cached_m0_session_facts_version: "NULL AS cached_m0_session_facts_version",
153044
153176
  cached_m0_upgrade_state: "NULL AS cached_m0_upgrade_state",
153177
+ cached_m0_system_hash: "NULL AS cached_m0_system_hash",
153178
+ cached_m0_tool_set_hash: "NULL AS cached_m0_tool_set_hash",
153179
+ cached_m0_model_key: "NULL AS cached_m0_model_key",
153045
153180
  last_observed_model_key: "NULL AS last_observed_model_key",
153046
153181
  upgrade_reminded_at: "NULL AS upgrade_reminded_at"
153047
153182
  };
@@ -163018,25 +163153,56 @@ var init_conflict_warning_hook = __esm(() => {
163018
163153
  // src/features/magic-context/key-files/aft-availability.ts
163019
163154
  import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
163020
163155
  import { homedir as homedir8 } from "node:os";
163021
- import { join as join14 } from "node:path";
163156
+ import { isAbsolute as isAbsolute3, join as join14, resolve as resolve5 } from "node:path";
163157
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
163022
163158
  function parseConfig(path6) {
163023
163159
  if (!existsSync12(path6))
163024
163160
  return null;
163025
163161
  return import_comment_json3.parse(readFileSync10(path6, "utf-8"));
163026
163162
  }
163027
- function entryMatchesAft(entry) {
163163
+ function stringMentionsAft(value) {
163164
+ return AFT_NAME_NEEDLES.some((needle) => value.includes(needle));
163165
+ }
163166
+ function resolveLocalEntryPackageName(value, configDir) {
163167
+ let dir = null;
163168
+ if (value.startsWith("file://")) {
163169
+ try {
163170
+ dir = fileURLToPath2(value);
163171
+ } catch {
163172
+ return null;
163173
+ }
163174
+ } else if (value.startsWith("~/")) {
163175
+ dir = join14(homedir8(), value.slice(2));
163176
+ } else if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../")) {
163177
+ dir = isAbsolute3(value) ? value : resolve5(configDir, value);
163178
+ } else {
163179
+ return null;
163180
+ }
163181
+ try {
163182
+ const pkg = JSON.parse(readFileSync10(join14(dir, "package.json"), "utf-8"));
163183
+ return typeof pkg.name === "string" ? pkg.name : null;
163184
+ } catch {
163185
+ return null;
163186
+ }
163187
+ }
163188
+ function entryMatchesAft(entry, configDir) {
163028
163189
  const value = Array.isArray(entry) ? entry[0] : entry;
163029
- return typeof value === "string" && (value.includes("@cortexkit/aft") || value.includes("aft-opencode") || value.includes("aft-pi"));
163190
+ if (typeof value !== "string")
163191
+ return false;
163192
+ if (stringMentionsAft(value))
163193
+ return true;
163194
+ const name2 = resolveLocalEntryPackageName(value, configDir);
163195
+ return name2 != null && stringMentionsAft(name2);
163030
163196
  }
163031
- function hasAftInArray(value) {
163032
- return Array.isArray(value) && value.some(entryMatchesAft);
163197
+ function hasAftInArray(value, configDir) {
163198
+ return Array.isArray(value) && value.some((entry) => entryMatchesAft(entry, configDir));
163033
163199
  }
163034
- function hasAftAtKeys(value, keys) {
163200
+ function hasAftAtKeys(value, keys, configDir) {
163035
163201
  if (!value || typeof value !== "object")
163036
163202
  return false;
163037
163203
  const record2 = value;
163038
163204
  for (const key of keys) {
163039
- if (hasAftInArray(record2[key]))
163205
+ if (hasAftInArray(record2[key], configDir))
163040
163206
  return true;
163041
163207
  }
163042
163208
  return false;
@@ -163053,7 +163219,8 @@ function getAftAvailability() {
163053
163219
  for (const path6 of opencodePaths) {
163054
163220
  try {
163055
163221
  const config2 = parseConfig(path6);
163056
- if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"])) {
163222
+ const configDir = join14(path6, "..");
163223
+ if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"], configDir)) {
163057
163224
  opencode = true;
163058
163225
  break;
163059
163226
  }
@@ -163063,12 +163230,13 @@ function getAftAvailability() {
163063
163230
  for (const path6 of piPaths) {
163064
163231
  try {
163065
163232
  const config2 = parseConfig(path6);
163066
- if (hasAftAtKeys(config2, ["packages", "extensions"])) {
163233
+ const configDir = join14(path6, "..");
163234
+ if (hasAftAtKeys(config2, ["packages", "extensions"], configDir)) {
163067
163235
  pi = true;
163068
163236
  break;
163069
163237
  }
163070
163238
  const agent = config2?.agent;
163071
- if (hasAftAtKeys(agent, ["packages", "extensions"])) {
163239
+ if (hasAftAtKeys(agent, ["packages", "extensions"], configDir)) {
163072
163240
  pi = true;
163073
163241
  break;
163074
163242
  }
@@ -163085,9 +163253,10 @@ function getAftAvailability() {
163085
163253
  function isAftAvailable() {
163086
163254
  return getAftAvailability().available;
163087
163255
  }
163088
- var import_comment_json3, overrideAvailability = null;
163256
+ var import_comment_json3, overrideAvailability = null, AFT_NAME_NEEDLES;
163089
163257
  var init_aft_availability = __esm(() => {
163090
163258
  import_comment_json3 = __toESM(require_src2(), 1);
163259
+ AFT_NAME_NEEDLES = ["@cortexkit/aft", "aft-opencode", "aft-pi"];
163091
163260
  });
163092
163261
 
163093
163262
  // src/features/magic-context/memory/storage-memory-embeddings.ts
@@ -163706,7 +163875,7 @@ function cosineSimilarity(a, b) {
163706
163875
  }
163707
163876
 
163708
163877
  // src/features/magic-context/memory/embedding-identity.ts
163709
- function normalizeEndpoint(endpoint) {
163878
+ function normalizeEndpoint2(endpoint) {
163710
163879
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
163711
163880
  }
163712
163881
  function getEmbeddingProviderIdentity(config2) {
@@ -163716,7 +163885,7 @@ function getEmbeddingProviderIdentity(config2) {
163716
163885
  const identityInput = config2.provider === "openai-compatible" ? {
163717
163886
  provider: "openai-compatible",
163718
163887
  model: config2.model.trim(),
163719
- endpoint: normalizeEndpoint(config2.endpoint),
163888
+ endpoint: normalizeEndpoint2(config2.endpoint),
163720
163889
  apiKeyPresent: Boolean(config2.api_key?.trim()),
163721
163890
  inputType: config2.input_type?.trim() || ""
163722
163891
  } : {
@@ -163772,7 +163941,7 @@ async function acquireModelLoadLock(lockPath) {
163772
163941
  if (Date.now() - waitStart > MAX_LOCK_WAIT_MS) {
163773
163942
  throw new Error(`[magic-context] embedding-load lock wait exceeded ${MAX_LOCK_WAIT_MS}ms; another process is still loading the model. Skipping this init attempt to avoid an unsynchronized native load.`);
163774
163943
  }
163775
- await new Promise((resolve6) => setTimeout(resolve6, LOCK_POLL_MS));
163944
+ await new Promise((resolve7) => setTimeout(resolve7, LOCK_POLL_MS));
163776
163945
  }
163777
163946
  }
163778
163947
  }
@@ -163941,7 +164110,7 @@ class LocalEmbeddingProvider {
163941
164110
  }
163942
164111
  const delayMs = 300 * attempt + Math.floor(Math.random() * 200);
163943
164112
  log(`[magic-context] embedding model load attempt ${attempt}/${MAX_ATTEMPTS} failed transiently, retrying in ${delayMs}ms`);
163944
- await new Promise((resolve6) => setTimeout(resolve6, delayMs));
164113
+ await new Promise((resolve7) => setTimeout(resolve7, delayMs));
163945
164114
  }
163946
164115
  }
163947
164116
  if (this.pipeline) {
@@ -163969,8 +164138,8 @@ class LocalEmbeddingProvider {
163969
164138
  if (this.inFlight === 0) {
163970
164139
  return Promise.resolve();
163971
164140
  }
163972
- return new Promise((resolve6) => {
163973
- this.inFlightWaiters.push(resolve6);
164141
+ return new Promise((resolve7) => {
164142
+ this.inFlightWaiters.push(resolve7);
163974
164143
  });
163975
164144
  }
163976
164145
  finishInFlight() {
@@ -164077,8 +164246,64 @@ var init_embedding_local = __esm(() => {
164077
164246
  MAX_LOCK_WAIT_MS = 5 * 60000;
164078
164247
  });
164079
164248
 
164249
+ // src/features/magic-context/memory/embedding-ssrf.ts
164250
+ function isLinkLocalIpv4(host) {
164251
+ return /^169\.254\.\d{1,3}\.\d{1,3}$/.test(host);
164252
+ }
164253
+ function ipv4FromMappedIpv6(host) {
164254
+ const m = /^::ffff:(.+)$/.exec(host);
164255
+ if (!m)
164256
+ return null;
164257
+ const tail = m[1];
164258
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(tail))
164259
+ return tail;
164260
+ const hex3 = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(tail);
164261
+ if (hex3) {
164262
+ const hi = Number.parseInt(hex3[1], 16);
164263
+ const lo = Number.parseInt(hex3[2], 16);
164264
+ if (Number.isNaN(hi) || Number.isNaN(lo))
164265
+ return null;
164266
+ return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
164267
+ }
164268
+ return null;
164269
+ }
164270
+ function blockedEmbeddingEndpointReason(endpoint) {
164271
+ const trimmed = endpoint.trim();
164272
+ if (trimmed.length === 0)
164273
+ return null;
164274
+ let url2;
164275
+ try {
164276
+ url2 = new URL(trimmed);
164277
+ } catch {
164278
+ return `embedding endpoint is not a valid URL: ${trimmed}`;
164279
+ }
164280
+ const host = url2.hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
164281
+ if (METADATA_HOSTNAMES.has(host)) {
164282
+ return `embedding endpoint host ${host} is a cloud metadata service (blocked)`;
164283
+ }
164284
+ if (IPV6_METADATA_HOSTS.has(host)) {
164285
+ return `embedding endpoint host ${host} is the AWS IPv6 metadata service (blocked)`;
164286
+ }
164287
+ if (isLinkLocalIpv4(host)) {
164288
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
164289
+ }
164290
+ const mappedV4 = ipv4FromMappedIpv6(host);
164291
+ if (mappedV4 && isLinkLocalIpv4(mappedV4)) {
164292
+ return `embedding endpoint host ${host} (IPv4-mapped ${mappedV4}) is link-local / cloud metadata (blocked)`;
164293
+ }
164294
+ if (host.startsWith("fe80:")) {
164295
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
164296
+ }
164297
+ return null;
164298
+ }
164299
+ var METADATA_HOSTNAMES, IPV6_METADATA_HOSTS;
164300
+ var init_embedding_ssrf = __esm(() => {
164301
+ METADATA_HOSTNAMES = new Set(["metadata.google.internal", "metadata.goog"]);
164302
+ IPV6_METADATA_HOSTS = new Set(["fd00:ec2::254"]);
164303
+ });
164304
+
164080
164305
  // src/features/magic-context/memory/embedding-openai.ts
164081
- function normalizeEndpoint2(endpoint) {
164306
+ function normalizeEndpoint3(endpoint) {
164082
164307
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
164083
164308
  }
164084
164309
 
@@ -164095,7 +164320,7 @@ class OpenAICompatibleEmbeddingProvider {
164095
164320
  openLogged = false;
164096
164321
  halfOpenProbeInFlight = false;
164097
164322
  constructor(options) {
164098
- this.endpoint = normalizeEndpoint2(options.endpoint);
164323
+ this.endpoint = normalizeEndpoint3(options.endpoint);
164099
164324
  this.model = options.model?.trim() ?? "";
164100
164325
  this.apiKey = options.apiKey?.trim() ?? "";
164101
164326
  this.inputType = options.inputType?.trim() ?? "";
@@ -164115,6 +164340,12 @@ class OpenAICompatibleEmbeddingProvider {
164115
164340
  this.initialized = false;
164116
164341
  return false;
164117
164342
  }
164343
+ const blockedReason = blockedEmbeddingEndpointReason(this.endpoint);
164344
+ if (blockedReason) {
164345
+ log(`[magic-context] embedding endpoint blocked: ${blockedReason}`);
164346
+ this.initialized = false;
164347
+ return false;
164348
+ }
164118
164349
  this.initialized = true;
164119
164350
  return true;
164120
164351
  }
@@ -164160,6 +164391,7 @@ class OpenAICompatibleEmbeddingProvider {
164160
164391
  ...this.inputType ? { input_type: this.inputType } : {},
164161
164392
  ...this.truncate ? { truncate: this.truncate } : {}
164162
164393
  }),
164394
+ redirect: "error",
164163
164395
  signal: internalController.signal
164164
164396
  });
164165
164397
  if (!response.ok) {
@@ -164290,6 +164522,7 @@ var FAILURE_THRESHOLD = 3, FAILURE_WINDOW_MS = 60000, OPEN_DURATION_MS, FETCH_TI
164290
164522
  var init_embedding_openai = __esm(() => {
164291
164523
  init_logger();
164292
164524
  init_embedding_identity();
164525
+ init_embedding_ssrf();
164293
164526
  OPEN_DURATION_MS = 5 * 60000;
164294
164527
  });
164295
164528
 
@@ -164530,11 +164763,15 @@ function resolveEmbeddingConfig(config2) {
164530
164763
  }
164531
164764
  if (config2.provider === "openai-compatible") {
164532
164765
  const apiKey = config2.api_key?.trim();
164766
+ const inputType = config2.input_type?.trim();
164767
+ const truncate = config2.truncate?.trim();
164533
164768
  return {
164534
164769
  provider: "openai-compatible",
164535
164770
  model: config2.model.trim(),
164536
164771
  endpoint: config2.endpoint.trim(),
164537
- ...apiKey ? { api_key: apiKey } : {}
164772
+ ...apiKey ? { api_key: apiKey } : {},
164773
+ ...inputType ? { input_type: inputType } : {},
164774
+ ...truncate ? { truncate } : {}
164538
164775
  };
164539
164776
  }
164540
164777
  return { provider: "off" };
@@ -164550,7 +164787,9 @@ function createProvider(config2) {
164550
164787
  return new OpenAICompatibleEmbeddingProvider({
164551
164788
  endpoint: config2.endpoint,
164552
164789
  model: config2.model,
164553
- apiKey: config2.api_key
164790
+ apiKey: config2.api_key,
164791
+ inputType: config2.input_type,
164792
+ truncate: config2.truncate
164554
164793
  });
164555
164794
  }
164556
164795
  return new LocalEmbeddingProvider(config2.model);
@@ -164957,7 +165196,7 @@ async function refreshModelLimitsFromApi(client, options) {
164957
165196
  if (ok)
164958
165197
  return;
164959
165198
  if (attempt < attempts) {
164960
- await new Promise((resolve6) => setTimeout(resolve6, delayMs));
165199
+ await new Promise((resolve7) => setTimeout(resolve7, delayMs));
164961
165200
  }
164962
165201
  }
164963
165202
  }
@@ -165328,15 +165567,15 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
165328
165567
  const existing = getPersistedCompactionMarkerState(db, sessionId);
165329
165568
  if (existing) {
165330
165569
  if (existing.boundaryOrdinal === lastCompartmentEnd) {
165331
- return;
165570
+ return true;
165332
165571
  }
165333
- try {
165334
- removeCompactionMarker(existing);
165335
- setPersistedCompactionMarkerState(db, sessionId, null);
165336
- sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
165337
- } catch (error51) {
165338
- sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}, proceeding with new injection:`, error51);
165572
+ const removed = removeCompactionMarker(existing);
165573
+ if (!removed) {
165574
+ sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}; preserving persisted state for retry (not injecting new marker this pass)`);
165575
+ return false;
165339
165576
  }
165577
+ setPersistedCompactionMarkerState(db, sessionId, null);
165578
+ sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
165340
165579
  }
165341
165580
  const result = injectCompactionMarker({
165342
165581
  sessionId,
@@ -165350,7 +165589,9 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
165350
165589
  boundaryOrdinal: lastCompartmentEnd
165351
165590
  });
165352
165591
  sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
165592
+ return true;
165353
165593
  }
165594
+ return false;
165354
165595
  }
165355
165596
  function removeCompactionMarkerForSession(db, sessionId) {
165356
165597
  const existing = getPersistedCompactionMarkerState(db, sessionId);
@@ -165385,10 +165626,10 @@ function checkCompactionMarkerConsistency(db) {
165385
165626
  const state = getPersistedCompactionMarkerState(db, row.session_id);
165386
165627
  if (!state)
165387
165628
  continue;
165388
- const boundaryExists = checkMessage.get(state.boundaryMessageId) !== null;
165389
- const summaryMessageExists = checkMessage.get(state.summaryMessageId) !== null;
165390
- const compactionPartExists = checkPart.get(state.compactionPartId) !== null;
165391
- const summaryPartExists = checkPart.get(state.summaryPartId) !== null;
165629
+ const boundaryExists = checkMessage.get(state.boundaryMessageId) != null;
165630
+ const summaryMessageExists = checkMessage.get(state.summaryMessageId) != null;
165631
+ const compactionPartExists = checkPart.get(state.compactionPartId) != null;
165632
+ const summaryPartExists = checkPart.get(state.summaryPartId) != null;
165392
165633
  const allPresent = boundaryExists && summaryMessageExists && compactionPartExists && summaryPartExists;
165393
165634
  if (allPresent)
165394
165635
  continue;
@@ -165648,6 +165889,26 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
165648
165889
  events: parsed.events.length > 0 ? parsed.events : undefined
165649
165890
  };
165650
165891
  }
165892
+ function buildHistorianFailureNotice(failureCount, lastError) {
165893
+ if (failureCount >= HISTORIAN_PERSISTENT_FAILURE_THRESHOLD) {
165894
+ return [
165895
+ "## Magic Context — history comparting needs attention",
165896
+ "",
165897
+ `Magic Context has been unable to compart this session's history ${failureCount} times in a row. This usually means the configured historian model is misconfigured or unreachable (Magic Context already retried every fallback model automatically).`,
165898
+ "",
165899
+ `Last error: ${lastError}`,
165900
+ "",
165901
+ "Check your historian model in magic-context.jsonc, then restart. Your conversation keeps working normally in the meantime — this only affects how older history is summarized."
165902
+ ].join(`
165903
+ `);
165904
+ }
165905
+ return [
165906
+ "## Magic Context",
165907
+ "",
165908
+ "Hit a transient issue comparting history this turn — Magic Context will retry automatically on the next turn. Nothing is lost and your conversation continues normally. You'll only be alerted again if this keeps happening."
165909
+ ].join(`
165910
+ `);
165911
+ }
165651
165912
  function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationError) {
165652
165913
  return [
165653
165914
  originalPrompt,
@@ -165736,7 +165997,7 @@ function getReducedRecompTokenBudget(currentBudget) {
165736
165997
  const reducedBudget = Math.max(MIN_RECOMP_CHUNK_TOKEN_BUDGET, Math.floor(currentBudget / 2));
165737
165998
  return reducedBudget < currentBudget ? reducedBudget : null;
165738
165999
  }
165739
- var MIN_RECOMP_CHUNK_TOKEN_BUDGET = 20;
166000
+ var MIN_RECOMP_CHUNK_TOKEN_BUDGET = 20, HISTORIAN_PERSISTENT_FAILURE_THRESHOLD = 3;
165740
166001
  var init_compartment_runner_validation = __esm(async () => {
165741
166002
  init_compartment_parser();
165742
166003
  await init_compartment_runner_mapping();
@@ -165943,12 +166204,12 @@ async function runHistorianPrompt(args) {
165943
166204
  error: `Historian failed while processing this session: ${desc.brief}`
165944
166205
  };
165945
166206
  } finally {
165946
- if (agentSessionId && outcomeOk) {
166207
+ if (agentSessionId && outcomeOk && !shouldKeepSubagents()) {
165947
166208
  await client.session.delete({ path: { id: agentSessionId } }).catch((e) => {
165948
166209
  sessionLog(parentSessionId, "compartment agent: session cleanup failed", getErrorMessage(e));
165949
166210
  });
165950
- } else if (agentSessionId && !outcomeOk) {
165951
- sessionLog(parentSessionId, `historian: KEEPING failed child session ${agentSessionId} for debugging (not deleted)`);
166211
+ } else if (agentSessionId && (!outcomeOk || shouldKeepSubagents())) {
166212
+ sessionLog(parentSessionId, `historian: KEEPING child session ${agentSessionId} (${outcomeOk ? "keep_subagents" : "failed"}) — not deleted`);
165952
166213
  }
165953
166214
  }
165954
166215
  }
@@ -166027,8 +166288,8 @@ function isTransientHistorianPromptError(message) {
166027
166288
  ].some((token) => normalized.includes(token));
166028
166289
  }
166029
166290
  function sleep(ms) {
166030
- return new Promise((resolve6) => {
166031
- setTimeout(resolve6, ms);
166291
+ return new Promise((resolve7) => {
166292
+ setTimeout(resolve7, ms);
166032
166293
  });
166033
166294
  }
166034
166295
  function cleanupHistorianDump(sessionId, dumpPath) {
@@ -166439,6 +166700,9 @@ function escapeXmlAttr2(s) {
166439
166700
  function escapeXmlContent2(s) {
166440
166701
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
166441
166702
  }
166703
+ function isTieredRow(c) {
166704
+ return typeof c.p1 === "string" && c.p1.length > 0;
166705
+ }
166442
166706
  function tierBody(c, tier2) {
166443
166707
  const tiers = [c.p1, c.p2, c.p3, c.p4];
166444
166708
  const requested = tiers[tier2 - 1];
@@ -166468,12 +166732,13 @@ function renderOneCompartment(c, tier2) {
166468
166732
  const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
166469
166733
  if (tier2 >= 5)
166470
166734
  return "";
166471
- if (c.legacy === 1) {
166472
- if (tier2 >= 4)
166735
+ if (c.legacy === 1 || !isTieredRow(c)) {
166736
+ const flat = (c.content ?? "").trim();
166737
+ if (tier2 >= 4 || flat.length === 0)
166473
166738
  return `<compartment ${baseAttrs} />`;
166474
166739
  return [
166475
166740
  `<compartment ${baseAttrs}>`,
166476
- escapeXmlContent2(legacyBodyForTier(c.content, tier2)),
166741
+ escapeXmlContent2(legacyBodyForTier(flat, tier2)),
166477
166742
  "</compartment>"
166478
166743
  ].join(`
166479
166744
  `);
@@ -166936,7 +167201,18 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
166936
167201
  const lastCompartment = compartments[compartments.length - 1];
166937
167202
  const lastEnd = lastCompartment.endMessage;
166938
167203
  const lastEndMessageId = lastCompartment.endMessageId;
166939
- if (lastEndMessageId.length === 0) {
167204
+ let trimEndMessageId = lastEndMessageId;
167205
+ if (!isCacheBusting) {
167206
+ const baseline = readCachedBaselineState(db, sessionId);
167207
+ if (baseline.hasCachedM0) {
167208
+ if (baseline.boundary) {
167209
+ trimEndMessageId = baseline.boundary;
167210
+ } else {
167211
+ trimEndMessageId = "";
167212
+ }
167213
+ }
167214
+ }
167215
+ if (trimEndMessageId.length === 0) {
166940
167216
  sessionLog(sessionId, "injecting legacy compartments without visible-prefix trimming because latest stored compartment has no end_message_id", {
166941
167217
  compartmentCount: compartments.length,
166942
167218
  compartmentEndMessage: lastEnd
@@ -166955,18 +167231,18 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
166955
167231
  return result2;
166956
167232
  }
166957
167233
  let skippedVisibleMessages = 0;
166958
- const cutoffIndex = messages.findIndex((message) => message.info.id === lastEndMessageId);
167234
+ const cutoffIndex = messages.findIndex((message) => message.info.id === trimEndMessageId);
166959
167235
  if (cutoffIndex >= 0) {
166960
167236
  skippedVisibleMessages = cutoffIndex + 1;
166961
167237
  const remaining = messages.slice(cutoffIndex + 1);
166962
167238
  messages.splice(0, messages.length, ...remaining);
166963
167239
  } else {
166964
- sessionLog(sessionId, `compartment injection entering degraded mode: boundary ${lastEndMessageId} not in visible messages`);
167240
+ sessionLog(sessionId, `compartment injection entering degraded mode: boundary ${trimEndMessageId} not in visible messages`);
166965
167241
  }
166966
167242
  const result = {
166967
167243
  block,
166968
167244
  compartmentEndMessage: lastEnd,
166969
- compartmentEndMessageId: cutoffIndex >= 0 ? lastEndMessageId : null,
167245
+ compartmentEndMessageId: cutoffIndex >= 0 ? trimEndMessageId : null,
166970
167246
  compartmentCount: compartments.length,
166971
167247
  skippedVisibleMessages,
166972
167248
  factCount: facts.length,
@@ -166976,6 +167252,14 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
166976
167252
  injectionCache.set(sessionId, { kind: "populated", injection: result });
166977
167253
  return result;
166978
167254
  }
167255
+ function readCachedBaselineState(db, sessionId) {
167256
+ const row = db.prepare("SELECT cached_m0_bytes AS m0, cached_m0_last_baseline_end_message_id AS boundary FROM session_meta WHERE session_id = ?").get(sessionId);
167257
+ const boundary = row?.boundary;
167258
+ return {
167259
+ hasCachedM0: row?.m0 != null,
167260
+ boundary: boundary && boundary.length > 0 ? boundary : null
167261
+ };
167262
+ }
166979
167263
  function renderCompartmentInjection(sessionId, messages, prepared) {
166980
167264
  const historyBlock = `<session-history>
166981
167265
  ${prepared.block}
@@ -167019,6 +167303,10 @@ function findFirstTextPart(parts) {
167019
167303
  function isDroppedPlaceholder(text) {
167020
167304
  return /^\[dropped §\d+§\]$/.test(text.trim());
167021
167305
  }
167306
+ function lastCompartmentBoundaryId(compartments) {
167307
+ const last = compartments.at(-1);
167308
+ return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
167309
+ }
167022
167310
  function cachedStatement(cache, db, sql) {
167023
167311
  let stmt = cache.get(db);
167024
167312
  if (!stmt) {
@@ -167037,12 +167325,6 @@ function getMaxCompartmentSeq(db, sessionId) {
167037
167325
  const row = cachedStatement(maxCompartmentSeqStatements, db, "SELECT COALESCE(MAX(sequence), -1) AS s FROM compartments WHERE session_id = ?").get(sessionId);
167038
167326
  return numberFromRow(row, "s");
167039
167327
  }
167040
- function normalizeCachedMaxCompartmentSeq(db, sessionId, stored) {
167041
- if (stored === 0 && getMaxCompartmentSeq(db, sessionId) === EMPTY_MAX_COMPARTMENT_SEQ) {
167042
- return EMPTY_MAX_COMPARTMENT_SEQ;
167043
- }
167044
- return stored;
167045
- }
167046
167328
  function getMaxMemoryId(db, projectPath) {
167047
167329
  if (!projectPath)
167048
167330
  return 0;
@@ -167066,6 +167348,7 @@ function getGlobalUserProfileVersion(db) {
167066
167348
  }
167067
167349
  function readCurrentM0SnapshotMarkers(args) {
167068
167350
  const projectDirectory = args.projectDirectory ?? args.projectPath ?? "";
167351
+ const hard = args.hardSignals ?? EMPTY_HARD_SIGNALS;
167069
167352
  return {
167070
167353
  projectMemoryEpoch: getProjectMemoryEpoch(args.db, args.projectPath),
167071
167354
  projectUserProfileVersion: getGlobalUserProfileVersion(args.db),
@@ -167076,7 +167359,10 @@ function readCurrentM0SnapshotMarkers(args) {
167076
167359
  projectDocsHash: projectDirectory ? computeProjectDocsHash(projectDirectory) : "",
167077
167360
  materializedAt: Date.now(),
167078
167361
  sessionFactsVersion: getSessionFactsVersion(args.db, args.sessionId),
167079
- upgradeState: getUpgradeState(args.db, args.sessionId)
167362
+ upgradeState: getUpgradeState(args.db, args.sessionId),
167363
+ systemHash: hard.systemHash,
167364
+ toolSetHash: hard.toolSetHash,
167365
+ modelKey: hard.modelKey
167080
167366
  };
167081
167367
  }
167082
167368
  function snapshotMarkersFromCachedM0(state) {
@@ -167106,7 +167392,10 @@ function snapshotMarkersFromCachedM0(state) {
167106
167392
  projectDocsHash: state.cachedM0ProjectDocsHash ?? "",
167107
167393
  materializedAt: state.cachedM0MaterializedAt ?? 0,
167108
167394
  sessionFactsVersion: state.cachedM0SessionFactsVersion,
167109
- upgradeState: state.cachedM0UpgradeState
167395
+ upgradeState: state.cachedM0UpgradeState,
167396
+ systemHash: state.cachedM0SystemHash ?? "",
167397
+ toolSetHash: state.cachedM0ToolSetHash ?? "",
167398
+ modelKey: state.cachedM0ModelKey ?? ""
167110
167399
  };
167111
167400
  }
167112
167401
  function mustMaterialize(args) {
@@ -167114,16 +167403,22 @@ function mustMaterialize(args) {
167114
167403
  return { value: true, reason: "first_render" };
167115
167404
  if (!args.state.cachedM1Bytes)
167116
167405
  return { value: true, reason: "cached_m1_missing" };
167406
+ const hard = args.hardSignals ?? EMPTY_HARD_SIGNALS;
167117
167407
  const current = readCurrentM0SnapshotMarkers(args);
167118
- if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
167119
- return { value: true, reason: "project_memory_epoch" };
167408
+ if (hard.modelKey !== "" && hard.modelKey !== (args.state.cachedM0ModelKey ?? "")) {
167409
+ return { value: true, reason: "model_change" };
167410
+ }
167411
+ if (hard.systemHash !== "" && hard.systemHash !== (args.state.cachedM0SystemHash ?? "")) {
167412
+ return { value: true, reason: "system_hash" };
167413
+ }
167414
+ if (hard.toolSetHash !== "" && hard.toolSetHash !== (args.state.cachedM0ToolSetHash ?? "")) {
167415
+ return { value: true, reason: "tool_set_hash" };
167120
167416
  }
167121
- if (args.state.cachedM0ProjectUserProfileVersion !== current.projectUserProfileVersion) {
167122
- return { value: true, reason: "project_user_profile_version" };
167417
+ if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (args.state.cachedM0MaterializedAt ?? 0)) {
167418
+ return { value: true, reason: "ttl_idle" };
167123
167419
  }
167124
- const cachedMaxSeq = args.state.cachedM0MaxCompartmentSeq === null ? null : normalizeCachedMaxCompartmentSeq(args.db, args.sessionId, args.state.cachedM0MaxCompartmentSeq);
167125
- if (cachedMaxSeq !== current.maxCompartmentSeq) {
167126
- return { value: true, reason: "max_compartment_seq" };
167420
+ if (args.state.cachedM0ProjectMemoryEpoch !== current.projectMemoryEpoch) {
167421
+ return { value: true, reason: "project_memory_epoch" };
167127
167422
  }
167128
167423
  if (args.state.cachedM0MaxMutationId !== current.maxMutationId) {
167129
167424
  return { value: true, reason: "max_mutation_id" };
@@ -167312,6 +167607,9 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
167312
167607
  state.cachedM0MaterializedAt = markers.materializedAt;
167313
167608
  state.cachedM0SessionFactsVersion = markers.sessionFactsVersion;
167314
167609
  state.cachedM0UpgradeState = markers.upgradeState;
167610
+ state.cachedM0SystemHash = markers.systemHash;
167611
+ state.cachedM0ToolSetHash = markers.toolSetHash;
167612
+ state.cachedM0ModelKey = markers.modelKey;
167315
167613
  state.snapshotMarkers = markers;
167316
167614
  }
167317
167615
  function historySliceTokens(m0Text) {
@@ -167336,7 +167634,8 @@ function materializeM0(options) {
167336
167634
  db: options.db,
167337
167635
  sessionId: options.sessionId,
167338
167636
  projectPath,
167339
- projectDirectory
167637
+ projectDirectory,
167638
+ hardSignals: options.hardSignals
167340
167639
  });
167341
167640
  docs = projectDirectory ? readProjectDocsCanonical(projectDirectory) : { renderedBlock: "", canonicalHash: "" };
167342
167641
  snapshotMarkers.projectDocsHash = docs.canonicalHash;
@@ -167402,7 +167701,10 @@ function materializeM0(options) {
167402
167701
  projectDocsHash: phase3ProjectDocsHash,
167403
167702
  materializedAt: Date.now(),
167404
167703
  sessionFactsVersion: getSessionFactsVersion(options.db, options.sessionId),
167405
- upgradeState: getUpgradeState(options.db, options.sessionId)
167704
+ upgradeState: getUpgradeState(options.db, options.sessionId),
167705
+ systemHash: snapshotMarkers.systemHash,
167706
+ toolSetHash: snapshotMarkers.toolSetHash,
167707
+ modelKey: snapshotMarkers.modelKey
167406
167708
  };
167407
167709
  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;
167408
167710
  if (stale) {
@@ -167424,9 +167726,14 @@ function materializeM0(options) {
167424
167726
  projectDocsHash: snapshotMarkers.projectDocsHash,
167425
167727
  materializedAt: snapshotMarkers.materializedAt,
167426
167728
  sessionFactsVersion: snapshotMarkers.sessionFactsVersion,
167427
- upgradeState: snapshotMarkers.upgradeState
167729
+ upgradeState: snapshotMarkers.upgradeState,
167730
+ systemHash: snapshotMarkers.systemHash,
167731
+ toolSetHash: snapshotMarkers.toolSetHash,
167732
+ modelKey: snapshotMarkers.modelKey
167428
167733
  });
167429
167734
  options.db.prepare("UPDATE session_meta SET memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(renderedMemoryIds.length, JSON.stringify(renderedMemoryIds), options.sessionId);
167735
+ const baselineEndMessageId = lastCompartmentBoundaryId(compartments);
167736
+ options.db.prepare("UPDATE session_meta SET cached_m0_last_baseline_end_message_id = ? WHERE session_id = ?").run(baselineEndMessageId, options.sessionId);
167430
167737
  options.db.exec("COMMIT");
167431
167738
  } catch (error51) {
167432
167739
  try {
@@ -167583,6 +167890,9 @@ function readCachedM0M1Row(db, sessionId) {
167583
167890
  cached_m0_materialized_at,
167584
167891
  cached_m0_session_facts_version,
167585
167892
  cached_m0_upgrade_state,
167893
+ cached_m0_system_hash,
167894
+ cached_m0_tool_set_hash,
167895
+ cached_m0_model_key,
167586
167896
  memory_block_ids
167587
167897
  FROM session_meta
167588
167898
  WHERE session_id = ?`).get(sessionId);
@@ -167614,11 +167924,14 @@ function markersFromCachedRow(row) {
167614
167924
  projectDocsHash: row.cached_m0_project_docs_hash ?? "",
167615
167925
  materializedAt: row.cached_m0_materialized_at ?? 0,
167616
167926
  sessionFactsVersion: row.cached_m0_session_facts_version,
167617
- upgradeState: row.cached_m0_upgrade_state
167927
+ upgradeState: row.cached_m0_upgrade_state,
167928
+ systemHash: row.cached_m0_system_hash ?? "",
167929
+ toolSetHash: row.cached_m0_tool_set_hash ?? "",
167930
+ modelKey: row.cached_m0_model_key ?? ""
167618
167931
  };
167619
167932
  }
167620
167933
  function cachedRowMatchesState(row, state) {
167621
- 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);
167934
+ 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 ?? "");
167622
167935
  }
167623
167936
  function applyCachedRowToState(state, row) {
167624
167937
  const markers = markersFromCachedRow(row);
@@ -167637,6 +167950,9 @@ function applyCachedRowToState(state, row) {
167637
167950
  state.cachedM0MaterializedAt = markers.materializedAt;
167638
167951
  state.cachedM0SessionFactsVersion = markers.sessionFactsVersion;
167639
167952
  state.cachedM0UpgradeState = markers.upgradeState;
167953
+ state.cachedM0SystemHash = markers.systemHash;
167954
+ state.cachedM0ToolSetHash = markers.toolSetHash;
167955
+ state.cachedM0ModelKey = markers.modelKey;
167640
167956
  state.snapshotMarkers = markers;
167641
167957
  }
167642
167958
  function replayCachedM1(state) {
@@ -167664,7 +167980,8 @@ function softRefreshCachedM1(options) {
167664
167980
  const renderedMemoryIds = parseMemoryBlockIds(row.memory_block_ids);
167665
167981
  const rendered = renderM1WithMetadata({ ...options, preRenderedKeyFilesBlock }, markers, renderedMemoryIds);
167666
167982
  const m1Bytes = Buffer4.from(rendered.text, "utf8");
167667
- options.db.prepare("UPDATE session_meta SET cached_m1_bytes = ? WHERE session_id = ?").run(m1Bytes, options.sessionId);
167983
+ const baselineEndMessageId = getLastCompartmentEndMessageId(options.db, options.sessionId);
167984
+ options.db.prepare("UPDATE session_meta SET cached_m1_bytes = ?, cached_m0_last_baseline_end_message_id = ? WHERE session_id = ?").run(m1Bytes, baselineEndMessageId, options.sessionId);
167668
167985
  options.db.exec("COMMIT");
167669
167986
  options.state.cachedM1Bytes = m1Bytes;
167670
167987
  options.state.snapshotMarkers = markers;
@@ -167753,7 +168070,8 @@ function injectM0M1(options) {
167753
168070
  sessionId: options.sessionId,
167754
168071
  state: options.state,
167755
168072
  projectPath: options.projectPath,
167756
- projectDirectory: options.projectDirectory
168073
+ projectDirectory: options.projectDirectory,
168074
+ hardSignals: options.hardSignals
167757
168075
  });
167758
168076
  let rematerialized = false;
167759
168077
  let contentionExhausted = false;
@@ -167810,8 +168128,15 @@ function injectM0M1(options) {
167810
168128
  } else {
167811
168129
  m1Text = replayCachedM1(options.state);
167812
168130
  }
167813
- const M0_DRIFT_RATIO_FLOOR = 2000;
167814
- if (!rematerialized && !contentionExhausted && m1Recomputed && options.isCacheBustingPass && (memoryUpdateCount > 40 || m1Text !== M1_EMPTY_PLACEHOLDER && m0Text.length >= M0_DRIFT_RATIO_FLOOR && m1Text.length > m0Text.length * 0.15)) {
168131
+ const M0_DRIFT_RATIO_FLOOR_TOKENS = 500;
168132
+ const M1_DRIFT_RATIO = 0.15;
168133
+ const M1_ABSOLUTE_CAP_RATIO = 0.2;
168134
+ const m1AbsoluteBudget = (options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
168135
+ const m1HasContent = m1Text !== M1_EMPTY_PLACEHOLDER;
168136
+ const m1Tokens = m1HasContent ? estimateTokens(m1Text) : 0;
168137
+ const m0Tokens = estimateTokens(m0Text);
168138
+ const m1OverAbsoluteCap = m1HasContent && m1Tokens > m1AbsoluteBudget;
168139
+ if (!rematerialized && !contentionExhausted && m1Recomputed && options.isCacheBustingPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1HasContent && m0Tokens >= M0_DRIFT_RATIO_FLOOR_TOKENS && m1Tokens > m0Tokens * M1_DRIFT_RATIO)) {
167815
168140
  try {
167816
168141
  const refolded = materializeWithRetry(options);
167817
168142
  applyMarkersToState(options.state, refolded.m0Bytes, refolded.snapshotMarkers, refolded.m1Bytes);
@@ -167835,7 +168160,7 @@ function injectM0M1(options) {
167835
168160
  m1Text
167836
168161
  };
167837
168162
  }
167838
- var INJECTION_CACHE_MAX = 100, injectionCache, CONSTRAINT_KEYWORDS, MaterializeContentionError, RenderM1InvalidMarkersError, DEFAULT_HISTORY_BUDGET_TOKENS = 60000, DEFAULT_MEMORY_BUDGET_TOKENS = 8000, MEMORY_BLOCK_WRAPPER_TOKENS = 6, DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000, M0_EMPTY_BODY = "<session-history></session-history>", M1_EMPTY_PLACEHOLDER = "<session-history-since>(no new content since last materialization)</session-history-since>", maxCompartmentSeqStatements, maxMemoryIdStatements, legacyCompartmentCountStatements, m0CompartmentStatements, newCompartmentStatements, EMPTY_MAX_COMPARTMENT_SEQ = -1;
168163
+ var INJECTION_CACHE_MAX = 100, injectionCache, CONSTRAINT_KEYWORDS, EMPTY_HARD_SIGNALS, MaterializeContentionError, RenderM1InvalidMarkersError, DEFAULT_HISTORY_BUDGET_TOKENS = 60000, DEFAULT_MEMORY_BUDGET_TOKENS = 8000, MEMORY_BLOCK_WRAPPER_TOKENS = 6, DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000, M0_EMPTY_BODY = "<session-history></session-history>", M1_EMPTY_PLACEHOLDER = "<session-history-since>(no new content since last materialization)</session-history-since>", maxCompartmentSeqStatements, maxMemoryIdStatements, legacyCompartmentCountStatements, m0CompartmentStatements, newCompartmentStatements;
167839
168164
  var init_inject_compartments = __esm(async () => {
167840
168165
  init_compartment_storage();
167841
168166
  init_constants();
@@ -167851,6 +168176,13 @@ var init_inject_compartments = __esm(async () => {
167851
168176
  ]);
167852
168177
  injectionCache = new BoundedSessionMap(INJECTION_CACHE_MAX);
167853
168178
  CONSTRAINT_KEYWORDS = /\b(must|never|always|cannot|should not|must not)\b/i;
168179
+ EMPTY_HARD_SIGNALS = {
168180
+ systemHash: "",
168181
+ toolSetHash: "",
168182
+ modelKey: "",
168183
+ cacheExpired: false,
168184
+ lastResponseTime: 0
168185
+ };
167854
168186
  MaterializeContentionError = class MaterializeContentionError extends Error {
167855
168187
  retries;
167856
168188
  reason;
@@ -169715,7 +170047,7 @@ ${body}
169715
170047
  function renderSessionRefCompartment(c) {
169716
170048
  const importance = c.importance ?? 50;
169717
170049
  const attrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr(c.title)}"` + (c.episodeType ? ` episode_type="${escapeXmlAttr(c.episodeType)}"` : "") + ` importance="${importance}"`;
169718
- if (c.p1 != null) {
170050
+ if (typeof c.p1 === "string" && c.p1.length > 0) {
169719
170051
  const p4 = c.p4 && c.p4.length > 0 ? `<p4>
169720
170052
  ${escapeXmlContent(c.p4)}
169721
170053
  </p4>` : "<p4/>";
@@ -169966,13 +170298,9 @@ async function runCompartmentAgent(deps) {
169966
170298
  const existingValidationError = validateStoredCompartments(priorCompartments);
169967
170299
  if (existingValidationError) {
169968
170300
  sessionLog(sessionId, `historian failure: source=existing-validation reason="${existingValidationError}"`);
169969
- incrementHistorianFailure(db, sessionId, existingValidationError);
170301
+ const failCount = incrementHistorianFailure(db, sessionId, existingValidationError);
169970
170302
  telemetry.failureReason = `existing-validation: ${existingValidationError}`;
169971
- await notifyHistorianIssue(`## Historian alert
169972
-
169973
- Historian skipped this session because existing stored compartments are invalid: ${existingValidationError}
169974
-
169975
- No new compartments or facts were written. Rebuild or clear the broken compartments before continuing.`);
170303
+ await notifyHistorianIssue(buildHistorianFailureNotice(failCount, existingValidationError));
169976
170304
  return;
169977
170305
  }
169978
170306
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
@@ -169998,12 +170326,8 @@ No new compartments or facts were written. Rebuild or clear the broken compartme
169998
170326
  if (chunkCoverageError) {
169999
170327
  telemetry.failureReason = `chunk-coverage: ${chunkCoverageError}`;
170000
170328
  sessionLog(sessionId, `historian failure: source=chunk-coverage reason="${chunkCoverageError}" chunkRange=${chunk.startIndex}-${chunk.endIndex}`);
170001
- incrementHistorianFailure(db, sessionId, chunkCoverageError);
170002
- await notifyHistorianIssue(`## Historian alert
170003
-
170004
- Historian skipped this session because the raw chunk could not be represented safely: ${chunkCoverageError}
170005
-
170006
- No new compartments or facts were written.`);
170329
+ const failCount = incrementHistorianFailure(db, sessionId, chunkCoverageError);
170330
+ await notifyHistorianIssue(buildHistorianFailureNotice(failCount, chunkCoverageError));
170007
170331
  return;
170008
170332
  }
170009
170333
  const projectPath = resolveProjectIdentity(directory ?? process.cwd());
@@ -170044,13 +170368,9 @@ ${chunk.text}`,
170044
170368
  });
170045
170369
  if (!validatedPass.ok) {
170046
170370
  sessionLog(sessionId, `historian failure: source=validation reason="${validatedPass.error}" chunkRange=${chunk.startIndex}-${chunk.endIndex} fallbackModel=${deps.fallbackModelId ?? "<none>"} twoPass=${deps.historianTwoPass ? "true" : "false"}`);
170047
- incrementHistorianFailure(db, sessionId, validatedPass.error);
170371
+ const failCount = incrementHistorianFailure(db, sessionId, validatedPass.error);
170048
170372
  telemetry.failureReason = `validation: ${validatedPass.error}`;
170049
- await notifyHistorianIssue(`## Historian alert
170050
-
170051
- ${validatedPass.error}
170052
-
170053
- No new compartments or facts were written. Check the historian model/output and try again.`);
170373
+ await notifyHistorianIssue(buildHistorianFailureNotice(failCount, validatedPass.error));
170054
170374
  return;
170055
170375
  }
170056
170376
  const emittedCompartments = validatedPass.compartments;
@@ -170071,12 +170391,8 @@ No new compartments or facts were written. Check the historian model/output and
170071
170391
  if (lastNewEnd + 1 <= offset) {
170072
170392
  telemetry.failureReason = `no forward progress beyond raw message ${offset - 1}`;
170073
170393
  sessionLog(sessionId, `historian failure: source=no-progress reason="historian returned compartments that did not advance past raw message ${offset - 1}" newCompartmentCount=${newCompartments.length} lastNewEnd=${lastNewEnd} priorEnd=${offset - 1}`);
170074
- incrementHistorianFailure(db, sessionId, `no forward progress beyond raw message ${offset - 1}`);
170075
- await notifyHistorianIssue(`## Historian alert
170076
-
170077
- Historian returned compartments that made no forward progress beyond raw message ${offset - 1}.
170078
-
170079
- No new compartments or facts were written. Check the historian model/output and try again.`);
170394
+ const failCount = incrementHistorianFailure(db, sessionId, `no forward progress beyond raw message ${offset - 1}`);
170395
+ await notifyHistorianIssue(buildHistorianFailureNotice(failCount, `historian made no forward progress beyond raw message ${offset - 1}`));
170080
170396
  return;
170081
170397
  }
170082
170398
  const deferMarkerApplication = deps.preserveInjectionCacheUntilConsumed === true;
@@ -170117,7 +170433,6 @@ No new compartments or facts were written. Check the historian model/output and
170117
170433
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
170118
170434
  clearInjectionCache(sessionId);
170119
170435
  }
170120
- deps.onCompartmentStatePublished?.(sessionId);
170121
170436
  const promotionDirectory = sessionDirectory || deps.directory;
170122
170437
  const discardedLast = persistedCompartments.length < emittedCompartments.length;
170123
170438
  const embeddingActive = !!promotionDirectory && deps.memoryEnabled !== false;
@@ -170144,6 +170459,7 @@ No new compartments or facts were written. Check the historian model/output and
170144
170459
  embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
170145
170460
  }
170146
170461
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
170462
+ deps.onCompartmentStatePublished?.(sessionId);
170147
170463
  if (deferMarkerApplication) {
170148
170464
  deps.onDeferredMarkerPending?.(sessionId);
170149
170465
  } else {
@@ -170188,12 +170504,8 @@ No new compartments or facts were written. Check the historian model/output and
170188
170504
  telemetry.failureReason = `exception: ${desc.brief}`;
170189
170505
  sessionLog(sessionId, `historian failure: source=exception ${desc.brief}${desc.stackHead ? ` stackHead="${desc.stackHead}"` : ""}`);
170190
170506
  if (!issueNotified) {
170191
- incrementHistorianFailure(db, sessionId, desc.brief);
170192
- await notifyHistorianIssue(`## Historian alert
170193
-
170194
- Historian failed unexpectedly: ${desc.brief}
170195
-
170196
- No new compartments or facts were written. Check the historian model/output and try again.`);
170507
+ const failCount = incrementHistorianFailure(db, sessionId, desc.brief);
170508
+ await notifyHistorianIssue(buildHistorianFailureNotice(failCount, desc.brief));
170197
170509
  }
170198
170510
  } finally {
170199
170511
  if (!completedSuccessfully) {
@@ -170357,7 +170669,6 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
170357
170669
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
170358
170670
  clearInjectionCache(sessionId);
170359
170671
  }
170360
- deps.onCompartmentStatePublished?.(sessionId);
170361
170672
  promoted2.facts;
170362
170673
  if (deps.memoryEnabled !== false) {
170363
170674
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
@@ -170370,11 +170681,14 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
170370
170681
  if (lastCompartmentEnd2 > 0) {
170371
170682
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd2);
170372
170683
  }
170684
+ deps.onCompartmentStatePublished?.(sessionId);
170373
170685
  if (lastCompartmentEnd2 > 0) {
170374
- updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
170375
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
170376
- if (stalePending) {
170377
- clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
170686
+ const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
170687
+ if (markerUpdated) {
170688
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
170689
+ if (stalePending) {
170690
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
170691
+ }
170378
170692
  }
170379
170693
  }
170380
170694
  return [
@@ -170563,13 +170877,13 @@ Another process acquired the compartment-state lease before recomp could publish
170563
170877
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
170564
170878
  clearInjectionCache(sessionId);
170565
170879
  }
170566
- deps.onCompartmentStatePublished?.(sessionId);
170567
170880
  const finalCompartments = promoted?.compartments ?? candidateCompartments;
170568
170881
  const finalFacts = promoted?.facts ?? candidateFacts;
170569
170882
  const lastCompartmentEnd = finalCompartments[finalCompartments.length - 1]?.endMessage ?? 0;
170570
170883
  if (lastCompartmentEnd > 0) {
170571
170884
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
170572
170885
  }
170886
+ deps.onCompartmentStatePublished?.(sessionId);
170573
170887
  if (deps.memoryEnabled !== false) {
170574
170888
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
170575
170889
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
@@ -170578,10 +170892,12 @@ Another process acquired the compartment-state lease before recomp could publish
170578
170892
  embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
170579
170893
  }
170580
170894
  if (lastCompartmentEnd > 0) {
170581
- updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
170582
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
170583
- if (stalePending) {
170584
- clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
170895
+ const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
170896
+ if (markerUpdated) {
170897
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
170898
+ if (stalePending) {
170899
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
170900
+ }
170585
170901
  }
170586
170902
  }
170587
170903
  return [
@@ -170738,7 +171054,7 @@ Could not acquire the compartment-state lease for this session.`;
170738
171054
  log(`[magic-context] partial recomp merged validation failed: ${mergedError}`);
170739
171055
  return null;
170740
171056
  }
170741
- saveRecompStagingPass(db, sessionId, passCount + 1, merged, currentFacts);
171057
+ saveRecompStagingPass(db, sessionId, passCount + 1, merged, stagedFacts);
170742
171058
  const promoted = promoteRecompStagingWithM0Mutation(db, sessionId, leaseHolderId);
170743
171059
  if (!promoted) {
170744
171060
  log("[magic-context] partial recomp promote returned null");
@@ -170758,10 +171074,12 @@ Could not acquire the compartment-state lease for this session.`;
170758
171074
  }
170759
171075
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
170760
171076
  if (lastEnd > 0) {
170761
- updateCompactionMarkerAfterPublication(db, sessionId, lastEnd, deps.directory);
170762
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
170763
- if (stalePending) {
170764
- clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
171077
+ const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastEnd, deps.directory);
171078
+ if (markerUpdated) {
171079
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
171080
+ if (stalePending) {
171081
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
171082
+ }
170765
171083
  }
170766
171084
  }
170767
171085
  return { compartmentCount: merged.length, lastEndMessage: lastEnd };
@@ -170803,10 +171121,7 @@ Snapped range ${snapStart}-${snapEnd} would cross into the protected tail (start
170803
171121
  ].join(`
170804
171122
  `);
170805
171123
  }
170806
- const currentFacts = getSessionFacts(db, sessionId).map((f) => ({
170807
- category: f.category,
170808
- content: f.content
170809
- }));
171124
+ const stagedFacts = [];
170810
171125
  const parentSessionResponse = await client.session.get({ path: { id: sessionId } }).catch(() => null);
170811
171126
  const parentSession = normalizeSDKResponse(parentSessionResponse, null, { preferResponseOnMissingData: true });
170812
171127
  const sessionDirectory = parentSession?.directory ?? directory;
@@ -170823,7 +171138,7 @@ Snapped range ${snapStart}-${snapEnd} would cross into the protected tail (start
170823
171138
  candidateCompartments = priorCompartments.map((c, idx) => compartmentToInput(c, idx));
170824
171139
  passCount = 0;
170825
171140
  offset = snapStart;
170826
- saveRecompStagingPass(db, sessionId, 0, candidateCompartments, currentFacts);
171141
+ saveRecompStagingPass(db, sessionId, 0, candidateCompartments, stagedFacts);
170827
171142
  setRecompPartialRange(db, sessionId, { start: snapStart, end: snapEnd });
170828
171143
  }
170829
171144
  let currentTokenBudget = historianChunkTokens;
@@ -170916,7 +171231,7 @@ Original state preserved (staging kept for retry).`;
170916
171231
  passCount += 1;
170917
171232
  currentTokenBudget = historianChunkTokens;
170918
171233
  passAttempt = 1;
170919
- saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, currentFacts);
171234
+ saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, stagedFacts);
170920
171235
  const nextOffset = (validatedPass.compartments?.[validatedPass.compartments.length - 1]?.endMessage ?? chunk.endIndex) + 1;
170921
171236
  if (nextOffset <= offset) {
170922
171237
  return `## Magic Recomp — Failed
@@ -170937,7 +171252,6 @@ Partial recomp completed historian passes but the final compartment set failed v
170937
171252
  ...resumed ? ["Resumed from previous interrupted partial run."] : [],
170938
171253
  `Rebuilt compartments covering messages ${snapStart}-${snapEnd} using ${passCount} historian pass${passCount === 1 ? "" : "es"}.`,
170939
171254
  `Preserved ${priorCompartments.length} prior compartment(s) and ${tailCompartments.length} tail compartment(s) unchanged.`,
170940
- `Facts unchanged (${currentFacts.length} entr${currentFacts.length === 1 ? "y" : "ies"}).`,
170941
171255
  `Total compartments: ${finalResult.compartmentCount}.`
170942
171256
  ].join(`
170943
171257
  `);
@@ -171256,6 +171570,10 @@ async function runMemoryMigration(deps) {
171256
171570
  const cleanupChildSession = async (sid) => {
171257
171571
  if (!sid)
171258
171572
  return;
171573
+ if (shouldKeepSubagents()) {
171574
+ sessionLog(parentSessionId, `memory-migration: KEEPING child session ${sid} (keep_subagents)`);
171575
+ return;
171576
+ }
171259
171577
  await client.session.delete({ path: { id: sid } }).catch((e) => {
171260
171578
  sessionLog(parentSessionId, `memory-migration: child cleanup failed: ${String(e)}`);
171261
171579
  });
@@ -171927,6 +172245,95 @@ function migrateLegacyExperimental(rawConfig, warnings) {
171927
172245
  return patched;
171928
172246
  }
171929
172247
 
172248
+ // src/config/project-security.ts
172249
+ var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
172250
+ var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
172251
+ function isPlainObject(value) {
172252
+ return typeof value === "object" && value !== null && !Array.isArray(value);
172253
+ }
172254
+ function stripUnsafeProjectConfigFields(projectRaw) {
172255
+ const warnings = [];
172256
+ if ("auto_update" in projectRaw) {
172257
+ delete projectRaw.auto_update;
172258
+ warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
172259
+ }
172260
+ for (const agentKey of HIDDEN_AGENT_KEYS) {
172261
+ const block = projectRaw[agentKey];
172262
+ if (!isPlainObject(block))
172263
+ continue;
172264
+ const removed = [];
172265
+ for (const field of AGENT_ESCALATION_FIELDS) {
172266
+ if (field in block) {
172267
+ delete block[field];
172268
+ removed.push(field);
172269
+ }
172270
+ }
172271
+ if (removed.length > 0) {
172272
+ warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
172273
+ }
172274
+ }
172275
+ return warnings;
172276
+ }
172277
+ function normalizeEndpoint(value) {
172278
+ if (typeof value !== "string")
172279
+ return;
172280
+ const trimmed = value.trim().replace(/\/+$/, "");
172281
+ return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
172282
+ }
172283
+ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
172284
+ const projectEmbedding = projectRaw.embedding;
172285
+ if (!isPlainObject(projectEmbedding))
172286
+ return [];
172287
+ const redirectsEndpoint = "endpoint" in projectEmbedding;
172288
+ if (!redirectsEndpoint)
172289
+ return [];
172290
+ const userEmbedding = userRaw?.embedding;
172291
+ if (isPlainObject(userEmbedding)) {
172292
+ const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
172293
+ const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
172294
+ if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
172295
+ return [];
172296
+ }
172297
+ }
172298
+ const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
172299
+ if (providesOwnKey)
172300
+ return [];
172301
+ const mergedEmbedding = mergedRaw.embedding;
172302
+ if (!isPlainObject(mergedEmbedding))
172303
+ return [];
172304
+ if (!("api_key" in mergedEmbedding))
172305
+ return [];
172306
+ delete mergedEmbedding.api_key;
172307
+ return [
172308
+ "Dropped inherited user embedding api_key because project config redirected " + "embedding.endpoint without supplying its own key (security: prevents key " + "exfiltration to a repository-chosen endpoint)."
172309
+ ];
172310
+ }
172311
+
172312
+ // src/config/prune-config-leaf.ts
172313
+ function isPlainObject2(value) {
172314
+ return typeof value === "object" && value !== null && !Array.isArray(value);
172315
+ }
172316
+ function pruneNestedConfigLeaf(block, relativePath) {
172317
+ if (relativePath.length === 0)
172318
+ return null;
172319
+ const result = { ...block };
172320
+ let cursor = result;
172321
+ for (let i = 0;i < relativePath.length - 1; i++) {
172322
+ const seg = String(relativePath[i]);
172323
+ const child = cursor[seg];
172324
+ if (!isPlainObject2(child))
172325
+ return null;
172326
+ const clonedChild = { ...child };
172327
+ cursor[seg] = clonedChild;
172328
+ cursor = clonedChild;
172329
+ }
172330
+ const leaf = String(relativePath[relativePath.length - 1]);
172331
+ if (!(leaf in cursor))
172332
+ return null;
172333
+ delete cursor[leaf];
172334
+ return { block: result, removed: relativePath.map(String).join(".") };
172335
+ }
172336
+
171930
172337
  // src/config/index.ts
171931
172338
  init_magic_context();
171932
172339
 
@@ -171937,6 +172344,21 @@ import { homedir } from "node:os";
171937
172344
  import { dirname, isAbsolute, resolve } from "node:path";
171938
172345
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
171939
172346
  var FILE_PATTERN = /\{file:([^}]+)\}/g;
172347
+ function sensitiveFilePathReason(resolvedPath) {
172348
+ const home = homedir();
172349
+ const sensitiveDirs = [
172350
+ { dir: resolve(home, ".ssh"), label: "SSH keys" },
172351
+ { dir: resolve(home, ".aws"), label: "AWS credentials" },
172352
+ { dir: resolve(home, ".gnupg"), label: "GnuPG keyring" },
172353
+ { dir: resolve(home, ".config", "gh"), label: "GitHub CLI auth" }
172354
+ ];
172355
+ for (const { dir, label } of sensitiveDirs) {
172356
+ if (resolvedPath === dir || resolvedPath.startsWith(`${dir}/`)) {
172357
+ return label;
172358
+ }
172359
+ }
172360
+ return null;
172361
+ }
171940
172362
  function substituteConfigVariables(input) {
171941
172363
  const warnings = [];
171942
172364
  let text = input.text;
@@ -171990,6 +172412,10 @@ function substituteConfigVariables(input) {
171990
172412
  } else if (!isAbsolute(filePath)) {
171991
172413
  filePath = resolve(configDir, filePath);
171992
172414
  }
172415
+ const sensitiveReason = sensitiveFilePathReason(filePath);
172416
+ if (sensitiveReason) {
172417
+ warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
172418
+ }
171993
172419
  if (!existsSync2(filePath)) {
171994
172420
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
171995
172421
  continue;
@@ -172092,9 +172518,6 @@ function deepMergeRawConfig(base, override) {
172092
172518
  }
172093
172519
  return result;
172094
172520
  }
172095
- function getProjectUserOnlyFields(config2) {
172096
- return "auto_update" in config2 ? ["auto_update"] : [];
172097
- }
172098
172521
  function redactConfigValue(value) {
172099
172522
  if (value === undefined)
172100
172523
  return "<missing>";
@@ -172133,12 +172556,16 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
172133
172556
  const warnings = [];
172134
172557
  const errorPaths = new Set;
172135
172558
  const customMessagesByKey = new Map;
172559
+ const issuePathsByKey = new Map;
172136
172560
  const GENERIC_ZOD_PREFIXES = ["Too big", "Too small", "Invalid input", "Invalid", "Expected"];
172137
172561
  for (const issue2 of parsed.error.issues) {
172138
172562
  const topKey = issue2.path[0];
172139
172563
  if (topKey !== undefined) {
172140
172564
  const key = String(topKey);
172141
172565
  errorPaths.add(key);
172566
+ const paths = issuePathsByKey.get(key) ?? [];
172567
+ paths.push([...issue2.path]);
172568
+ issuePathsByKey.set(key, paths);
172142
172569
  const msg = issue2.message;
172143
172570
  if (msg && !GENERIC_ZOD_PREFIXES.some((p) => msg.startsWith(p))) {
172144
172571
  if (!customMessagesByKey.has(key)) {
@@ -172154,12 +172581,33 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
172154
172581
  if (isAgentConfig) {
172155
172582
  delete patched[key];
172156
172583
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
172157
- } else {
172158
- delete patched[key];
172159
- const defaultVal = defaults[key];
172160
- const reason = customMessagesByKey.get(key);
172161
- warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
172584
+ continue;
172585
+ }
172586
+ const issuePaths = issuePathsByKey.get(key) ?? [];
172587
+ const rawValue = rawConfig[key];
172588
+ const allNested = issuePaths.length > 0 && issuePaths.every((p) => p.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
172589
+ if (allNested) {
172590
+ let prunedBlock = {
172591
+ ...rawValue
172592
+ };
172593
+ const prunedLeaves = [];
172594
+ for (const p of issuePaths) {
172595
+ const relative = p.slice(1);
172596
+ const result = pruneNestedConfigLeaf(prunedBlock, relative);
172597
+ if (result) {
172598
+ prunedBlock = result.block;
172599
+ prunedLeaves.push(result.removed);
172600
+ }
172601
+ }
172602
+ patched[key] = prunedBlock;
172603
+ const reason2 = customMessagesByKey.get(key);
172604
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.${reason2 ? ` ${reason2}` : ""}`);
172605
+ continue;
172162
172606
  }
172607
+ delete patched[key];
172608
+ const defaultVal = defaults[key];
172609
+ const reason = customMessagesByKey.get(key);
172610
+ warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
172163
172611
  }
172164
172612
  const retryMigrated = migrateLegacyExperimental(patched, preMigrationWarnings);
172165
172613
  const retryParsed = MagicContextConfigSchema.safeParse(retryMigrated);
@@ -172195,14 +172643,13 @@ function loadPluginConfig(directory) {
172195
172643
  if (projectLoaded) {
172196
172644
  allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
172197
172645
  const projectRaw = { ...projectLoaded.config };
172198
- const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
172199
- if (strippedUserOnlyFields.length > 0) {
172200
- for (const key of strippedUserOnlyFields) {
172201
- delete projectRaw[key];
172202
- }
172203
- allWarnings.push(`[project config] Ignoring ${strippedUserOnlyFields.join(", ")} from project config (security: these settings only honor user-level config)`);
172646
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
172647
+ allWarnings.push(`[project config] ${warning}`);
172204
172648
  }
172205
172649
  mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
172650
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userLoaded?.config)) {
172651
+ allWarnings.push(`[project config] ${warning}`);
172652
+ }
172206
172653
  }
172207
172654
  const config2 = parsePluginConfig(mergedRaw);
172208
172655
  if (config2.configWarnings?.length) {
@@ -172276,14 +172723,13 @@ function loadPluginConfigDetailed(directory) {
172276
172723
  if (projectLoaded) {
172277
172724
  allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
172278
172725
  const projectRaw = { ...projectLoaded.config };
172279
- const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
172280
- if (strippedUserOnlyFields.length > 0) {
172281
- for (const key of strippedUserOnlyFields) {
172282
- delete projectRaw[key];
172283
- }
172284
- allWarnings.push(`[project config] Ignoring ${strippedUserOnlyFields.join(", ")} from project config (security: these settings only honor user-level config)`);
172726
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
172727
+ allWarnings.push(`[project config] ${warning}`);
172285
172728
  }
172286
172729
  mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
172730
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userLoaded?.config)) {
172731
+ allWarnings.push(`[project config] ${warning}`);
172732
+ }
172287
172733
  }
172288
172734
  const recoveredTopLevelKeys = [];
172289
172735
  const config2 = parsePluginConfig(mergedRaw, recoveredTopLevelKeys);
@@ -172821,7 +173267,7 @@ async function runSidekick(deps) {
172821
173267
  }
172822
173268
  return null;
172823
173269
  } finally {
172824
- if (agentSessionId) {
173270
+ if (agentSessionId && !shouldKeepSubagents()) {
172825
173271
  await deps.client.session.delete({
172826
173272
  path: { id: agentSessionId }
172827
173273
  }).catch((error51) => {
@@ -173098,6 +173544,11 @@ var CACHE_DIR = join7(getOpenCodeCacheRoot(), "packages");
173098
173544
  var USER_OPENCODE_CONFIG = join7(getOpenCodeConfigRoot(), "opencode.json");
173099
173545
  var USER_OPENCODE_CONFIG_JSONC = join7(getOpenCodeConfigRoot(), "opencode.jsonc");
173100
173546
 
173547
+ // src/hooks/auto-update-checker/semver.ts
173548
+ function isValidSemver(version2) {
173549
+ return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version2);
173550
+ }
173551
+
173101
173552
  // src/hooks/auto-update-checker/types.ts
173102
173553
  init_zod();
173103
173554
  var NpmPackageEnvelopeSchema = exports_external.object({
@@ -173433,6 +173884,10 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
173433
173884
  }
173434
173885
  function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
173435
173886
  try {
173887
+ if (!isValidSemver(version2)) {
173888
+ warn2(`[auto-update-checker] Refusing to prepare update for invalid version "${version2}"`);
173889
+ return null;
173890
+ }
173436
173891
  const installContext = resolveInstallContext(runtimePackageJsonPath);
173437
173892
  if (!installContext) {
173438
173893
  warn2("[auto-update-checker] No install context found for auto-update");
@@ -173873,13 +174328,14 @@ init_shared();
173873
174328
  init_assistant_message_extractor();
173874
174329
  init_logger();
173875
174330
  import { readFileSync as readFileSync11 } from "node:fs";
173876
- import { isAbsolute as isAbsolute3, join as join15, relative as relative2 } from "node:path";
174331
+ import { isAbsolute as isAbsolute4, join as join15, relative as relative2 } from "node:path";
174332
+ init_subagent_token_capture();
173877
174333
  init_aft_availability();
173878
174334
  init_project_key_files();
173879
174335
 
173880
174336
  // src/features/magic-context/key-files/read-history.ts
173881
174337
  import { realpathSync as realpathSync3 } from "node:fs";
173882
- import { relative, resolve as resolve5 } from "node:path";
174338
+ import { relative, resolve as resolve6 } from "node:path";
173883
174339
  function toMs(value) {
173884
174340
  if (typeof value === "number" && Number.isFinite(value))
173885
174341
  return value;
@@ -173916,7 +174372,7 @@ function coalesceRanges(ranges) {
173916
174372
  }
173917
174373
  function normalizeProjectRelativePath(projectPath, filePath) {
173918
174374
  const root = realpathSync3(projectPath);
173919
- const abs = filePath.startsWith("/") ? resolve5(filePath) : resolve5(root, filePath);
174375
+ const abs = filePath.startsWith("/") ? resolve6(filePath) : resolve6(root, filePath);
173920
174376
  let real = abs;
173921
174377
  try {
173922
174378
  real = realpathSync3(abs);
@@ -174284,11 +174740,13 @@ async function runKeyFilesLlm(args) {
174284
174740
  const text = extractLatestAssistantText(messages);
174285
174741
  if (!text)
174286
174742
  throw new Error("Dreamer returned no key-files output.");
174287
- return text;
174743
+ return { text, messages };
174288
174744
  } finally {
174289
- await args.client.session.delete({ path: { id: agentSessionId } }).catch(() => {
174290
- return;
174291
- });
174745
+ if (!shouldKeepSubagents()) {
174746
+ await args.client.session.delete({ path: { id: agentSessionId } }).catch(() => {
174747
+ return;
174748
+ });
174749
+ }
174292
174750
  }
174293
174751
  }
174294
174752
  async function runKeyFilesTask(args) {
@@ -174334,9 +174792,27 @@ async function runKeyFilesTask(args) {
174334
174792
  log(`[key-files] lease renewal threw: ${getErrorMessage(error51)}`);
174335
174793
  }
174336
174794
  }, 60000);
174795
+ let invocationRecorded = false;
174796
+ const llmStartedAt = Date.now();
174797
+ const recordKeyFilesInvocation = (params) => {
174798
+ if (!args.parentSessionId || invocationRecorded)
174799
+ return;
174800
+ invocationRecorded = true;
174801
+ recordChildInvocation({
174802
+ db: args.db,
174803
+ parentSessionId: args.parentSessionId,
174804
+ harness: getHarness(),
174805
+ subagent: "dreamer",
174806
+ task: "key files",
174807
+ startedAt: llmStartedAt,
174808
+ status: params.status,
174809
+ messages: params.messages,
174810
+ error: params.error
174811
+ });
174812
+ };
174337
174813
  try {
174338
174814
  try {
174339
- const raw = await runKeyFilesLlm({
174815
+ const { text: raw, messages: llmMessages } = await runKeyFilesLlm({
174340
174816
  client: args.client,
174341
174817
  parentSessionId: args.parentSessionId,
174342
174818
  projectPath,
@@ -174344,8 +174820,10 @@ async function runKeyFilesTask(args) {
174344
174820
  deadline: args.deadline,
174345
174821
  fallbackModels: args.fallbackModels
174346
174822
  });
174823
+ recordKeyFilesInvocation({ status: "completed", messages: llmMessages });
174347
174824
  validated = validateLlmOutput(raw, args.config, projectPath, new Set(candidates.map((candidate) => candidate.path)));
174348
174825
  } catch (error51) {
174826
+ recordKeyFilesInvocation({ status: "failed", error: error51 });
174349
174827
  log(`[key-files] LLM validation failed: ${getErrorMessage(error51)}`);
174350
174828
  throw error51;
174351
174829
  }
@@ -174444,7 +174922,8 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
174444
174922
  db: args.db,
174445
174923
  parentSessionId: args.parentSessionId,
174446
174924
  harness: "opencode",
174447
- subagent: "user_memory_review",
174925
+ subagent: "dreamer",
174926
+ task: "user memories",
174448
174927
  startedAt,
174449
174928
  status: params.status,
174450
174929
  messages: params.messages,
@@ -174568,7 +175047,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
174568
175047
  return result;
174569
175048
  } finally {
174570
175049
  clearInterval(leaseInterval);
174571
- if (agentSessionId) {
175050
+ if (agentSessionId && !shouldKeepSubagents()) {
174572
175051
  await args.client.session.delete({
174573
175052
  path: { id: agentSessionId },
174574
175053
  query: { directory: args.sessionDirectory }
@@ -174694,7 +175173,7 @@ async function runDream(args) {
174694
175173
  }
174695
175174
  }
174696
175175
  const deadline = startedAt + args.maxRuntimeMinutes * 60 * 1000;
174697
- const lastDreamAt = getDreamState(args.db, `last_dream_at:${args.projectIdentity}`) ?? getDreamState(args.db, "last_dream_at");
175176
+ const lastDreamAt = getDreamState(args.db, `last_dream_at:${args.projectIdentity}`);
174698
175177
  log(`[dreamer] last dream at: ${lastDreamAt ?? "never"} (project=${args.projectIdentity})`);
174699
175178
  let lastErrorSignature = null;
174700
175179
  let consecutiveSameErrorFailures = 0;
@@ -174891,14 +175370,14 @@ async function runDream(args) {
174891
175370
  }
174892
175371
  } finally {
174893
175372
  clearInterval(leaseRenewalInterval);
174894
- if (agentSessionId && !taskFailed) {
175373
+ if (agentSessionId && !taskFailed && !shouldKeepSubagents()) {
174895
175374
  await args.client.session.delete({
174896
175375
  path: { id: agentSessionId }
174897
175376
  }).catch((error51) => {
174898
175377
  log("[dreamer] failed to delete child session:", error51);
174899
175378
  });
174900
- } else if (agentSessionId && taskFailed) {
174901
- log(`[dreamer] KEEPING failed child session ${agentSessionId} for task ${taskName} (debugging)`);
175379
+ } else if (agentSessionId && (taskFailed || shouldKeepSubagents())) {
175380
+ log(`[dreamer] KEEPING child session ${agentSessionId} for task ${taskName} (${taskFailed ? "failed" : "keep_subagents"})`);
174902
175381
  }
174903
175382
  }
174904
175383
  if (lostLease) {
@@ -175076,7 +175555,6 @@ async function runDream(args) {
175076
175555
  const hasSuccessfulTask = result.tasks.some((t) => !t.error && !POST_TASK_NAMES.has(t.name));
175077
175556
  if (hasSuccessfulTask && !lostLease) {
175078
175557
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
175079
- setDreamState(args.db, "last_dream_at", String(result.finishedAt));
175080
175558
  }
175081
175559
  const totalDuration = ((result.finishedAt - startedAt) / 1000).toFixed(1);
175082
175560
  const succeeded = result.tasks.filter((t) => !t.error).length;
@@ -175183,7 +175661,7 @@ Only include notes whose conditions you could definitively evaluate against exte
175183
175661
  parts: [{ type: "text", text: evaluationPrompt, synthetic: true }]
175184
175662
  }
175185
175663
  }, {
175186
- timeoutMs: Math.min(remainingMs, 300000),
175664
+ timeoutMs: Math.min(remainingMs, 5 * 60 * 1000),
175187
175665
  signal: abortController.signal,
175188
175666
  fallbackModels: args.fallbackModels,
175189
175667
  callContext: "dreamer:smart-notes"
@@ -175259,7 +175737,7 @@ Only include notes whose conditions you could definitively evaluate against exte
175259
175737
  });
175260
175738
  } finally {
175261
175739
  clearInterval(leaseInterval);
175262
- if (agentSessionId) {
175740
+ if (agentSessionId && !shouldKeepSubagents()) {
175263
175741
  await args.client.session.delete({
175264
175742
  path: { id: agentSessionId }
175265
175743
  }).catch(() => {});
@@ -175270,7 +175748,7 @@ var MAX_LEASE_RETRIES = 3;
175270
175748
  async function processDreamQueue(args) {
175271
175749
  const maxRuntimeMs = args.maxRuntimeMinutes * 60 * 1000;
175272
175750
  if (!hasActiveDreamLease(args.db)) {
175273
- clearStaleEntries(args.db, maxRuntimeMs + 1800000, args.projectIdentity);
175751
+ clearStaleEntries(args.db, maxRuntimeMs + 30 * 60 * 1000, args.projectIdentity);
175274
175752
  }
175275
175753
  const entry = dequeueNext(args.db, args.projectIdentity);
175276
175754
  if (!entry) {
@@ -175353,8 +175831,7 @@ function findProjectsNeedingDream(db) {
175353
175831
  const now = new Date;
175354
175832
  for (const row of projectRows) {
175355
175833
  const lastDreamAtStr = getDreamState(db, `last_dream_at:${row.project_path}`);
175356
- const fallbackStr = !lastDreamAtStr ? getDreamState(db, "last_dream_at") : null;
175357
- const lastDreamAt = Number(lastDreamAtStr ?? fallbackStr ?? "0") || 0;
175834
+ const lastDreamAt = Number(lastDreamAtStr ?? "0") || 0;
175358
175835
  if (lastDreamAt > 0 && isDreamFromCurrentWindow(lastDreamAt, now)) {
175359
175836
  continue;
175360
175837
  }
@@ -176004,10 +176481,20 @@ var EMBEDDING_AFFECTING_KEYS = new Set([
176004
176481
  "embedding.api_key",
176005
176482
  "embedding.endpoint",
176006
176483
  "embedding.model",
176007
- "embedding.provider"
176484
+ "embedding.provider",
176485
+ "embedding.input_type",
176486
+ "embedding.truncate"
176008
176487
  ]);
176009
176488
  var EMBEDDING_AFFECTING_TOP_LEVEL_KEYS = new Set(["embedding", "memory", "experimental"]);
176010
- var EMBEDDING_WARNING_TERMS = ["api_key", "endpoint", "model", "provider", "embedding"];
176489
+ var EMBEDDING_WARNING_TERMS = [
176490
+ "api_key",
176491
+ "endpoint",
176492
+ "model",
176493
+ "provider",
176494
+ "embedding",
176495
+ "input_type",
176496
+ "truncate"
176497
+ ];
176011
176498
  var loggedFailureSignatures = new Map;
176012
176499
  function sha256Prefix2(value, length = 16) {
176013
176500
  return createHash8("sha256").update(value).digest("hex").slice(0, length);
@@ -176629,6 +177116,7 @@ function createTagger() {
176629
177116
  // src/hooks/magic-context/hook.ts
176630
177117
  init_magic_context();
176631
177118
  init_project_identity();
177119
+ init_tool_definition_tokens();
176632
177120
  await init_storage();
176633
177121
  init_logger();
176634
177122
  init_resolve_fallbacks();
@@ -178610,7 +179098,7 @@ async function runCompartmentPhase(args) {
178610
179098
  async function awaitCompartmentRun(activeRun, reason) {
178611
179099
  sessionLog(args.sessionId, reason);
178612
179100
  const timeoutMs = args.historianTimeoutMs ?? 120000;
178613
- const timeout = new Promise((resolve6) => setTimeout(() => resolve6("timeout"), timeoutMs));
179101
+ const timeout = new Promise((resolve7) => setTimeout(() => resolve7("timeout"), timeoutMs));
178614
179102
  const result = await Promise.race([activeRun.promise.then(() => "done"), timeout]);
178615
179103
  if (result === "timeout") {
178616
179104
  sessionLog(args.sessionId, `transform: compartment await timed out after ${timeoutMs}ms — proceeding without waiting`);
@@ -179021,32 +179509,53 @@ function hasAnyMeaningfulPart(parts) {
179021
179509
  }
179022
179510
  return false;
179023
179511
  }
179024
- function dropStaleReduceCalls(messages, protectedCount = 0) {
179025
- let didDrop = false;
179512
+ function messageHasReducePart(message) {
179513
+ for (const part of message.parts) {
179514
+ if (isSentinel(part))
179515
+ continue;
179516
+ if (isReduceToolPart(part))
179517
+ return true;
179518
+ }
179519
+ return false;
179520
+ }
179521
+ function sentinelizeReduceParts(message) {
179522
+ let touched = false;
179523
+ for (let j = 0;j < message.parts.length; j++) {
179524
+ const part = message.parts[j];
179525
+ if (isSentinel(part))
179526
+ continue;
179527
+ if (isReduceToolPart(part)) {
179528
+ message.parts[j] = makeSentinel(part);
179529
+ touched = true;
179530
+ }
179531
+ }
179532
+ if (touched && !hasAnyMeaningfulPart(message.parts)) {
179533
+ message.parts.length = 0;
179534
+ message.parts.push(makeSentinel(undefined));
179535
+ }
179536
+ return touched;
179537
+ }
179538
+ function dropStaleReduceCalls(messages, frozenIds, options = {}) {
179539
+ const detect = options.detect ?? false;
179540
+ const protectedCount = options.protectedCount ?? 0;
179026
179541
  const protectedStart = messages.length - protectedCount;
179542
+ const newlyStrippedIds = [];
179543
+ let didDrop = false;
179027
179544
  for (let i = 0;i < messages.length; i++) {
179028
- if (i >= protectedStart)
179029
- break;
179030
179545
  const message = messages[i];
179031
- let touched = false;
179032
- for (let j = 0;j < message.parts.length; j++) {
179033
- const part = message.parts[j];
179034
- if (isSentinel(part))
179035
- continue;
179036
- if (isReduceToolPart(part)) {
179037
- message.parts[j] = makeSentinel(part);
179038
- touched = true;
179039
- }
179040
- }
179546
+ const id = typeof message.info.id === "string" ? message.info.id : undefined;
179547
+ const inFrozen = id !== undefined && frozenIds.has(id);
179548
+ const isNewDetection = !inFrozen && detect && i < protectedStart && id !== undefined && messageHasReducePart(message);
179549
+ if (!inFrozen && !isNewDetection)
179550
+ continue;
179551
+ const touched = sentinelizeReduceParts(message);
179041
179552
  if (touched) {
179042
179553
  didDrop = true;
179043
- if (!hasAnyMeaningfulPart(message.parts)) {
179044
- message.parts.length = 0;
179045
- message.parts.push(makeSentinel(undefined));
179046
- }
179554
+ if (isNewDetection && id !== undefined)
179555
+ newlyStrippedIds.push(id);
179047
179556
  }
179048
179557
  }
179049
- return didDrop;
179558
+ return { didDrop, newlyStrippedIds };
179050
179559
  }
179051
179560
 
179052
179561
  // src/hooks/magic-context/tag-messages.ts
@@ -180093,10 +180602,10 @@ var AUTO_SEARCH_TIMEOUT_MS = 3000;
180093
180602
  async function unifiedSearchWithTimeout(db, sessionId, projectPath, prompt, options, timeoutMs) {
180094
180603
  const controller = new AbortController;
180095
180604
  let timer;
180096
- const timeoutPromise = new Promise((resolve6) => {
180605
+ const timeoutPromise = new Promise((resolve7) => {
180097
180606
  timer = setTimeout(() => {
180098
180607
  controller.abort();
180099
- resolve6(null);
180608
+ resolve7(null);
180100
180609
  }, timeoutMs);
180101
180610
  });
180102
180611
  try {
@@ -180632,7 +181141,7 @@ function isTodoItem(value) {
180632
181141
 
180633
181142
  // src/hooks/magic-context/transform-postprocess-phase.ts
180634
181143
  var DEGRADE_CACHE_WARNING_THRESHOLD = 10;
180635
- var degradedCacheCountBySession = new Map;
181144
+ var degradedCacheCountBySession = new BoundedSessionMap(100);
180636
181145
  function resetDegradedCacheCount(sessionId) {
180637
181146
  degradedCacheCountBySession.delete(sessionId);
180638
181147
  }
@@ -180767,14 +181276,19 @@ async function runPostTransformPhase(args) {
180767
181276
  if (didMutateFromPendingOperations && isCacheBustingPass) {
180768
181277
  args.nudgePlacements.clear(args.sessionId);
180769
181278
  }
180770
- if (shouldRunHeuristics && (args.didMutateFromFlushedStatuses || didMutateFromPendingOperations)) {
180771
- try {
180772
- const t8 = performance.now();
180773
- dropStaleReduceCalls(args.messages, args.protectedTags);
180774
- logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
180775
- } catch (error51) {
180776
- sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
181279
+ try {
181280
+ const t8 = performance.now();
181281
+ const frozenStaleReduceIds = getStaleReduceStrippedIds(args.db, args.sessionId);
181282
+ const staleReduceResult = dropStaleReduceCalls(args.messages, frozenStaleReduceIds, {
181283
+ detect: isCacheBustingPass,
181284
+ protectedCount: args.protectedTags
181285
+ });
181286
+ if (isCacheBustingPass && staleReduceResult.newlyStrippedIds.length > 0) {
181287
+ addStaleReduceStrippedIds(args.db, args.sessionId, staleReduceResult.newlyStrippedIds);
180777
181288
  }
181289
+ logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
181290
+ } catch (error51) {
181291
+ sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
180778
181292
  }
180779
181293
  const m0M1Enabled = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
180780
181294
  if (m0M1Enabled && args.m0M1) {
@@ -180790,7 +181304,8 @@ async function runPostTransformPhase(args) {
180790
181304
  memoryInjectionBudgetTokens: args.m0M1.memoryInjectionBudgetTokens,
180791
181305
  historyBudgetTokens: args.m0M1.historyBudgetTokens,
180792
181306
  keyFiles: args.m0M1.keyFiles,
180793
- isCacheBustingPass
181307
+ isCacheBustingPass,
181308
+ hardSignals: args.m0M1.hardSignals
180794
181309
  });
180795
181310
  if (result.injected) {
180796
181311
  sessionLog(args.sessionId, `transform: injected m[0]/m[1] (rematerialized=${result.m0RematerializedThisPass}, reason=${result.decision.reason ?? "cache_hit"})`);
@@ -180805,6 +181320,7 @@ async function runPostTransformPhase(args) {
180805
181320
  sessionLog(args.sessionId, "transform: legacy fallback injection also failed:", getErrorMessage(fallbackError));
180806
181321
  }
180807
181322
  }
181323
+ clearInjectionCache(args.sessionId);
180808
181324
  }
180809
181325
  logTransformTiming(args.sessionId, "pp.injectM0M1", tInjectM0M1);
180810
181326
  } else if (args.fullFeatureMode && args.pendingCompartmentInjection) {
@@ -180828,7 +181344,7 @@ async function runPostTransformPhase(args) {
180828
181344
  if (missingIds.length > 0) {
180829
181345
  for (const id of missingIds)
180830
181346
  persistedIds.delete(id);
180831
- setStrippedPlaceholderIds(args.db, args.sessionId, persistedIds);
181347
+ applyStrippedPlaceholderDelta(args.db, args.sessionId, { remove: missingIds });
180832
181348
  }
180833
181349
  }
180834
181350
  if (isCacheBustingPass) {
@@ -180837,11 +181353,13 @@ async function runPostTransformPhase(args) {
180837
181353
  const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.liveProviderID);
180838
181354
  const newlyNeutralized = droppedResult.sentineledIds.length + systemInjectedResult.sentineledIds.length;
180839
181355
  if (newlyNeutralized > 0) {
180840
- for (const id of droppedResult.sentineledIds)
181356
+ const addedIds = [
181357
+ ...droppedResult.sentineledIds,
181358
+ ...systemInjectedResult.sentineledIds
181359
+ ];
181360
+ for (const id of addedIds)
180841
181361
  persistedIds.add(id);
180842
- for (const id of systemInjectedResult.sentineledIds)
180843
- persistedIds.add(id);
180844
- setStrippedPlaceholderIds(args.db, args.sessionId, persistedIds);
181362
+ applyStrippedPlaceholderDelta(args.db, args.sessionId, { add: addedIds });
180845
181363
  sessionLog(args.sessionId, `neutralized ${droppedResult.stripped} dropped + ${systemInjectedResult.stripped} system-injected messages (${newlyNeutralized} new, ${persistedIds.size} total persisted)`);
180846
181364
  }
180847
181365
  }
@@ -181360,7 +181878,11 @@ function createTransform(deps) {
181360
181878
  lastEmergencyNotificationCount.set(sessionId, historianFailureState.failureCount);
181361
181879
  sendIgnoredMessage(deps.client, sessionId, `⚠️ Context Emergency — Context is at ${emergencyPercentage}% and historian has failed ${historianFailureState.failureCount} times (last error: ${truncateHistorianEmergencyError(historianFailureState.lastError)}). Aborting this message to prevent context overflow. Historian will retry automatically. If this persists, change your historian model in magic-context.jsonc and restart OpenCode.`, notificationParams);
181362
181880
  }
181363
- startRecoveryRun();
181881
+ const recoveryStarted = startRecoveryRun();
181882
+ if (!recoveryStarted && !getEligibleHistoryForCompartment()) {
181883
+ clearEmergencyRecovery(db, sessionId);
181884
+ sessionLog(sessionId, "transform: disarming emergency recovery — no eligible pre-tail history to compact (would otherwise loop at 95%)");
181885
+ }
181364
181886
  sessionLog(sessionId, `EMERGENCY: aborting session at ${emergencyPercentage}%, historian failures: ${historianFailureState.failureCount}`);
181365
181887
  } else if (fullFeatureMode && isFirstTransformPassForSession && historianFailureState.failureCount > 0 && getEligibleHistoryForCompartment() && startRecoveryRun()) {
181366
181888
  sessionLog(sessionId, `transform: historian recovery triggered on session load after ${historianFailureState.failureCount} failure(s)`);
@@ -181538,6 +182060,22 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
181538
182060
  const compartmentInProgress = compartmentPhase.compartmentInProgress;
181539
182061
  sessionMeta = { ...sessionMeta, compartmentInProgress };
181540
182062
  logTransformTiming(sessionId, "compartmentPhase", tCompartmentPhase);
182063
+ const hardModel = deps.liveModelBySession?.get(sessionId);
182064
+ const hardModelKey = hardModel ? `${hardModel.providerID}/${hardModel.modelID}` : "";
182065
+ const hardToolSetHash = deps.getToolSetHash?.(sessionId) ?? "";
182066
+ const hardSystemHash = typeof sessionMeta.systemPromptHash === "string" ? sessionMeta.systemPromptHash : "";
182067
+ let hardTtlMs = 5 * 60 * 1000;
182068
+ try {
182069
+ hardTtlMs = parseCacheTtl(sessionMeta.cacheTtl);
182070
+ } catch {}
182071
+ const hardCacheExpired = sessionMeta.lastResponseTime > 0 && Date.now() - sessionMeta.lastResponseTime >= hardTtlMs;
182072
+ const m0HardSignals = {
182073
+ systemHash: hardSystemHash,
182074
+ toolSetHash: hardToolSetHash,
182075
+ modelKey: hardModelKey,
182076
+ cacheExpired: hardCacheExpired,
182077
+ lastResponseTime: sessionMeta.lastResponseTime
182078
+ };
181541
182079
  const lateActiveRunBlocksMaterialization = getActiveCompartmentRun(sessionId) !== undefined && contextUsageEarly.percentage < FORCE_MATERIALIZE_PERCENTAGE;
181542
182080
  const canConsumeDeferredLate = canConsumeDeferredOnThisPass({
181543
182081
  schedulerDecision: midTurnAdjustedSchedulerDecision,
@@ -181601,7 +182139,8 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
181601
182139
  keyFiles: {
181602
182140
  enabled: deps.experimentalPinKeyFiles === true,
181603
182141
  tokenBudget: deps.experimentalPinKeyFilesTokenBudget ?? 1e4
181604
- }
182142
+ },
182143
+ hardSignals: m0HardSignals
181605
182144
  }
181606
182145
  });
181607
182146
  logTransformTiming(sessionId, "postTransformPhase", tPostProcess);
@@ -182386,6 +182925,8 @@ function createEventHook(args) {
182386
182925
  sessionLog(assistantInfo.sessionID, `model changed (${previous.providerID}/${previous.modelID} -> ${assistantInfo.providerID}/${assistantInfo.modelID}), clearing historian failure state and reasoning watermark`);
182387
182926
  clearHistorianFailureState(args.db, assistantInfo.sessionID);
182388
182927
  clearPersistedReasoningWatermark(args.db, assistantInfo.sessionID);
182928
+ clearDetectedContextLimit(args.db, assistantInfo.sessionID);
182929
+ clearEmergencyRecovery(args.db, assistantInfo.sessionID);
182389
182930
  updateSessionMeta(args.db, assistantInfo.sessionID, {
182390
182931
  clearedReasoningThroughTag: 0,
182391
182932
  observedSafeInputTokens: 0,
@@ -182489,6 +183030,7 @@ var PARTNER_FRAME_CLOSER_REDUCE = `
182489
183030
  Reduction prompts are routine housekeeping to keep the session fast and cheap — act on them as light maintenance, never as scarcity warnings. Keep individual operations efficient, but never let context size change *what* work you take on or *how thoroughly* you do it.`;
182490
183031
  var PARTNER_FRAME_CLOSER_NO_REDUCE = `
182491
183032
  Context is managed for you entirely automatically — there's nothing to prune and no warnings to act on. Stay reasonably concise per operation, and never let context size change *what* work you take on or *how thoroughly* you do it.`;
183033
+ var CTX_NOTE_GUIDANCE = `Use \`ctx_note\` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).`;
182492
183034
  function getToolHistoryGuidance(dropToolStructure) {
182493
183035
  if (dropToolStructure) {
182494
183036
  return `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
@@ -182499,7 +183041,8 @@ var BASE_INTRO = (protectedTags, dropToolStructure) => `Messages and tool output
182499
183041
  Use \`ctx_reduce\` to manage context size. It supports one operation:
182500
183042
  - \`drop\`: Remove entirely (best for tool outputs you already acted on).
182501
183043
  Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
182502
- Use \`ctx_note\` for deferred intentions things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
183044
+ Do not announce or narrate \`ctx_reduce\` drops just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
183045
+ ${CTX_NOTE_GUIDANCE}
182503
183046
  Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
182504
183047
  **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
182505
183048
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
@@ -182519,7 +183062,7 @@ NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
182519
183062
  NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
182520
183063
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
182521
183064
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
182522
- var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
183065
+ var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `${CTX_NOTE_GUIDANCE}
182523
183066
  Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
182524
183067
  **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
182525
183068
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
@@ -182970,6 +183513,12 @@ function createMagicContextHook(deps) {
182970
183513
  const model = liveModelBySession.get(sessionId);
182971
183514
  return resolveModelKey(model?.providerID, model?.modelID);
182972
183515
  },
183516
+ getToolSetHash: (sessionId) => {
183517
+ const model = liveModelBySession.get(sessionId);
183518
+ if (!model)
183519
+ return "";
183520
+ return getCurrentToolSetHash(model.providerID, model.modelID, agentBySession.get(sessionId));
183521
+ },
182973
183522
  getFallbackModelId: (sessionId) => {
182974
183523
  const model = liveModelBySession.get(sessionId);
182975
183524
  return model ? `${model.providerID}/${model.modelID}` : undefined;
@@ -184357,7 +184906,7 @@ function projectIdentityForStoredPath(rawProjectPath) {
184357
184906
  return normalizeStoredProjectPath(rawProjectPath);
184358
184907
  }
184359
184908
  function memoryBelongsToProject(memory, projectPath) {
184360
- return memory.projectPath === projectPath || projectIdentityForStoredPath(memory.projectPath) === projectPath;
184909
+ return storedPathBelongsToIdentity(memory.projectPath, projectPath);
184361
184910
  }
184362
184911
  function updateMemoryContentInCurrentTransaction(db, memory, content, normalizedHash) {
184363
184912
  db.prepare("UPDATE memories SET content = ?, normalized_hash = ?, updated_at = ? WHERE id = ?").run(content, normalizedHash, Date.now(), memory.id);
@@ -184850,7 +185399,7 @@ import { tool as tool4 } from "@opencode-ai/plugin";
184850
185399
  // src/features/magic-context/range-parser.ts
184851
185400
  function parseRangeString(input) {
184852
185401
  const maxRangeElements = 1000;
184853
- const trimmed = input.trim();
185402
+ const trimmed = input.replace(/§/g, "").trim();
184854
185403
  if (trimmed === "") {
184855
185404
  throw new Error("Range string must not be empty");
184856
185405
  }
@@ -185096,6 +185645,7 @@ function createCtxSearchTool(deps) {
185096
185645
  return "Error: 'query' is required.";
185097
185646
  }
185098
185647
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, toolContext.sessionID);
185648
+ const messageOrdinalCutoff = lastCompartmentEnd >= 0 ? lastCompartmentEnd : 0;
185099
185649
  const visibleMemoryIds = getVisibleMemoryIds(deps.db, toolContext.sessionID);
185100
185650
  const projectPath = deps.resolveProjectPath(toolContext.directory);
185101
185651
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
@@ -185113,7 +185663,7 @@ function createCtxSearchTool(deps) {
185113
185663
  },
185114
185664
  isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
185115
185665
  readMessages: deps.readMessages,
185116
- maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
185666
+ maxMessageOrdinal: messageOrdinalCutoff,
185117
185667
  gitCommitsEnabled,
185118
185668
  sources: normalizeSources(args.sources),
185119
185669
  visibleMemoryIds,
@@ -185216,7 +185766,7 @@ init_models_dev_cache();
185216
185766
 
185217
185767
  // src/shared/rpc-server.ts
185218
185768
  init_logger();
185219
- import { randomBytes } from "node:crypto";
185769
+ import { randomBytes, timingSafeEqual } from "node:crypto";
185220
185770
  import {
185221
185771
  mkdirSync as mkdirSync9,
185222
185772
  readdirSync,
@@ -185283,6 +185833,14 @@ function isValidPort(port) {
185283
185833
  }
185284
185834
 
185285
185835
  // src/shared/rpc-server.ts
185836
+ function tokensMatch(presented, expected) {
185837
+ const a = Buffer.from(presented, "utf8");
185838
+ const b = Buffer.from(expected, "utf8");
185839
+ if (a.length !== b.length)
185840
+ return false;
185841
+ return timingSafeEqual(a, b);
185842
+ }
185843
+
185286
185844
  class MagicContextRpcServer {
185287
185845
  server = null;
185288
185846
  port = 0;
@@ -185299,7 +185857,7 @@ class MagicContextRpcServer {
185299
185857
  this.handlers.set(method, handler);
185300
185858
  }
185301
185859
  async start() {
185302
- return new Promise((resolve6, reject) => {
185860
+ return new Promise((resolve7, reject) => {
185303
185861
  const server = createServer((req, res) => this.dispatch(req, res));
185304
185862
  server.on("error", (err) => {
185305
185863
  log(`[rpc] server error: ${err.message}`);
@@ -185329,7 +185887,7 @@ class MagicContextRpcServer {
185329
185887
  } catch (err) {
185330
185888
  log(`[rpc] failed to write port file: ${err}`);
185331
185889
  }
185332
- resolve6(this.port);
185890
+ resolve7(this.port);
185333
185891
  });
185334
185892
  server.unref();
185335
185893
  });
@@ -185370,7 +185928,7 @@ class MagicContextRpcServer {
185370
185928
  }
185371
185929
  const auth = req.headers.authorization;
185372
185930
  const presented = typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
185373
- if (presented !== this.token) {
185931
+ if (!tokensMatch(presented, this.token)) {
185374
185932
  res.writeHead(401, { "Content-Type": "application/json" });
185375
185933
  res.end(JSON.stringify({ error: "Unauthorized" }));
185376
185934
  req.resume();
@@ -185422,6 +185980,7 @@ var plugin = async (ctx) => {
185422
185980
  cacheSizeMb: pluginConfig.sqlite.cache_size_mb,
185423
185981
  mmapSizeMb: pluginConfig.sqlite.mmap_size_mb
185424
185982
  });
185983
+ setKeepSubagents(pluginConfig.keep_subagents === true);
185425
185984
  const autoUpdateAbort = new AbortController;
185426
185985
  process.once("exit", () => {
185427
185986
  autoUpdateAbort.abort();