@cortexkit/opencode-magic-context 0.31.2 → 0.31.4

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 (108) hide show
  1. package/dist/config/index.d.ts.map +1 -1
  2. package/dist/config/project-security.d.ts +16 -12
  3. package/dist/config/project-security.d.ts.map +1 -1
  4. package/dist/config/schema/magic-context.d.ts +13 -0
  5. package/dist/config/schema/magic-context.d.ts.map +1 -1
  6. package/dist/features/magic-context/dreamer/classify-prompt.d.ts +2 -2
  7. package/dist/features/magic-context/dreamer/classify-prompt.d.ts.map +1 -1
  8. package/dist/features/magic-context/dreamer/classify.d.ts +8 -0
  9. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  10. package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
  11. package/dist/features/magic-context/dreamer/lease.d.ts +1 -0
  12. package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
  13. package/dist/features/magic-context/dreamer/manifest-parser.d.ts +7 -0
  14. package/dist/features/magic-context/dreamer/manifest-parser.d.ts.map +1 -0
  15. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +2 -3
  16. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
  17. package/dist/features/magic-context/dreamer/map-memories.d.ts +8 -0
  18. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  19. package/dist/features/magic-context/dreamer/promote-primers.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts +5 -0
  21. package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/verify-prompt.d.ts +2 -2
  23. package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
  24. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  25. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +4 -0
  26. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  27. package/dist/features/magic-context/memory/storage-memory.d.ts +12 -0
  28. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  29. package/dist/features/magic-context/memory/verification-paths.d.ts +16 -0
  30. package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
  31. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  32. package/dist/features/magic-context/message-index.d.ts +6 -0
  33. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  34. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  35. package/dist/features/magic-context/search.d.ts.map +1 -1
  36. package/dist/features/magic-context/smart-notes/capabilities.d.ts +1 -0
  37. package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
  38. package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -2
  39. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  40. package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
  41. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +3 -2
  42. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
  43. package/dist/features/magic-context/smart-notes/schedule.d.ts.map +1 -1
  44. package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +17 -6
  45. package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
  46. package/dist/features/magic-context/smart-notes/types.d.ts +6 -1
  47. package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
  48. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  49. package/dist/features/magic-context/storage-meta-persisted.d.ts +8 -0
  50. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  51. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  52. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  53. package/dist/features/magic-context/storage.d.ts +1 -1
  54. package/dist/features/magic-context/storage.d.ts.map +1 -1
  55. package/dist/features/magic-context/workspaces.d.ts +8 -0
  56. package/dist/features/magic-context/workspaces.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/compartment-runner.d.ts +2 -0
  59. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/tag-messages.d.ts +1 -0
  64. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/todo-view.d.ts +27 -2
  66. package/dist/hooks/magic-context/todo-view.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/transform-message-helpers.d.ts +6 -3
  68. package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts +1 -0
  70. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  71. package/dist/index.js +1046 -333
  72. package/dist/shared/assistant-message-extractor.d.ts +1 -0
  73. package/dist/shared/assistant-message-extractor.d.ts.map +1 -1
  74. package/dist/shared/redaction.d.ts.map +1 -1
  75. package/dist/shared/rpc-notifications.d.ts +21 -16
  76. package/dist/shared/rpc-notifications.d.ts.map +1 -1
  77. package/dist/shared/rpc-server.d.ts +1 -0
  78. package/dist/shared/rpc-server.d.ts.map +1 -1
  79. package/dist/shared/rpc-utils.d.ts +3 -1
  80. package/dist/shared/rpc-utils.d.ts.map +1 -1
  81. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  82. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  83. package/dist/tools/ctx-search/types.d.ts +1 -1
  84. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  85. package/dist/tui/data/notification-socket.d.ts +1 -0
  86. package/dist/tui/data/notification-socket.d.ts.map +1 -1
  87. package/dist/tui-compiled/badge-contrast.d.ts +46 -0
  88. package/dist/tui-compiled/badge-contrast.d.ts.map +1 -0
  89. package/dist/tui-compiled/data/context-db.d.ts +45 -0
  90. package/dist/tui-compiled/data/context-db.d.ts.map +1 -0
  91. package/dist/tui-compiled/data/notification-socket.d.ts +40 -0
  92. package/dist/tui-compiled/data/notification-socket.d.ts.map +1 -0
  93. package/package.json +7 -4
  94. package/src/shared/assistant-message-extractor.ts +20 -0
  95. package/src/shared/redaction.ts +1 -0
  96. package/src/shared/rpc-client.test.ts +164 -5
  97. package/src/shared/rpc-notifications.ts +73 -21
  98. package/src/shared/rpc-server.ts +69 -18
  99. package/src/shared/rpc-utils.ts +12 -2
  100. package/src/tui/data/notification-socket.test.ts +131 -0
  101. package/src/tui/data/notification-socket.ts +81 -16
  102. package/src/tui/entry.mjs +36 -0
  103. package/src/tui-compiled/badge-contrast.ts +84 -0
  104. package/src/tui-compiled/data/context-db.ts +353 -0
  105. package/src/tui-compiled/data/notification-socket.ts +294 -0
  106. package/src/tui-compiled/index.tsx +1204 -0
  107. package/src/tui-compiled/slots/sidebar-content.tsx +1052 -0
  108. package/src/tui-compiled/types/opencode-plugin-tui.d.ts +232 -0
