@cortexkit/opencode-magic-context 0.22.4 → 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 (61) 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/variable.d.ts.map +1 -1
  9. package/dist/features/magic-context/compartment-storage.d.ts +9 -6
  10. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  11. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  12. package/dist/features/magic-context/dreamer/scheduler.d.ts.map +1 -1
  13. package/dist/features/magic-context/key-files/read-stats.d.ts +0 -2
  14. package/dist/features/magic-context/key-files/read-stats.d.ts.map +1 -1
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  16. package/dist/features/magic-context/memory/embedding-ssrf.d.ts +29 -0
  17. package/dist/features/magic-context/memory/embedding-ssrf.d.ts.map +1 -0
  18. package/dist/features/magic-context/memory/project-identity.d.ts +10 -0
  19. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  20. package/dist/features/magic-context/project-docs-hash.d.ts.map +1 -1
  21. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  22. package/dist/features/magic-context/range-parser.d.ts +6 -0
  23. package/dist/features/magic-context/range-parser.d.ts.map +1 -1
  24. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  25. package/dist/features/magic-context/storage-meta-persisted.d.ts +41 -1
  26. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  27. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  28. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  29. package/dist/features/magic-context/storage.d.ts +2 -2
  30. package/dist/features/magic-context/storage.d.ts.map +1 -1
  31. package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
  32. package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
  33. package/dist/hooks/auto-update-checker/semver.d.ts +17 -0
  34. package/dist/hooks/auto-update-checker/semver.d.ts.map +1 -0
  35. package/dist/hooks/magic-context/command-handler.d.ts +1 -6
  36. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +1 -1
  38. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/compartment-runner-validation.d.ts +25 -0
  43. package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/decay-render.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts +36 -1
  46. package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  53. package/dist/index.js +552 -189
  54. package/dist/plugin/embedding-bootstrap-helpers.d.ts.map +1 -1
  55. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  56. package/dist/shared/rpc-server.d.ts.map +1 -1
  57. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  58. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  59. package/package.json +1 -1
  60. package/src/shared/rpc-server.ts +18 -2
  61. 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",
@@ -15162,6 +15162,9 @@ function normalizeStoredProjectPath(rawOrStored) {
15162
15162
  return directoryFallback(rawOrStored);
15163
15163
  }
15164
15164
  }
15165
+ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
15166
+ return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
15167
+ }
15165
15168
  var GIT_TIMEOUT_MS = 5000, identityCache, directoryFallbackCache, ProjectIdentityError;