package/dist/index.js CHANGED
@@ -490,7 +490,7 @@ __export(exports_util, {
490
490
  jsonStringifyReplacer: () => jsonStringifyReplacer,
491
491
  joinValues: () => joinValues,
492
492
  issue: () => issue,
493
- isPlainObject: () => isPlainObject3,
493
+ isPlainObject: () => isPlainObject,
494
494
  isObject: () => isObject,
495
495
  hexToUint8Array: () => hexToUint8Array,
496
496
  getSizableOrigin: () => getSizableOrigin,
@@ -655,7 +655,7 @@ function slugify(input) {
655
655
  function isObject(data) {
656
656
  return typeof data === "object" && data !== null && !Array.isArray(data);
657
657
  }
658
- function isPlainObject3(o) {
658
+ function isPlainObject(o) {
659
659
  if (isObject(o) === false)
660
660
  return false;
661
661
  const ctor = o.constructor;
@@ -672,7 +672,7 @@ function isPlainObject3(o) {
672
672
  return true;
673
673
  }
674
674
  function shallowClone(o) {
675
- if (isPlainObject3(o))
675
+ if (isPlainObject(o))
676
676
  return { ...o };
677
677
  if (Array.isArray(o))
678
678
  return [...o];
@@ -812,7 +812,7 @@ function omit(schema, mask) {
812
812
  return clone(schema, def);
813
813
  }
814
814
  function extend(schema, shape) {
815
- if (!isPlainObject3(shape)) {
815
+ if (!isPlainObject(shape)) {
816
816
  throw new Error("Invalid input to extend: expected a plain object");
817
817
  }
818
818
  const checks = schema._zod.def.checks;
@@ -835,7 +835,7 @@ function extend(schema, shape) {
835
835
  return clone(schema, def);
836
836
  }
837
837
  function safeExtend(schema, shape) {
838
- if (!isPlainObject3(shape)) {
838
+ if (!isPlainObject(shape)) {
839
839
  throw new Error("Invalid input to safeExtend: expected a plain object");
840
840
  }
841
841
  const def = mergeDefs(schema._zod.def, {
@@ -2318,7 +2318,7 @@ function mergeValues(a, b) {
2318
2318
  if (a instanceof Date && b instanceof Date && +a === +b) {
2319
2319
  return { valid: true, data: a };
2320
2320
  }
2321
- if (isPlainObject3(a) && isPlainObject3(b)) {
2321
+ if (isPlainObject(a) && isPlainObject(b)) {
2322
2322
  const bKeys = Object.keys(b);
2323
2323
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
2324
2324
  const newObj = { ...a, ...b };
@@ -3575,7 +3575,7 @@ var init_schemas = __esm(() => {
3575
3575
  $ZodType.init(inst, def);
3576
3576
  inst._zod.parse = (payload, ctx) => {
3577
3577
  const input = payload.value;
3578
- if (!isPlainObject3(input)) {
3578
+ if (!isPlainObject(input)) {
3579
3579
  payload.issues.push({
3580
3580
  expected: "record",
3581
3581
  code: "invalid_type",
@@ -15408,6 +15408,10 @@ var init_magic_context = __esm(() => {
15408
15408
  }).describe("Embedding provider configuration"),
15409
15409
  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).'),
15410
15410
  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."),
15411
+ todowrite: exports_external.object({
15412
+ enabled: exports_external.boolean().default(true).describe("Pi only: register Magic Context's todowrite task-list tool. Disable if you use your own todo extension. OpenCode ships its own built-in todowrite; this setting has no effect there."),
15413
+ overlay: exports_external.boolean().default(true).describe("Pi only: show the persistent todo overlay above the editor while tasks are active.")
15414
+ }).default({ enabled: true, overlay: true }).describe("Pi-only todowrite tool and overlay controls. Pi registers tools and widgets at extension boot, so changing this after /cd requires /reload or restart."),
15411
15415
  smart_drops: exports_external.boolean().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 5), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
15412
15416
  caveman_text_compression: exports_external.object({
15413
15417
  enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Active for primary sessions when enabled; never for subagents. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
@@ -16658,6 +16662,22 @@ function extractLatestAssistantText(messages) {
16658
16662
  return getTextParts(latest).map((part) => part.text).join(`
16659
16663
  `) || null;
16660
16664
  }
16665
+ function hasLengthCappedOutput(value) {
16666
+ if (Array.isArray(value))
16667
+ return value.some((item) => hasLengthCappedOutput(item));
16668
+ if (!isRecord(value))
16669
+ return false;
16670
+ if (value.length_capped === true || value.lengthCapped === true)
16671
+ return true;
16672
+ const finishReason = value.finish_reason ?? value.finishReason;
16673
+ if (typeof finishReason === "string") {
16674
+ const normalized = finishReason.toLowerCase();
16675
+ if (normalized === "length" || normalized === "max_tokens" || normalized === "max_output_tokens") {
16676
+ return true;
16677
+ }
16678
+ }
16679
+ return Object.values(value).some((item) => hasLengthCappedOutput(item));
16680
+ }
16661
16681
  var init_assistant_message_extractor = () => {};
16662
16682
 
16663
16683
  // src/shared/keep-subagents.ts
@@ -17326,7 +17346,8 @@ function isMidTurn(_deps, sessionId) {
17326
17346
  }
17327
17347
  function isMidTurnFromOpenCodeDb(db, sessionId) {
17328
17348
  const latestAssistant = db.prepare(`SELECT id,
17329
- json_extract(data, '$.finish') as finish
17349
+ json_extract(data, '$.finish') as finish,
17350
+ time_created as timeCreated
17330
17351
  FROM message
17331
17352
  WHERE session_id = ?
17332
17353
  AND json_extract(data, '$.role') = 'assistant'
@@ -17334,6 +17355,8 @@ function isMidTurnFromOpenCodeDb(db, sessionId) {
17334
17355
  LIMIT 1`).get(sessionId);
17335
17356
  if (typeof latestAssistant?.id !== "string")
17336
17357
  return false;
17358
+ if (hasNewerRealUserMessage(db, sessionId, latestAssistant.timeCreated))
17359
+ return false;
17337
17360
  if (latestAssistant.finish === "tool-calls")
17338
17361
  return true;
17339
17362
  const partRows = db.prepare("SELECT data FROM part WHERE session_id = ? AND message_id = ?").all(sessionId, latestAssistant.id);
@@ -17348,6 +17371,18 @@ function isMidTurnFromOpenCodeDb(db, sessionId) {
17348
17371
  }
17349
17372
  });
17350
17373
  }
17374
+ function hasNewerRealUserMessage(db, sessionId, latestAssistantTimeCreated) {
17375
+ if (typeof latestAssistantTimeCreated !== "number")
17376
+ return false;
17377
+ const row = db.prepare(`SELECT 1 as one
17378
+ FROM message
17379
+ WHERE session_id = ?
17380
+ AND time_created > ?
17381
+ AND json_extract(data, '$.role') = 'user'
17382
+ AND COALESCE(json_extract(data, '$.synthetic'), 0) NOT IN (1, 'true')
17383
+ LIMIT 1`).get(sessionId, latestAssistantTimeCreated);
17384
+ return row?.one === 1;
17385
+ }
17351
17386
  function getMessageTimesFromOpenCodeDb(sessionId, messageIds) {
17352
17387
  const result = new Map;
17353
17388
  if (messageIds.length === 0)
@@ -150816,12 +150851,19 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150816
150851
  let totalTokens = 0;
150817
150852
  let messagesProcessed = 0;
150818
150853
  let lastOrdinal = startOrdinal - 1;
150854
+ let highestScannedOrdinal = startOrdinal - 1;
150819
150855
  let lastMessageId = "";
150820
150856
  let firstMessageId = "";
150821
150857
  let currentBlock = null;
150822
150858
  let pendingNoiseMeta = [];
150823
150859
  let commitClusters = 0;
150824
150860
  let lastFlushedRole = "";
150861
+ function recordFilteredNoise(meta3) {
150862
+ pendingNoiseMeta.push(meta3);
150863
+ if (!currentBlock) {
150864
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, meta3.ordinal);
150865
+ }
150866
+ }
150825
150867
  function flushCurrentBlock() {
150826
150868
  if (!currentBlock)
150827
150869
  return true;
@@ -150837,6 +150879,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150837
150879
  if (!firstMessageId)
150838
150880
  firstMessageId = currentBlock.meta[0]?.messageId ?? "";
150839
150881
  lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
150882
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, lastOrdinal);
150840
150883
  lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
150841
150884
  messagesProcessed += currentBlock.meta.length;
150842
150885
  lines.push(blockText);
@@ -150860,7 +150903,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150860
150903
  if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
150861
150904
  const tcSummaries = extractToolCallSummaries(msg.parts);
150862
150905
  if (tcSummaries.length === 0) {
150863
- pendingNoiseMeta.push(meta3);
150906
+ recordFilteredNoise(meta3);
150864
150907
  continue;
150865
150908
  }
150866
150909
  const tcText = tcSummaries.join(" / ");
@@ -150892,7 +150935,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150892
150935
  const compacted = compactTextForSummary(allParts.join(" / "), msg.role);
150893
150936
  const text = compacted.text;
150894
150937
  if (!text) {
150895
- pendingNoiseMeta.push(meta3);
150938
+ recordFilteredNoise(meta3);
150896
150939
  continue;
150897
150940
  }
150898
150941
  const msgHasNarrative = textParts.length > 0;
@@ -150919,7 +150962,9 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150919
150962
  };
150920
150963
  pendingNoiseMeta = [];
150921
150964
  }
150922
- flushCurrentBlock();
150965
+ if (flushCurrentBlock() && pendingNoiseMeta.length > 0) {
150966
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, pendingNoiseMeta[pendingNoiseMeta.length - 1]?.ordinal ?? highestScannedOrdinal);
150967
+ }
150923
150968
  const toolOnlyRanges = [];
150924
150969
  for (const range of flushedToolOnlyBlocks) {
150925
150970
  const last = toolOnlyRanges[toolOnlyRanges.length - 1];
@@ -150936,7 +150981,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150936
150981
  endMessageId: lastMessageId,
150937
150982
  messageCount: messagesProcessed,
150938
150983
  tokenEstimate: totalTokens,
150939
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
150984
+ hasMore: Math.max(lastOrdinal, highestScannedOrdinal) < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
150940
150985
  text: lines.join(`
150941
150986
  `),
150942
150987
  lines: lineMeta,
@@ -150962,7 +151007,7 @@ function normalizeIndexText(text) {
150962
151007
  function getLastIndexedStatement(db) {
150963
151008
  let stmt = lastIndexedStatements.get(db);
150964
151009
  if (!stmt) {
150965
- stmt = db.prepare("SELECT last_indexed_ordinal FROM message_history_index WHERE session_id = ?");
151010
+ stmt = db.prepare("SELECT last_indexed_ordinal, dirty_floor_ordinal FROM message_history_index WHERE session_id = ?");
150966
151011
  lastIndexedStatements.set(db, stmt);
150967
151012
  }
150968
151013
  return stmt;
@@ -150983,6 +151028,22 @@ function getUpsertIndexStatement(db) {
150983
151028
  }
150984
151029
  return stmt;
150985
151030
  }
151031
+ function getUpsertCleanIndexStatement(db) {
151032
+ let stmt = upsertCleanIndexStatements.get(db);
151033
+ if (!stmt) {
151034
+ stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, dirty_floor_ordinal, updated_at, harness) VALUES (?, ?, 0, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = excluded.last_indexed_ordinal, dirty_floor_ordinal = 0, updated_at = excluded.updated_at");
151035
+ upsertCleanIndexStatements.set(db, stmt);
151036
+ }
151037
+ return stmt;
151038
+ }
151039
+ function getUpsertDirtyFloorStatement(db) {
151040
+ let stmt = upsertDirtyFloorStatements.get(db);
151041
+ if (!stmt) {
151042
+ stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, dirty_floor_ordinal, updated_at, harness) VALUES (?, ?, ?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = MAX(message_history_index.last_indexed_ordinal, excluded.last_indexed_ordinal), dirty_floor_ordinal = CASE WHEN message_history_index.dirty_floor_ordinal <= 0 THEN excluded.dirty_floor_ordinal WHEN excluded.dirty_floor_ordinal <= 0 THEN message_history_index.dirty_floor_ordinal ELSE MIN(message_history_index.dirty_floor_ordinal, excluded.dirty_floor_ordinal) END, updated_at = excluded.updated_at");
151043
+ upsertDirtyFloorStatements.set(db, stmt);
151044
+ }
151045
+ return stmt;
151046
+ }
150986
151047
  function getDeleteFtsStatement(db) {
150987
151048
  let stmt = deleteFtsStatements.get(db);
150988
151049
  if (!stmt) {
@@ -150991,6 +151052,14 @@ function getDeleteFtsStatement(db) {
150991
151052
  }
150992
151053
  return stmt;
150993
151054
  }
151055
+ function getDeleteFtsFromOrdinalStatement(db) {
151056
+ let stmt = deleteFtsFromOrdinalStatements.get(db);
151057
+ if (!stmt) {
151058
+ stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ? AND CAST(message_ordinal AS INTEGER) >= ?");
151059
+ deleteFtsFromOrdinalStatements.set(db, stmt);
151060
+ }
151061
+ return stmt;
151062
+ }
150994
151063
  function getDeleteIndexStatement(db) {
150995
151064
  let stmt = deleteIndexStatements.get(db);
150996
151065
  if (!stmt) {
@@ -151011,6 +151080,14 @@ function getLastIndexedOrdinal(db, sessionId) {
151011
151080
  const row = getLastIndexedStatement(db).get(sessionId);
151012
151081
  return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
151013
151082
  }
151083
+ function getDirtyIndexFloor(db, sessionId) {
151084
+ const row = getLastIndexedStatement(db).get(sessionId);
151085
+ return typeof row?.dirty_floor_ordinal === "number" && row.dirty_floor_ordinal > 0 ? row.dirty_floor_ordinal : null;
151086
+ }
151087
+ function markMessageIndexDirty(db, sessionId, floorOrdinal) {
151088
+ const dirtyFloor = Math.max(1, Math.floor(floorOrdinal));
151089
+ getUpsertDirtyFloorStatement(db).run(sessionId, getLastIndexedOrdinal(db, sessionId), dirtyFloor, Date.now(), getHarness());
151090
+ }
151014
151091
  function isMessageAlreadyIndexed(db, sessionId, messageId) {
151015
151092
  const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
151016
151093
  return (typeof row?.count === "number" ? row.count : 0) > 0;
@@ -151084,7 +151161,15 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
151084
151161
  db.exec("BEGIN IMMEDIATE");
151085
151162
  let committed = false;
151086
151163
  try {
151087
- const effectiveWatermark = Math.max(lastIndexedOrdinal, getLastIndexedOrdinal(db, sessionId));
151164
+ let effectiveWatermark = Math.max(lastIndexedOrdinal, getLastIndexedOrdinal(db, sessionId));
151165
+ const dirtyFloor = getDirtyIndexFloor(db, sessionId);
151166
+ if (dirtyFloor !== null) {
151167
+ const rewindOrdinal = Math.max(1, Math.min(dirtyFloor, finalWatermark + 1));
151168
+ if (rewindOrdinal <= finalWatermark) {
151169
+ getDeleteFtsFromOrdinalStatement(db).run(sessionId, rewindOrdinal);
151170
+ }
151171
+ effectiveWatermark = Math.min(effectiveWatermark, rewindOrdinal - 1);
151172
+ }
151088
151173
  const insertMessage = getInsertMessageStatement(db);
151089
151174
  for (const message of messages) {
151090
151175
  if (message.ordinal <= effectiveWatermark) {
@@ -151101,7 +151186,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
151101
151186
  inserted++;
151102
151187
  }
151103
151188
  const newWatermark = Math.max(effectiveWatermark, finalWatermark);
151104
- getUpsertIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
151189
+ getUpsertCleanIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
151105
151190
  db.exec("COMMIT");
151106
151191
  committed = true;
151107
151192
  } finally {
@@ -151113,14 +151198,17 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
151113
151198
  }
151114
151199
  return inserted;
151115
151200
  }
151116
- var lastIndexedStatements, insertMessageStatements, upsertIndexStatements, deleteFtsStatements, deleteIndexStatements, countIndexedMessageStatements;
151201
+ var lastIndexedStatements, insertMessageStatements, upsertIndexStatements, upsertCleanIndexStatements, upsertDirtyFloorStatements, deleteFtsStatements, deleteFtsFromOrdinalStatements, deleteIndexStatements, countIndexedMessageStatements;
151117
151202
  var init_message_index = __esm(async () => {
151118
151203
  init_compression_depth_storage();
151119
151204
  await init_read_session_chunk();
151120
151205
  lastIndexedStatements = new WeakMap;
151121
151206
  insertMessageStatements = new WeakMap;
151122
151207
  upsertIndexStatements = new WeakMap;
151208
+ upsertCleanIndexStatements = new WeakMap;
151209
+ upsertDirtyFloorStatements = new WeakMap;
151123
151210
  deleteFtsStatements = new WeakMap;
151211
+ deleteFtsFromOrdinalStatements = new WeakMap;
151124
151212
  deleteIndexStatements = new WeakMap;
151125
151213
  countIndexedMessageStatements = new WeakMap;
151126
151214
  });
@@ -151159,6 +151247,7 @@ function healNullTextColumns(db) {
151159
151247
  ["cache_ttl", ""],
151160
151248
  ["last_nudge_band", ""],
151161
151249
  ["last_nudge_level", ""],
151250
+ ["channel2_nudge_claim_token", ""],
151162
151251
  ["last_transform_error", ""],
151163
151252
  ["nudge_anchor_message_id", ""],
151164
151253
  ["nudge_anchor_text", ""],
@@ -151291,23 +151380,39 @@ function uniqueSorted(values) {
151291
151380
  function placeholders(values) {
151292
151381
  return values.map(() => "?").join(", ");
151293
151382
  }
151383
+ function defaultWorkspaceShareCategories() {
151384
+ return [...DEFAULT_WORKSPACE_SHARE_CATEGORIES];
151385
+ }
151386
+ function warnInvalidShareCategories(reason, raw) {
151387
+ log("[magic-context] WARN: invalid workspace share_categories; sharing no foreign memory categories", {
151388
+ reason,
151389
+ raw
151390
+ });
151391
+ }
151294
151392
  function normalizeShareCategories(raw) {
151295
- if (raw === null || raw === undefined)
151296
- return null;
151297
- if (typeof raw !== "string")
151298
- return null;
151393
+ if (raw === null || raw === undefined) {
151394
+ return defaultWorkspaceShareCategories();
151395
+ }
151396
+ if (typeof raw !== "string") {
151397
+ warnInvalidShareCategories("not a string", raw);
151398
+ return [];
151399
+ }
151299
151400
  let parsed;
151300
151401
  try {
151301
151402
  parsed = JSON.parse(raw);
151302
151403
  } catch {
151303
- return null;
151404
+ warnInvalidShareCategories("malformed JSON", raw);
151405
+ return [];
151406
+ }
151407
+ if (!Array.isArray(parsed)) {
151408
+ warnInvalidShareCategories("not a JSON array", raw);
151409
+ return [];
151304
151410
  }
151305
- if (!Array.isArray(parsed))
151306
- return null;
151307
151411
  const categories = [];
151308
151412
  for (const value of parsed) {
151309
151413
  if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
151310
- return null;
151414
+ warnInvalidShareCategories("unknown category", raw);
151415
+ return [];
151311
151416
  }
151312
151417
  if (!categories.includes(value))
151313
151418
  categories.push(value);
@@ -151316,16 +151421,33 @@ function normalizeShareCategories(raw) {
151316
151421
  }
151317
151422
  function selectWorkspaceShareCategories(db, identities) {
151318
151423
  const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
151319
- if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
151424
+ if (candidates.length === 0 || !tableExists(db, "workspace_members")) {
151320
151425
  return null;
151321
151426
  }
151427
+ const hasMembership = Boolean(db.prepare(`SELECT 1
151428
+ FROM workspace_members
151429
+ WHERE project_path IN (${placeholders(candidates)})
151430
+ LIMIT 1`).get(...candidates));
151431
+ if (!hasMembership)
151432
+ return null;
151433
+ if (!tableExists(db, "workspaces")) {
151434
+ log("[magic-context] WARN: workspace member has no workspaces table; sharing no foreign memory categories");
151435
+ return [];
151436
+ }
151437
+ if (!columnExists(db, "workspaces", "share_categories")) {
151438
+ return defaultWorkspaceShareCategories();
151439
+ }
151322
151440
  const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
151323
151441
  FROM workspace_members AS member
151324
151442
  JOIN workspaces AS workspace ON workspace.id = member.workspace_id
151325
151443
  WHERE member.project_path IN (${placeholders(candidates)})
151326
151444
  ORDER BY workspace.id ASC
151327
151445
  LIMIT 1`).get(...candidates);
151328
- return normalizeShareCategories(row?.shareCategories ?? null);
151446
+ if (!row) {
151447
+ log("[magic-context] WARN: workspace member has no workspace share_categories row; sharing no foreign memory categories");
151448
+ return [];
151449
+ }
151450
+ return normalizeShareCategories(row.shareCategories);
151329
151451
  }
151330
151452
  function resolveWorkspaceShareCategories(db, projectIdentity) {
151331
151453
  return selectWorkspaceShareCategories(db, [projectIdentity]);
@@ -151430,7 +151552,7 @@ function computeWorkspaceEpochFingerprint(db, identities) {
151430
151552
  const hash2 = createHash2("sha256");
151431
151553
  hash2.update("share_categories", "utf8");
151432
151554
  hash2.update("\x00");
151433
- hash2.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
151555
+ hash2.update(shareCategories === null ? "NO_WORKSPACE" : JSON.stringify(shareCategories), "utf8");
151434
151556
  hash2.update(`
151435
151557
  `);
151436
151558
  for (const identity of canonical) {
@@ -151502,11 +151624,13 @@ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
151502
151624
  throw error51;
151503
151625
  }
151504
151626
  }
151505
- var VALID_SHARE_CATEGORIES;
151627
+ var VALID_SHARE_CATEGORIES, DEFAULT_WORKSPACE_SHARE_CATEGORIES;
151506
151628
  var init_workspaces = __esm(() => {
151629
+ init_logger();
151507
151630
  init_constants();
151508
151631
  init_project_identity2();
151509
151632
  VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
151633
+ DEFAULT_WORKSPACE_SHARE_CATEGORIES = ["CONSTRAINTS"];
151510
151634
  });
151511
151635
 
151512
151636
  // src/features/magic-context/migrations.ts
@@ -153991,6 +154115,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153991
154115
  CREATE TABLE IF NOT EXISTS message_history_index (
153992
154116
  session_id TEXT PRIMARY KEY,
153993
154117
  last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
154118
+ dirty_floor_ordinal INTEGER NOT NULL DEFAULT 0,
153994
154119
  updated_at INTEGER NOT NULL,
153995
154120
  harness TEXT NOT NULL DEFAULT 'opencode'
153996
154121
  );
@@ -154020,6 +154145,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154020
154145
  last_nudge_level TEXT DEFAULT '',
154021
154146
  channel2_nudge_state TEXT DEFAULT '',
154022
154147
  channel2_nudge_claimed_at INTEGER DEFAULT 0,
154148
+ channel2_nudge_claim_token TEXT DEFAULT '',
154023
154149
  last_emergency_input_sample INTEGER DEFAULT 0,
154024
154150
  last_transform_error TEXT DEFAULT '',
154025
154151
  nudge_anchor_message_id TEXT DEFAULT '',
@@ -154266,6 +154392,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154266
154392
  ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
154267
154393
  ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
154268
154394
  ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
154395
+ ensureColumn(db, "session_meta", "channel2_nudge_claim_token", "TEXT DEFAULT ''");
154269
154396
  ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
154270
154397
  ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
154271
154398
  ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
@@ -154478,6 +154605,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154478
154605
  ON transform_decisions(session_id, harness);
154479
154606
  `);
154480
154607
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
154608
+ ensureColumn(db, "message_history_index", "dirty_floor_ordinal", "INTEGER NOT NULL DEFAULT 0");
154481
154609
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
154482
154610
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
154483
154611
  ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -154492,7 +154620,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154492
154620
  function healWedgedChannel2Claims(db) {
154493
154621
  try {
154494
154622
  const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
154495
- db.prepare("UPDATE session_meta SET channel2_nudge_state = 'pending', channel2_nudge_claimed_at = 0 WHERE channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)").run(staleBefore);
154623
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = 'pending', channel2_nudge_claimed_at = 0, channel2_nudge_claim_token = '' WHERE channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)").run(staleBefore);
154496
154624
  } catch {}
154497
154625
  }
154498
154626
  function openDatabase(dbPathOrOptions) {
@@ -155226,19 +155354,42 @@ function getChannel2NudgeState(db, sessionId) {
155226
155354
  const raw = result.channel2_nudge_state;
155227
155355
  return raw === "pending" || raw === "claimed" || raw === "delivered" ? raw : "";
155228
155356
  }
155229
- function setChannel2NudgeState(db, sessionId, state) {
155357
+ function getChannel2NudgeClaim(db, sessionId) {
155358
+ const result = db.prepare("SELECT channel2_nudge_state, channel2_nudge_claimed_at, channel2_nudge_claim_token FROM session_meta WHERE session_id = ?").get(sessionId);
155359
+ const rawState = typeof result?.channel2_nudge_state === "string" ? result.channel2_nudge_state : "";
155360
+ const state = rawState === "pending" || rawState === "claimed" || rawState === "delivered" ? rawState : "";
155361
+ return {
155362
+ state,
155363
+ claimedAt: typeof result?.channel2_nudge_claimed_at === "number" ? result.channel2_nudge_claimed_at : 0,
155364
+ claimToken: typeof result?.channel2_nudge_claim_token === "string" ? result.channel2_nudge_claim_token : ""
155365
+ };
155366
+ }
155367
+ function casChannel2NudgeState(db, sessionId, from, to) {
155368
+ let changed = false;
155230
155369
  db.transaction(() => {
155231
155370
  ensureSessionMetaRow(db, sessionId);
155232
- const claimedAt = state === "claimed" ? Date.now() : 0;
155233
- db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ?").run(state, claimedAt, sessionId);
155371
+ const claimedAt = to === "claimed" ? Date.now() : 0;
155372
+ const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ?, channel2_nudge_claim_token = '' WHERE session_id = ? AND channel2_nudge_state = ?").run(to, claimedAt, sessionId, from);
155373
+ changed = (result.changes ?? 0) > 0;
155234
155374
  })();
155375
+ return changed;
155235
155376
  }
155236
- function casChannel2NudgeState(db, sessionId, from, to) {
155377
+ function claimChannel2NudgeState(db, sessionId, claimToken) {
155378
+ let changed = false;
155379
+ db.transaction(() => {
155380
+ ensureSessionMetaRow(db, sessionId);
155381
+ const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = 'claimed', channel2_nudge_claimed_at = ?, channel2_nudge_claim_token = ? WHERE session_id = ? AND channel2_nudge_state = 'pending'").run(Date.now(), claimToken, sessionId);
155382
+ changed = (result.changes ?? 0) > 0;
155383
+ })();
155384
+ return changed;
155385
+ }
155386
+ function casChannel2NudgeClaim(db, sessionId, to, claimToken) {
155237
155387
  let changed = false;
155238
155388
  db.transaction(() => {
155239
155389
  ensureSessionMetaRow(db, sessionId);
155240
155390
  const claimedAt = to === "claimed" ? Date.now() : 0;
155241
- const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ? AND channel2_nudge_state = ?").run(to, claimedAt, sessionId, from);
155391
+ const nextClaimToken = to === "claimed" ? claimToken : "";
155392
+ const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ?, channel2_nudge_claim_token = ? WHERE session_id = ? AND channel2_nudge_state = 'claimed' AND channel2_nudge_claim_token = ?").run(to, claimedAt, nextClaimToken, sessionId, claimToken);
155242
155393
  changed = (result.changes ?? 0) > 0;
155243
155394
  })();
155244
155395
  return changed;
@@ -166114,12 +166265,41 @@ function pushNotification(type, payload, sessionId) {
166114
166265
  queue = kept.sort((a, b) => a.id - b.id);
166115
166266
  }
166116
166267
  }
166117
- function drainNotifications(lastReceivedId = 0, sessionId) {
166268
+ function cursor(value) {
166269
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
166270
+ }
166271
+ function drainNotifications(lastReceivedId = 0, sessionId, options = {}) {
166272
+ const sessionCursor = cursor(lastReceivedId);
166273
+ if (options.globalOnly) {
166274
+ queue = queue.filter((notification) => notification.sessionId !== undefined || notification.id > sessionCursor);
166275
+ return queue.filter((notification) => notification.sessionId === undefined && notification.id > sessionCursor);
166276
+ }
166277
+ if (options.sessionOnly) {
166278
+ if (sessionId === undefined)
166279
+ return [];
166280
+ queue = queue.filter((notification) => notification.sessionId !== sessionId || notification.id > sessionCursor);
166281
+ return queue.filter((notification) => notification.sessionId === sessionId && notification.id > sessionCursor);
166282
+ }
166283
+ if (sessionId !== undefined && options.globalLastReceivedId !== undefined) {
166284
+ const globalCursor = cursor(options.globalLastReceivedId);
166285
+ queue = queue.filter((notification) => {
166286
+ if (notification.sessionId === undefined)
166287
+ return notification.id > globalCursor;
166288
+ if (notification.sessionId === sessionId)
166289
+ return notification.id > sessionCursor;
166290
+ return true;
166291
+ });
166292
+ return queue.filter((notification) => {
166293
+ if (notification.sessionId === undefined)
166294
+ return notification.id > globalCursor;
166295
+ return notification.sessionId === sessionId && notification.id > sessionCursor;
166296
+ });
166297
+ }
166118
166298
  const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
166119
- if (lastReceivedId > 0) {
166120
- queue = queue.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
166299
+ if (sessionCursor > 0) {
166300
+ queue = queue.filter((notification) => !(notification.id <= sessionCursor && matchesClient(notification)));
166121
166301
  }
166122
- return queue.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
166302
+ return queue.filter((notification) => notification.id > sessionCursor && matchesClient(notification));
166123
166303
  }
166124
166304
  function isTuiConnected(sessionId) {
166125
166305
  if (sinks.size === 0)
@@ -167958,6 +168138,14 @@ function getSaveEmbeddingStatement(db) {
167958
168138
  }
167959
168139
  return stmt;
167960
168140
  }
168141
+ function getSaveEmbeddingIfHashMatchesStatement(db) {
168142
+ let stmt = saveEmbeddingIfHashMatchesStatements.get(db);
168143
+ if (!stmt) {
168144
+ stmt = db.prepare("INSERT INTO memory_embeddings (memory_id, embedding, model_id) SELECT ?, ?, ? FROM memories WHERE id = ? AND normalized_hash = ? ON CONFLICT(memory_id, model_id) DO UPDATE SET embedding = excluded.embedding");
168145
+ saveEmbeddingIfHashMatchesStatements.set(db, stmt);
168146
+ }
168147
+ return stmt;
168148
+ }
167961
168149
  function getLoadAllEmbeddingsStatement(db) {
167962
168150
  let stmt = loadAllEmbeddingsStatements.get(db);
167963
168151
  if (!stmt) {
@@ -167986,6 +168174,10 @@ function saveEmbedding(db, memoryId, embedding, modelId) {
167986
168174
  const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
167987
168175
  getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
167988
168176
  }
168177
+ function saveEmbeddingIfHashMatches(db, memoryId, embedding, modelId, normalizedHash) {
168178
+ const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
168179
+ return getSaveEmbeddingIfHashMatchesStatement(db).run(memoryId, blob, modelId, memoryId, normalizedHash).changes > 0;
168180
+ }
167989
168181
  function loadAllEmbeddings(db, projectPath, modelId) {
167990
168182
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath, modelId).filter(isEmbeddingRow);
167991
168183
  const embeddings = new Map;
@@ -168017,9 +168209,10 @@ function getMemoryEmbedCoverage(db, projectPath, modelId) {
168017
168209
  embedded: typeof row?.embedded === "number" ? row.embedded : 0
168018
168210
  };
168019
168211
  }
168020
- var saveEmbeddingStatements, loadAllEmbeddingsStatements, deleteEmbeddingStatements, getStoredModelIdStatements, clearAllEmbeddingsStatements, clearModelEmbeddingsStatements, getDistinctStoredModelIdsStatements;
168212
+ var saveEmbeddingStatements, saveEmbeddingIfHashMatchesStatements, loadAllEmbeddingsStatements, deleteEmbeddingStatements, getStoredModelIdStatements, clearAllEmbeddingsStatements, clearModelEmbeddingsStatements, getDistinctStoredModelIdsStatements;
168021
168213
  var init_storage_memory_embeddings = __esm(() => {
168022
168214
  saveEmbeddingStatements = new WeakMap;
168215
+ saveEmbeddingIfHashMatchesStatements = new WeakMap;
168023
168216
  loadAllEmbeddingsStatements = new WeakMap;
168024
168217
  deleteEmbeddingStatements = new WeakMap;
168025
168218
  getStoredModelIdStatements = new WeakMap;
@@ -168844,12 +169037,12 @@ function isUnembeddedMemoryRow(row) {
168844
169037
  if (row === null || typeof row !== "object")
168845
169038
  return false;
168846
169039
  const candidate = row;
168847
- return typeof candidate.id === "number" && typeof candidate.content === "string";
169040
+ return typeof candidate.id === "number" && typeof candidate.content === "string" && typeof candidate.normalized_hash === "string";
168848
169041
  }
168849
169042
  function getLoadUnembeddedMemoriesStatement(db) {
168850
169043
  let stmt = loadUnembeddedMemoriesStatements.get(db);
168851
169044
  if (!stmt) {
168852
- stmt = db.prepare("SELECT m.id AS id, m.content AS content FROM memories m LEFT JOIN memory_embeddings me ON m.id = me.memory_id AND me.model_id = ? WHERE m.project_path = ? AND m.status = 'active' AND me.memory_id IS NULL LIMIT ?");
169045
+ stmt = db.prepare("SELECT m.id AS id, m.content AS content, m.normalized_hash AS normalized_hash FROM memories m LEFT JOIN memory_embeddings me ON m.id = me.memory_id AND me.model_id = ? WHERE m.project_path = ? AND m.status = 'active' AND me.memory_id IS NULL LIMIT ?");
168853
169046
  loadUnembeddedMemoriesStatements.set(db, stmt);
168854
169047
  }
168855
169048
  return stmt;
@@ -168872,8 +169065,9 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
168872
169065
  const embedding = result.vectors[index];
168873
169066
  if (!embedding)
168874
169067
  continue;
168875
- saveEmbedding(db, memory.id, embedding, result.modelId);
168876
- embeddedCount += 1;
169068
+ if (saveEmbeddingIfHashMatches(db, memory.id, embedding, result.modelId, memory.normalized_hash)) {
169069
+ embeddedCount += 1;
169070
+ }
168877
169071
  }
168878
169072
  })();
168879
169073
  return embeddedCount;
@@ -169339,6 +169533,9 @@ function isMemorySourceType(value) {
169339
169533
  function isVerificationStatus(value) {
169340
169534
  return typeof value === "string" && value in VERIFICATION_STATUS_LOOKUP;
169341
169535
  }
169536
+ function isUniqueConstraintError(error51) {
169537
+ return error51 instanceof Error && "code" in error51 && error51.code === "SQLITE_CONSTRAINT_UNIQUE";
169538
+ }
169342
169539
  function isNullableString(value) {
169343
169540
  return value === null || typeof value === "string";
169344
169541
  }
@@ -169504,28 +169701,55 @@ function getMemoryCountsByStatusStatement(db) {
169504
169701
  }
169505
169702
  return stmt;
169506
169703
  }
169507
- function insertMemory(db, input) {
169508
- const now = Date.now();
169509
- const normalizedHash = computeNormalizedHash(input.content);
169704
+ function buildInsertMemoryValues(input, normalizedHash, now, includeImportance) {
169510
169705
  const insertValues = [
169511
169706
  input.projectPath,
169512
169707
  input.category,
169513
169708
  input.content,
169514
169709
  normalizedHash
169515
169710
  ];
169516
- if (hasMemoryImportanceColumn(db)) {
169711
+ if (includeImportance) {
169517
169712
  insertValues.push(input.importance ?? 50);
169518
169713
  }
169519
169714
  insertValues.push(input.sourceSessionId ?? null, input.sourceType ?? "historian", 1, 0, now, now, now, now, null, "active", input.expiresAt ?? null, "unverified", null, null, null, input.metadataJson ?? null);
169520
- const result = getInsertMemoryStatement(db).run(...insertValues);
169521
- const insertedResult = result;
169522
- const inserted = getMemoryById(db, Number(insertedResult.lastInsertRowid));
169715
+ return insertValues;
169716
+ }
169717
+ function loadInsertedMemory(db, rowid) {
169718
+ const inserted = getMemoryById(db, Number(rowid));
169523
169719
  if (!inserted) {
169524
169720
  throw new Error("Failed to load inserted memory row");
169525
169721
  }
169722
+ return inserted;
169723
+ }
169724
+ function insertMemory(db, input) {
169725
+ const now = Date.now();
169726
+ const normalizedHash = computeNormalizedHash(input.content);
169727
+ const insertValues = buildInsertMemoryValues(input, normalizedHash, now, hasMemoryImportanceColumn(db));
169728
+ const result = getInsertMemoryStatement(db).run(...insertValues);
169729
+ const insertedResult = result;
169730
+ const inserted = loadInsertedMemory(db, insertedResult.lastInsertRowid);
169526
169731
  invalidateProject(input.projectPath);
169527
169732
  return inserted;
169528
169733
  }
169734
+ function insertMemoryIdempotent(db, input) {
169735
+ try {
169736
+ return { memory: insertMemory(db, input), inserted: true };
169737
+ } catch (error51) {
169738
+ if (!isUniqueConstraintError(error51)) {
169739
+ throw error51;
169740
+ }
169741
+ const normalizedHash = computeNormalizedHash(input.content);
169742
+ const existing = getMemoryByHash(db, input.projectPath, input.category, normalizedHash);
169743
+ if (!existing) {
169744
+ throw error51;
169745
+ }
169746
+ updateMemorySeenCount(db, existing.id);
169747
+ return {
169748
+ memory: getMemoryById(db, existing.id) ?? existing,
169749
+ inserted: false
169750
+ };
169751
+ }
169752
+ }
169529
169753
  function getMemoryByHash(db, projectPath, category, normalizedHash) {
169530
169754
  const result = getMemoryByHashStatement(db).get(projectPath, category, normalizedHash);
169531
169755
  if (!isMemoryRow(result)) {
@@ -170078,7 +170302,7 @@ import path6 from "node:path";
170078
170302
  import { promisify } from "node:util";
170079
170303
  async function runGit(cwd, args) {
170080
170304
  try {
170081
- const result = await execFileAsync("git", [...args], {
170305
+ const result = await execFileForVerificationPaths("git", args, {
170082
170306
  cwd,
170083
170307
  timeout: GIT_TIMEOUT_MS2,
170084
170308
  maxBuffer: 16 * 1024 * 1024,
@@ -170258,9 +170482,10 @@ async function normalizeVerificationFiles(args) {
170258
170482
  gitRoot
170259
170483
  };
170260
170484
  }
170261
- var execFileAsync, GIT_TIMEOUT_MS2 = 1e4;
170485
+ var execFileAsync, GIT_TIMEOUT_MS2 = 1e4, defaultExecFileForVerificationPaths = async (file2, args, options) => await execFileAsync(file2, [...args], options), execFileForVerificationPaths;
170262
170486
  var init_verification_paths = __esm(() => {
170263
170487
  execFileAsync = promisify(execFile);
170488
+ execFileForVerificationPaths = defaultExecFileForVerificationPaths;
170264
170489
  });
170265
170490
 
170266
170491
  // src/features/magic-context/memory/index.ts
@@ -180430,6 +180655,23 @@ function recursiveByteLength(value) {
180430
180655
  }
180431
180656
  return String(value).length;
180432
180657
  }
180658
+ function updateFnv1a32(hash2, text) {
180659
+ let next = hash2;
180660
+ for (let index = 0;index < text.length; index += 1) {
180661
+ next ^= text.charCodeAt(index);
180662
+ next = Math.imul(next, FNV1A_32_PRIME) >>> 0;
180663
+ }
180664
+ return next;
180665
+ }
180666
+ function contentStringsHash(fields) {
180667
+ let hash2 = FNV1A_32_OFFSET;
180668
+ for (const field of fields) {
180669
+ hash2 = updateFnv1a32(hash2, `${field.length}:`);
180670
+ hash2 = updateFnv1a32(hash2, field);
180671
+ hash2 = updateFnv1a32(hash2, "\x00");
180672
+ }
180673
+ return hash2.toString(16).padStart(8, "0");
180674
+ }
180433
180675
  function rawPartVersion(part) {
180434
180676
  return part.__magicContextPartUpdatedAt ?? part.updated_at ?? part.updatedAt ?? part.version ?? part.revision ?? "";
180435
180677
  }
@@ -180777,13 +181019,12 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
180777
181019
  function partContentFingerprint(part) {
180778
181020
  if (!isRecord(part))
180779
181021
  return `${typeof part}:${recursiveByteLength(part)}`;
180780
- const type = partType(part);
180781
181022
  const tool = toolSignalFromPart(part);
180782
181023
  if (tool) {
180783
- return `${type}:${tool.callId}:${tool.inputText.length}:${tool.outputText.length}`;
181024
+ return contentStringsHash([tool.inputText, tool.outputText]);
180784
181025
  }
180785
181026
  const text = firstStringField(part, ["text", "thinking", "reasoning", "content", "url"]) ?? "";
180786
- return `${type}:${text.length}`;
181027
+ return contentStringsHash([text]);
180787
181028
  }
180788
181029
  function computeRawRangeFingerprint(messages, startInclusive, endExclusive) {
180789
181030
  const pieces = [];
@@ -180808,7 +181049,7 @@ function invalidateTrueRawTokenCache(args) {
180808
181049
  }
180809
181050
  args.reason;
180810
181051
  }
180811
- var MAX_MESSAGE_CACHE_ENTRIES = 1e5, MAX_MESSAGE_CACHE_KEY_BYTES, messageEstimateCache, messageEstimateCacheBytes = 0, EMPTY_BREAKDOWN;
181052
+ var MAX_MESSAGE_CACHE_ENTRIES = 1e5, MAX_MESSAGE_CACHE_KEY_BYTES, FNV1A_32_OFFSET = 2166136261, FNV1A_32_PRIME = 16777619, messageEstimateCache, messageEstimateCacheBytes = 0, EMPTY_BREAKDOWN;
180812
181053
  var init_read_session_true_raw_tokens = __esm(() => {
180813
181054
  init_read_session_formatting();
180814
181055
  MAX_MESSAGE_CACHE_KEY_BYTES = 64 * 1024 * 1024;
@@ -184819,6 +185060,15 @@ async function executeContextRecompWithResult(deps, options = {}) {
184819
185060
  published: false
184820
185061
  };
184821
185062
  }
185063
+ options.onLeaseAcquired?.();
185064
+ if (isWrapupInProgress(deps.db, sessionId)) {
185065
+ sessionLog(sessionId, "recomp skipped: /ctx-wrapup became active");
185066
+ releaseCompartmentLease(deps.db, sessionId, holderId);
185067
+ return {
185068
+ message: "## Magic Recomp — Skipped\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
185069
+ published: false
185070
+ };
185071
+ }
184822
185072
  const renewal = startLeaseRenewal(deps, holderId);
184823
185073
  const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
184824
185074
  const promise2 = options.range ? executePartialRecompInternal(runnerDeps, options.range) : executeContextRecompInternal(runnerDeps);
@@ -186333,12 +186583,108 @@ function migrateLegacyExperimental(rawConfig, warnings) {
186333
186583
  }
186334
186584
 
186335
186585
  // src/config/project-security.ts
186586
+ init_magic_context();
186336
186587
  var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
186588
+ var HISTORIAN_USER_ONLY_FIELDS = ["model", "fallback_models"];
186337
186589
  var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
186338
186590
  var EMBEDDING_DESTINATION_FIELDS = ["endpoint", "provider"];
186339
- function isPlainObject(value) {
186591
+ var PERCENTAGE_THRESHOLD_REASON = "security: a repository may only raise compaction thresholds above the user's effective value; it cannot force earlier historian work or cloned-repo cost escalation.";
186592
+ var TOKEN_THRESHOLD_REASON = "security: a repository may only raise execute_threshold_tokens above the user's trusted token threshold; it cannot force earlier historian work or cloned-repo cost escalation.";
186593
+ var TOKEN_THRESHOLD_INTRODUCTION_REASON = "security: a repository cannot introduce a new execute_threshold_tokens override when the user has no trusted token threshold for that key; that could force earlier historian work or cloned-repo cost escalation.";
186594
+ function isPlainObject2(value) {
186340
186595
  return typeof value === "object" && value !== null && !Array.isArray(value);
186341
186596
  }
186597
+ function isValidPercentageThreshold(value) {
186598
+ return typeof value === "number" && Number.isFinite(value) && value >= 20 && value <= 80;
186599
+ }
186600
+ function isValidTokenThreshold(value) {
186601
+ return typeof value === "number" && Number.isFinite(value) && value >= 5000 && value <= 2000000;
186602
+ }
186603
+ function normalizeTrustedPercentageThresholds(value) {
186604
+ if (typeof value === "number" && Number.isFinite(value)) {
186605
+ return { defaultValue: value, overrides: new Map };
186606
+ }
186607
+ if (isPlainObject2(value) && typeof value.default === "number" && Number.isFinite(value.default)) {
186608
+ const overrides = new Map;
186609
+ for (const [key, child] of Object.entries(value)) {
186610
+ if (key === "default")
186611
+ continue;
186612
+ if (typeof child === "number" && Number.isFinite(child)) {
186613
+ overrides.set(key, child);
186614
+ }
186615
+ }
186616
+ return { defaultValue: value.default, overrides };
186617
+ }
186618
+ return { defaultValue: DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, overrides: new Map };
186619
+ }
186620
+ function normalizeTrustedTokenThresholds(value) {
186621
+ if (!isPlainObject2(value)) {
186622
+ return { defaultValue: undefined, overrides: new Map };
186623
+ }
186624
+ const overrides = new Map;
186625
+ for (const [key, child] of Object.entries(value)) {
186626
+ if (key === "default")
186627
+ continue;
186628
+ if (typeof child === "number" && Number.isFinite(child)) {
186629
+ overrides.set(key, child);
186630
+ }
186631
+ }
186632
+ return {
186633
+ defaultValue: typeof value.default === "number" && Number.isFinite(value.default) ? value.default : undefined,
186634
+ overrides
186635
+ };
186636
+ }
186637
+ function clonePercentageThresholds(value) {
186638
+ return {
186639
+ defaultValue: value.defaultValue,
186640
+ overrides: new Map(value.overrides)
186641
+ };
186642
+ }
186643
+ function cloneTokenThresholds(value) {
186644
+ return {
186645
+ defaultValue: value.defaultValue,
186646
+ overrides: new Map(value.overrides)
186647
+ };
186648
+ }
186649
+ function percentageThresholdsEqual(left, right) {
186650
+ if (left.defaultValue !== right.defaultValue)
186651
+ return false;
186652
+ if (left.overrides.size !== right.overrides.size)
186653
+ return false;
186654
+ for (const [key, value] of left.overrides) {
186655
+ if (right.overrides.get(key) !== value)
186656
+ return false;
186657
+ }
186658
+ return true;
186659
+ }
186660
+ function setMergedPercentageThreshold(mergedRaw, value) {
186661
+ if (value.overrides.size === 0) {
186662
+ mergedRaw.execute_threshold_percentage = value.defaultValue;
186663
+ return;
186664
+ }
186665
+ const serialized = { default: value.defaultValue };
186666
+ for (const [key, threshold] of value.overrides) {
186667
+ serialized[key] = threshold;
186668
+ }
186669
+ mergedRaw.execute_threshold_percentage = serialized;
186670
+ }
186671
+ function setMergedTokenThreshold(mergedRaw, value) {
186672
+ if (value.defaultValue === undefined && value.overrides.size === 0) {
186673
+ delete mergedRaw.execute_threshold_tokens;
186674
+ return;
186675
+ }
186676
+ const serialized = {};
186677
+ if (value.defaultValue !== undefined) {
186678
+ serialized.default = value.defaultValue;
186679
+ }
186680
+ for (const [key, threshold] of value.overrides) {
186681
+ serialized[key] = threshold;
186682
+ }
186683
+ mergedRaw.execute_threshold_tokens = serialized;
186684
+ }
186685
+ function makeProjectThresholdWarning(field, reason) {
186686
+ return `Ignoring ${field} from project config (${reason})`;
186687
+ }
186342
186688
  function stripUnsafeProjectConfigFields(projectRaw) {
186343
186689
  const warnings = [];
186344
186690
  if ("auto_update" in projectRaw) {
@@ -186354,7 +186700,7 @@ function stripUnsafeProjectConfigFields(projectRaw) {
186354
186700
  warnings.push("Ignoring sqlite.* from project config (security: SQLite cache/mmap PRAGMAs apply to the " + "process-global shared database handle; only user-level config may set them).");
186355
186701
  }
186356
186702
  const embedding = projectRaw.embedding;
186357
- if (isPlainObject(embedding)) {
186703
+ if (isPlainObject2(embedding)) {
186358
186704
  const removed = [];
186359
186705
  for (const field of EMBEDDING_DESTINATION_FIELDS) {
186360
186706
  if (field in embedding) {
@@ -186366,9 +186712,22 @@ function stripUnsafeProjectConfigFields(projectRaw) {
186366
186712
  warnings.push(`Ignoring embedding.${removed.join("/")} from project config ` + "(security: a repository cannot choose where private text is embedded).");
186367
186713
  }
186368
186714
  }
186715
+ const historian = projectRaw.historian;
186716
+ if (isPlainObject2(historian)) {
186717
+ const removed = [];
186718
+ for (const field of HISTORIAN_USER_ONLY_FIELDS) {
186719
+ if (field in historian) {
186720
+ delete historian[field];
186721
+ removed.push(field);
186722
+ }
186723
+ }
186724
+ if (removed.length > 0) {
186725
+ warnings.push(`Ignoring historian.${removed.join("/")} from project config ` + "(security: historian model selection is user-level only; a repository cannot force extra compaction cost).");
186726
+ }
186727
+ }
186369
186728
  for (const agentKey of HIDDEN_AGENT_KEYS) {
186370
186729
  const block = projectRaw[agentKey];
186371
- if (!isPlainObject(block))
186730
+ if (!isPlainObject2(block))
186372
186731
  continue;
186373
186732
  const removed = [];
186374
186733
  for (const field of AGENT_ESCALATION_FIELDS) {
@@ -186383,6 +186742,101 @@ function stripUnsafeProjectConfigFields(projectRaw) {
186383
186742
  }
186384
186743
  return warnings;
186385
186744
  }
186745
+ function constrainProjectThresholdOverrides(args) {
186746
+ const warnings = [];
186747
+ const basePercentage = normalizeTrustedPercentageThresholds(args.trustedBaseConfig.execute_threshold_percentage);
186748
+ const baseTokens = normalizeTrustedTokenThresholds(args.trustedBaseConfig.execute_threshold_tokens);
186749
+ if ("execute_threshold_percentage" in args.projectRaw) {
186750
+ const projectValue = args.projectRaw.execute_threshold_percentage;
186751
+ if (isValidPercentageThreshold(projectValue)) {
186752
+ const constrained = clonePercentageThresholds(basePercentage);
186753
+ constrained.defaultValue = Math.max(basePercentage.defaultValue, projectValue);
186754
+ for (const [modelKey, threshold] of basePercentage.overrides) {
186755
+ const raisedThreshold = Math.max(threshold, projectValue);
186756
+ if (raisedThreshold === constrained.defaultValue) {
186757
+ constrained.overrides.delete(modelKey);
186758
+ } else {
186759
+ constrained.overrides.set(modelKey, raisedThreshold);
186760
+ }
186761
+ }
186762
+ setMergedPercentageThreshold(args.mergedRaw, constrained);
186763
+ if (percentageThresholdsEqual(constrained, basePercentage)) {
186764
+ warnings.push(makeProjectThresholdWarning("execute_threshold_percentage", PERCENTAGE_THRESHOLD_REASON));
186765
+ }
186766
+ } else if (isPlainObject2(projectValue)) {
186767
+ const constrained = clonePercentageThresholds(basePercentage);
186768
+ let touchedValidEntry = false;
186769
+ if (isValidPercentageThreshold(projectValue.default)) {
186770
+ touchedValidEntry = true;
186771
+ if (projectValue.default > basePercentage.defaultValue) {
186772
+ constrained.defaultValue = projectValue.default;
186773
+ } else {
186774
+ warnings.push(makeProjectThresholdWarning("execute_threshold_percentage.default", PERCENTAGE_THRESHOLD_REASON));
186775
+ }
186776
+ }
186777
+ for (const [modelKey, rawValue] of Object.entries(projectValue)) {
186778
+ if (modelKey === "default")
186779
+ continue;
186780
+ if (!isValidPercentageThreshold(rawValue))
186781
+ continue;
186782
+ touchedValidEntry = true;
186783
+ const baseValue = basePercentage.overrides.get(modelKey) ?? basePercentage.defaultValue;
186784
+ if (rawValue > baseValue) {
186785
+ if (rawValue === constrained.defaultValue) {
186786
+ constrained.overrides.delete(modelKey);
186787
+ } else {
186788
+ constrained.overrides.set(modelKey, rawValue);
186789
+ }
186790
+ } else {
186791
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_percentage.${modelKey}`, PERCENTAGE_THRESHOLD_REASON));
186792
+ }
186793
+ }
186794
+ if (touchedValidEntry) {
186795
+ setMergedPercentageThreshold(args.mergedRaw, constrained);
186796
+ }
186797
+ }
186798
+ }
186799
+ if ("execute_threshold_tokens" in args.projectRaw && isPlainObject2(args.projectRaw.execute_threshold_tokens)) {
186800
+ const projectValue = args.projectRaw.execute_threshold_tokens;
186801
+ const constrained = cloneTokenThresholds(baseTokens);
186802
+ let touchedValidEntry = false;
186803
+ if (isValidTokenThreshold(projectValue.default)) {
186804
+ touchedValidEntry = true;
186805
+ if (baseTokens.defaultValue === undefined) {
186806
+ warnings.push(makeProjectThresholdWarning("execute_threshold_tokens.default", TOKEN_THRESHOLD_INTRODUCTION_REASON));
186807
+ } else if (projectValue.default > baseTokens.defaultValue) {
186808
+ constrained.defaultValue = projectValue.default;
186809
+ } else {
186810
+ warnings.push(makeProjectThresholdWarning("execute_threshold_tokens.default", TOKEN_THRESHOLD_REASON));
186811
+ }
186812
+ }
186813
+ for (const [modelKey, rawValue] of Object.entries(projectValue)) {
186814
+ if (modelKey === "default")
186815
+ continue;
186816
+ if (!isValidTokenThreshold(rawValue))
186817
+ continue;
186818
+ touchedValidEntry = true;
186819
+ const baseValue = baseTokens.overrides.get(modelKey) ?? baseTokens.defaultValue;
186820
+ if (baseValue === undefined) {
186821
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_tokens.${modelKey}`, TOKEN_THRESHOLD_INTRODUCTION_REASON));
186822
+ continue;
186823
+ }
186824
+ if (rawValue > baseValue) {
186825
+ if (rawValue === constrained.defaultValue) {
186826
+ constrained.overrides.delete(modelKey);
186827
+ } else {
186828
+ constrained.overrides.set(modelKey, rawValue);
186829
+ }
186830
+ } else {
186831
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_tokens.${modelKey}`, TOKEN_THRESHOLD_REASON));
186832
+ }
186833
+ }
186834
+ if (touchedValidEntry) {
186835
+ setMergedTokenThreshold(args.mergedRaw, constrained);
186836
+ }
186837
+ }
186838
+ return warnings;
186839
+ }
186386
186840
  function normalizeEndpoint(value) {
186387
186841
  if (typeof value !== "string")
186388
186842
  return;
@@ -186391,13 +186845,13 @@ function normalizeEndpoint(value) {
186391
186845
  }
186392
186846
  function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
186393
186847
  const projectEmbedding = projectRaw.embedding;
186394
- if (!isPlainObject(projectEmbedding))
186848
+ if (!isPlainObject2(projectEmbedding))
186395
186849
  return [];
186396
186850
  const redirectsEndpoint = "endpoint" in projectEmbedding;
186397
186851
  if (!redirectsEndpoint)
186398
186852
  return [];
186399
186853
  const userEmbedding = userRaw?.embedding;
186400
- if (isPlainObject(userEmbedding)) {
186854
+ if (isPlainObject2(userEmbedding)) {
186401
186855
  const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
186402
186856
  const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
186403
186857
  if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
@@ -186408,7 +186862,7 @@ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
186408
186862
  if (providesOwnKey)
186409
186863
  return [];
186410
186864
  const mergedEmbedding = mergedRaw.embedding;
186411
- if (!isPlainObject(mergedEmbedding))
186865
+ if (!isPlainObject2(mergedEmbedding))
186412
186866
  return [];
186413
186867
  if (!("api_key" in mergedEmbedding))
186414
186868
  return [];
@@ -186419,7 +186873,7 @@ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
186419
186873
  }
186420
186874
 
186421
186875
  // src/config/prune-config-leaf.ts
186422
- function isPlainObject2(value) {
186876
+ function isPlainObject3(value) {
186423
186877
  return typeof value === "object" && value !== null && !Array.isArray(value);
186424
186878
  }
186425
186879
  function pruneNestedConfigLeaf(block, relativePath) {
@@ -186430,7 +186884,7 @@ function pruneNestedConfigLeaf(block, relativePath) {
186430
186884
  for (let i = 0;i < relativePath.length - 1; i++) {
186431
186885
  const seg = String(relativePath[i]);
186432
186886
  const child = cursor[seg];
186433
- if (!isPlainObject2(child)) {
186887
+ if (!isPlainObject3(child)) {
186434
186888
  if (!(seg in cursor))
186435
186889
  return null;
186436
186890
  delete cursor[seg];
@@ -186782,6 +187236,7 @@ function loadPluginConfigDetailed(directory) {
186782
187236
  const projectLoaded = projectDetected.format !== "none" ? loadConfigFileDetailed(projectDetected.path, "project") : projectLegacyFallback.source ? loadConfigFileDetailed(projectLegacyFallback.source.path, "project") : null;
186783
187237
  const allWarnings = [];
186784
187238
  let mergedRaw = {};
187239
+ const trustedBaseConfig = parsePluginConfig(userLoaded?.config ?? {});
186785
187240
  if (userLegacyFallback.source) {
186786
187241
  allWarnings.push(`[user config] reading legacy config from ${userLegacyFallback.source.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`);
186787
187242
  } else if (legacyUserUnmigrated) {
@@ -186806,6 +187261,13 @@ function loadPluginConfigDetailed(directory) {
186806
187261
  for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userLoaded?.config)) {
186807
187262
  allWarnings.push(`[project config] ${warning}`);
186808
187263
  }
187264
+ for (const warning of constrainProjectThresholdOverrides({
187265
+ mergedRaw,
187266
+ projectRaw,
187267
+ trustedBaseConfig
187268
+ })) {
187269
+ allWarnings.push(`[project config] ${warning}`);
187270
+ }
186809
187271
  }
186810
187272
  const recoveredTopLevelKeys = [];
186811
187273
  const config2 = parsePluginConfig(mergedRaw, recoveredTopLevelKeys);
@@ -188529,6 +188991,14 @@ function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
188529
188991
  return true;
188530
188992
  });
188531
188993
  }
188994
+ function runLeaseGuardedWrite(db, holderId, leaseKey, fn) {
188995
+ return runImmediate(db, () => {
188996
+ if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
188997
+ throw new Error("Dream lease lost before guarded write");
188998
+ }
188999
+ return fn();
189000
+ });
189001
+ }
188532
189002
  var LEASE_HEARTBEAT_INTERVAL_MS = 60 * 1000;
188533
189003
  function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalMs = LEASE_HEARTBEAT_INTERVAL_MS) {
188534
189004
  let lost = false;
@@ -188687,8 +189157,7 @@ class OpenCodeRetrospectiveRawProvider {
188687
189157
  LEFT JOIN session_meta m ON m.session_id = sp.session_id
188688
189158
  WHERE sp.project_path = ? AND sp.harness = 'opencode'
188689
189159
  AND COALESCE(m.is_subagent, 0) = 0
188690
- ORDER BY sp.updated_at DESC, sp.session_id DESC
188691
- LIMIT ?`).all(projectIdentity, RETROSPECTIVE_MAX_SESSIONS_PER_RUN);
189160
+ ORDER BY sp.updated_at ASC, sp.session_id ASC`).all(projectIdentity);
188692
189161
  return rows.map((row) => ({
188693
189162
  sessionId: row.session_id,
188694
189163
  updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
@@ -188713,6 +189182,12 @@ class OpenCodeRetrospectiveRawProvider {
188713
189182
  return { messages: [], truncated: false };
188714
189183
  }
188715
189184
  }
189185
+ readOldestMessageTimesSince(sessionIds, sinceMs) {
189186
+ const db = this.resolveDb();
189187
+ if (!db || sessionIds.length === 0)
189188
+ return new Map;
189189
+ return readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs);
189190
+ }
188716
189191
  readUserMessagesBefore(sessionId, beforeMs, count) {
188717
189192
  const db = this.resolveDb();
188718
189193
  if (!db)
@@ -188734,13 +189209,28 @@ class OpenCodeRetrospectiveRawProvider {
188734
189209
  async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkMs, overlapUserCount, options) {
188735
189210
  const maxMessages = options?.maxMessagesPerRun ?? RETROSPECTIVE_MAX_MESSAGES_PER_RUN;
188736
189211
  const capPerSession = options?.capPerSession ?? RETROSPECTIVE_MAX_MESSAGES_PER_SESSION;
188737
- const sessionReadPageSize = Math.max(1, Math.floor(options?.maxSessionsPerRun ?? RETROSPECTIVE_MAX_SESSIONS_PER_RUN));
189212
+ const sessionLimit = Math.max(1, Math.floor(options?.maxSessionsPerRun ?? RETROSPECTIVE_MAX_SESSIONS_PER_RUN));
188738
189213
  try {
188739
- const sessions = await provider.listProjectSessions(projectIdentity);
189214
+ const allSessions = await provider.listProjectSessions(projectIdentity);
189215
+ const eligibleSessions = allSessions.map((session, index) => ({ session, index })).filter(({ session }) => (session.updatedAt ?? Number.POSITIVE_INFINITY) > watermarkMs);
189216
+ const oldestBySession = provider.readOldestMessageTimesSince ? await provider.readOldestMessageTimesSince(eligibleSessions.map(({ session }) => session.sessionId), watermarkMs) : null;
189217
+ const sessions = (oldestBySession ? eligibleSessions.filter(({ session }) => oldestBySession.has(session.sessionId)) : eligibleSessions).sort((a, b) => {
189218
+ const aFrontier = oldestBySession?.get(a.session.sessionId);
189219
+ const bFrontier = oldestBySession?.get(b.session.sessionId);
189220
+ if (aFrontier !== undefined || bFrontier !== undefined) {
189221
+ return (aFrontier ?? Number.POSITIVE_INFINITY) - (bFrontier ?? Number.POSITIVE_INFINITY) || a.index - b.index;
189222
+ }
189223
+ const aUpdated = a.session.updatedAt ?? Number.POSITIVE_INFINITY;
189224
+ const bUpdated = b.session.updatedAt ?? Number.POSITIVE_INFINITY;
189225
+ const byUpdated = aUpdated - bUpdated;
189226
+ return byUpdated || a.index - b.index;
189227
+ });
189228
+ const sessionsToRead = sessions.slice(0, sessionLimit).map(({ session }) => session);
189229
+ const firstExcludedSession = sessions[sessionLimit]?.session;
189230
+ const firstExcludedPendingTs = firstExcludedSession ? oldestBySession?.get(firstExcludedSession.sessionId) : undefined;
188740
189231
  const sinceReads = [];
188741
- for (let i = 0;i < sessions.length; i += sessionReadPageSize) {
188742
- const page = sessions.slice(i, i + sessionReadPageSize);
188743
- sinceReads.push(...await Promise.all(page.map((session) => provider.readUserMessagesSince(session.sessionId, watermarkMs, capPerSession))));
189232
+ if (sessionsToRead.length > 0) {
189233
+ sinceReads.push(...await Promise.all(sessionsToRead.map((session) => provider.readUserMessagesSince(session.sessionId, watermarkMs, capPerSession))));
188744
189234
  }
188745
189235
  let saturatedFrontier = Number.POSITIVE_INFINITY;
188746
189236
  for (const read of sinceReads) {
@@ -188762,9 +189252,14 @@ async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkM
188762
189252
  if (firstDropped) {
188763
189253
  frontier = Math.min(frontier, firstDropped.ts - 1);
188764
189254
  }
189255
+ if (typeof firstExcludedPendingTs === "number") {
189256
+ frontier = Math.min(frontier, firstExcludedPendingTs - 1);
189257
+ } else if (typeof firstExcludedSession?.updatedAt === "number") {
189258
+ frontier = Math.min(frontier, firstExcludedSession.updatedAt - 1);
189259
+ }
188765
189260
  maxScannedTs = Math.max(watermarkMs, Math.min(maxScannedTs, frontier));
188766
189261
  const keptSessionIds = new Set(keptSince.map((message) => message.sessionId));
188767
- const overlapSessions = sessions.filter((session) => keptSessionIds.has(session.sessionId));
189262
+ const overlapSessions = sessionsToRead.filter((session) => keptSessionIds.has(session.sessionId));
188768
189263
  const overlapBatches = overlapUserCount > 0 && watermarkMs > 0 ? await Promise.all(overlapSessions.map((session) => provider.readUserMessagesBefore(session.sessionId, watermarkMs, overlapUserCount))) : [];
188769
189264
  const seen = new Set;
188770
189265
  const merged = [];
@@ -188792,6 +189287,24 @@ function readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession) {
188792
189287
  const kept = truncated ? rows.slice(0, limit) : rows;
188793
189288
  return { messages: normalizeOpenCodeRows(db, sessionId, kept), truncated };
188794
189289
  }
189290
+ function readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs) {
189291
+ const result = new Map;
189292
+ const uniqueIds = Array.from(new Set(sessionIds.filter((id) => id.length > 0)));
189293
+ const chunkSize = 500;
189294
+ for (let i = 0;i < uniqueIds.length; i += chunkSize) {
189295
+ const chunk = uniqueIds.slice(i, i + chunkSize);
189296
+ const placeholders3 = chunk.map(() => "?").join(", ");
189297
+ const rows = db.prepare(`SELECT session_id, MIN(time_created) AS oldest_ts
189298
+ FROM message
189299
+ WHERE time_created > ? AND session_id IN (${placeholders3})
189300
+ GROUP BY session_id`).all(sinceMs, ...chunk);
189301
+ for (const row of rows) {
189302
+ if (typeof row.oldest_ts === "number")
189303
+ result.set(row.session_id, row.oldest_ts);
189304
+ }
189305
+ }
189306
+ return result;
189307
+ }
188795
189308
  function readOpenCodeUserMessagesBefore(db, sessionId, beforeMs, count) {
188796
189309
  const want = Math.max(1, Math.floor(count));
188797
189310
  const window = Math.max(want * 5, 32);
@@ -189410,6 +189923,39 @@ function hasShareabilitySensitiveText(text) {
189410
189923
  init_memory();
189411
189924
  init_subagent_token_capture();
189412
189925
 
189926
+ // src/features/magic-context/dreamer/manifest-parser.ts
189927
+ function extractCompleteManifestBody(text, rootName) {
189928
+ const escapedRoot = rootName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
189929
+ const rootMatch = new RegExp(`<${escapedRoot}\\b[^>]*>([\\s\\S]*?)<\\/${escapedRoot}>`, "i").exec(text);
189930
+ if (rootMatch)
189931
+ return rootMatch[1];
189932
+ const hasOpenRoot = new RegExp(`<${escapedRoot}\\b`, "i").test(text);
189933
+ const hasCloseRoot = new RegExp(`<\\/${escapedRoot}>`, "i").test(text);
189934
+ if (hasOpenRoot && !hasCloseRoot) {
189935
+ throw new Error(`${rootName} manifest missing closing root tag`);
189936
+ }
189937
+ throw new Error(`${rootName} manifest missing complete root element`);
189938
+ }
189939
+ function assertNoDuplicateManifestIds(ids, rootName) {
189940
+ const seen = new Set;
189941
+ for (const id of ids) {
189942
+ if (seen.has(id))
189943
+ throw new Error(`${rootName} manifest contains duplicate id ${id}`);
189944
+ seen.add(id);
189945
+ }
189946
+ }
189947
+ function assertManifestCoversExactly(ids, expectedIds, rootName) {
189948
+ assertNoDuplicateManifestIds(ids, rootName);
189949
+ for (const id of ids) {
189950
+ if (!expectedIds.has(id))
189951
+ throw new Error(`${rootName} manifest contains unknown id ${id}`);
189952
+ }
189953
+ for (const id of expectedIds) {
189954
+ if (!ids.includes(id))
189955
+ throw new Error(`${rootName} manifest missing id ${id}`);
189956
+ }
189957
+ }
189958
+
189413
189959
  // src/features/magic-context/dreamer/classify-prompt.ts
189414
189960
  var SCORING_GUIDANCE = `### How to score importance (1-100)
189415
189961
  Importance decides which memories survive when the injected memory block is over budget: high scores stay in context, low scores drop first. So the score is only useful if it **discriminates** — if most memories land in the same band, you have not classified them, you have just labelled them.
@@ -189476,14 +190022,15 @@ ${renderPool(args.memories)}`;
189476
190022
  var SCOPES = new Set(["project", "ecosystem", "universe"]);
189477
190023
  function parseClassifyManifest(text) {
189478
190024
  const out = [];
189479
- for (const m of text.matchAll(/<memory\b([^>]*)\/?>/g)) {
190025
+ const body = extractCompleteManifestBody(text, "classify");
190026
+ for (const m of body.matchAll(/<memory\b([^>]*)\/?>/g)) {
189480
190027
  const attrs = m[1];
189481
190028
  const idMatch = attrs.match(/\bid\s*=\s*"(\d+)"/);
189482
190029
  if (!idMatch)
189483
- continue;
190030
+ throw new Error("classify manifest entry missing numeric id");
189484
190031
  const id = Number.parseInt(idMatch[1], 10);
189485
190032
  if (!Number.isInteger(id))
189486
- continue;
190033
+ throw new Error("classify manifest entry missing numeric id");
189487
190034
  const entry = { id };
189488
190035
  const impMatch = attrs.match(/\bimportance\s*=\s*"(\d+)"/);
189489
190036
  if (impMatch) {
@@ -189492,18 +190039,23 @@ function parseClassifyManifest(text) {
189492
190039
  entry.importance = Math.max(1, Math.min(100, imp));
189493
190040
  }
189494
190041
  const scopeMatch = attrs.match(/\bscope\s*=\s*"([a-z]+)"/i);
189495
- if (scopeMatch && SCOPES.has(scopeMatch[1].toLowerCase())) {
189496
- entry.scope = scopeMatch[1].toLowerCase();
190042
+ if (scopeMatch) {
190043
+ const scope = scopeMatch[1].toLowerCase();
190044
+ if (!SCOPES.has(scope))
190045
+ throw new Error(`classify manifest invalid scope ${scope}`);
190046
+ entry.scope = scope;
189497
190047
  }
189498
190048
  const shareMatch = attrs.match(/\bshareable\s*=\s*"(true|false|1|0)"/i);
189499
190049
  if (shareMatch) {
189500
190050
  const v = shareMatch[1].toLowerCase();
189501
190051
  entry.shareable = v === "true" || v === "1";
189502
190052
  }
189503
- if (entry.importance !== undefined || entry.scope || entry.shareable !== undefined) {
189504
- out.push(entry);
190053
+ if (entry.importance === undefined && !entry.scope && entry.shareable === undefined) {
190054
+ throw new Error(`classify manifest entry ${id} missing classification fields`);
189505
190055
  }
190056
+ out.push(entry);
189506
190057
  }
190058
+ assertNoDuplicateManifestIds(out.map((entry) => entry.id), "classify");
189507
190059
  return out;
189508
190060
  }
189509
190061
 
@@ -189638,9 +190190,13 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
189638
190190
  });
189639
190191
  },
189640
190192
  validateOutput: (messages) => {
190193
+ if (hasLengthCappedOutput(messages)) {
190194
+ throw new Error("classify returned length-capped output");
190195
+ }
189641
190196
  const text = extractLatestAssistantText(messages);
189642
190197
  if (!text)
189643
190198
  throw new Error("classify returned no output");
190199
+ parseClassifyManifest(text);
189644
190200
  return text;
189645
190201
  }
189646
190202
  });
@@ -189666,17 +190222,13 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
189666
190222
  }
189667
190223
  function applyClassifications(args, chunk, manifestText) {
189668
190224
  const byId = new Map(chunk.map((m) => [m.id, m]));
189669
- const parsed = parseClassifyManifest(manifestText).filter((p) => byId.has(p.id));
190225
+ const parsed = parseClassifyManifest(manifestText);
190226
+ assertManifestCoversExactly(parsed.map((entry) => entry.id), new Set(byId.keys()), "classify");
189670
190227
  if (parsed.length === 0)
189671
190228
  return { classified: 0, changed: 0 };
189672
190229
  let classified = 0;
189673
190230
  let changed = 0;
189674
- let leaseLost = false;
189675
- args.db.transaction(() => {
189676
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
189677
- leaseLost = true;
189678
- return;
189679
- }
190231
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
189680
190232
  for (const p of parsed) {
189681
190233
  const memory = byId.get(p.id);
189682
190234
  if (!memory)
@@ -189691,9 +190243,7 @@ function applyClassifications(args, chunk, manifestText) {
189691
190243
  if (didChange)
189692
190244
  changed += 1;
189693
190245
  }
189694
- })();
189695
- if (leaseLost)
189696
- throw new Error("Dream lease lost during classify commit");
190246
+ });
189697
190247
  return { classified, changed };
189698
190248
  }
189699
190249
  function recordInvocation(args, startedAt, params) {
@@ -189743,9 +190293,11 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
189743
190293
 
189744
190294
  class SmartNoteNetworkError extends Error {
189745
190295
  isSmartNoteNetworkError = true;
189746
- constructor(message) {
190296
+ terminal;
190297
+ constructor(message, options = {}) {
189747
190298
  super(message);
189748
190299
  this.name = "SmartNoteNetworkError";
190300
+ this.terminal = options.terminal ?? false;
189749
190301
  }
189750
190302
  }
189751
190303
 
@@ -189759,6 +190311,9 @@ class SmartNoteSecurityError extends Error {
189759
190311
  function isSmartNoteNetworkError(error51) {
189760
190312
  return error51 instanceof SmartNoteNetworkError || error51 instanceof Error && (error51.name === "SmartNoteNetworkError" || error51.message.includes("SmartNoteNetworkError") || error51.message.includes("SMART_NOTE_NETWORK"));
189761
190313
  }
190314
+ function isTerminalSmartNoteNetworkError(error51) {
190315
+ return error51 instanceof SmartNoteNetworkError && error51.terminal;
190316
+ }
189762
190317
  function parseSmartNoteManifest(json2) {
189763
190318
  if (!json2)
189764
190319
  return { capabilities: [] };
@@ -189788,6 +190343,7 @@ function stringArray(value) {
189788
190343
  var DNS_TIMEOUT_MS = 3000;
189789
190344
  var DEFAULT_HTTP_TIMEOUT_MS = 5000;
189790
190345
  var DEFAULT_HTTP_BODY_LIMIT_BYTES = 64 * 1024;
190346
+ var MAX_HTTP_ADDRESS_CANDIDATES = 4;
189791
190347
  var defaultResolver = {
189792
190348
  async lookup(hostname3, signal) {
189793
190349
  return await withAbortAndTimeout(dnsLookup(hostname3, { all: true, verbatim: true }), signal, DNS_TIMEOUT_MS, "DNS lookup timed out").then((rows) => rows.filter((row) => row.family === 4 || row.family === 6).map((row) => ({ address: row.address, family: row.family })));
@@ -189824,18 +190380,21 @@ async function guardedSmartNoteHttpGet(input, options) {
189824
190380
  });
189825
190381
  const timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
189826
190382
  const bodyLimitBytes = options.bodyLimitBytes ?? DEFAULT_HTTP_BODY_LIMIT_BYTES;
190383
+ const requestAddress = options.requestAddress ?? requestValidatedAddress;
190384
+ const candidates = validation.addresses.slice(0, MAX_HTTP_ADDRESS_CANDIDATES);
189827
190385
  let lastError;
189828
- for (const candidate of validation.addresses) {
190386
+ for (const candidate of candidates) {
189829
190387
  try {
189830
- return await requestValidatedAddress(validation, candidate, {
190388
+ return await requestAddress(validation, candidate, {
189831
190389
  signal: options.signal,
189832
190390
  timeoutMs,
189833
190391
  bodyLimitBytes
189834
190392
  });
189835
190393
  } catch (error51) {
189836
190394
  lastError = error51;
189837
- if (error51 instanceof SmartNoteSecurityError || options.signal.aborted)
190395
+ if (error51 instanceof SmartNoteSecurityError || options.signal.aborted || isTerminalSmartNoteNetworkError(error51)) {
189838
190396
  throw error51;
190397
+ }
189839
190398
  }
189840
190399
  }
189841
190400
  throw toNetworkError(lastError, "all validated addresses failed");
@@ -189905,7 +190464,9 @@ function requestValidatedAddress(validation, candidate, options) {
189905
190464
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
189906
190465
  bytes += buf.byteLength;
189907
190466
  if (bytes > options.bodyLimitBytes) {
189908
- request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large"));
190467
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large", { terminal: true }));
190468
+ response.destroy();
190469
+ request2.destroy();
189909
190470
  return;
189910
190471
  }
189911
190472
  chunks.push(buf);
@@ -189922,10 +190483,16 @@ function requestValidatedAddress(validation, candidate, options) {
189922
190483
  resolve4({ status, body: Buffer.concat(chunks).toString("utf8") });
189923
190484
  });
189924
190485
  });
189925
- const onAbort = () => request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted"));
190486
+ const onAbort = () => {
190487
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted"));
190488
+ request2.destroy();
190489
+ };
189926
190490
  options.signal.addEventListener("abort", onAbort, { once: true });
189927
190491
  request2.on("timeout", () => {
189928
- request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: request timed out"));
190492
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: request timed out", {
190493
+ terminal: true
190494
+ }));
190495
+ request2.destroy();
189929
190496
  });
189930
190497
  request2.on("error", (error51) => {
189931
190498
  options.signal.removeEventListener("abort", onAbort);
@@ -190246,6 +190813,20 @@ function withSandboxLock(fn) {
190246
190813
  var DEFAULT_TIMEOUT_MS = 2000;
190247
190814
  var DEFAULT_HEAP_LIMIT_BYTES = 8 * 1024 * 1024;
190248
190815
  var DEFAULT_STACK_LIMIT_BYTES = 512 * 1024;
190816
+ function resolveCapabilitiesForRun(options, signal) {
190817
+ if (options.capabilityFactory) {
190818
+ return options.capabilityFactory(signal);
190819
+ }
190820
+ if (options.capabilities) {
190821
+ return options.capabilities;
190822
+ }
190823
+ throw new Error("smart-note check requires capabilities");
190824
+ }
190825
+ function throwIfRunAborted(signal) {
190826
+ if (signal.aborted) {
190827
+ throw signal.reason ?? new Error("smart-note check aborted");
190828
+ }
190829
+ }
190249
190830
  async function runCompiledSmartNoteCheck(options) {
190250
190831
  return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options));
190251
190832
  }
@@ -190253,17 +190834,24 @@ async function runCompiledSmartNoteCheckLocked(options) {
190253
190834
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
190254
190835
  const controller = new AbortController;
190255
190836
  const externalAbort = () => controller.abort(options.signal?.reason);
190256
- options.signal?.addEventListener("abort", externalAbort, { once: true });
190837
+ if (options.signal?.aborted) {
190838
+ externalAbort();
190839
+ } else {
190840
+ options.signal?.addEventListener("abort", externalAbort, { once: true });
190841
+ }
190257
190842
  const timer = setTimeout(() => controller.abort(new Error("smart-note check timed out")), timeoutMs);
190258
190843
  try {
190844
+ throwIfRunAborted(controller.signal);
190845
+ const capabilities = resolveCapabilitiesForRun(options, controller.signal);
190259
190846
  const deadline = Date.now() + timeoutMs;
190260
190847
  const quickjs = await getAsyncModule();
190848
+ throwIfRunAborted(controller.signal);
190261
190849
  const context = quickjs.newContext();
190262
190850
  try {
190263
190851
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
190264
190852
  context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
190265
190853
  context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
190266
- installCapabilityObject(context, options.capabilities);
190854
+ installCapabilityObject(context, capabilities);
190267
190855
  disableAmbientDynamicCode(context);
190268
190856
  const result = await evalCheck(context, options.compiledCheck);
190269
190857
  const checkResult = result;
@@ -190416,6 +191004,7 @@ Remember: output only the JSON object described by the system prompt.`;
190416
191004
  }
190417
191005
  }, {
190418
191006
  timeoutMs: remainingMs,
191007
+ signal: args.signal,
190419
191008
  fallbackModels: args.fallbackModels,
190420
191009
  callContext: "dreamer:smart-note-compiler",
190421
191010
  fetchOutput: async () => {
@@ -190440,7 +191029,8 @@ Remember: output only the JSON object described by the system prompt.`;
190440
191029
  }
190441
191030
  const dryRun = await runCompiledSmartNoteCheck({
190442
191031
  compiledCheck,
190443
- capabilities: args.capabilities,
191032
+ capabilityFactory: args.capabilityFactory,
191033
+ signal: args.signal,
190444
191034
  timeoutMs: 2000
190445
191035
  });
190446
191036
  if (!dryRun.ok) {
@@ -190593,7 +191183,9 @@ function nextSmartNoteCheckDueAt(cron, options = {}) {
190593
191183
  const rawNext = cron?.trim() ? nextDueAtMs(cron, now) : null;
190594
191184
  const rawDelta = rawNext ? rawNext - now : SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS;
190595
191185
  const clamped = Math.min(ceilingMs, Math.max(floorMs, rawDelta));
190596
- return now + clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
191186
+ const jittered = clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
191187
+ const bounded = Math.min(ceilingMs, Math.max(floorMs, jittered));
191188
+ return now + bounded;
190597
191189
  }
190598
191190
  function deterministicJitterMs(intervalMs, noteId, hash2) {
190599
191191
  const max = Math.min(60000, Math.floor(intervalMs * 0.1));
@@ -190763,9 +191355,9 @@ async function runDueCompiledSmartNoteChecks(args) {
190763
191355
  try {
190764
191356
  const result = await runCompiledSmartNoteCheck({
190765
191357
  compiledCheck: note.compiledCheck,
190766
- capabilities: createSmartNoteCapabilities({
191358
+ capabilityFactory: (signal) => createSmartNoteCapabilities({
190767
191359
  projectRoot: args.projectRoot,
190768
- signal: controller.signal
191360
+ signal
190769
191361
  }),
190770
191362
  signal: controller.signal,
190771
191363
  timeoutMs: Math.min(2000, remaining)
@@ -190830,6 +191422,24 @@ var MAX_COMPILE_PER_RUN = 5;
190830
191422
  var MAX_FALLBACK_PER_RUN = 3;
190831
191423
  var MAX_COMPILATION_FAILURES = 3;
190832
191424
  var SMART_NOTE_CONFIRMATION_SYSTEM_PROMPT = 'You are a no-tool smart-note confirmation evaluator. Output only JSON shaped as {"met": boolean}. Return true only when the supplied text alone proves the condition is satisfied.';
191425
+ function createPromptAbortSignal(parent, timeoutMs, timeoutMessage) {
191426
+ const controller = new AbortController;
191427
+ const abortFromParent = () => {
191428
+ controller.abort(parent.reason ?? new Error("smart-note prompt aborted by lease loss"));
191429
+ };
191430
+ if (parent.aborted)
191431
+ abortFromParent();
191432
+ else
191433
+ parent.addEventListener("abort", abortFromParent, { once: true });
191434
+ const timer = setTimeout(() => controller.abort(new Error(timeoutMessage)), timeoutMs);
191435
+ return {
191436
+ signal: controller.signal,
191437
+ cleanup: () => {
191438
+ clearTimeout(timer);
191439
+ parent.removeEventListener("abort", abortFromParent);
191440
+ }
191441
+ };
191442
+ }
190833
191443
  async function evaluateSmartNotes(args) {
190834
191444
  const projectRoot = args.sessionDirectory ?? args.projectIdentity;
190835
191445
  const pendingAtStart = getPendingSmartNotes(args.db, args.projectIdentity).length;
@@ -190838,6 +191448,7 @@ async function evaluateSmartNotes(args) {
190838
191448
  return { surfaced: 0, pending: 0, ran: false };
190839
191449
  }
190840
191450
  let leaseLost = false;
191451
+ const leaseAbortController = new AbortController;
190841
191452
  const leaseHeld = () => !leaseLost && peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey);
190842
191453
  const assertLeaseHeld = (phase) => {
190843
191454
  if (!leaseHeld()) {
@@ -190847,6 +191458,7 @@ async function evaluateSmartNotes(args) {
190847
191458
  };
190848
191459
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => {
190849
191460
  leaseLost = true;
191461
+ leaseAbortController.abort(new Error("Dream lease lost during smart notes"));
190850
191462
  log("[dreamer] smart notes: lease lost — aborting");
190851
191463
  args.onLeaseLost?.("smart notes");
190852
191464
  });
@@ -190869,7 +191481,7 @@ async function evaluateSmartNotes(args) {
190869
191481
  break;
190870
191482
  assertLeaseHeld("compile start");
190871
191483
  didWork = true;
190872
- const compiled = await compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld);
191484
+ const compiled = await compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld, leaseAbortController.signal);
190873
191485
  if (compiled)
190874
191486
  surfaced += 1;
190875
191487
  }
@@ -190889,7 +191501,7 @@ async function evaluateSmartNotes(args) {
190889
191501
  break;
190890
191502
  assertLeaseHeld("fallback start");
190891
191503
  didWork = true;
190892
- const met = await confirmReadOnly(args, note.id, note.content, note.surfaceCondition);
191504
+ const met = await confirmReadOnly(args, note.id, note.content, note.surfaceCondition, leaseAbortController.signal);
190893
191505
  const now = Date.now();
190894
191506
  assertLeaseHeld("fallback commit");
190895
191507
  commitSmartNoteState(args.db, {
@@ -190915,9 +191527,8 @@ async function evaluateSmartNotes(args) {
190915
191527
  heartbeat.stop();
190916
191528
  }
190917
191529
  }
190918
- async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld) {
190919
- const controller = new AbortController;
190920
- const timer = setTimeout(() => controller.abort(new Error("smart-note compile deadline")), Math.max(1000, args.deadline - Date.now()));
191530
+ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld, leaseSignal) {
191531
+ const promptSignal = createPromptAbortSignal(leaseSignal, Math.max(1000, args.deadline - Date.now()), "smart-note compile deadline");
190921
191532
  try {
190922
191533
  const result = await compileSmartNoteCheck({
190923
191534
  client: args.client,
@@ -190926,7 +191537,8 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld)
190926
191537
  sessionDirectory: args.sessionDirectory,
190927
191538
  projectIdentity: args.projectIdentity,
190928
191539
  note,
190929
- capabilities: createSmartNoteCapabilities({ projectRoot, signal: controller.signal }),
191540
+ capabilityFactory: (signal) => createSmartNoteCapabilities({ projectRoot, signal }),
191541
+ signal: promptSignal.signal,
190930
191542
  deadline: args.deadline,
190931
191543
  model: args.model,
190932
191544
  fallbackModels: args.fallbackModels
@@ -190971,7 +191583,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld)
190971
191583
  });
190972
191584
  return result.dryRun.met;
190973
191585
  } finally {
190974
- clearTimeout(timer);
191586
+ promptSignal.cleanup();
190975
191587
  }
190976
191588
  }
190977
191589
  async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseHeld) {
@@ -190982,7 +191594,7 @@ async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseH
190982
191594
  try {
190983
191595
  const result = await runCompiledSmartNoteCheck({
190984
191596
  compiledCheck: note.compiledCheck,
190985
- capabilities: createSmartNoteCapabilities({ projectRoot, signal: controller.signal }),
191597
+ capabilityFactory: (signal) => createSmartNoteCapabilities({ projectRoot, signal }),
190986
191598
  signal: controller.signal,
190987
191599
  timeoutMs: 2000
190988
191600
  });
@@ -191011,7 +191623,7 @@ async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseH
191011
191623
  clearTimeout(timer);
191012
191624
  }
191013
191625
  }
191014
- async function confirmReadOnly(args, noteId, content, surfaceCondition) {
191626
+ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSignal) {
191015
191627
  let childSessionId = null;
191016
191628
  const startedAt = Date.now();
191017
191629
  let invocationRecorded = false;
@@ -191054,42 +191666,49 @@ Note content: ${JSON.stringify(content)}
191054
191666
  Surface condition: ${JSON.stringify(surfaceCondition ?? "")}
191055
191667
 
191056
191668
  Output exactly JSON: {"met": false}`;
191057
- const run = await promptSyncWithValidatedOutputRetry(args.client, {
191058
- path: { id: childSessionId },
191059
- query: { directory: args.sessionDirectory ?? args.projectIdentity },
191060
- body: {
191061
- agent: SMART_NOTE_COMPILER_AGENT,
191062
- system: SMART_NOTE_CONFIRMATION_SYSTEM_PROMPT,
191063
- ...modelBodyField(args.model),
191064
- parts: [{ type: "text", text: prompt, synthetic: true }]
191065
- }
191066
- }, {
191067
- timeoutMs: Math.max(1000, args.deadline - Date.now()),
191068
- fallbackModels: args.fallbackModels,
191069
- callContext: "dreamer:smart-note-read-only-confirm",
191070
- fetchOutput: async () => {
191071
- const messagesResponse = await args.client.session.messages({
191072
- path: { id: childSessionId },
191073
- query: {
191074
- directory: args.sessionDirectory ?? args.projectIdentity,
191075
- limit: 20
191076
- }
191077
- });
191078
- return normalizeSDKResponse(messagesResponse, [], {
191079
- preferResponseOnMissingData: true
191080
- });
191081
- },
191082
- validateOutput: (messages) => {
191083
- const text = extractLatestAssistantText(messages) ?? "";
191084
- const match = text.match(/\{[\s\S]*\}/);
191085
- if (!match)
191086
- throw new Error("confirmation evaluator returned no JSON");
191087
- const parsed = JSON.parse(match[0]);
191088
- if (typeof parsed.met !== "boolean")
191089
- throw new Error("confirmation met missing");
191090
- return parsed.met;
191091
- }
191092
- });
191669
+ const promptSignal = createPromptAbortSignal(leaseSignal, Math.max(1000, args.deadline - Date.now()), "smart-note confirmation deadline");
191670
+ let run;
191671
+ try {
191672
+ run = await promptSyncWithValidatedOutputRetry(args.client, {
191673
+ path: { id: childSessionId },
191674
+ query: { directory: args.sessionDirectory ?? args.projectIdentity },
191675
+ body: {
191676
+ agent: SMART_NOTE_COMPILER_AGENT,
191677
+ system: SMART_NOTE_CONFIRMATION_SYSTEM_PROMPT,
191678
+ ...modelBodyField(args.model),
191679
+ parts: [{ type: "text", text: prompt, synthetic: true }]
191680
+ }
191681
+ }, {
191682
+ timeoutMs: Math.max(1000, args.deadline - Date.now()),
191683
+ signal: promptSignal.signal,
191684
+ fallbackModels: args.fallbackModels,
191685
+ callContext: "dreamer:smart-note-read-only-confirm",
191686
+ fetchOutput: async () => {
191687
+ const messagesResponse = await args.client.session.messages({
191688
+ path: { id: childSessionId },
191689
+ query: {
191690
+ directory: args.sessionDirectory ?? args.projectIdentity,
191691
+ limit: 20
191692
+ }
191693
+ });
191694
+ return normalizeSDKResponse(messagesResponse, [], {
191695
+ preferResponseOnMissingData: true
191696
+ });
191697
+ },
191698
+ validateOutput: (messages) => {
191699
+ const text = extractLatestAssistantText(messages) ?? "";
191700
+ const match = text.match(/\{[\s\S]*\}/);
191701
+ if (!match)
191702
+ throw new Error("confirmation evaluator returned no JSON");
191703
+ const parsed = JSON.parse(match[0]);
191704
+ if (typeof parsed.met !== "boolean")
191705
+ throw new Error("confirmation met missing");
191706
+ return parsed.met;
191707
+ }
191708
+ });
191709
+ } finally {
191710
+ promptSignal.cleanup();
191711
+ }
191093
191712
  recordInvocation2({ status: "completed", messages: run.output });
191094
191713
  return run.validated;
191095
191714
  } catch (error51) {
@@ -191308,19 +191927,21 @@ ${list}
191308
191927
  }
191309
191928
  function parseMapMemoriesManifest(text) {
191310
191929
  const out = [];
191311
- for (const m of text.matchAll(/<memory\b([^>]*)\/?>/g)) {
191930
+ const body = extractCompleteManifestBody(text, "mappings");
191931
+ for (const m of body.matchAll(/<memory\b([^>]*)\/?>/g)) {
191312
191932
  const attrs = m[1];
191313
191933
  const idMatch = attrs.match(/\bid\s*=\s*"(\d+)"/);
191314
191934
  if (!idMatch)
191315
- continue;
191935
+ throw new Error("mappings manifest entry missing numeric id");
191316
191936
  const id = Number.parseInt(idMatch[1], 10);
191317
191937
  if (!Number.isInteger(id))
191318
- continue;
191938
+ throw new Error("mappings manifest entry missing numeric id");
191319
191939
  const independent = /\bindependent\s*=\s*"(?:true|1)"/i.test(attrs);
191320
191940
  const filesMatch = attrs.match(/\bfiles\s*=\s*"([^"]*)"/);
191321
191941
  const files = filesMatch ? filesMatch[1].split(",").map((f) => f.trim()).filter(Boolean) : [];
191322
191942
  out.push({ id, files, independent: independent || files.length === 0 });
191323
191943
  }
191944
+ assertNoDuplicateManifestIds(out.map((entry) => entry.id), "mappings");
191324
191945
  return out;
191325
191946
  }
191326
191947
 
@@ -191409,9 +192030,13 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
191409
192030
  });
191410
192031
  },
191411
192032
  validateOutput: (messages) => {
192033
+ if (hasLengthCappedOutput(messages)) {
192034
+ throw new Error("map-memories returned length-capped output");
192035
+ }
191412
192036
  const text = extractLatestAssistantText(messages);
191413
192037
  if (!text)
191414
192038
  throw new Error("map-memories returned no output");
192039
+ parseMapMemoriesManifest(text);
191415
192040
  return text;
191416
192041
  }
191417
192042
  });
@@ -191437,7 +192062,8 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
191437
192062
  }
191438
192063
  async function applyBatchMappings(args, batch, manifestText) {
191439
192064
  const batchIds = new Set(batch.map((m) => m.id));
191440
- const parsed = parseMapMemoriesManifest(manifestText).filter((p) => batchIds.has(p.id));
192065
+ const parsed = parseMapMemoriesManifest(manifestText);
192066
+ assertManifestCoversExactly(parsed.map((entry) => entry.id), batchIds, "mappings");
191441
192067
  if (parsed.length === 0)
191442
192068
  return { mapped: 0, independent: 0 };
191443
192069
  const planned = [];
@@ -191459,12 +192085,7 @@ async function applyBatchMappings(args, batch, manifestText) {
191459
192085
  const now = Date.now();
191460
192086
  let mapped = 0;
191461
192087
  let independent = 0;
191462
- let leaseLost = false;
191463
- args.db.transaction(() => {
191464
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
191465
- leaseLost = true;
191466
- return;
191467
- }
192088
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
191468
192089
  for (const item of planned) {
191469
192090
  recordMemoryMapping(args.db, item.id, item.files, now);
191470
192091
  if (item.independent)
@@ -191472,9 +192093,7 @@ async function applyBatchMappings(args, batch, manifestText) {
191472
192093
  else
191473
192094
  mapped += 1;
191474
192095
  }
191475
- })();
191476
- if (leaseLost)
191477
- throw new Error("Dream lease lost during map-memories commit");
192096
+ });
191478
192097
  return { mapped, independent };
191479
192098
  }
191480
192099
  function recordInvocation2(args, startedAt, params) {
@@ -191705,11 +192324,7 @@ async function promotePrimers(args) {
191705
192324
  activePrimers: primers,
191706
192325
  threshold: PRIMER_CLUSTER_THRESHOLD
191707
192326
  });
191708
- args.db.transaction(() => {
191709
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
191710
- leaseLost = true;
191711
- return;
191712
- }
192327
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
191713
192328
  for (const cluster of clusters) {
191714
192329
  if (cluster.candidates.length === 0)
191715
192330
  continue;
@@ -191740,7 +192355,7 @@ async function promotePrimers(args) {
191740
192355
  });
191741
192356
  result.promoted += 1;
191742
192357
  }
191743
- })();
192358
+ });
191744
192359
  if (leaseLost)
191745
192360
  throw new Error("Dream lease lost during promote-primers commit");
191746
192361
  log(`[dreamer] primers: candidates=${result.candidates} promoted=${result.promoted} updated=${result.updated}`);
@@ -192018,16 +192633,9 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
192018
192633
  log(`[dreamer] refresh-primers: primer #${primer.id} answer not committed (no investigation tool calls)`);
192019
192634
  return false;
192020
192635
  }
192021
- let leaseLost = false;
192022
- args.db.transaction(() => {
192023
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
192024
- leaseLost = true;
192025
- return;
192026
- }
192636
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
192027
192637
  updatePrimerAnswer(args.db, primer.id, answer);
192028
- })();
192029
- if (leaseLost)
192030
- throw new Error("Dream lease lost during refresh-primers commit");
192638
+ });
192031
192639
  return true;
192032
192640
  } catch (error51) {
192033
192641
  const desc = describeError(error51);
@@ -192296,7 +192904,7 @@ async function partitionVerifyScope(args) {
192296
192904
  continue;
192297
192905
  }
192298
192906
  const files = v?.files ?? [];
192299
- const needs = files.some((file2) => !verificationFileExists(gitRoot, file2) || uncommitted.has(file2) || (changeTimes.get(file2) ?? 0) > verifiedAt);
192907
+ const needs = files.some((file2) => !verificationFileExists(gitRoot, file2) || uncommitted.has(file2) || (changeTimes.get(file2) ?? 0) >= verifiedAt - 1000);
192300
192908
  if (needs)
192301
192909
  inScope.push(toPrompt(m));
192302
192910
  else
@@ -192362,22 +192970,26 @@ function filesOf(s) {
192362
192970
  }
192363
192971
  function parseVerifyManifest(text) {
192364
192972
  const out = { verified: [], updated: [], archived: [] };
192365
- for (const m of text.matchAll(/<verified\b([^>]*)\/?>/g)) {
192973
+ const body = extractCompleteManifestBody(text, "verify");
192974
+ for (const m of body.matchAll(/<verified\b([^>]*)\/?>/g)) {
192366
192975
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
192367
- if (Number.isInteger(id))
192368
- out.verified.push({ id, files: filesOf(m[1]) });
192976
+ if (!Number.isInteger(id))
192977
+ throw new Error("verify manifest entry missing numeric id");
192978
+ out.verified.push({ id, files: filesOf(m[1]) });
192369
192979
  }
192370
- for (const m of text.matchAll(/<update\b([^>]*?)(?:\/>|>([\s\S]*?)<\/update>)/g)) {
192980
+ for (const m of body.matchAll(/<update\b([^>]*?)(?:\/>|>([\s\S]*?)<\/update>)/g)) {
192371
192981
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
192372
- if (Number.isInteger(id)) {
192373
- out.updated.push({ id, files: filesOf(m[1]), content: (m[2] ?? "").trim() });
192374
- }
192982
+ if (!Number.isInteger(id))
192983
+ throw new Error("verify manifest entry missing numeric id");
192984
+ out.updated.push({ id, files: filesOf(m[1]), content: (m[2] ?? "").trim() });
192375
192985
  }
192376
- for (const m of text.matchAll(/<archive\b([^>]*)\/?>/g)) {
192986
+ for (const m of body.matchAll(/<archive\b([^>]*)\/?>/g)) {
192377
192987
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
192378
- if (Number.isInteger(id))
192379
- out.archived.push({ id, reason: attrOf(m[1], "reason") ?? "" });
192988
+ if (!Number.isInteger(id))
192989
+ throw new Error("verify manifest entry missing numeric id");
192990
+ out.archived.push({ id, reason: attrOf(m[1], "reason") ?? "" });
192380
192991
  }
192992
+ assertNoDuplicateManifestIds([...out.verified, ...out.updated, ...out.archived].map((entry) => entry.id), "verify");
192381
192993
  return out;
192382
192994
  }
192383
192995
 
@@ -192470,9 +193082,13 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
192470
193082
  });
192471
193083
  },
192472
193084
  validateOutput: (messages) => {
193085
+ if (hasLengthCappedOutput(messages)) {
193086
+ throw new Error("verify returned length-capped output");
193087
+ }
192473
193088
  const text = extractLatestAssistantText(messages);
192474
193089
  if (!text)
192475
193090
  throw new Error("verify returned no output");
193091
+ parseVerifyManifest(text);
192476
193092
  return text;
192477
193093
  }
192478
193094
  });
@@ -192499,24 +193115,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
192499
193115
  async function applyVerifyManifest(args, batch, manifestText) {
192500
193116
  const batchIds = new Set(batch.map((m) => m.id));
192501
193117
  const parsed = parseVerifyManifest(manifestText);
193118
+ assertManifestCoversExactly([...parsed.verified, ...parsed.updated, ...parsed.archived].map((entry) => entry.id), batchIds, "verify");
192502
193119
  const now = Date.now();
192503
193120
  const writes = [];
192504
- const verdictCounts = new Map;
192505
- for (const verdict of [...parsed.verified, ...parsed.updated, ...parsed.archived]) {
192506
- if (!batchIds.has(verdict.id))
192507
- continue;
192508
- verdictCounts.set(verdict.id, (verdictCounts.get(verdict.id) ?? 0) + 1);
192509
- }
192510
- const conflictingIds = new Set(Array.from(verdictCounts.entries()).filter(([, count]) => count !== 1).map(([id]) => id));
192511
193121
  for (const v of parsed.verified) {
192512
- if (!batchIds.has(v.id) || conflictingIds.has(v.id))
192513
- continue;
192514
193122
  const files = await normalizeFiles(args, v.files);
192515
193123
  writes.push({ kind: "verify", id: v.id, files });
192516
193124
  }
192517
193125
  for (const u of parsed.updated) {
192518
- if (!batchIds.has(u.id) || conflictingIds.has(u.id))
192519
- continue;
192520
193126
  const content = u.content.trim();
192521
193127
  if (!content || content.length > 20000) {
192522
193128
  const files2 = await normalizeFiles(args, u.files);
@@ -192533,8 +193139,6 @@ async function applyVerifyManifest(args, batch, manifestText) {
192533
193139
  });
192534
193140
  }
192535
193141
  for (const a of parsed.archived) {
192536
- if (!batchIds.has(a.id) || conflictingIds.has(a.id))
192537
- continue;
192538
193142
  writes.push({ kind: "archive", id: a.id, reason: a.reason });
192539
193143
  }
192540
193144
  if (writes.length === 0)
@@ -192542,12 +193146,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
192542
193146
  let verified = 0;
192543
193147
  let updated = 0;
192544
193148
  let archived = 0;
192545
- let leaseLost = false;
192546
- args.db.transaction(() => {
192547
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
192548
- leaseLost = true;
192549
- return;
192550
- }
193149
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
192551
193150
  for (const w of writes) {
192552
193151
  const memory = getMemoryById(args.db, w.id);
192553
193152
  if (!isPrimaryMutable(memory))
@@ -192575,9 +193174,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
192575
193174
  archived += 1;
192576
193175
  }
192577
193176
  }
192578
- })();
192579
- if (leaseLost)
192580
- throw new Error("Dream lease lost during verify commit");
193177
+ });
192581
193178
  return { verified, updated, archived };
192582
193179
  }
192583
193180
  async function normalizeFiles(args, rawFiles) {
@@ -193071,11 +193668,7 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
193071
193668
  throw new Error("Dream lease lost during retrospective");
193072
193669
  const sourceSessionId = flagged[0]?.sessionId ?? userMessages[0]?.sessionId ?? "retrospective";
193073
193670
  const learnings = parseRetrospectiveLearnings(deepenRun.validated);
193074
- const applied = db.transaction(() => {
193075
- if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
193076
- leaseLost = true;
193077
- return null;
193078
- }
193671
+ const applied = runLeaseGuardedWrite(db, holderId, leaseKey, () => {
193079
193672
  const result = applyRetrospectiveLearnings({
193080
193673
  db,
193081
193674
  projectIdentity,
@@ -193086,7 +193679,7 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
193086
193679
  });
193087
193680
  recordRetrospectiveWindowProcessed(db, projectIdentity, windowKey);
193088
193681
  return result;
193089
- })();
193682
+ });
193090
193683
  if (leaseLost || !applied)
193091
193684
  throw new Error("Dream lease lost during retrospective commit");
193092
193685
  log(`[dreamer] retrospective: flagged=${flagged.length} learnings=${learnings.length} memory=${applied.memoryWritten} observations=${applied.observationsInserted} dropped=${applied.observationsDropped} rejected=${applied.rejected.length}`);
@@ -194608,7 +195201,7 @@ function createTagger() {
194608
195201
  }
194609
195202
  return map2;
194610
195203
  }
194611
- function isUniqueConstraintError(error51) {
195204
+ function isUniqueConstraintError2(error51) {
194612
195205
  return error51 instanceof Error && "code" in error51 && error51.code === "SQLITE_CONSTRAINT_UNIQUE";
194613
195206
  }
194614
195207
  function syncCounterAtLeast(sessionId, db, value) {
@@ -194646,7 +195239,7 @@ function createTagger() {
194646
195239
  getUpsertCounterStatement(db).run(sessionId, next, getHarness());
194647
195240
  })();
194648
195241
  } catch (error51) {
194649
- if (!isUniqueConstraintError(error51)) {
195242
+ if (!isUniqueConstraintError2(error51)) {
194650
195243
  throw error51;
194651
195244
  }
194652
195245
  const racedRow = dbExistingLookup();
@@ -195813,8 +196406,9 @@ function scheduleReconciliation(db, sessionId, readMessages) {
195813
196406
  reconciliationScheduledSessions.add(sessionId);
195814
196407
  defer(() => {
195815
196408
  reconcileSessionIndex(db, sessionId, readMessages).catch((error51) => {
195816
- reconciliationScheduledSessions.delete(sessionId);
195817
196409
  logIndexingError(sessionId, "reconciliation", error51);
196410
+ }).finally(() => {
196411
+ reconciliationScheduledSessions.delete(sessionId);
195818
196412
  });
195819
196413
  });
195820
196414
  }
@@ -195826,13 +196420,17 @@ function scheduleIncrementalIndex(db, sessionId, messageId, readSingleMessage) {
195826
196420
  const timer = setTimeout(() => {
195827
196421
  incrementalTimers.delete(key);
195828
196422
  pendingIncrementalKeys.add(key);
196423
+ let attemptedOrdinal = null;
195829
196424
  runWithSessionLock(sessionId, () => {
195830
196425
  const message = readSingleMessage(sessionId, messageId);
195831
196426
  if (!message) {
195832
196427
  return;
195833
196428
  }
196429
+ attemptedOrdinal = message.ordinal;
195834
196430
  indexSingleMessage(db, sessionId, message);
195835
196431
  }).catch((error51) => {
196432
+ markMessageIndexDirty(db, sessionId, attemptedOrdinal ?? getLastIndexedOrdinal(db, sessionId) + 1);
196433
+ reconciledSessions.delete(sessionId);
195836
196434
  logIndexingError(sessionId, `incremental index for ${messageId}`, error51);
195837
196435
  }).finally(() => {
195838
196436
  pendingIncrementalKeys.delete(key);
@@ -196026,6 +196624,7 @@ init_models_dev_cache();
196026
196624
  // src/hooks/magic-context/channel2-delivery.ts
196027
196625
  init_storage_meta_persisted();
196028
196626
  init_logger();
196627
+ import { randomUUID as randomUUID2 } from "node:crypto";
196029
196628
 
196030
196629
  // src/hooks/magic-context/ctx-reduce-nudge.ts
196031
196630
  init_tag_content_primitives();
@@ -196206,19 +196805,6 @@ ${body}${hintText}
196206
196805
  }
196207
196806
 
196208
196807
  // src/hooks/magic-context/channel2-delivery.ts
196209
- function sealDeliveredAfterUnconfirmedSend(db, sessionId) {
196210
- try {
196211
- if (getChannel2NudgeState(db, sessionId) === "delivered") {
196212
- return "already-delivered";
196213
- }
196214
- } catch {}
196215
- try {
196216
- setChannel2NudgeState(db, sessionId, "delivered");
196217
- return "sealed";
196218
- } catch {
196219
- return "stuck-claimed";
196220
- }
196221
- }
196222
196808
  async function maybeDeliverChannel2(sessionId, deps) {
196223
196809
  let state;
196224
196810
  try {
@@ -196244,7 +196830,8 @@ async function maybeDeliverChannel2(sessionId, deps) {
196244
196830
  const client = deps.client;
196245
196831
  if (!client)
196246
196832
  return false;
196247
- if (!casChannel2NudgeState(deps.db, sessionId, "pending", "claimed")) {
196833
+ const claimToken = randomUUID2();
196834
+ if (!claimChannel2NudgeState(deps.db, sessionId, claimToken)) {
196248
196835
  return false;
196249
196836
  }
196250
196837
  try {
@@ -196268,40 +196855,36 @@ async function maybeDeliverChannel2(sessionId, deps) {
196268
196855
  if (typeof session?.promptAsync !== "function") {
196269
196856
  throw new Error("client has no session.promptAsync");
196270
196857
  }
196858
+ const claim = getChannel2NudgeClaim(deps.db, sessionId);
196859
+ if (claim.state !== "claimed" || claim.claimToken !== claimToken) {
196860
+ sessionLog(sessionId, `channel2 ceiling nudge delivery skipped: claim no longer owned before send (state=${claim.state || "empty"})`);
196861
+ return false;
196862
+ }
196271
196863
  await session.promptAsync({ path: { id: sessionId }, body });
196272
196864
  } catch (error51) {
196273
196865
  try {
196274
- casChannel2NudgeState(deps.db, sessionId, "claimed", "pending");
196275
- sessionLog(sessionId, "channel2 ceiling nudge delivery failed (will retry):", error51);
196866
+ const restored = casChannel2NudgeClaim(deps.db, sessionId, "pending", claimToken);
196867
+ if (restored) {
196868
+ sessionLog(sessionId, "channel2 ceiling nudge delivery failed (will retry):", error51);
196869
+ } else {
196870
+ sessionLog(sessionId, "channel2 ceiling nudge delivery failed after its claim was no longer owned; lease state left unchanged:", error51);
196871
+ }
196276
196872
  } catch (revertError) {
196277
196873
  sessionLog(sessionId, "channel2 ceiling nudge delivery failed; pending restore was busy so the stale claim will heal later:", { deliveryError: error51, revertError });
196278
196874
  }
196279
196875
  return false;
196280
196876
  }
196281
196877
  try {
196282
- const confirmed = casChannel2NudgeState(deps.db, sessionId, "claimed", "delivered");
196878
+ const confirmed = casChannel2NudgeClaim(deps.db, sessionId, "delivered", claimToken);
196283
196879
  if (confirmed) {
196284
196880
  sessionLog(sessionId, "channel2 ceiling nudge delivered");
196285
196881
  return true;
196286
196882
  }
196287
- const outcome = sealDeliveredAfterUnconfirmedSend(deps.db, sessionId);
196288
- if (outcome === "already-delivered") {
196289
- sessionLog(sessionId, "channel2 ceiling nudge duplicate window: our send returned after a sibling reclaimed the stale lease and already delivered");
196290
- } else if (outcome === "sealed") {
196291
- sessionLog(sessionId, "channel2 ceiling nudge sent but claim confirmation was lost; sealed delivered without an authoritative confirm");
196292
- } else {
196293
- sessionLog(sessionId, "channel2 ceiling nudge sent but claim confirmation was lost; lease stayed claimed and will heal later");
196294
- }
196883
+ const claim = getChannel2NudgeClaim(deps.db, sessionId);
196884
+ sessionLog(sessionId, `channel2 ceiling nudge sent but claim confirmation was not ours (state=${claim.state || "empty"}); leaving existing lease state unchanged`);
196295
196885
  return false;
196296
196886
  } catch (error51) {
196297
- const outcome = sealDeliveredAfterUnconfirmedSend(deps.db, sessionId);
196298
- if (outcome === "already-delivered") {
196299
- sessionLog(sessionId, "channel2 ceiling nudge duplicate window: our send returned after a sibling reclaimed the stale lease and already delivered");
196300
- } else if (outcome === "sealed") {
196301
- sessionLog(sessionId, "channel2 ceiling nudge sent but confirm failed:", error51);
196302
- } else {
196303
- sessionLog(sessionId, "channel2 ceiling nudge sent but confirm failed; lease stayed claimed and will heal later:", error51);
196304
- }
196887
+ sessionLog(sessionId, "channel2 ceiling nudge sent but token-confirm failed; lease state left unchanged:", error51);
196305
196888
  return false;
196306
196889
  }
196307
196890
  }
@@ -197731,6 +198314,8 @@ function injectToolPartIntoLatestAssistant(messages, part) {
197731
198314
  continue;
197732
198315
  if (typeof message.info.id !== "string")
197733
198316
  continue;
198317
+ if (!isReplayableAssistantAnchor(message))
198318
+ continue;
197734
198319
  if (hasToolPartWithCallId(message, part.callID)) {
197735
198320
  return message.info.id;
197736
198321
  }
@@ -197745,6 +198330,8 @@ function injectToolPartIntoAssistantById(messages, messageId, part) {
197745
198330
  continue;
197746
198331
  if (message.info.role !== "assistant")
197747
198332
  continue;
198333
+ if (!isReplayableAssistantAnchor(message))
198334
+ return false;
197748
198335
  if (hasToolPartWithCallId(message, part.callID))
197749
198336
  return true;
197750
198337
  message.parts.push(part);
@@ -197764,6 +198351,9 @@ function hasToolPartWithCallId(message, callId) {
197764
198351
  }
197765
198352
  return false;
197766
198353
  }
198354
+ function isReplayableAssistantAnchor(message) {
198355
+ return message.info.error === undefined || message.info.error === null;
198356
+ }
197767
198357
  function appendReminderToUserMessage(message, reminder) {
197768
198358
  for (const part of message.parts) {
197769
198359
  if (!isTextPart(part)) {
@@ -198620,24 +199210,46 @@ function getMessageSearchStatementWithCutoff(db) {
198620
199210
  return stmt;
198621
199211
  }
198622
199212
  var ftsRowCountStatements = new WeakMap;
199213
+ var ftsRowCountStatementsWithCutoff = new WeakMap;
198623
199214
  var ftsMatchCountStatements = new WeakMap;
198624
- function getSessionFtsRowCount(db, sessionId) {
198625
- let stmt = ftsRowCountStatements.get(db);
198626
- if (!stmt) {
198627
- stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
198628
- ftsRowCountStatements.set(db, stmt);
198629
- }
198630
- const row = stmt.get(sessionId);
199215
+ var ftsMatchCountStatementsWithCutoff = new WeakMap;
199216
+ function getSessionFtsRowCount(db, sessionId, cutoff) {
199217
+ const stmt = cutoff === null ? (() => {
199218
+ let cached2 = ftsRowCountStatements.get(db);
199219
+ if (!cached2) {
199220
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
199221
+ ftsRowCountStatements.set(db, cached2);
199222
+ }
199223
+ return cached2;
199224
+ })() : (() => {
199225
+ let cached2 = ftsRowCountStatementsWithCutoff.get(db);
199226
+ if (!cached2) {
199227
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND CAST(message_ordinal AS INTEGER) <= ?");
199228
+ ftsRowCountStatementsWithCutoff.set(db, cached2);
199229
+ }
199230
+ return cached2;
199231
+ })();
199232
+ const row = cutoff === null ? stmt.get(sessionId) : stmt.get(sessionId, cutoff);
198631
199233
  return typeof row?.n === "number" ? row.n : 0;
198632
199234
  }
198633
- function countSessionFtsMatches(db, sessionId, ftsQuery) {
198634
- let stmt = ftsMatchCountStatements.get(db);
198635
- if (!stmt) {
198636
- stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
198637
- ftsMatchCountStatements.set(db, stmt);
198638
- }
199235
+ function countSessionFtsMatches(db, sessionId, ftsQuery, cutoff) {
199236
+ const stmt = cutoff === null ? (() => {
199237
+ let cached2 = ftsMatchCountStatements.get(db);
199238
+ if (!cached2) {
199239
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
199240
+ ftsMatchCountStatements.set(db, cached2);
199241
+ }
199242
+ return cached2;
199243
+ })() : (() => {
199244
+ let cached2 = ftsMatchCountStatementsWithCutoff.get(db);
199245
+ if (!cached2) {
199246
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ? AND CAST(message_ordinal AS INTEGER) <= ?");
199247
+ ftsMatchCountStatementsWithCutoff.set(db, cached2);
199248
+ }
199249
+ return cached2;
199250
+ })();
198639
199251
  try {
198640
- const row = stmt.get(sessionId, ftsQuery);
199252
+ const row = cutoff === null ? stmt.get(sessionId, ftsQuery) : stmt.get(sessionId, ftsQuery, cutoff);
198641
199253
  return typeof row?.n === "number" ? row.n : 0;
198642
199254
  } catch {
198643
199255
  return 0;
@@ -198887,7 +199499,7 @@ function searchMessages(args) {
198887
199499
  role: row.role
198888
199500
  }));
198889
199501
  }
198890
- const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
199502
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId, cutoff);
198891
199503
  const queryLists = [];
198892
199504
  if (baseQuery.length > 0) {
198893
199505
  queryLists.push({
@@ -198900,7 +199512,7 @@ function searchMessages(args) {
198900
199512
  const probeQuery = sanitizeFtsQuery(probe);
198901
199513
  if (probeQuery.length === 0)
198902
199514
  continue;
198903
- const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
199515
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery, cutoff);
198904
199516
  const weight = probeDiscriminationWeight(df, corpusSize);
198905
199517
  probeWeights.set(probe, weight);
198906
199518
  queryLists.push({
@@ -200194,8 +200806,31 @@ function readFilePath(target) {
200194
200806
 
200195
200807
  // src/hooks/magic-context/todo-view.ts
200196
200808
  import { createHash as createHash12 } from "node:crypto";
200197
- var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
200198
- var TITLE_DONE_STATUSES = new Set(["completed"]);
200809
+ var TODO_STATUS_PENDING = "pending";
200810
+ var TODO_STATUS_IN_PROGRESS = "in_progress";
200811
+ var TODO_STATUS_COMPLETED = "completed";
200812
+ var TODO_STATUS_CANCELLED = "cancelled";
200813
+ var TODO_PRIORITY_HIGH = "high";
200814
+ var TODO_PRIORITY_MEDIUM = "medium";
200815
+ var TODO_PRIORITY_LOW = "low";
200816
+ var TODO_STATUSES = [
200817
+ TODO_STATUS_PENDING,
200818
+ TODO_STATUS_IN_PROGRESS,
200819
+ TODO_STATUS_COMPLETED,
200820
+ TODO_STATUS_CANCELLED
200821
+ ];
200822
+ var TODO_PRIORITIES = [
200823
+ TODO_PRIORITY_HIGH,
200824
+ TODO_PRIORITY_MEDIUM,
200825
+ TODO_PRIORITY_LOW
200826
+ ];
200827
+ var TODO_STATUS_SET = new Set(TODO_STATUSES);
200828
+ var TODO_PRIORITY_SET = new Set(TODO_PRIORITIES);
200829
+ var TERMINAL_STATUSES = new Set([
200830
+ TODO_STATUS_COMPLETED,
200831
+ TODO_STATUS_CANCELLED
200832
+ ]);
200833
+ var TITLE_DONE_STATUSES = new Set([TODO_STATUS_COMPLETED]);
200199
200834
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
200200
200835
  function normalizeTodoStateJson(todos) {
200201
200836
  if (!Array.isArray(todos))
@@ -200207,7 +200842,7 @@ function normalizeTodoStateJson(todos) {
200207
200842
  normalized.push({
200208
200843
  content: todo.content,
200209
200844
  status: todo.status,
200210
- priority: todo.priority ?? "medium"
200845
+ priority: todo.priority ?? TODO_PRIORITY_MEDIUM
200211
200846
  });
200212
200847
  }
200213
200848
  return JSON.stringify(normalized);
@@ -200251,11 +200886,11 @@ function parseTodoState(stateJson) {
200251
200886
  const result = [];
200252
200887
  for (const item of parsed) {
200253
200888
  if (!isTodoItem(item))
200254
- continue;
200889
+ return null;
200255
200890
  result.push({
200256
200891
  content: item.content,
200257
200892
  status: item.status,
200258
- priority: item.priority ?? "medium"
200893
+ priority: item.priority ?? TODO_PRIORITY_MEDIUM
200259
200894
  });
200260
200895
  }
200261
200896
  return result;
@@ -200263,11 +200898,17 @@ function parseTodoState(stateJson) {
200263
200898
  return null;
200264
200899
  }
200265
200900
  }
200901
+ function isTodoStatus(value) {
200902
+ return typeof value === "string" && TODO_STATUS_SET.has(value);
200903
+ }
200904
+ function isTodoPriority(value) {
200905
+ return typeof value === "string" && TODO_PRIORITY_SET.has(value);
200906
+ }
200266
200907
  function isTodoItem(value) {
200267
200908
  if (value === null || typeof value !== "object")
200268
200909
  return false;
200269
200910
  const todo = value;
200270
- return typeof todo.content === "string" && typeof todo.status === "string" && (todo.priority === undefined || typeof todo.priority === "string");
200911
+ return typeof todo.content === "string" && isTodoStatus(todo.status) && (todo.priority === undefined || isTodoPriority(todo.priority));
200271
200912
  }
200272
200913
 
200273
200914
  // src/hooks/magic-context/tool-reclaim.ts
@@ -200457,7 +201098,7 @@ async function runPostTransformPhase(args) {
200457
201098
  if (args.schedulerDecision === "execute" && !materializationRequested) {
200458
201099
  updateSessionMeta(args.db, args.sessionId, { lastResponseTime: Date.now() });
200459
201100
  }
200460
- const toolReclaimExecutePass = args.schedulerDecision === "execute";
201101
+ const toolReclaimExecutePass = args.schedulerDecision === "execute" || m0HardFoldThisPass;
200461
201102
  const alreadyMutatingThisPass = pendingOpsDidMutate || heuristicOrReasoningDidMutate;
200462
201103
  let autoReclaimTargetCount = 0;
200463
201104
  let autoReclaimDidMutate = false;
@@ -200549,12 +201190,12 @@ async function runPostTransformPhase(args) {
200549
201190
  sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
200550
201191
  }
200551
201192
  }
200552
- if (canUseEmptySentinels && args.watermark > 0) {
201193
+ if (canUseEmptySentinels) {
200553
201194
  try {
200554
201195
  const tImg = performance.now();
200555
201196
  const frozenImageIds = getProcessedImageStrippedIds(args.db, args.sessionId);
200556
201197
  const imageResult = stripProcessedImages(args.messages, frozenImageIds, {
200557
- detect: isCacheBustingPass,
201198
+ detect: isCacheBustingPass && args.watermark > 0,
200558
201199
  watermark: args.watermark,
200559
201200
  messageTagNumbers: args.messageTagNumbers
200560
201201
  });
@@ -202120,12 +202761,25 @@ await __promiseAll([
202120
202761
  ]);
202121
202762
  import * as crypto3 from "node:crypto";
202122
202763
  var WAIT_FOR_LEASE_MS = 1000;
202764
+ var MAX_WRAPUP_LEASE_WAIT_MS = 10 * 60 * 1000;
202123
202765
  function sleep2(ms) {
202124
202766
  return new Promise((resolve4) => setTimeout(resolve4, ms));
202125
202767
  }
202126
202768
  function plural(value, word) {
202127
202769
  return `${value} ${word}${value === 1 ? "" : "s"}`;
202128
202770
  }
202771
+ function resolveWrapupLeaseWaitTimeout(ctx) {
202772
+ const configured = ctx.wrapupLeaseWaitTimeoutMs ?? MAX_WRAPUP_LEASE_WAIT_MS;
202773
+ return Number.isFinite(configured) && configured >= 0 ? configured : MAX_WRAPUP_LEASE_WAIT_MS;
202774
+ }
202775
+ async function waitForActiveRunWithin(promise2, timeoutMs) {
202776
+ if (timeoutMs <= 0)
202777
+ return "timeout";
202778
+ return Promise.race([
202779
+ promise2.then(() => "settled", () => "settled"),
202780
+ sleep2(timeoutMs).then(() => "timeout")
202781
+ ]);
202782
+ }
202129
202783
  function formatAlreadyRunningMessage(state) {
202130
202784
  if (!state)
202131
202785
  return "/ctx-wrapup is already running for this session.";
@@ -202168,44 +202822,55 @@ function emitWrapupProgress(ctx, sessionId, progress) {
202168
202822
  note: progress.note
202169
202823
  });
202170
202824
  }
202171
- async function waitForExistingIncrementalRun(sessionId) {
202825
+ async function waitForExistingIncrementalRun(sessionId, maxWaitMs) {
202172
202826
  const active = getActiveCompartmentRun(sessionId);
202173
202827
  if (!active)
202174
202828
  return "ok";
202175
202829
  if (active.kind === "recomp" || active.kind === "wrapup")
202176
202830
  return "busy";
202177
- try {
202178
- await active.promise;
202179
- } catch {}
202831
+ const outcome = await waitForActiveRunWithin(active.promise, maxWaitMs);
202832
+ if (outcome === "timeout")
202833
+ return "timeout";
202180
202834
  return "ok";
202181
202835
  }
202182
202836
  async function acquireCompartmentLeaseForWrapup(ctx, sessionId, renewWrapupMarker) {
202183
202837
  const holderId = crypto3.randomUUID();
202838
+ const waitStartedAt = Date.now();
202839
+ const maxWaitMs = resolveWrapupLeaseWaitTimeout(ctx);
202840
+ const remainingMs = () => Math.max(0, waitStartedAt + maxWaitMs - Date.now());
202184
202841
  for (;; ) {
202842
+ if (remainingMs() <= 0) {
202843
+ sessionLog(sessionId, "wrapup: timed out waiting for the compartment-state lease");
202844
+ return { ok: false, reason: "timeout" };
202845
+ }
202185
202846
  const active = getActiveCompartmentRun(sessionId);
202186
- if (active?.kind === "recomp" || active?.kind === "wrapup")
202187
- return null;
202847
+ if (active?.kind === "recomp" || active?.kind === "wrapup") {
202848
+ return { ok: false, reason: "busy" };
202849
+ }
202188
202850
  if (active) {
202189
202851
  emitWrapupProgress(ctx, sessionId, { note: "Waiting for the active historian run…" });
202190
- try {
202191
- await active.promise;
202192
- } catch {}
202852
+ const outcome = await waitForActiveRunWithin(active.promise, remainingMs());
202853
+ if (outcome === "timeout") {
202854
+ sessionLog(sessionId, "wrapup: timed out waiting for the active historian run");
202855
+ return { ok: false, reason: "timeout" };
202856
+ }
202193
202857
  continue;
202194
202858
  }
202195
202859
  const lease = acquireCompartmentLease(ctx.db, sessionId, holderId);
202196
202860
  if (lease)
202197
- return holderId;
202861
+ return { ok: true, holderId };
202198
202862
  emitWrapupProgress(ctx, sessionId, { note: "Waiting for the compartment-state lease…" });
202199
202863
  if (!renewWrapupMarker({}))
202200
- return null;
202201
- await sleep2(WAIT_FOR_LEASE_MS);
202864
+ return { ok: false, reason: "ownership_lost" };
202865
+ await sleep2(Math.min(WAIT_FOR_LEASE_MS, remainingMs()));
202202
202866
  }
202203
202867
  }
202204
202868
  async function runOneWrapupIteration(args) {
202205
202869
  const { ctx, sessionId, plan, messagesToKeep, anchorRawMessageCount } = args;
202206
- const leaseHolderId = await acquireCompartmentLeaseForWrapup(ctx, sessionId, args.renewWrapupMarker);
202207
- if (!leaseHolderId)
202208
- return false;
202870
+ const acquired = await acquireCompartmentLeaseForWrapup(ctx, sessionId, args.renewWrapupMarker);
202871
+ if (!acquired.ok)
202872
+ return { ran: false, reason: acquired.reason };
202873
+ const leaseHolderId = acquired.holderId;
202209
202874
  const renewal = setInterval(() => {
202210
202875
  try {
202211
202876
  if (!renewCompartmentLease(ctx.db, sessionId, leaseHolderId)) {
@@ -202250,7 +202915,7 @@ async function runOneWrapupIteration(args) {
202250
202915
  registerActiveCompartmentRun(sessionId, runnerPromise, "wrapup");
202251
202916
  try {
202252
202917
  await runnerPromise;
202253
- return true;
202918
+ return { ran: true };
202254
202919
  } finally {
202255
202920
  clearInterval(renewal);
202256
202921
  releaseCompartmentLease(ctx.db, sessionId, leaseHolderId);
@@ -202325,7 +202990,7 @@ ${message2}`;
202325
202990
  }, 60000);
202326
202991
  markerRenewal.unref?.();
202327
202992
  try {
202328
- const activeAtStart = await waitForExistingIncrementalRun(sessionId);
202993
+ const activeAtStart = await waitForExistingIncrementalRun(sessionId, resolveWrapupLeaseWaitTimeout(ctx));
202329
202994
  if (activeAtStart === "busy") {
202330
202995
  const message2 = "Another Magic Context rebuild is already running for this session.";
202331
202996
  setRecompTerminal(ctx.liveSessionState, sessionId, "skipped", message2);
@@ -202333,16 +202998,22 @@ ${message2}`;
202333
202998
 
202334
202999
  ${message2}`;
202335
203000
  }
202336
- try {
202337
- sendIgnoredMessage(ctx.client, sessionId, `Magic Wrapup started — compacting about ${plural(expectedChunks, "chunk")} of history. This can take a few minutes; the result posts here when done.`, ctx.getNotificationParams(sessionId));
202338
- } catch {}
202339
- try {
202340
- pushNotification("action", { action: "wrapup-progress-kick" }, sessionId);
202341
- } catch {}
202342
- if (ownershipLost) {
203001
+ if (activeAtStart === "timeout") {
203002
+ stoppedForFailure = true;
203003
+ stoppedReason = "Timed out waiting for the active historian run.";
203004
+ }
203005
+ if (!stoppedForFailure) {
203006
+ try {
203007
+ sendIgnoredMessage(ctx.client, sessionId, `Magic Wrapup started — compacting about ${plural(expectedChunks, "chunk")} of history. This can take a few minutes; the result posts here when done.`, ctx.getNotificationParams(sessionId));
203008
+ } catch {}
203009
+ try {
203010
+ pushNotification("action", { action: "wrapup-progress-kick" }, sessionId);
203011
+ } catch {}
203012
+ }
203013
+ if (!stoppedForFailure && ownershipLost) {
202343
203014
  stoppedForFailure = true;
202344
203015
  stoppedReason = ownershipLostReason;
202345
- } else {
203016
+ } else if (!stoppedForFailure) {
202346
203017
  emitWrapupProgress(ctx, sessionId, {
202347
203018
  processedMessages: Math.max(0, lastEnd),
202348
203019
  totalMessages: Math.max(0, initialPlan.targetEligibleEndOrdinal - 1),
@@ -202389,7 +203060,7 @@ ${message2}`;
202389
203060
  }
202390
203061
  const beforeEnd = lastEnd;
202391
203062
  const beforeFailures = getHistorianFailureState(ctx.db, sessionId).failureCount;
202392
- const ran = await runOneWrapupIteration({
203063
+ const iteration = await runOneWrapupIteration({
202393
203064
  ctx,
202394
203065
  sessionId,
202395
203066
  plan,
@@ -202397,9 +203068,9 @@ ${message2}`;
202397
203068
  anchorRawMessageCount: initialPlan.anchorRawMessageCount,
202398
203069
  renewWrapupMarker
202399
203070
  });
202400
- if (!ran) {
203071
+ if (!iteration.ran) {
202401
203072
  stoppedForFailure = true;
202402
- stoppedReason = ownershipLost ? ownershipLostReason : "Another Magic Context rebuild started while wrapup was waiting.";
203073
+ stoppedReason = ownershipLost || iteration.reason === "ownership_lost" ? ownershipLostReason : iteration.reason === "timeout" ? "Timed out waiting for another process to release the compartment-state lease." : "Another Magic Context rebuild started while wrapup was waiting.";
202403
203074
  break;
202404
203075
  }
202405
203076
  const afterEnd = getLastCompartmentEndMessage(ctx.db, sessionId);
@@ -204957,13 +205628,18 @@ function queueMemoryEmbedding(args) {
204957
205628
  if (!snapshot?.enabled) {
204958
205629
  return;
204959
205630
  }
205631
+ const normalizedHash = computeNormalizedHash(args.content);
204960
205632
  (async () => {
204961
205633
  const result = await embedTextForProject(args.projectPath, args.content);
204962
205634
  if (!result) {
204963
205635
  sessionLog(args.sessionId, `memory embedding skipped for memory ${args.memoryId}: provider unavailable or embedding generation failed.`);
204964
205636
  return;
204965
205637
  }
204966
- saveEmbedding(args.deps.db, args.memoryId, result.vector, result.modelId);
205638
+ const saved = saveEmbeddingIfHashMatches(args.deps.db, args.memoryId, result.vector, result.modelId, normalizedHash);
205639
+ if (!saved) {
205640
+ sessionLog(args.sessionId, `memory embedding skipped for memory ${args.memoryId}: content changed before the embedding finished.`);
205641
+ return;
205642
+ }
204967
205643
  sessionLog(args.sessionId, `proactively embedded memory ${args.memoryId}.`);
204968
205644
  })().catch((error51) => {
204969
205645
  sessionLog(args.sessionId, `memory embedding failed for memory ${args.memoryId}:`, error51);
@@ -205048,8 +205724,9 @@ function createCtxMemoryTool(deps) {
205048
205724
  const isOwn = targetIdentityForStoredPath(memory.projectPath) === projectPath;
205049
205725
  if (isOwn)
205050
205726
  return true;
205051
- return toolShareCategories === null || toolShareCategories.includes(memory.category);
205727
+ return toolShareCategories?.includes(memory.category) ?? false;
205052
205728
  };
205729
+ const memoryOwnedByTool = (memory) => workspaceIdentitySet.identities.length > 1 ? targetIdentityForStoredPath(memory.projectPath) === projectPath : memoryBelongsToProject(memory, projectPath);
205053
205730
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
205054
205731
  if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
205055
205732
  return getDisabledMessage();
@@ -205072,21 +205749,24 @@ function createCtxMemoryTool(deps) {
205072
205749
  updateMemorySeenCount(deps.db, existingMemory.id);
205073
205750
  return `Memory already exists [ID: ${existingMemory.id}] in ${category} (seen count incremented).`;
205074
205751
  }
205075
- const memory = insertMemory(deps.db, {
205752
+ const insertResult = insertMemoryIdempotent(deps.db, {
205076
205753
  projectPath,
205077
205754
  category,
205078
205755
  content,
205079
205756
  sourceSessionId: toolContext.sessionID,
205080
205757
  sourceType: toolContext.agent === DREAMER_AGENT ? "dreamer" : getSourceType(deps)
205081
205758
  });
205759
+ if (!insertResult.inserted) {
205760
+ return `Memory already exists [ID: ${insertResult.memory.id}] in ${category} (seen count incremented).`;
205761
+ }
205082
205762
  queueMemoryEmbedding({
205083
205763
  deps,
205084
205764
  sessionId: toolContext.sessionID,
205085
205765
  projectPath,
205086
- memoryId: memory.id,
205766
+ memoryId: insertResult.memory.id,
205087
205767
  content
205088
205768
  });
205089
- return `Saved memory [ID: ${memory.id}] in ${category}.`;
205769
+ return `Saved memory [ID: ${insertResult.memory.id}] in ${category}.`;
205090
205770
  }
205091
205771
  if (args.action === "list") {
205092
205772
  const limit = normalizeLimit2(args.limit);
@@ -205106,7 +205786,8 @@ function createCtxMemoryTool(deps) {
205106
205786
  }
205107
205787
  const rawProjectPath = projectPathForMemoryId(deps.db, updateId);
205108
205788
  const memory = getMemoryById(deps.db, updateId);
205109
- if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
205789
+ const updateAllowed = memory ? toolContext.agent === DREAMER_AGENT ? memoryVisibleToTool(memory) : memoryOwnedByTool(memory) : false;
205790
+ if (!memory || !rawProjectPath || !updateAllowed) {
205110
205791
  return `Error: Memory with ID ${updateId} was not found.`;
205111
205792
  }
205112
205793
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -205154,7 +205835,7 @@ function createCtxMemoryTool(deps) {
205154
205835
  return "Error: One or more source memories were not found.";
205155
205836
  }
205156
205837
  if (toolContext.agent !== DREAMER_AGENT) {
205157
- const foreign = sourceMemories.find((memory) => !memoryVisibleToTool(memory));
205838
+ const foreign = sourceMemories.find((memory) => !memoryOwnedByTool(memory));
205158
205839
  if (foreign) {
205159
205840
  return `Error: Memory with ID ${foreign.id} was not found.`;
205160
205841
  }
@@ -205177,11 +205858,6 @@ function createCtxMemoryTool(deps) {
205177
205858
  return "Error: A valid category is required when action is 'merge'.";
205178
205859
  }
205179
205860
  const normalizedHash = computeNormalizedHash(content);
205180
- const duplicate = getMemoryByHash(deps.db, projectPath, category, normalizedHash);
205181
- const canonicalExisting = duplicate && ids.includes(duplicate.id) ? duplicate : null;
205182
- if (duplicate && !canonicalExisting) {
205183
- return `Error: Memory content already exists as ID ${duplicate.id}; update or archive existing duplicates instead.`;
205184
- }
205185
205861
  const mergedFrom = JSON.stringify(Array.from(new Set(sourceMemories.flatMap((memory) => {
205186
205862
  let parsed;
205187
205863
  try {
@@ -205197,14 +205873,21 @@ function createCtxMemoryTool(deps) {
205197
205873
  const mergedSeenCount = sourceMemories.reduce((sum, memory) => sum + memory.seenCount, 0);
205198
205874
  const mergedRetrievalCount = sourceMemories.reduce((sum, memory) => sum + memory.retrievalCount, 0);
205199
205875
  const mergedStatus = sourceMemories.some((memory) => memory.status === "permanent") ? "permanent" : "active";
205200
- const canonicalMemory = deps.db.transaction(() => {
205201
- const nextCanonical = canonicalExisting ?? insertMemory(deps.db, {
205876
+ let mergeConflict = null;
205877
+ const canonicalMemory = runImmediateTransaction(deps.db, () => {
205878
+ const lockedDuplicate = getMemoryByHash(deps.db, projectPath, category, normalizedHash);
205879
+ const canonicalExisting = lockedDuplicate && ids.includes(lockedDuplicate.id) ? lockedDuplicate : null;
205880
+ if (lockedDuplicate && !canonicalExisting) {
205881
+ mergeConflict = `Error: Memory content already exists as ID ${lockedDuplicate.id}; update or archive existing duplicates instead.`;
205882
+ return null;
205883
+ }
205884
+ const nextCanonical = canonicalExisting?.id != null ? canonicalExisting : insertMemoryIdempotent(deps.db, {
205202
205885
  projectPath,
205203
205886
  category,
205204
205887
  content,
205205
205888
  sourceSessionId: toolContext.sessionID,
205206
205889
  sourceType: toolContext.agent === DREAMER_AGENT ? "dreamer" : getSourceType(deps)
205207
- });
205890
+ }).memory;
205208
205891
  const canonicalContentChanged = nextCanonical.content !== content || nextCanonical.normalizedHash !== normalizedHash;
205209
205892
  if (canonicalContentChanged) {
205210
205893
  updateMemoryContentInCurrentTransaction(deps.db, nextCanonical, content, normalizedHash);
@@ -205232,7 +205915,10 @@ function createCtxMemoryTool(deps) {
205232
205915
  });
205233
205916
  }
205234
205917
  return nextCanonical;
205235
- })();
205918
+ });
205919
+ if (mergeConflict || !canonicalMemory) {
205920
+ return mergeConflict ?? "Error: Failed to merge memories.";
205921
+ }
205236
205922
  queueMemoryEmbedding({
205237
205923
  deps,
205238
205924
  sessionId: toolContext.sessionID,
@@ -205253,7 +205939,8 @@ function createCtxMemoryTool(deps) {
205253
205939
  for (const memoryId of archiveIds) {
205254
205940
  const rawProjectPath = projectPathForMemoryId(deps.db, memoryId);
205255
205941
  const memory = getMemoryById(deps.db, memoryId);
205256
- if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
205942
+ const archiveAllowed = memory ? toolContext.agent === DREAMER_AGENT ? memoryVisibleToTool(memory) : memoryOwnedByTool(memory) : false;
205943
+ if (!memory || !rawProjectPath || !archiveAllowed) {
205257
205944
  return `Error: Memory with ID ${memoryId} was not found.`;
205258
205945
  }
205259
205946
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -205708,7 +206395,7 @@ function normalizeLimit3(limit) {
205708
206395
  return Math.max(1, Math.floor(limit));
205709
206396
  }
205710
206397
  function normalizeSources(sources) {
205711
- if (!sources || sources.length === 0)
206398
+ if (sources === undefined)
205712
206399
  return;
205713
206400
  const result = [];
205714
206401
  const seen = new Set;
@@ -205721,7 +206408,7 @@ function normalizeSources(sources) {
205721
206408
  }
205722
206409
  }
205723
206410
  }
205724
- return result.length > 0 ? result : undefined;
206411
+ return result;
205725
206412
  }
205726
206413
  function formatAge2(committedAtMs) {
205727
206414
  const ageMs = Date.now() - committedAtMs;
@@ -205813,7 +206500,7 @@ function createCtxSearchTool(deps) {
205813
206500
  args: {
205814
206501
  query: tool5.schema.string().describe("Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."),
205815
206502
  limit: tool5.schema.number().optional().describe("Maximum results to return (default: 10)"),
205816
- sources: tool5.schema.array(tool5.schema.enum(["memory", "message", "git_commit", "primer", "note"])).optional().describe('Optional. Restrict to specific sources. Examples: ["primer"] for standing project explanations, ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["note"] for parked decisions or follow-ups, ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.')
206503
+ sources: tool5.schema.array(tool5.schema.enum(["memory", "message", "git_commit", "primer", "note"])).optional().describe('Optional. Restrict to specific sources. Examples: ["primer"] for standing project explanations, ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["note"] for parked decisions or follow-ups, ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources; pass [] to search no sources.')
205817
206504
  },
205818
206505
  async execute(args, toolContext) {
205819
206506
  const query = args.query?.trim();
@@ -205988,8 +206675,9 @@ function projectHash(directory) {
205988
206675
  function rpcPortDir(storageDir, directory) {
205989
206676
  return join20(storageDir, "rpc", projectHash(directory));
205990
206677
  }
205991
- function rpcPortFilePath(storageDir, directory, pid = process.pid) {
205992
- return join20(rpcPortDir(storageDir, directory), `port-${pid}.json`);
206678
+ function rpcPortFilePath(storageDir, directory, pid = process.pid, instanceId) {
206679
+ const suffix = instanceId ? `-${instanceId}` : "";
206680
+ return join20(rpcPortDir(storageDir, directory), `port-${pid}${suffix}.json`);
205993
206681
  }
205994
206682
  function isPidAlive(pid) {
205995
206683
  if (!Number.isInteger(pid) || pid <= 0)
@@ -206017,7 +206705,8 @@ function parseRpcPortFile(content, fallbackPid = 0) {
206017
206705
  port: port2,
206018
206706
  pid,
206019
206707
  started_at: Number.isFinite(startedAt) ? startedAt : 0,
206020
- token: typeof parsed.token === "string" ? parsed.token : undefined
206708
+ token: typeof parsed.token === "string" ? parsed.token : undefined,
206709
+ instance_id: typeof parsed.instance_id === "string" ? parsed.instance_id : undefined
206021
206710
  };
206022
206711
  } catch {
206023
206712
  return null;
@@ -206043,6 +206732,13 @@ function tokensMatch(presented, expected) {
206043
206732
  return false;
206044
206733
  return timingSafeEqual(a, b);
206045
206734
  }
206735
+ function bearerToken(req) {
206736
+ const auth = req.headers.get("authorization");
206737
+ return typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
206738
+ }
206739
+ function websocketToken(req, url2) {
206740
+ return bearerToken(req) || url2.searchParams.get("token") || "";
206741
+ }
206046
206742
 
206047
206743
  class MagicContextRpcServer {
206048
206744
  server = null;
@@ -206051,16 +206747,18 @@ class MagicContextRpcServer {
206051
206747
  portFilePath;
206052
206748
  portDir;
206053
206749
  startedAt = Date.now();
206750
+ instanceId = randomBytes(8).toString("hex");
206054
206751
  sockets = new Set;
206055
206752
  token = randomBytes(32).toString("hex");
206056
206753
  constructor(storageDir, directory) {
206057
- this.portFilePath = rpcPortFilePath(storageDir, directory);
206754
+ this.portFilePath = rpcPortFilePath(storageDir, directory, process.pid, this.instanceId);
206058
206755
  this.portDir = rpcPortDir(storageDir, directory);
206059
206756
  }
206060
206757
  handle(method, handler) {
206061
206758
  this.handlers.set(method, handler);
206062
206759
  }
206063
206760
  async start() {
206761
+ this.startedAt = Date.now();
206064
206762
  const self2 = this;
206065
206763
  const server = Bun.serve({
206066
206764
  port: 0,
@@ -206103,7 +206801,8 @@ class MagicContextRpcServer {
206103
206801
  port: this.port,
206104
206802
  pid: process.pid,
206105
206803
  started_at: this.startedAt,
206106
- token: this.token
206804
+ token: this.token,
206805
+ instance_id: this.instanceId
206107
206806
  }), { encoding: "utf-8", mode: 384 });
206108
206807
  renameSync5(tmpPath, this.portFilePath);
206109
206808
  try {
@@ -206149,6 +206848,9 @@ class MagicContextRpcServer {
206149
206848
  async handleFetch(req, srv) {
206150
206849
  const url2 = new URL(req.url);
206151
206850
  if (url2.pathname === "/ws") {
206851
+ if (!tokensMatch(websocketToken(req, url2), this.token)) {
206852
+ return new Response("Unauthorized", { status: 401 });
206853
+ }
206152
206854
  const ok = srv.upgrade(req, { data: { authed: false } });
206153
206855
  if (ok)
206154
206856
  return;
@@ -206160,9 +206862,7 @@ class MagicContextRpcServer {
206160
206862
  if (req.method !== "POST" || !url2.pathname.startsWith("/rpc/")) {
206161
206863
  return new Response("Not Found", { status: 404 });
206162
206864
  }
206163
- const auth = req.headers.get("authorization");
206164
- const presented = typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
206165
- if (!tokensMatch(presented, this.token)) {
206865
+ if (!tokensMatch(bearerToken(req), this.token)) {
206166
206866
  return json2({ error: "Unauthorized" }, 401);
206167
206867
  }
206168
206868
  const method = url2.pathname.slice(5);
@@ -206197,6 +206897,8 @@ class MagicContextRpcServer {
206197
206897
  } catch {
206198
206898
  return;
206199
206899
  }
206900
+ if (msg.type !== "hello" && !ws.data.authed)
206901
+ return;
206200
206902
  if (msg.type === "hello") {
206201
206903
  if (!tokensMatch(typeof msg.token === "string" ? msg.token : "", this.token)) {
206202
206904
  ws.send(JSON.stringify({ type: "error", error: "unauthorized" }));
@@ -206209,6 +206911,8 @@ class MagicContextRpcServer {
206209
206911
  }
206210
206912
  ws.data.authed = true;
206211
206913
  ws.data.sessionId = typeof msg.sessionId === "string" && msg.sessionId.length > 0 ? msg.sessionId : undefined;
206914
+ ws.data.unregister?.();
206915
+ ws.data.unregister = undefined;
206212
206916
  const sink = {
206213
206917
  sessionId: ws.data.sessionId,
206214
206918
  send: (notification) => {
@@ -206218,7 +206922,10 @@ class MagicContextRpcServer {
206218
206922
  ws.data.unregister = registerNotificationSink(sink);
206219
206923
  this.sockets.add(ws);
206220
206924
  const lastReceivedId = Number(msg.lastReceivedId ?? 0);
206221
- const backlog = drainNotifications(Number.isFinite(lastReceivedId) ? lastReceivedId : 0, ws.data.sessionId);
206925
+ const sessionCursor = Number.isFinite(lastReceivedId) ? lastReceivedId : 0;
206926
+ const hasGlobalCursor = typeof msg.globalLastReceivedId === "number";
206927
+ const globalLastReceivedId = hasGlobalCursor ? Number.isFinite(msg.globalLastReceivedId) ? msg.globalLastReceivedId : 0 : 0;
206928
+ const backlog = ws.data.sessionId === undefined && hasGlobalCursor ? drainNotifications(globalLastReceivedId, undefined, { globalOnly: true }) : drainNotifications(sessionCursor, ws.data.sessionId, hasGlobalCursor ? { globalLastReceivedId } : undefined);
206222
206929
  for (const notification of backlog) {
206223
206930
  ws.send(JSON.stringify({ type: "notification", notification }));
206224
206931
  }
@@ -206228,7 +206935,13 @@ class MagicContextRpcServer {
206228
206935
  if (msg.type === "ack") {
206229
206936
  const lastReceivedId = Number(msg.lastReceivedId ?? 0);
206230
206937
  if (Number.isFinite(lastReceivedId) && lastReceivedId > 0) {
206231
- drainNotifications(lastReceivedId, ws.data.sessionId);
206938
+ if (msg.ackScope === "global") {
206939
+ drainNotifications(lastReceivedId, undefined, { globalOnly: true });
206940
+ } else if (typeof msg.sessionId === "string" && msg.sessionId.length > 0) {
206941
+ drainNotifications(lastReceivedId, msg.sessionId, { sessionOnly: true });
206942
+ } else {
206943
+ drainNotifications(lastReceivedId, ws.data.sessionId);
206944
+ }
206232
206945
  }
206233
206946
  }
206234
206947
  }