15166
15169
  var init_project_identity = __esm(() => {
15167
15170
  identityCache = new Map;
@@ -16118,12 +16121,6 @@ function isCompartmentRow(row) {
16118
16121
  const candidate = row;
16119
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";
16120
16123
  }
16121
- function isSessionFactRow(row) {
16122
- if (row === null || typeof row !== "object")
16123
- return false;
16124
- const candidate = row;
16125
- 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";
16126
- }
16127
16124
  function insertCompartmentRows(db, sessionId, compartments, now) {
16128
16125
  const stmt = getInsertCompartmentStatement(db);
16129
16126
  for (const compartment of compartments) {
@@ -16152,16 +16149,6 @@ function toCompartment(row) {
16152
16149
  createdAt: row.created_at
16153
16150
  };
16154
16151
  }
16155
- function toSessionFact(row) {
16156
- return {
16157
- id: row.id,
16158
- sessionId: row.session_id,
16159
- category: row.category,
16160
- content: row.content,
16161
- createdAt: row.created_at,
16162
- updatedAt: row.updated_at
16163
- };
16164
- }
16165
16152
  function getCompartments(db, sessionId) {
16166
16153
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? ORDER BY sequence ASC").all(sessionId).filter(isCompartmentRow);
16167
16154
  return rows.map(toCompartment);
@@ -16170,6 +16157,11 @@ function getLastCompartmentEndMessage(db, sessionId) {
16170
16157
  const row = db.prepare("SELECT MAX(end_message) as max_end FROM compartments WHERE session_id = ?").get(sessionId);
16171
16158
  return row?.max_end ?? -1;
16172
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
+ }
16173
16165
  function getCompartmentsByEndMessageId(db, sessionId, endMessageId) {
16174
16166
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? AND end_message_id = ? ORDER BY sequence ASC").all(sessionId, endMessageId).filter(isCompartmentRow);
16175
16167
  return rows.map(toCompartment);
@@ -16182,10 +16174,6 @@ function appendCompartments(db, sessionId, compartments) {
16182
16174
  insertCompartmentRows(db, sessionId, compartments, now);
16183
16175
  })();
16184
16176
  }
16185
- function getSessionFacts(db, sessionId) {
16186
- const rows = db.prepare("SELECT * FROM session_facts WHERE session_id = ? ORDER BY category ASC, id ASC").all(sessionId).filter(isSessionFactRow);
16187
- return rows.map(toSessionFact);
16188
- }
16189
16177
  function buildCompartmentBlock(compartments, facts, memoryBlock, dateRanges) {
16190
16178
  const lines = [];
16191
16179
  if (memoryBlock) {
@@ -151079,6 +151067,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151079
151067
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
151080
151068
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151081
151069
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151070
+ ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151082
151071
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
151083
151072
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
151084
151073
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -151229,6 +151218,7 @@ function healNullTextColumns(db) {
151229
151218
  ["todo_synthetic_state_json", ""],
151230
151219
  ["system_prompt_hash", ""],
151231
151220
  ["stripped_placeholder_ids", ""],
151221
+ ["stale_reduce_stripped_ids", ""],
151232
151222
  ["memory_block_cache", ""],
151233
151223
  ["memory_block_ids", ""],
151234
151224
  ["compaction_marker_state", ""],
@@ -152199,7 +152189,7 @@ var init_migrations = __esm(async () => {
152199
152189
 
152200
152190
  // src/features/magic-context/project-docs-hash.ts
152201
152191
  import { createHash as createHash4 } from "node:crypto";
152202
- import { readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152192
+ import { lstatSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
152203
152193
  import path4 from "node:path";
152204
152194
  function canonicalizeDocContent(raw) {
152205
152195
  return raw.replace(/^\uFEFF/, "").replace(/\r\n/g, `
@@ -152209,9 +152199,10 @@ function canonicalizeDocContent(raw) {
152209
152199
  }
152210
152200
  function fingerprintFile(filePath) {
152211
152201
  try {
152212
- const stat = statSync2(filePath);
152202
+ const stat = lstatSync(filePath);
152203
+ const isReadableDoc = stat.isFile() && stat.size <= MAX_PROJECT_DOC_BYTES;
152213
152204
  return {
152214
- exists: stat.isFile(),
152205
+ exists: isReadableDoc,
152215
152206
  mtimeMs: stat.mtimeMs,
152216
152207
  size: stat.size
152217
152208
  };
@@ -152254,6 +152245,16 @@ function readCanonicalPieces(projectDirectory, files) {
152254
152245
  if (!fingerprint?.exists) {
152255
152246
  continue;
152256
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
+ }
152257
152258
  const canonicalContent = canonicalizeDocContent(readFileSync5(filePath, "utf8"));
152258
152259
  hashPieces.push(`file:${filename}
152259
152260
  ${canonicalContent}`);
@@ -152308,10 +152309,11 @@ var PROJECT_DOC_FILES, PROJECT_DOCS_DELIMITER = `
152308
152309
 
152309
152310
  ---
152310
152311
 
152311
- `, docsCache;
152312
+ `, MAX_PROJECT_DOC_BYTES, docsCache;
152312
152313
  var init_project_docs_hash = __esm(() => {
152313
152314
  init_compartment_storage();
152314
152315
  PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
152316
+ MAX_PROJECT_DOC_BYTES = 256 * 1024;
152315
152317
  docsCache = new Map;
152316
152318
  });
152317
152319
 
@@ -152648,7 +152650,8 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
152648
152650
  }
152649
152651
  for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
152650
152652
  const row = db.prepare(`SELECT ${column} FROM session_meta WHERE session_id = ?`).get(sessionId);
152651
- const currentBlob = row?.[column] ?? "[]";
152653
+ const rawCurrent = row?.[column] ?? null;
152654
+ const currentBlob = rawCurrent ?? "[]";
152652
152655
  const current = parseJsonArray(currentBlob, validator);
152653
152656
  const next = mutate(current);
152654
152657
  if (next === null)
@@ -152656,7 +152659,7 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
152656
152659
  const nextBlob = stableStringify(next);
152657
152660
  if (nextBlob === currentBlob)
152658
152661
  return true;
152659
- 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);
152660
152663
  if (result.changes > 0)
152661
152664
  return true;
152662
152665
  }
@@ -152803,14 +152806,16 @@ function getHistorianFailureState(db, sessionId) {
152803
152806
  };
152804
152807
  }
152805
152808
  function incrementHistorianFailure(db, sessionId, error51) {
152809
+ let nextCount = 1;
152806
152810
  db.transaction(() => {
152807
152811
  ensureSessionMetaRow(db, sessionId);
152808
152812
  const current = getHistorianFailureState(db, sessionId);
152809
- const nextCount = current.failureCount + 1;
152813
+ nextCount = current.failureCount + 1;
152810
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);
152811
152815
  const reason = error51.replace(/\s+/g, " ").trim().slice(0, 300);
152812
152816
  sessionLog(sessionId, `historian failure recorded: count=${nextCount} reason="${reason}"`);
152813
152817
  })();
152818
+ return nextCount;
152814
152819
  }
152815
152820
  function clearHistorianFailureState(db, sessionId) {
152816
152821
  db.transaction(() => {
@@ -152887,19 +152892,78 @@ function getStrippedPlaceholderIds(db, sessionId) {
152887
152892
  } catch {}
152888
152893
  return new Set;
152889
152894
  }
152890
- 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;
152891
152900
  ensureSessionMetaRow(db, sessionId);
152892
- const json2 = ids.size > 0 ? JSON.stringify([...ids]) : "";
152893
- 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 [];
152894
152928
  }
152895
152929
  function removeStrippedPlaceholderId(db, sessionId, messageId) {
152896
- const ids = getStrippedPlaceholderIds(db, sessionId);
152897
- if (!ids.delete(messageId)) {
152930
+ const before = getStrippedPlaceholderIds(db, sessionId);
152931
+ if (!before.has(messageId)) {
152898
152932
  return false;
152899
152933
  }
152900
- setStrippedPlaceholderIds(db, sessionId, ids);
152934
+ applyStrippedPlaceholderDelta(db, sessionId, { remove: [messageId] });
152901
152935
  return true;
152902
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
+ }
152903
152967
  function isPendingCompactionMarker(value) {
152904
152968
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
152905
152969
  }
@@ -163811,7 +163875,7 @@ function cosineSimilarity(a, b) {
163811
163875
  }
163812
163876
 
163813
163877
  // src/features/magic-context/memory/embedding-identity.ts
163814
- function normalizeEndpoint(endpoint) {
163878
+ function normalizeEndpoint2(endpoint) {
163815
163879
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
163816
163880
  }
163817
163881
  function getEmbeddingProviderIdentity(config2) {
@@ -163821,7 +163885,7 @@ function getEmbeddingProviderIdentity(config2) {
163821
163885
  const identityInput = config2.provider === "openai-compatible" ? {
163822
163886
  provider: "openai-compatible",
163823
163887
  model: config2.model.trim(),
163824
- endpoint: normalizeEndpoint(config2.endpoint),
163888
+ endpoint: normalizeEndpoint2(config2.endpoint),
163825
163889
  apiKeyPresent: Boolean(config2.api_key?.trim()),
163826
163890
  inputType: config2.input_type?.trim() || ""
163827
163891
  } : {
@@ -164182,8 +164246,64 @@ var init_embedding_local = __esm(() => {
164182
164246
  MAX_LOCK_WAIT_MS = 5 * 60000;
164183
164247
  });
164184
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
+
164185
164305
  // src/features/magic-context/memory/embedding-openai.ts
164186
- function normalizeEndpoint2(endpoint) {
164306
+ function normalizeEndpoint3(endpoint) {
164187
164307
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
164188
164308
  }
164189
164309
 
@@ -164200,7 +164320,7 @@ class OpenAICompatibleEmbeddingProvider {
164200
164320
  openLogged = false;
164201
164321
  halfOpenProbeInFlight = false;
164202
164322
  constructor(options) {
164203
- this.endpoint = normalizeEndpoint2(options.endpoint);
164323
+ this.endpoint = normalizeEndpoint3(options.endpoint);
164204
164324
  this.model = options.model?.trim() ?? "";
164205
164325
  this.apiKey = options.apiKey?.trim() ?? "";
164206
164326
  this.inputType = options.inputType?.trim() ?? "";
@@ -164220,6 +164340,12 @@ class OpenAICompatibleEmbeddingProvider {
164220
164340
  this.initialized = false;
164221
164341
  return false;
164222
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
+ }
164223
164349
  this.initialized = true;
164224
164350
  return true;
164225
164351
  }
@@ -164265,6 +164391,7 @@ class OpenAICompatibleEmbeddingProvider {
164265
164391
  ...this.inputType ? { input_type: this.inputType } : {},
164266
164392
  ...this.truncate ? { truncate: this.truncate } : {}
164267
164393
  }),
164394
+ redirect: "error",
164268
164395
  signal: internalController.signal
164269
164396
  });
164270
164397
  if (!response.ok) {
@@ -164395,6 +164522,7 @@ var FAILURE_THRESHOLD = 3, FAILURE_WINDOW_MS = 60000, OPEN_DURATION_MS, FETCH_TI
164395
164522
  var init_embedding_openai = __esm(() => {
164396
164523
  init_logger();
164397
164524
  init_embedding_identity();
164525
+ init_embedding_ssrf();
164398
164526
  OPEN_DURATION_MS = 5 * 60000;
164399
164527
  });
164400
164528
 
@@ -164635,11 +164763,15 @@ function resolveEmbeddingConfig(config2) {
164635
164763
  }
164636
164764
  if (config2.provider === "openai-compatible") {
164637
164765
  const apiKey = config2.api_key?.trim();
164766
+ const inputType = config2.input_type?.trim();
164767
+ const truncate = config2.truncate?.trim();
164638
164768
  return {
164639
164769
  provider: "openai-compatible",
164640
164770
  model: config2.model.trim(),
164641
164771
  endpoint: config2.endpoint.trim(),
164642
- ...apiKey ? { api_key: apiKey } : {}
164772
+ ...apiKey ? { api_key: apiKey } : {},
164773
+ ...inputType ? { input_type: inputType } : {},
164774
+ ...truncate ? { truncate } : {}
164643
164775
  };
164644
164776
  }
164645
164777
  return { provider: "off" };
@@ -164655,7 +164787,9 @@ function createProvider(config2) {
164655
164787
  return new OpenAICompatibleEmbeddingProvider({
164656
164788
  endpoint: config2.endpoint,
164657
164789
  model: config2.model,
164658
- apiKey: config2.api_key
164790
+ apiKey: config2.api_key,
164791
+ inputType: config2.input_type,
164792
+ truncate: config2.truncate
164659
164793
  });
164660
164794
  }
164661
164795
  return new LocalEmbeddingProvider(config2.model);
@@ -165433,15 +165567,15 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
165433
165567
  const existing = getPersistedCompactionMarkerState(db, sessionId);
165434
165568
  if (existing) {
165435
165569
  if (existing.boundaryOrdinal === lastCompartmentEnd) {
165436
- return;
165570
+ return true;
165437
165571
  }
165438
- try {
165439
- removeCompactionMarker(existing);
165440
- setPersistedCompactionMarkerState(db, sessionId, null);
165441
- sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
165442
- } catch (error51) {
165443
- 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;
165444
165576
  }
165577
+ setPersistedCompactionMarkerState(db, sessionId, null);
165578
+ sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
165445
165579
  }
165446
165580
  const result = injectCompactionMarker({
165447
165581
  sessionId,
@@ -165455,7 +165589,9 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
165455
165589
  boundaryOrdinal: lastCompartmentEnd
165456
165590
  });
165457
165591
  sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
165592
+ return true;
165458
165593
  }
165594
+ return false;
165459
165595
  }
165460
165596
  function removeCompactionMarkerForSession(db, sessionId) {
165461
165597
  const existing = getPersistedCompactionMarkerState(db, sessionId);
@@ -165490,10 +165626,10 @@ function checkCompactionMarkerConsistency(db) {
165490
165626
  const state = getPersistedCompactionMarkerState(db, row.session_id);
165491
165627
  if (!state)
165492
165628
  continue;
165493
- const boundaryExists = checkMessage.get(state.boundaryMessageId) !== null;
165494
- const summaryMessageExists = checkMessage.get(state.summaryMessageId) !== null;
165495
- const compactionPartExists = checkPart.get(state.compactionPartId) !== null;
165496
- 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;
165497
165633
  const allPresent = boundaryExists && summaryMessageExists && compactionPartExists && summaryPartExists;
165498
165634
  if (allPresent)
165499
165635
  continue;
@@ -165753,6 +165889,26 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
165753
165889
  events: parsed.events.length > 0 ? parsed.events : undefined
165754
165890
  };
165755
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
+ }
165756
165912
  function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationError) {
165757
165913
  return [
165758
165914
  originalPrompt,
@@ -165841,7 +165997,7 @@ function getReducedRecompTokenBudget(currentBudget) {
165841
165997
  const reducedBudget = Math.max(MIN_RECOMP_CHUNK_TOKEN_BUDGET, Math.floor(currentBudget / 2));
165842
165998
  return reducedBudget < currentBudget ? reducedBudget : null;
165843
165999
  }
165844
- var MIN_RECOMP_CHUNK_TOKEN_BUDGET = 20;
166000
+ var MIN_RECOMP_CHUNK_TOKEN_BUDGET = 20, HISTORIAN_PERSISTENT_FAILURE_THRESHOLD = 3;
165845
166001
  var init_compartment_runner_validation = __esm(async () => {
165846
166002
  init_compartment_parser();
165847
166003
  await init_compartment_runner_mapping();
@@ -166544,6 +166700,9 @@ function escapeXmlAttr2(s) {
166544
166700
  function escapeXmlContent2(s) {
166545
166701
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
166546
166702
  }
166703
+ function isTieredRow(c) {
166704
+ return typeof c.p1 === "string" && c.p1.length > 0;
166705
+ }
166547
166706
  function tierBody(c, tier2) {
166548
166707
  const tiers = [c.p1, c.p2, c.p3, c.p4];
166549
166708
  const requested = tiers[tier2 - 1];
@@ -166573,12 +166732,13 @@ function renderOneCompartment(c, tier2) {
166573
166732
  const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
166574
166733
  if (tier2 >= 5)
166575
166734
  return "";
166576
- if (c.legacy === 1) {
166577
- if (tier2 >= 4)
166735
+ if (c.legacy === 1 || !isTieredRow(c)) {
166736
+ const flat = (c.content ?? "").trim();
166737
+ if (tier2 >= 4 || flat.length === 0)
166578
166738
  return `<compartment ${baseAttrs} />`;
166579
166739
  return [
166580
166740
  `<compartment ${baseAttrs}>`,
166581
- escapeXmlContent2(legacyBodyForTier(c.content, tier2)),
166741
+ escapeXmlContent2(legacyBodyForTier(flat, tier2)),
166582
166742
  "</compartment>"
166583
166743
  ].join(`
166584
166744
  `);
@@ -167041,7 +167201,18 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
167041
167201
  const lastCompartment = compartments[compartments.length - 1];
167042
167202
  const lastEnd = lastCompartment.endMessage;
167043
167203
  const lastEndMessageId = lastCompartment.endMessageId;
167044
- 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) {
167045
167216
  sessionLog(sessionId, "injecting legacy compartments without visible-prefix trimming because latest stored compartment has no end_message_id", {
167046
167217
  compartmentCount: compartments.length,
167047
167218
  compartmentEndMessage: lastEnd
@@ -167060,18 +167231,18 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
167060
167231
  return result2;
167061
167232
  }
167062
167233
  let skippedVisibleMessages = 0;
167063
- const cutoffIndex = messages.findIndex((message) => message.info.id === lastEndMessageId);
167234
+ const cutoffIndex = messages.findIndex((message) => message.info.id === trimEndMessageId);
167064
167235
  if (cutoffIndex >= 0) {
167065
167236
  skippedVisibleMessages = cutoffIndex + 1;
167066
167237
  const remaining = messages.slice(cutoffIndex + 1);
167067
167238
  messages.splice(0, messages.length, ...remaining);
167068
167239
  } else {
167069
- 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`);
167070
167241
  }
167071
167242
  const result = {
167072
167243
  block,
167073
167244
  compartmentEndMessage: lastEnd,
167074
- compartmentEndMessageId: cutoffIndex >= 0 ? lastEndMessageId : null,
167245
+ compartmentEndMessageId: cutoffIndex >= 0 ? trimEndMessageId : null,
167075
167246
  compartmentCount: compartments.length,
167076
167247
  skippedVisibleMessages,
167077
167248
  factCount: facts.length,
@@ -167081,6 +167252,14 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
167081
167252
  injectionCache.set(sessionId, { kind: "populated", injection: result });
167082
167253
  return result;
167083
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
+ }
167084
167263
  function renderCompartmentInjection(sessionId, messages, prepared) {
167085
167264
  const historyBlock = `<session-history>
167086
167265
  ${prepared.block}
@@ -167124,6 +167303,10 @@ function findFirstTextPart(parts) {
167124
167303
  function isDroppedPlaceholder(text) {
167125
167304
  return /^\[dropped §\d+§\]$/.test(text.trim());
167126
167305
  }
167306
+ function lastCompartmentBoundaryId(compartments) {
167307
+ const last = compartments.at(-1);
167308
+ return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
167309
+ }
167127
167310
  function cachedStatement(cache, db, sql) {
167128
167311
  let stmt = cache.get(db);
167129
167312
  if (!stmt) {
@@ -167549,6 +167732,8 @@ function materializeM0(options) {
167549
167732
  modelKey: snapshotMarkers.modelKey
167550
167733
  });
167551
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);
167552
167737
  options.db.exec("COMMIT");
167553
167738
  } catch (error51) {
167554
167739
  try {
@@ -167795,7 +167980,8 @@ function softRefreshCachedM1(options) {
167795
167980
  const renderedMemoryIds = parseMemoryBlockIds(row.memory_block_ids);
167796
167981
  const rendered = renderM1WithMetadata({ ...options, preRenderedKeyFilesBlock }, markers, renderedMemoryIds);
167797
167982
  const m1Bytes = Buffer4.from(rendered.text, "utf8");
167798
- 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);
167799
167985
  options.db.exec("COMMIT");
167800
167986
  options.state.cachedM1Bytes = m1Bytes;
167801
167987
  options.state.snapshotMarkers = markers;
@@ -167942,11 +168128,15 @@ function injectM0M1(options) {
167942
168128
  } else {
167943
168129
  m1Text = replayCachedM1(options.state);
167944
168130
  }
167945
- const M0_DRIFT_RATIO_FLOOR = 2000;
168131
+ const M0_DRIFT_RATIO_FLOOR_TOKENS = 500;
168132
+ const M1_DRIFT_RATIO = 0.15;
167946
168133
  const M1_ABSOLUTE_CAP_RATIO = 0.2;
167947
168134
  const m1AbsoluteBudget = (options.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
167948
- const m1OverAbsoluteCap = m1Text !== M1_EMPTY_PLACEHOLDER && estimateTokens(m1Text) > m1AbsoluteBudget;
167949
- if (!rematerialized && !contentionExhausted && m1Recomputed && options.isCacheBustingPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1Text !== M1_EMPTY_PLACEHOLDER && m0Text.length >= M0_DRIFT_RATIO_FLOOR && m1Text.length > m0Text.length * 0.15)) {
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)) {
167950
168140
  try {
167951
168141
  const refolded = materializeWithRetry(options);
167952
168142
  applyMarkersToState(options.state, refolded.m0Bytes, refolded.snapshotMarkers, refolded.m1Bytes);
@@ -169857,7 +170047,7 @@ ${body}
169857
170047
  function renderSessionRefCompartment(c) {
169858
170048
  const importance = c.importance ?? 50;
169859
170049
  const attrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr(c.title)}"` + (c.episodeType ? ` episode_type="${escapeXmlAttr(c.episodeType)}"` : "") + ` importance="${importance}"`;
169860
- if (c.p1 != null) {
170050
+ if (typeof c.p1 === "string" && c.p1.length > 0) {
169861
170051
  const p4 = c.p4 && c.p4.length > 0 ? `<p4>
169862
170052
  ${escapeXmlContent(c.p4)}
169863
170053
  </p4>` : "<p4/>";
@@ -170108,13 +170298,9 @@ async function runCompartmentAgent(deps) {
170108
170298
  const existingValidationError = validateStoredCompartments(priorCompartments);
170109
170299
  if (existingValidationError) {
170110
170300
  sessionLog(sessionId, `historian failure: source=existing-validation reason="${existingValidationError}"`);
170111
- incrementHistorianFailure(db, sessionId, existingValidationError);
170301
+ const failCount = incrementHistorianFailure(db, sessionId, existingValidationError);
170112
170302
  telemetry.failureReason = `existing-validation: ${existingValidationError}`;
170113
- await notifyHistorianIssue(`## Historian alert
170114
-
170115
- Historian skipped this session because existing stored compartments are invalid: ${existingValidationError}
170116
-
170117
- No new compartments or facts were written. Rebuild or clear the broken compartments before continuing.`);
170303
+ await notifyHistorianIssue(buildHistorianFailureNotice(failCount, existingValidationError));
170118
170304
  return;
170119
170305
  }
170120
170306
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
@@ -170140,12 +170326,8 @@ No new compartments or facts were written. Rebuild or clear the broken compartme
170140
170326
  if (chunkCoverageError) {
170141
170327
  telemetry.failureReason = `chunk-coverage: ${chunkCoverageError}`;
170142
170328
  sessionLog(sessionId, `historian failure: source=chunk-coverage reason="${chunkCoverageError}" chunkRange=${chunk.startIndex}-${chunk.endIndex}`);
170143
- incrementHistorianFailure(db, sessionId, chunkCoverageError);
170144
- await notifyHistorianIssue(`## Historian alert
170145
-
170146
- Historian skipped this session because the raw chunk could not be represented safely: ${chunkCoverageError}
170147
-
170148
- No new compartments or facts were written.`);
170329
+ const failCount = incrementHistorianFailure(db, sessionId, chunkCoverageError);
170330
+ await notifyHistorianIssue(buildHistorianFailureNotice(failCount, chunkCoverageError));
170149
170331
  return;
170150
170332
  }
170151
170333
  const projectPath = resolveProjectIdentity(directory ?? process.cwd());
@@ -170186,13 +170368,9 @@ ${chunk.text}`,
170186
170368
  });
170187
170369
  if (!validatedPass.ok) {
170188
170370
  sessionLog(sessionId, `historian failure: source=validation reason="${validatedPass.error}" chunkRange=${chunk.startIndex}-${chunk.endIndex} fallbackModel=${deps.fallbackModelId ?? "<none>"} twoPass=${deps.historianTwoPass ? "true" : "false"}`);
170189
- incrementHistorianFailure(db, sessionId, validatedPass.error);
170371
+ const failCount = incrementHistorianFailure(db, sessionId, validatedPass.error);
170190
170372
  telemetry.failureReason = `validation: ${validatedPass.error}`;
170191
- await notifyHistorianIssue(`## Historian alert
170192
-
170193
- ${validatedPass.error}
170194
-
170195
- No new compartments or facts were written. Check the historian model/output and try again.`);
170373
+ await notifyHistorianIssue(buildHistorianFailureNotice(failCount, validatedPass.error));
170196
170374
  return;
170197
170375
  }
170198
170376
  const emittedCompartments = validatedPass.compartments;
@@ -170213,12 +170391,8 @@ No new compartments or facts were written. Check the historian model/output and
170213
170391
  if (lastNewEnd + 1 <= offset) {
170214
170392
  telemetry.failureReason = `no forward progress beyond raw message ${offset - 1}`;
170215
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}`);
170216
- incrementHistorianFailure(db, sessionId, `no forward progress beyond raw message ${offset - 1}`);
170217
- await notifyHistorianIssue(`## Historian alert
170218
-
170219
- Historian returned compartments that made no forward progress beyond raw message ${offset - 1}.
170220
-
170221
- 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}`));
170222
170396
  return;
170223
170397
  }
170224
170398
  const deferMarkerApplication = deps.preserveInjectionCacheUntilConsumed === true;
@@ -170259,7 +170433,6 @@ No new compartments or facts were written. Check the historian model/output and
170259
170433
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
170260
170434
  clearInjectionCache(sessionId);
170261
170435
  }
170262
- deps.onCompartmentStatePublished?.(sessionId);
170263
170436
  const promotionDirectory = sessionDirectory || deps.directory;
170264
170437
  const discardedLast = persistedCompartments.length < emittedCompartments.length;
170265
170438
  const embeddingActive = !!promotionDirectory && deps.memoryEnabled !== false;
@@ -170286,6 +170459,7 @@ No new compartments or facts were written. Check the historian model/output and
170286
170459
  embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
170287
170460
  }
170288
170461
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
170462
+ deps.onCompartmentStatePublished?.(sessionId);
170289
170463
  if (deferMarkerApplication) {
170290
170464
  deps.onDeferredMarkerPending?.(sessionId);
170291
170465
  } else {
@@ -170330,12 +170504,8 @@ No new compartments or facts were written. Check the historian model/output and
170330
170504
  telemetry.failureReason = `exception: ${desc.brief}`;
170331
170505
  sessionLog(sessionId, `historian failure: source=exception ${desc.brief}${desc.stackHead ? ` stackHead="${desc.stackHead}"` : ""}`);
170332
170506
  if (!issueNotified) {
170333
- incrementHistorianFailure(db, sessionId, desc.brief);
170334
- await notifyHistorianIssue(`## Historian alert
170335
-
170336
- Historian failed unexpectedly: ${desc.brief}
170337
-
170338
- 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));
170339
170509
  }
170340
170510
  } finally {
170341
170511
  if (!completedSuccessfully) {
@@ -170499,7 +170669,6 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
170499
170669
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
170500
170670
  clearInjectionCache(sessionId);
170501
170671
  }
170502
- deps.onCompartmentStatePublished?.(sessionId);
170503
170672
  promoted2.facts;
170504
170673
  if (deps.memoryEnabled !== false) {
170505
170674
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
@@ -170512,11 +170681,14 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
170512
170681
  if (lastCompartmentEnd2 > 0) {
170513
170682
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd2);
170514
170683
  }
170684
+ deps.onCompartmentStatePublished?.(sessionId);
170515
170685
  if (lastCompartmentEnd2 > 0) {
170516
- updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
170517
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
170518
- if (stalePending) {
170519
- 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
+ }
170520
170692
  }
170521
170693
  }
170522
170694
  return [
@@ -170705,13 +170877,13 @@ Another process acquired the compartment-state lease before recomp could publish
170705
170877
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
170706
170878
  clearInjectionCache(sessionId);
170707
170879
  }
170708
- deps.onCompartmentStatePublished?.(sessionId);
170709
170880
  const finalCompartments = promoted?.compartments ?? candidateCompartments;
170710
170881
  const finalFacts = promoted?.facts ?? candidateFacts;
170711
170882
  const lastCompartmentEnd = finalCompartments[finalCompartments.length - 1]?.endMessage ?? 0;
170712
170883
  if (lastCompartmentEnd > 0) {
170713
170884
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
170714
170885
  }
170886
+ deps.onCompartmentStatePublished?.(sessionId);
170715
170887
  if (deps.memoryEnabled !== false) {
170716
170888
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
170717
170889
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
@@ -170720,10 +170892,12 @@ Another process acquired the compartment-state lease before recomp could publish
170720
170892
  embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
170721
170893
  }
170722
170894
  if (lastCompartmentEnd > 0) {
170723
- updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
170724
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
170725
- if (stalePending) {
170726
- 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
+ }
170727
170901
  }
170728
170902
  }
170729
170903
  return [
@@ -170880,7 +171054,7 @@ Could not acquire the compartment-state lease for this session.`;
170880
171054
  log(`[magic-context] partial recomp merged validation failed: ${mergedError}`);
170881
171055
  return null;
170882
171056
  }
170883
- saveRecompStagingPass(db, sessionId, passCount + 1, merged, currentFacts);
171057
+ saveRecompStagingPass(db, sessionId, passCount + 1, merged, stagedFacts);
170884
171058
  const promoted = promoteRecompStagingWithM0Mutation(db, sessionId, leaseHolderId);
170885
171059
  if (!promoted) {
170886
171060
  log("[magic-context] partial recomp promote returned null");
@@ -170900,10 +171074,12 @@ Could not acquire the compartment-state lease for this session.`;
170900
171074
  }
170901
171075
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
170902
171076
  if (lastEnd > 0) {
170903
- updateCompactionMarkerAfterPublication(db, sessionId, lastEnd, deps.directory);
170904
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
170905
- if (stalePending) {
170906
- 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
+ }
170907
171083
  }
170908
171084
  }
170909
171085
  return { compartmentCount: merged.length, lastEndMessage: lastEnd };
@@ -170945,10 +171121,7 @@ Snapped range ${snapStart}-${snapEnd} would cross into the protected tail (start
170945
171121
  ].join(`
170946
171122
  `);
170947
171123
  }
170948
- const currentFacts = getSessionFacts(db, sessionId).map((f) => ({
170949
- category: f.category,
170950
- content: f.content
170951
- }));
171124
+ const stagedFacts = [];
170952
171125
  const parentSessionResponse = await client.session.get({ path: { id: sessionId } }).catch(() => null);
170953
171126
  const parentSession = normalizeSDKResponse(parentSessionResponse, null, { preferResponseOnMissingData: true });
170954
171127
  const sessionDirectory = parentSession?.directory ?? directory;
@@ -170965,7 +171138,7 @@ Snapped range ${snapStart}-${snapEnd} would cross into the protected tail (start
170965
171138
  candidateCompartments = priorCompartments.map((c, idx) => compartmentToInput(c, idx));
170966
171139
  passCount = 0;
170967
171140
  offset = snapStart;
170968
- saveRecompStagingPass(db, sessionId, 0, candidateCompartments, currentFacts);
171141
+ saveRecompStagingPass(db, sessionId, 0, candidateCompartments, stagedFacts);
170969
171142
  setRecompPartialRange(db, sessionId, { start: snapStart, end: snapEnd });
170970
171143
  }
170971
171144
  let currentTokenBudget = historianChunkTokens;
@@ -171058,7 +171231,7 @@ Original state preserved (staging kept for retry).`;
171058
171231
  passCount += 1;
171059
171232
  currentTokenBudget = historianChunkTokens;
171060
171233
  passAttempt = 1;
171061
- saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, currentFacts);
171234
+ saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, stagedFacts);
171062
171235
  const nextOffset = (validatedPass.compartments?.[validatedPass.compartments.length - 1]?.endMessage ?? chunk.endIndex) + 1;
171063
171236
  if (nextOffset <= offset) {
171064
171237
  return `## Magic Recomp — Failed
@@ -171079,7 +171252,6 @@ Partial recomp completed historian passes but the final compartment set failed v
171079
171252
  ...resumed ? ["Resumed from previous interrupted partial run."] : [],
171080
171253
  `Rebuilt compartments covering messages ${snapStart}-${snapEnd} using ${passCount} historian pass${passCount === 1 ? "" : "es"}.`,
171081
171254
  `Preserved ${priorCompartments.length} prior compartment(s) and ${tailCompartments.length} tail compartment(s) unchanged.`,
171082
- `Facts unchanged (${currentFacts.length} entr${currentFacts.length === 1 ? "y" : "ies"}).`,
171083
171255
  `Total compartments: ${finalResult.compartmentCount}.`
171084
171256
  ].join(`
171085
171257
  `);
@@ -172073,6 +172245,95 @@ function migrateLegacyExperimental(rawConfig, warnings) {
172073
172245
  return patched;
172074
172246
  }
172075
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
+
172076
172337
  // src/config/index.ts
172077
172338
  init_magic_context();
172078
172339
 
@@ -172083,6 +172344,21 @@ import { homedir } from "node:os";
172083
172344
  import { dirname, isAbsolute, resolve } from "node:path";
172084
172345
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
172085
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
+ }
172086
172362
  function substituteConfigVariables(input) {
172087
172363
  const warnings = [];
172088
172364
  let text = input.text;
@@ -172136,6 +172412,10 @@ function substituteConfigVariables(input) {
172136
172412
  } else if (!isAbsolute(filePath)) {
172137
172413
  filePath = resolve(configDir, filePath);
172138
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
+ }
172139
172419
  if (!existsSync2(filePath)) {
172140
172420
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
172141
172421
  continue;
@@ -172238,9 +172518,6 @@ function deepMergeRawConfig(base, override) {
172238
172518
  }
172239
172519
  return result;
172240
172520
  }
172241
- function getProjectUserOnlyFields(config2) {
172242
- return "auto_update" in config2 ? ["auto_update"] : [];
172243
- }
172244
172521
  function redactConfigValue(value) {
172245
172522
  if (value === undefined)
172246
172523
  return "<missing>";
@@ -172279,12 +172556,16 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
172279
172556
  const warnings = [];
172280
172557
  const errorPaths = new Set;
172281
172558
  const customMessagesByKey = new Map;
172559
+ const issuePathsByKey = new Map;
172282
172560
  const GENERIC_ZOD_PREFIXES = ["Too big", "Too small", "Invalid input", "Invalid", "Expected"];
172283
172561
  for (const issue2 of parsed.error.issues) {
172284
172562
  const topKey = issue2.path[0];
172285
172563
  if (topKey !== undefined) {
172286
172564
  const key = String(topKey);
172287
172565
  errorPaths.add(key);
172566
+ const paths = issuePathsByKey.get(key) ?? [];
172567
+ paths.push([...issue2.path]);
172568
+ issuePathsByKey.set(key, paths);
172288
172569
  const msg = issue2.message;
172289
172570
  if (msg && !GENERIC_ZOD_PREFIXES.some((p) => msg.startsWith(p))) {
172290
172571
  if (!customMessagesByKey.has(key)) {
@@ -172300,12 +172581,33 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
172300
172581
  if (isAgentConfig) {
172301
172582
  delete patched[key];
172302
172583
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
172303
- } else {
172304
- delete patched[key];
172305
- const defaultVal = defaults[key];
172306
- const reason = customMessagesByKey.get(key);
172307
- 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;
172308
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}` : ""}`);
172309
172611
  }
172310
172612
  const retryMigrated = migrateLegacyExperimental(patched, preMigrationWarnings);
172311
172613
  const retryParsed = MagicContextConfigSchema.safeParse(retryMigrated);
@@ -172341,14 +172643,13 @@ function loadPluginConfig(directory) {
172341
172643
  if (projectLoaded) {
172342
172644
  allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
172343
172645
  const projectRaw = { ...projectLoaded.config };
172344
- const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
172345
- if (strippedUserOnlyFields.length > 0) {
172346
- for (const key of strippedUserOnlyFields) {
172347
- delete projectRaw[key];
172348
- }
172349
- 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}`);
172350
172648
  }
172351
172649
  mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
172650
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userLoaded?.config)) {
172651
+ allWarnings.push(`[project config] ${warning}`);
172652
+ }
172352
172653
  }
172353
172654
  const config2 = parsePluginConfig(mergedRaw);
172354
172655
  if (config2.configWarnings?.length) {
@@ -172422,14 +172723,13 @@ function loadPluginConfigDetailed(directory) {
172422
172723
  if (projectLoaded) {
172423
172724
  allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
172424
172725
  const projectRaw = { ...projectLoaded.config };
172425
- const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
172426
- if (strippedUserOnlyFields.length > 0) {
172427
- for (const key of strippedUserOnlyFields) {
172428
- delete projectRaw[key];
172429
- }
172430
- 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}`);
172431
172728
  }
172432
172729
  mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
172730
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userLoaded?.config)) {
172731
+ allWarnings.push(`[project config] ${warning}`);
172732
+ }
172433
172733
  }
172434
172734
  const recoveredTopLevelKeys = [];
172435
172735
  const config2 = parsePluginConfig(mergedRaw, recoveredTopLevelKeys);
@@ -173244,6 +173544,11 @@ var CACHE_DIR = join7(getOpenCodeCacheRoot(), "packages");
173244
173544
  var USER_OPENCODE_CONFIG = join7(getOpenCodeConfigRoot(), "opencode.json");
173245
173545
  var USER_OPENCODE_CONFIG_JSONC = join7(getOpenCodeConfigRoot(), "opencode.jsonc");
173246
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
+
173247
173552
  // src/hooks/auto-update-checker/types.ts
173248
173553
  init_zod();
173249
173554
  var NpmPackageEnvelopeSchema = exports_external.object({
@@ -173579,6 +173884,10 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
173579
173884
  }
173580
173885
  function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
173581
173886
  try {
173887
+ if (!isValidSemver(version2)) {
173888
+ warn2(`[auto-update-checker] Refusing to prepare update for invalid version "${version2}"`);
173889
+ return null;
173890
+ }
173582
173891
  const installContext = resolveInstallContext(runtimePackageJsonPath);
173583
173892
  if (!installContext) {
173584
173893
  warn2("[auto-update-checker] No install context found for auto-update");
@@ -174864,7 +175173,7 @@ async function runDream(args) {
174864
175173
  }
174865
175174
  }
174866
175175
  const deadline = startedAt + args.maxRuntimeMinutes * 60 * 1000;
174867
- 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}`);
174868
175177
  log(`[dreamer] last dream at: ${lastDreamAt ?? "never"} (project=${args.projectIdentity})`);
174869
175178
  let lastErrorSignature = null;
174870
175179
  let consecutiveSameErrorFailures = 0;
@@ -175246,7 +175555,6 @@ async function runDream(args) {
175246
175555
  const hasSuccessfulTask = result.tasks.some((t) => !t.error && !POST_TASK_NAMES.has(t.name));
175247
175556
  if (hasSuccessfulTask && !lostLease) {
175248
175557
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
175249
- setDreamState(args.db, "last_dream_at", String(result.finishedAt));
175250
175558
  }
175251
175559
  const totalDuration = ((result.finishedAt - startedAt) / 1000).toFixed(1);
175252
175560
  const succeeded = result.tasks.filter((t) => !t.error).length;
@@ -175523,8 +175831,7 @@ function findProjectsNeedingDream(db) {
175523
175831
  const now = new Date;
175524
175832
  for (const row of projectRows) {
175525
175833
  const lastDreamAtStr = getDreamState(db, `last_dream_at:${row.project_path}`);
175526
- const fallbackStr = !lastDreamAtStr ? getDreamState(db, "last_dream_at") : null;
175527
- const lastDreamAt = Number(lastDreamAtStr ?? fallbackStr ?? "0") || 0;
175834
+ const lastDreamAt = Number(lastDreamAtStr ?? "0") || 0;
175528
175835
  if (lastDreamAt > 0 && isDreamFromCurrentWindow(lastDreamAt, now)) {
175529
175836
  continue;
175530
175837
  }
@@ -176174,10 +176481,20 @@ var EMBEDDING_AFFECTING_KEYS = new Set([
176174
176481
  "embedding.api_key",
176175
176482
  "embedding.endpoint",
176176
176483
  "embedding.model",
176177
- "embedding.provider"
176484
+ "embedding.provider",
176485
+ "embedding.input_type",
176486
+ "embedding.truncate"
176178
176487
  ]);
176179
176488
  var EMBEDDING_AFFECTING_TOP_LEVEL_KEYS = new Set(["embedding", "memory", "experimental"]);
176180
- 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
+ ];
176181
176498
  var loggedFailureSignatures = new Map;
176182
176499
  function sha256Prefix2(value, length = 16) {
176183
176500
  return createHash8("sha256").update(value).digest("hex").slice(0, length);
@@ -179192,32 +179509,53 @@ function hasAnyMeaningfulPart(parts) {
179192
179509
  }
179193
179510
  return false;
179194
179511
  }
179195
- function dropStaleReduceCalls(messages, protectedCount = 0) {
179196
- 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;
179197
179541
  const protectedStart = messages.length - protectedCount;
179542
+ const newlyStrippedIds = [];
179543
+ let didDrop = false;
179198
179544
  for (let i = 0;i < messages.length; i++) {
179199
- if (i >= protectedStart)
179200
- break;
179201
179545
  const message = messages[i];
179202
- let touched = false;
179203
- for (let j = 0;j < message.parts.length; j++) {
179204
- const part = message.parts[j];
179205
- if (isSentinel(part))
179206
- continue;
179207
- if (isReduceToolPart(part)) {
179208
- message.parts[j] = makeSentinel(part);
179209
- touched = true;
179210
- }
179211
- }
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);
179212
179552
  if (touched) {
179213
179553
  didDrop = true;
179214
- if (!hasAnyMeaningfulPart(message.parts)) {
179215
- message.parts.length = 0;
179216
- message.parts.push(makeSentinel(undefined));
179217
- }
179554
+ if (isNewDetection && id !== undefined)
179555
+ newlyStrippedIds.push(id);
179218
179556
  }
179219
179557
  }
179220
- return didDrop;
179558
+ return { didDrop, newlyStrippedIds };
179221
179559
  }
179222
179560
 
179223
179561
  // src/hooks/magic-context/tag-messages.ts
@@ -180803,7 +181141,7 @@ function isTodoItem(value) {
180803
181141
 
180804
181142
  // src/hooks/magic-context/transform-postprocess-phase.ts
180805
181143
  var DEGRADE_CACHE_WARNING_THRESHOLD = 10;
180806
- var degradedCacheCountBySession = new Map;
181144
+ var degradedCacheCountBySession = new BoundedSessionMap(100);
180807
181145
  function resetDegradedCacheCount(sessionId) {
180808
181146
  degradedCacheCountBySession.delete(sessionId);
180809
181147
  }
@@ -180938,14 +181276,19 @@ async function runPostTransformPhase(args) {
180938
181276
  if (didMutateFromPendingOperations && isCacheBustingPass) {
180939
181277
  args.nudgePlacements.clear(args.sessionId);
180940
181278
  }
180941
- if (shouldRunHeuristics && (args.didMutateFromFlushedStatuses || didMutateFromPendingOperations)) {
180942
- try {
180943
- const t8 = performance.now();
180944
- dropStaleReduceCalls(args.messages, args.protectedTags);
180945
- logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
180946
- } catch (error51) {
180947
- 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);
180948
181288
  }
181289
+ logTransformTiming(args.sessionId, "dropStaleReduceCalls", t8);
181290
+ } catch (error51) {
181291
+ sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
180949
181292
  }
180950
181293
  const m0M1Enabled = args.fullFeatureMode && args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory);
180951
181294
  if (m0M1Enabled && args.m0M1) {
@@ -180977,6 +181320,7 @@ async function runPostTransformPhase(args) {
180977
181320
  sessionLog(args.sessionId, "transform: legacy fallback injection also failed:", getErrorMessage(fallbackError));
180978
181321
  }
180979
181322
  }
181323
+ clearInjectionCache(args.sessionId);
180980
181324
  }
180981
181325
  logTransformTiming(args.sessionId, "pp.injectM0M1", tInjectM0M1);
180982
181326
  } else if (args.fullFeatureMode && args.pendingCompartmentInjection) {
@@ -181000,7 +181344,7 @@ async function runPostTransformPhase(args) {
181000
181344
  if (missingIds.length > 0) {
181001
181345
  for (const id of missingIds)
181002
181346
  persistedIds.delete(id);
181003
- setStrippedPlaceholderIds(args.db, args.sessionId, persistedIds);
181347
+ applyStrippedPlaceholderDelta(args.db, args.sessionId, { remove: missingIds });
181004
181348
  }
181005
181349
  }
181006
181350
  if (isCacheBustingPass) {
@@ -181009,11 +181353,13 @@ async function runPostTransformPhase(args) {
181009
181353
  const systemInjectedResult = stripSystemInjectedMessages(args.messages, protectedTailStart, args.liveProviderID);
181010
181354
  const newlyNeutralized = droppedResult.sentineledIds.length + systemInjectedResult.sentineledIds.length;
181011
181355
  if (newlyNeutralized > 0) {
181012
- for (const id of droppedResult.sentineledIds)
181356
+ const addedIds = [
181357
+ ...droppedResult.sentineledIds,
181358
+ ...systemInjectedResult.sentineledIds
181359
+ ];
181360
+ for (const id of addedIds)
181013
181361
  persistedIds.add(id);
181014
- for (const id of systemInjectedResult.sentineledIds)
181015
- persistedIds.add(id);
181016
- setStrippedPlaceholderIds(args.db, args.sessionId, persistedIds);
181362
+ applyStrippedPlaceholderDelta(args.db, args.sessionId, { add: addedIds });
181017
181363
  sessionLog(args.sessionId, `neutralized ${droppedResult.stripped} dropped + ${systemInjectedResult.stripped} system-injected messages (${newlyNeutralized} new, ${persistedIds.size} total persisted)`);
181018
181364
  }
181019
181365
  }
@@ -181532,7 +181878,11 @@ function createTransform(deps) {
181532
181878
  lastEmergencyNotificationCount.set(sessionId, historianFailureState.failureCount);
181533
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);
181534
181880
  }
181535
- 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
+ }
181536
181886
  sessionLog(sessionId, `EMERGENCY: aborting session at ${emergencyPercentage}%, historian failures: ${historianFailureState.failureCount}`);
181537
181887
  } else if (fullFeatureMode && isFirstTransformPassForSession && historianFailureState.failureCount > 0 && getEligibleHistoryForCompartment() && startRecoveryRun()) {
181538
181888
  sessionLog(sessionId, `transform: historian recovery triggered on session load after ${historianFailureState.failureCount} failure(s)`);
@@ -182575,6 +182925,8 @@ function createEventHook(args) {
182575
182925
  sessionLog(assistantInfo.sessionID, `model changed (${previous.providerID}/${previous.modelID} -> ${assistantInfo.providerID}/${assistantInfo.modelID}), clearing historian failure state and reasoning watermark`);
182576
182926
  clearHistorianFailureState(args.db, assistantInfo.sessionID);
182577
182927
  clearPersistedReasoningWatermark(args.db, assistantInfo.sessionID);
182928
+ clearDetectedContextLimit(args.db, assistantInfo.sessionID);
182929
+ clearEmergencyRecovery(args.db, assistantInfo.sessionID);
182578
182930
  updateSessionMeta(args.db, assistantInfo.sessionID, {
182579
182931
  clearedReasoningThroughTag: 0,
182580
182932
  observedSafeInputTokens: 0,
@@ -182678,6 +183030,7 @@ var PARTNER_FRAME_CLOSER_REDUCE = `
182678
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.`;
182679
183031
  var PARTNER_FRAME_CLOSER_NO_REDUCE = `
182680
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).`;
182681
183034
  function getToolHistoryGuidance(dropToolStructure) {
182682
183035
  if (dropToolStructure) {
182683
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.`;
@@ -182688,7 +183041,8 @@ var BASE_INTRO = (protectedTags, dropToolStructure) => `Messages and tool output
182688
183041
  Use \`ctx_reduce\` to manage context size. It supports one operation:
182689
183042
  - \`drop\`: Remove entirely (best for tool outputs you already acted on).
182690
183043
  Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
182691
- 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}
182692
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.
182693
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:
182694
183048
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
@@ -182708,7 +183062,7 @@ NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
182708
183062
  NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
182709
183063
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
182710
183064
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
182711
- 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}
182712
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.
182713
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:
182714
183068
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
@@ -184552,7 +184906,7 @@ function projectIdentityForStoredPath(rawProjectPath) {
184552
184906
  return normalizeStoredProjectPath(rawProjectPath);
184553
184907
  }
184554
184908
  function memoryBelongsToProject(memory, projectPath) {
184555
- return memory.projectPath === projectPath || projectIdentityForStoredPath(memory.projectPath) === projectPath;
184909
+ return storedPathBelongsToIdentity(memory.projectPath, projectPath);
184556
184910
  }
184557
184911
  function updateMemoryContentInCurrentTransaction(db, memory, content, normalizedHash) {
184558
184912
  db.prepare("UPDATE memories SET content = ?, normalized_hash = ?, updated_at = ? WHERE id = ?").run(content, normalizedHash, Date.now(), memory.id);
@@ -185045,7 +185399,7 @@ import { tool as tool4 } from "@opencode-ai/plugin";
185045
185399
  // src/features/magic-context/range-parser.ts
185046
185400
  function parseRangeString(input) {
185047
185401
  const maxRangeElements = 1000;
185048
- const trimmed = input.trim();
185402
+ const trimmed = input.replace(/§/g, "").trim();
185049
185403
  if (trimmed === "") {
185050
185404
  throw new Error("Range string must not be empty");
185051
185405
  }
@@ -185291,6 +185645,7 @@ function createCtxSearchTool(deps) {
185291
185645
  return "Error: 'query' is required.";
185292
185646
  }
185293
185647
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, toolContext.sessionID);
185648
+ const messageOrdinalCutoff = lastCompartmentEnd >= 0 ? lastCompartmentEnd : 0;
185294
185649
  const visibleMemoryIds = getVisibleMemoryIds(deps.db, toolContext.sessionID);
185295
185650
  const projectPath = deps.resolveProjectPath(toolContext.directory);
185296
185651
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
@@ -185308,7 +185663,7 @@ function createCtxSearchTool(deps) {
185308
185663
  },
185309
185664
  isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
185310
185665
  readMessages: deps.readMessages,
185311
- maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
185666
+ maxMessageOrdinal: messageOrdinalCutoff,
185312
185667
  gitCommitsEnabled,
185313
185668
  sources: normalizeSources(args.sources),
185314
185669
  visibleMemoryIds,
@@ -185411,7 +185766,7 @@ init_models_dev_cache();
185411
185766
 
185412
185767
  // src/shared/rpc-server.ts
185413
185768
  init_logger();
185414
- import { randomBytes } from "node:crypto";
185769
+ import { randomBytes, timingSafeEqual } from "node:crypto";
185415
185770
  import {
185416
185771
  mkdirSync as mkdirSync9,
185417
185772
  readdirSync,
@@ -185478,6 +185833,14 @@ function isValidPort(port) {
185478
185833
  }
185479
185834
 
185480
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
+
185481
185844
  class MagicContextRpcServer {
185482
185845
  server = null;
185483
185846
  port = 0;
@@ -185565,7 +185928,7 @@ class MagicContextRpcServer {
185565
185928
  }
185566
185929
  const auth = req.headers.authorization;
185567
185930
  const presented = typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
185568
- if (presented !== this.token) {
185931
+ if (!tokensMatch(presented, this.token)) {
185569
185932
  res.writeHead(401, { "Content-Type": "application/json" });
185570
185933
  res.end(JSON.stringify({ error: "Unauthorized" }));
185571
185934
  req.resume();