@cortexkit/opencode-magic-context 0.31.3 → 0.31.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) 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/features/magic-context/dreamer/classify-prompt.d.ts +2 -2
  5. package/dist/features/magic-context/dreamer/classify-prompt.d.ts.map +1 -1
  6. package/dist/features/magic-context/dreamer/classify.d.ts +8 -0
  7. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  8. package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
  9. package/dist/features/magic-context/dreamer/lease.d.ts +1 -0
  10. package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
  11. package/dist/features/magic-context/dreamer/manifest-parser.d.ts +7 -0
  12. package/dist/features/magic-context/dreamer/manifest-parser.d.ts.map +1 -0
  13. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +2 -3
  14. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
  15. package/dist/features/magic-context/dreamer/map-memories.d.ts +8 -0
  16. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  17. package/dist/features/magic-context/dreamer/promote-primers.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts +5 -0
  19. package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/verify-prompt.d.ts +2 -2
  21. package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  23. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +4 -0
  24. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  25. package/dist/features/magic-context/memory/storage-memory.d.ts +12 -0
  26. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  27. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  28. package/dist/features/magic-context/message-index.d.ts +6 -0
  29. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  30. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  31. package/dist/features/magic-context/search.d.ts.map +1 -1
  32. package/dist/features/magic-context/smart-notes/capabilities.d.ts +1 -0
  33. package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
  34. package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -2
  35. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  36. package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
  37. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +3 -2
  38. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
  39. package/dist/features/magic-context/smart-notes/schedule.d.ts.map +1 -1
  40. package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +17 -6
  41. package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
  42. package/dist/features/magic-context/smart-notes/types.d.ts +6 -1
  43. package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
  44. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  45. package/dist/features/magic-context/storage-meta-persisted.d.ts +8 -0
  46. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  47. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  48. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  49. package/dist/features/magic-context/storage.d.ts +1 -1
  50. package/dist/features/magic-context/storage.d.ts.map +1 -1
  51. package/dist/features/magic-context/workspaces.d.ts +8 -0
  52. package/dist/features/magic-context/workspaces.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/compartment-runner.d.ts +2 -0
  55. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/tag-messages.d.ts +1 -0
  60. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/todo-view.d.ts +27 -2
  62. package/dist/hooks/magic-context/todo-view.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/transform-message-helpers.d.ts +6 -3
  64. package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts +1 -0
  66. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  67. package/dist/index.js +1039 -331
  68. package/dist/shared/assistant-message-extractor.d.ts +1 -0
  69. package/dist/shared/assistant-message-extractor.d.ts.map +1 -1
  70. package/dist/shared/redaction.d.ts.map +1 -1
  71. package/dist/shared/rpc-notifications.d.ts +21 -16
  72. package/dist/shared/rpc-notifications.d.ts.map +1 -1
  73. package/dist/shared/rpc-server.d.ts +1 -0
  74. package/dist/shared/rpc-server.d.ts.map +1 -1
  75. package/dist/shared/rpc-utils.d.ts +3 -1
  76. package/dist/shared/rpc-utils.d.ts.map +1 -1
  77. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  78. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  79. package/dist/tools/ctx-search/types.d.ts +1 -1
  80. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  81. package/dist/tui/data/notification-socket.d.ts +1 -0
  82. package/dist/tui/data/notification-socket.d.ts.map +1 -1
  83. package/dist/tui-compiled/badge-contrast.d.ts +46 -0
  84. package/dist/tui-compiled/badge-contrast.d.ts.map +1 -0
  85. package/dist/tui-compiled/data/context-db.d.ts +45 -0
  86. package/dist/tui-compiled/data/context-db.d.ts.map +1 -0
  87. package/dist/tui-compiled/data/notification-socket.d.ts +40 -0
  88. package/dist/tui-compiled/data/notification-socket.d.ts.map +1 -0
  89. package/package.json +3 -2
  90. package/src/shared/assistant-message-extractor.ts +20 -0
  91. package/src/shared/redaction.ts +1 -0
  92. package/src/shared/rpc-client.test.ts +164 -5
  93. package/src/shared/rpc-notifications.ts +73 -21
  94. package/src/shared/rpc-server.ts +69 -18
  95. package/src/shared/rpc-utils.ts +12 -2
  96. package/src/tui/data/notification-socket.test.ts +131 -0
  97. package/src/tui/data/notification-socket.ts +81 -16
  98. package/src/tui/entry.mjs +23 -3
  99. package/src/tui-compiled/data/notification-socket.ts +81 -16
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",
@@ -16662,6 +16662,22 @@ function extractLatestAssistantText(messages) {
16662
16662
  return getTextParts(latest).map((part) => part.text).join(`
16663
16663
  `) || null;
16664
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
+ }
16665
16681
  var init_assistant_message_extractor = () => {};
16666
16682
 
16667
16683
  // src/shared/keep-subagents.ts
@@ -17330,7 +17346,8 @@ function isMidTurn(_deps, sessionId) {
17330
17346
  }
17331
17347
  function isMidTurnFromOpenCodeDb(db, sessionId) {
17332
17348
  const latestAssistant = db.prepare(`SELECT id,
17333
- json_extract(data, '$.finish') as finish
17349
+ json_extract(data, '$.finish') as finish,
17350
+ time_created as timeCreated
17334
17351
  FROM message
17335
17352
  WHERE session_id = ?
17336
17353
  AND json_extract(data, '$.role') = 'assistant'
@@ -17338,6 +17355,8 @@ function isMidTurnFromOpenCodeDb(db, sessionId) {
17338
17355
  LIMIT 1`).get(sessionId);
17339
17356
  if (typeof latestAssistant?.id !== "string")
17340
17357
  return false;
17358
+ if (hasNewerRealUserMessage(db, sessionId, latestAssistant.timeCreated))
17359
+ return false;
17341
17360
  if (latestAssistant.finish === "tool-calls")
17342
17361
  return true;
17343
17362
  const partRows = db.prepare("SELECT data FROM part WHERE session_id = ? AND message_id = ?").all(sessionId, latestAssistant.id);
@@ -17352,6 +17371,18 @@ function isMidTurnFromOpenCodeDb(db, sessionId) {
17352
17371
  }
17353
17372
  });
17354
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
+ }
17355
17386
  function getMessageTimesFromOpenCodeDb(sessionId, messageIds) {
17356
17387
  const result = new Map;
17357
17388
  if (messageIds.length === 0)
@@ -150820,12 +150851,19 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150820
150851
  let totalTokens = 0;
150821
150852
  let messagesProcessed = 0;
150822
150853
  let lastOrdinal = startOrdinal - 1;
150854
+ let highestScannedOrdinal = startOrdinal - 1;
150823
150855
  let lastMessageId = "";
150824
150856
  let firstMessageId = "";
150825
150857
  let currentBlock = null;
150826
150858
  let pendingNoiseMeta = [];
150827
150859
  let commitClusters = 0;
150828
150860
  let lastFlushedRole = "";
150861
+ function recordFilteredNoise(meta3) {
150862
+ pendingNoiseMeta.push(meta3);
150863
+ if (!currentBlock) {
150864
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, meta3.ordinal);
150865
+ }
150866
+ }
150829
150867
  function flushCurrentBlock() {
150830
150868
  if (!currentBlock)
150831
150869
  return true;
@@ -150841,6 +150879,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150841
150879
  if (!firstMessageId)
150842
150880
  firstMessageId = currentBlock.meta[0]?.messageId ?? "";
150843
150881
  lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
150882
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, lastOrdinal);
150844
150883
  lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
150845
150884
  messagesProcessed += currentBlock.meta.length;
150846
150885
  lines.push(blockText);
@@ -150864,7 +150903,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150864
150903
  if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
150865
150904
  const tcSummaries = extractToolCallSummaries(msg.parts);
150866
150905
  if (tcSummaries.length === 0) {
150867
- pendingNoiseMeta.push(meta3);
150906
+ recordFilteredNoise(meta3);
150868
150907
  continue;
150869
150908
  }
150870
150909
  const tcText = tcSummaries.join(" / ");
@@ -150896,7 +150935,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150896
150935
  const compacted = compactTextForSummary(allParts.join(" / "), msg.role);
150897
150936
  const text = compacted.text;
150898
150937
  if (!text) {
150899
- pendingNoiseMeta.push(meta3);
150938
+ recordFilteredNoise(meta3);
150900
150939
  continue;
150901
150940
  }
150902
150941
  const msgHasNarrative = textParts.length > 0;
@@ -150923,7 +150962,9 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150923
150962
  };
150924
150963
  pendingNoiseMeta = [];
150925
150964
  }
150926
- flushCurrentBlock();
150965
+ if (flushCurrentBlock() && pendingNoiseMeta.length > 0) {
150966
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, pendingNoiseMeta[pendingNoiseMeta.length - 1]?.ordinal ?? highestScannedOrdinal);
150967
+ }
150927
150968
  const toolOnlyRanges = [];
150928
150969
  for (const range of flushedToolOnlyBlocks) {
150929
150970
  const last = toolOnlyRanges[toolOnlyRanges.length - 1];
@@ -150940,7 +150981,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
150940
150981
  endMessageId: lastMessageId,
150941
150982
  messageCount: messagesProcessed,
150942
150983
  tokenEstimate: totalTokens,
150943
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
150984
+ hasMore: Math.max(lastOrdinal, highestScannedOrdinal) < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
150944
150985
  text: lines.join(`
150945
150986
  `),
150946
150987
  lines: lineMeta,
@@ -150966,7 +151007,7 @@ function normalizeIndexText(text) {
150966
151007
  function getLastIndexedStatement(db) {
150967
151008
  let stmt = lastIndexedStatements.get(db);
150968
151009
  if (!stmt) {
150969
- 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 = ?");
150970
151011
  lastIndexedStatements.set(db, stmt);
150971
151012
  }
150972
151013
  return stmt;
@@ -150987,6 +151028,22 @@ function getUpsertIndexStatement(db) {
150987
151028
  }
150988
151029
  return stmt;
150989
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
+ }
150990
151047
  function getDeleteFtsStatement(db) {
150991
151048
  let stmt = deleteFtsStatements.get(db);
150992
151049
  if (!stmt) {
@@ -150995,6 +151052,14 @@ function getDeleteFtsStatement(db) {
150995
151052
  }
150996
151053
  return stmt;
150997
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
+ }
150998
151063
  function getDeleteIndexStatement(db) {
150999
151064
  let stmt = deleteIndexStatements.get(db);
151000
151065
  if (!stmt) {
@@ -151015,6 +151080,14 @@ function getLastIndexedOrdinal(db, sessionId) {
151015
151080
  const row = getLastIndexedStatement(db).get(sessionId);
151016
151081
  return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
151017
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
+ }
151018
151091
  function isMessageAlreadyIndexed(db, sessionId, messageId) {
151019
151092
  const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
151020
151093
  return (typeof row?.count === "number" ? row.count : 0) > 0;
@@ -151088,7 +151161,15 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
151088
151161
  db.exec("BEGIN IMMEDIATE");
151089
151162
  let committed = false;
151090
151163
  try {
151091
- 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
+ }
151092
151173
  const insertMessage = getInsertMessageStatement(db);
151093
151174
  for (const message of messages) {
151094
151175
  if (message.ordinal <= effectiveWatermark) {
@@ -151105,7 +151186,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
151105
151186
  inserted++;
151106
151187
  }
151107
151188
  const newWatermark = Math.max(effectiveWatermark, finalWatermark);
151108
- getUpsertIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
151189
+ getUpsertCleanIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
151109
151190
  db.exec("COMMIT");
151110
151191
  committed = true;
151111
151192
  } finally {
@@ -151117,14 +151198,17 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
151117
151198
  }
151118
151199
  return inserted;
151119
151200
  }
151120
- var lastIndexedStatements, insertMessageStatements, upsertIndexStatements, deleteFtsStatements, deleteIndexStatements, countIndexedMessageStatements;
151201
+ var lastIndexedStatements, insertMessageStatements, upsertIndexStatements, upsertCleanIndexStatements, upsertDirtyFloorStatements, deleteFtsStatements, deleteFtsFromOrdinalStatements, deleteIndexStatements, countIndexedMessageStatements;
151121
151202
  var init_message_index = __esm(async () => {
151122
151203
  init_compression_depth_storage();
151123
151204
  await init_read_session_chunk();
151124
151205
  lastIndexedStatements = new WeakMap;
151125
151206
  insertMessageStatements = new WeakMap;
151126
151207
  upsertIndexStatements = new WeakMap;
151208
+ upsertCleanIndexStatements = new WeakMap;
151209
+ upsertDirtyFloorStatements = new WeakMap;
151127
151210
  deleteFtsStatements = new WeakMap;
151211
+ deleteFtsFromOrdinalStatements = new WeakMap;
151128
151212
  deleteIndexStatements = new WeakMap;
151129
151213
  countIndexedMessageStatements = new WeakMap;
151130
151214
  });
@@ -151163,6 +151247,7 @@ function healNullTextColumns(db) {
151163
151247
  ["cache_ttl", ""],
151164
151248
  ["last_nudge_band", ""],
151165
151249
  ["last_nudge_level", ""],
151250
+ ["channel2_nudge_claim_token", ""],
151166
151251
  ["last_transform_error", ""],
151167
151252
  ["nudge_anchor_message_id", ""],
151168
151253
  ["nudge_anchor_text", ""],
@@ -151295,23 +151380,39 @@ function uniqueSorted(values) {
151295
151380
  function placeholders(values) {
151296
151381
  return values.map(() => "?").join(", ");
151297
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
+ }
151298
151392
  function normalizeShareCategories(raw) {
151299
- if (raw === null || raw === undefined)
151300
- return null;
151301
- if (typeof raw !== "string")
151302
- 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
+ }
151303
151400
  let parsed;
151304
151401
  try {
151305
151402
  parsed = JSON.parse(raw);
151306
151403
  } catch {
151307
- return null;
151404
+ warnInvalidShareCategories("malformed JSON", raw);
151405
+ return [];
151406
+ }
151407
+ if (!Array.isArray(parsed)) {
151408
+ warnInvalidShareCategories("not a JSON array", raw);
151409
+ return [];
151308
151410
  }
151309
- if (!Array.isArray(parsed))
151310
- return null;
151311
151411
  const categories = [];
151312
151412
  for (const value of parsed) {
151313
151413
  if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
151314
- return null;
151414
+ warnInvalidShareCategories("unknown category", raw);
151415
+ return [];
151315
151416
  }
151316
151417
  if (!categories.includes(value))
151317
151418
  categories.push(value);
@@ -151320,16 +151421,33 @@ function normalizeShareCategories(raw) {
151320
151421
  }
151321
151422
  function selectWorkspaceShareCategories(db, identities) {
151322
151423
  const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
151323
- 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")) {
151324
151425
  return null;
151325
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
+ }
151326
151440
  const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
151327
151441
  FROM workspace_members AS member
151328
151442
  JOIN workspaces AS workspace ON workspace.id = member.workspace_id
151329
151443
  WHERE member.project_path IN (${placeholders(candidates)})
151330
151444
  ORDER BY workspace.id ASC
151331
151445
  LIMIT 1`).get(...candidates);
151332
- 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);
151333
151451
  }
151334
151452
  function resolveWorkspaceShareCategories(db, projectIdentity) {
151335
151453
  return selectWorkspaceShareCategories(db, [projectIdentity]);
@@ -151434,7 +151552,7 @@ function computeWorkspaceEpochFingerprint(db, identities) {
151434
151552
  const hash2 = createHash2("sha256");
151435
151553
  hash2.update("share_categories", "utf8");
151436
151554
  hash2.update("\x00");
151437
- hash2.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
151555
+ hash2.update(shareCategories === null ? "NO_WORKSPACE" : JSON.stringify(shareCategories), "utf8");
151438
151556
  hash2.update(`
151439
151557
  `);
151440
151558
  for (const identity of canonical) {
@@ -151506,11 +151624,13 @@ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
151506
151624
  throw error51;
151507
151625
  }
151508
151626
  }
151509
- var VALID_SHARE_CATEGORIES;
151627
+ var VALID_SHARE_CATEGORIES, DEFAULT_WORKSPACE_SHARE_CATEGORIES;
151510
151628
  var init_workspaces = __esm(() => {
151629
+ init_logger();
151511
151630
  init_constants();
151512
151631
  init_project_identity2();
151513
151632
  VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
151633
+ DEFAULT_WORKSPACE_SHARE_CATEGORIES = ["CONSTRAINTS"];
151514
151634
  });
151515
151635
 
151516
151636
  // src/features/magic-context/migrations.ts
@@ -153995,6 +154115,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153995
154115
  CREATE TABLE IF NOT EXISTS message_history_index (
153996
154116
  session_id TEXT PRIMARY KEY,
153997
154117
  last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
154118
+ dirty_floor_ordinal INTEGER NOT NULL DEFAULT 0,
153998
154119
  updated_at INTEGER NOT NULL,
153999
154120
  harness TEXT NOT NULL DEFAULT 'opencode'
154000
154121
  );
@@ -154024,6 +154145,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154024
154145
  last_nudge_level TEXT DEFAULT '',
154025
154146
  channel2_nudge_state TEXT DEFAULT '',
154026
154147
  channel2_nudge_claimed_at INTEGER DEFAULT 0,
154148
+ channel2_nudge_claim_token TEXT DEFAULT '',
154027
154149
  last_emergency_input_sample INTEGER DEFAULT 0,
154028
154150
  last_transform_error TEXT DEFAULT '',
154029
154151
  nudge_anchor_message_id TEXT DEFAULT '',
@@ -154270,6 +154392,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154270
154392
  ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
154271
154393
  ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
154272
154394
  ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
154395
+ ensureColumn(db, "session_meta", "channel2_nudge_claim_token", "TEXT DEFAULT ''");
154273
154396
  ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
154274
154397
  ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
154275
154398
  ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
@@ -154482,6 +154605,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154482
154605
  ON transform_decisions(session_id, harness);
154483
154606
  `);
154484
154607
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
154608
+ ensureColumn(db, "message_history_index", "dirty_floor_ordinal", "INTEGER NOT NULL DEFAULT 0");
154485
154609
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
154486
154610
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
154487
154611
  ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -154496,7 +154620,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154496
154620
  function healWedgedChannel2Claims(db) {
154497
154621
  try {
154498
154622
  const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
154499
- 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);
154500
154624
  } catch {}
154501
154625
  }
154502
154626
  function openDatabase(dbPathOrOptions) {
@@ -155230,19 +155354,42 @@ function getChannel2NudgeState(db, sessionId) {
155230
155354
  const raw = result.channel2_nudge_state;
155231
155355
  return raw === "pending" || raw === "claimed" || raw === "delivered" ? raw : "";
155232
155356
  }
155233
- 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;
155234
155369
  db.transaction(() => {
155235
155370
  ensureSessionMetaRow(db, sessionId);
155236
- const claimedAt = state === "claimed" ? Date.now() : 0;
155237
- 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;
155238
155374
  })();
155375
+ return changed;
155239
155376
  }
155240
- 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) {
155241
155387
  let changed = false;
155242
155388
  db.transaction(() => {
155243
155389
  ensureSessionMetaRow(db, sessionId);
155244
155390
  const claimedAt = to === "claimed" ? Date.now() : 0;
155245
- 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);
155246
155393
  changed = (result.changes ?? 0) > 0;
155247
155394
  })();
155248
155395
  return changed;
@@ -166118,12 +166265,41 @@ function pushNotification(type, payload, sessionId) {
166118
166265
  queue = kept.sort((a, b) => a.id - b.id);
166119
166266
  }
166120
166267
  }
166121
- 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
+ }
166122
166298
  const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
166123
- if (lastReceivedId > 0) {
166124
- queue = queue.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
166299
+ if (sessionCursor > 0) {
166300
+ queue = queue.filter((notification) => !(notification.id <= sessionCursor && matchesClient(notification)));
166125
166301
  }
166126
- return queue.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
166302
+ return queue.filter((notification) => notification.id > sessionCursor && matchesClient(notification));
166127
166303
  }
166128
166304
  function isTuiConnected(sessionId) {
166129
166305
  if (sinks.size === 0)
@@ -167962,6 +168138,14 @@ function getSaveEmbeddingStatement(db) {
167962
168138
  }
167963
168139
  return stmt;
167964
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
+ }
167965
168149
  function getLoadAllEmbeddingsStatement(db) {
167966
168150
  let stmt = loadAllEmbeddingsStatements.get(db);
167967
168151
  if (!stmt) {
@@ -167990,6 +168174,10 @@ function saveEmbedding(db, memoryId, embedding, modelId) {
167990
168174
  const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
167991
168175
  getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
167992
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
+ }
167993
168181
  function loadAllEmbeddings(db, projectPath, modelId) {
167994
168182
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath, modelId).filter(isEmbeddingRow);
167995
168183
  const embeddings = new Map;
@@ -168021,9 +168209,10 @@ function getMemoryEmbedCoverage(db, projectPath, modelId) {
168021
168209
  embedded: typeof row?.embedded === "number" ? row.embedded : 0
168022
168210
  };
168023
168211
  }
168024
- var saveEmbeddingStatements, loadAllEmbeddingsStatements, deleteEmbeddingStatements, getStoredModelIdStatements, clearAllEmbeddingsStatements, clearModelEmbeddingsStatements, getDistinctStoredModelIdsStatements;
168212
+ var saveEmbeddingStatements, saveEmbeddingIfHashMatchesStatements, loadAllEmbeddingsStatements, deleteEmbeddingStatements, getStoredModelIdStatements, clearAllEmbeddingsStatements, clearModelEmbeddingsStatements, getDistinctStoredModelIdsStatements;
168025
168213
  var init_storage_memory_embeddings = __esm(() => {
168026
168214
  saveEmbeddingStatements = new WeakMap;
168215
+ saveEmbeddingIfHashMatchesStatements = new WeakMap;
168027
168216
  loadAllEmbeddingsStatements = new WeakMap;
168028
168217
  deleteEmbeddingStatements = new WeakMap;
168029
168218
  getStoredModelIdStatements = new WeakMap;
@@ -168848,12 +169037,12 @@ function isUnembeddedMemoryRow(row) {
168848
169037
  if (row === null || typeof row !== "object")
168849
169038
  return false;
168850
169039
  const candidate = row;
168851
- return typeof candidate.id === "number" && typeof candidate.content === "string";
169040
+ return typeof candidate.id === "number" && typeof candidate.content === "string" && typeof candidate.normalized_hash === "string";
168852
169041
  }
168853
169042
  function getLoadUnembeddedMemoriesStatement(db) {
168854
169043
  let stmt = loadUnembeddedMemoriesStatements.get(db);
168855
169044
  if (!stmt) {
168856
- 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 ?");
168857
169046
  loadUnembeddedMemoriesStatements.set(db, stmt);
168858
169047
  }
168859
169048
  return stmt;
@@ -168876,8 +169065,9 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
168876
169065
  const embedding = result.vectors[index];
168877
169066
  if (!embedding)
168878
169067
  continue;
168879
- saveEmbedding(db, memory.id, embedding, result.modelId);
168880
- embeddedCount += 1;
169068
+ if (saveEmbeddingIfHashMatches(db, memory.id, embedding, result.modelId, memory.normalized_hash)) {
169069
+ embeddedCount += 1;
169070
+ }
168881
169071
  }
168882
169072
  })();
168883
169073
  return embeddedCount;
@@ -169343,6 +169533,9 @@ function isMemorySourceType(value) {
169343
169533
  function isVerificationStatus(value) {
169344
169534
  return typeof value === "string" && value in VERIFICATION_STATUS_LOOKUP;
169345
169535
  }
169536
+ function isUniqueConstraintError(error51) {
169537
+ return error51 instanceof Error && "code" in error51 && error51.code === "SQLITE_CONSTRAINT_UNIQUE";
169538
+ }
169346
169539
  function isNullableString(value) {
169347
169540
  return value === null || typeof value === "string";
169348
169541
  }
@@ -169508,28 +169701,55 @@ function getMemoryCountsByStatusStatement(db) {
169508
169701
  }
169509
169702
  return stmt;
169510
169703
  }
169511
- function insertMemory(db, input) {
169512
- const now = Date.now();
169513
- const normalizedHash = computeNormalizedHash(input.content);
169704
+ function buildInsertMemoryValues(input, normalizedHash, now, includeImportance) {
169514
169705
  const insertValues = [
169515
169706
  input.projectPath,
169516
169707
  input.category,
169517
169708
  input.content,
169518
169709
  normalizedHash
169519
169710
  ];
169520
- if (hasMemoryImportanceColumn(db)) {
169711
+ if (includeImportance) {
169521
169712
  insertValues.push(input.importance ?? 50);
169522
169713
  }
169523
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);
169524
- const result = getInsertMemoryStatement(db).run(...insertValues);
169525
- const insertedResult = result;
169526
- const inserted = getMemoryById(db, Number(insertedResult.lastInsertRowid));
169715
+ return insertValues;
169716
+ }
169717
+ function loadInsertedMemory(db, rowid) {
169718
+ const inserted = getMemoryById(db, Number(rowid));
169527
169719
  if (!inserted) {
169528
169720
  throw new Error("Failed to load inserted memory row");
169529
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);
169530
169731
  invalidateProject(input.projectPath);
169531
169732
  return inserted;
169532
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
+ }
169533
169753
  function getMemoryByHash(db, projectPath, category, normalizedHash) {
169534
169754
  const result = getMemoryByHashStatement(db).get(projectPath, category, normalizedHash);
169535
169755
  if (!isMemoryRow(result)) {
@@ -180435,6 +180655,23 @@ function recursiveByteLength(value) {
180435
180655
  }
180436
180656
  return String(value).length;
180437
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
+ }
180438
180675
  function rawPartVersion(part) {
180439
180676
  return part.__magicContextPartUpdatedAt ?? part.updated_at ?? part.updatedAt ?? part.version ?? part.revision ?? "";
180440
180677
  }
@@ -180782,13 +181019,12 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
180782
181019
  function partContentFingerprint(part) {
180783
181020
  if (!isRecord(part))
180784
181021
  return `${typeof part}:${recursiveByteLength(part)}`;
180785
- const type = partType(part);
180786
181022
  const tool = toolSignalFromPart(part);
180787
181023
  if (tool) {
180788
- return `${type}:${tool.callId}:${tool.inputText.length}:${tool.outputText.length}`;
181024
+ return contentStringsHash([tool.inputText, tool.outputText]);
180789
181025
  }
180790
181026
  const text = firstStringField(part, ["text", "thinking", "reasoning", "content", "url"]) ?? "";
180791
- return `${type}:${text.length}`;
181027
+ return contentStringsHash([text]);
180792
181028
  }
180793
181029
  function computeRawRangeFingerprint(messages, startInclusive, endExclusive) {
180794
181030
  const pieces = [];
@@ -180813,7 +181049,7 @@ function invalidateTrueRawTokenCache(args) {
180813
181049
  }
180814
181050
  args.reason;
180815
181051
  }
180816
- 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;
180817
181053
  var init_read_session_true_raw_tokens = __esm(() => {
180818
181054
  init_read_session_formatting();
180819
181055
  MAX_MESSAGE_CACHE_KEY_BYTES = 64 * 1024 * 1024;
@@ -184824,6 +185060,15 @@ async function executeContextRecompWithResult(deps, options = {}) {
184824
185060
  published: false
184825
185061
  };
184826
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
+ }
184827
185072
  const renewal = startLeaseRenewal(deps, holderId);
184828
185073
  const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
184829
185074
  const promise2 = options.range ? executePartialRecompInternal(runnerDeps, options.range) : executeContextRecompInternal(runnerDeps);
@@ -186338,12 +186583,108 @@ function migrateLegacyExperimental(rawConfig, warnings) {
186338
186583
  }
186339
186584
 
186340
186585
  // src/config/project-security.ts
186586
+ init_magic_context();
186341
186587
  var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
186588
+ var HISTORIAN_USER_ONLY_FIELDS = ["model", "fallback_models"];
186342
186589
  var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
186343
186590
  var EMBEDDING_DESTINATION_FIELDS = ["endpoint", "provider"];
186344
- 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) {
186345
186595
  return typeof value === "object" && value !== null && !Array.isArray(value);
186346
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
+ }
186347
186688
  function stripUnsafeProjectConfigFields(projectRaw) {
186348
186689
  const warnings = [];
186349
186690
  if ("auto_update" in projectRaw) {
@@ -186359,7 +186700,7 @@ function stripUnsafeProjectConfigFields(projectRaw) {
186359
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).");
186360
186701
  }
186361
186702
  const embedding = projectRaw.embedding;
186362
- if (isPlainObject(embedding)) {
186703
+ if (isPlainObject2(embedding)) {
186363
186704
  const removed = [];
186364
186705
  for (const field of EMBEDDING_DESTINATION_FIELDS) {
186365
186706
  if (field in embedding) {
@@ -186371,9 +186712,22 @@ function stripUnsafeProjectConfigFields(projectRaw) {
186371
186712
  warnings.push(`Ignoring embedding.${removed.join("/")} from project config ` + "(security: a repository cannot choose where private text is embedded).");
186372
186713
  }
186373
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
+ }
186374
186728
  for (const agentKey of HIDDEN_AGENT_KEYS) {
186375
186729
  const block = projectRaw[agentKey];
186376
- if (!isPlainObject(block))
186730
+ if (!isPlainObject2(block))
186377
186731
  continue;
186378
186732
  const removed = [];
186379
186733
  for (const field of AGENT_ESCALATION_FIELDS) {
@@ -186388,6 +186742,101 @@ function stripUnsafeProjectConfigFields(projectRaw) {
186388
186742
  }
186389
186743
  return warnings;
186390
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
+ }
186391
186840
  function normalizeEndpoint(value) {
186392
186841
  if (typeof value !== "string")
186393
186842
  return;
@@ -186396,13 +186845,13 @@ function normalizeEndpoint(value) {
186396
186845
  }
186397
186846
  function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
186398
186847
  const projectEmbedding = projectRaw.embedding;
186399
- if (!isPlainObject(projectEmbedding))
186848
+ if (!isPlainObject2(projectEmbedding))
186400
186849
  return [];
186401
186850
  const redirectsEndpoint = "endpoint" in projectEmbedding;
186402
186851
  if (!redirectsEndpoint)
186403
186852
  return [];
186404
186853
  const userEmbedding = userRaw?.embedding;
186405
- if (isPlainObject(userEmbedding)) {
186854
+ if (isPlainObject2(userEmbedding)) {
186406
186855
  const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
186407
186856
  const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
186408
186857
  if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
@@ -186413,7 +186862,7 @@ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
186413
186862
  if (providesOwnKey)
186414
186863
  return [];
186415
186864
  const mergedEmbedding = mergedRaw.embedding;
186416
- if (!isPlainObject(mergedEmbedding))
186865
+ if (!isPlainObject2(mergedEmbedding))
186417
186866
  return [];
186418
186867
  if (!("api_key" in mergedEmbedding))
186419
186868
  return [];
@@ -186424,7 +186873,7 @@ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
186424
186873
  }
186425
186874
 
186426
186875
  // src/config/prune-config-leaf.ts
186427
- function isPlainObject2(value) {
186876
+ function isPlainObject3(value) {
186428
186877
  return typeof value === "object" && value !== null && !Array.isArray(value);
186429
186878
  }
186430
186879
  function pruneNestedConfigLeaf(block, relativePath) {
@@ -186435,7 +186884,7 @@ function pruneNestedConfigLeaf(block, relativePath) {
186435
186884
  for (let i = 0;i < relativePath.length - 1; i++) {
186436
186885
  const seg = String(relativePath[i]);
186437
186886
  const child = cursor[seg];
186438
- if (!isPlainObject2(child)) {
186887
+ if (!isPlainObject3(child)) {
186439
186888
  if (!(seg in cursor))
186440
186889
  return null;
186441
186890
  delete cursor[seg];
@@ -186787,6 +187236,7 @@ function loadPluginConfigDetailed(directory) {
186787
187236
  const projectLoaded = projectDetected.format !== "none" ? loadConfigFileDetailed(projectDetected.path, "project") : projectLegacyFallback.source ? loadConfigFileDetailed(projectLegacyFallback.source.path, "project") : null;
186788
187237
  const allWarnings = [];
186789
187238
  let mergedRaw = {};
187239
+ const trustedBaseConfig = parsePluginConfig(userLoaded?.config ?? {});
186790
187240
  if (userLegacyFallback.source) {
186791
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.`);
186792
187242
  } else if (legacyUserUnmigrated) {
@@ -186811,6 +187261,13 @@ function loadPluginConfigDetailed(directory) {
186811
187261
  for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userLoaded?.config)) {
186812
187262
  allWarnings.push(`[project config] ${warning}`);
186813
187263
  }
187264
+ for (const warning of constrainProjectThresholdOverrides({
187265
+ mergedRaw,
187266
+ projectRaw,
187267
+ trustedBaseConfig
187268
+ })) {
187269
+ allWarnings.push(`[project config] ${warning}`);
187270
+ }
186814
187271
  }
186815
187272
  const recoveredTopLevelKeys = [];
186816
187273
  const config2 = parsePluginConfig(mergedRaw, recoveredTopLevelKeys);
@@ -188534,6 +188991,14 @@ function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
188534
188991
  return true;
188535
188992
  });
188536
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
+ }
188537
189002
  var LEASE_HEARTBEAT_INTERVAL_MS = 60 * 1000;
188538
189003
  function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalMs = LEASE_HEARTBEAT_INTERVAL_MS) {
188539
189004
  let lost = false;
@@ -188692,8 +189157,7 @@ class OpenCodeRetrospectiveRawProvider {
188692
189157
  LEFT JOIN session_meta m ON m.session_id = sp.session_id
188693
189158
  WHERE sp.project_path = ? AND sp.harness = 'opencode'
188694
189159
  AND COALESCE(m.is_subagent, 0) = 0
188695
- ORDER BY sp.updated_at DESC, sp.session_id DESC
188696
- LIMIT ?`).all(projectIdentity, RETROSPECTIVE_MAX_SESSIONS_PER_RUN);
189160
+ ORDER BY sp.updated_at ASC, sp.session_id ASC`).all(projectIdentity);
188697
189161
  return rows.map((row) => ({
188698
189162
  sessionId: row.session_id,
188699
189163
  updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
@@ -188718,6 +189182,12 @@ class OpenCodeRetrospectiveRawProvider {
188718
189182
  return { messages: [], truncated: false };
188719
189183
  }
188720
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
+ }
188721
189191
  readUserMessagesBefore(sessionId, beforeMs, count) {
188722
189192
  const db = this.resolveDb();
188723
189193
  if (!db)
@@ -188739,13 +189209,28 @@ class OpenCodeRetrospectiveRawProvider {
188739
189209
  async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkMs, overlapUserCount, options) {
188740
189210
  const maxMessages = options?.maxMessagesPerRun ?? RETROSPECTIVE_MAX_MESSAGES_PER_RUN;
188741
189211
  const capPerSession = options?.capPerSession ?? RETROSPECTIVE_MAX_MESSAGES_PER_SESSION;
188742
- 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));
188743
189213
  try {
188744
- 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;
188745
189231
  const sinceReads = [];
188746
- for (let i = 0;i < sessions.length; i += sessionReadPageSize) {
188747
- const page = sessions.slice(i, i + sessionReadPageSize);
188748
- 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))));
188749
189234
  }
188750
189235
  let saturatedFrontier = Number.POSITIVE_INFINITY;
188751
189236
  for (const read of sinceReads) {
@@ -188767,9 +189252,14 @@ async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkM
188767
189252
  if (firstDropped) {
188768
189253
  frontier = Math.min(frontier, firstDropped.ts - 1);
188769
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
+ }
188770
189260
  maxScannedTs = Math.max(watermarkMs, Math.min(maxScannedTs, frontier));
188771
189261
  const keptSessionIds = new Set(keptSince.map((message) => message.sessionId));
188772
- const overlapSessions = sessions.filter((session) => keptSessionIds.has(session.sessionId));
189262
+ const overlapSessions = sessionsToRead.filter((session) => keptSessionIds.has(session.sessionId));
188773
189263
  const overlapBatches = overlapUserCount > 0 && watermarkMs > 0 ? await Promise.all(overlapSessions.map((session) => provider.readUserMessagesBefore(session.sessionId, watermarkMs, overlapUserCount))) : [];
188774
189264
  const seen = new Set;
188775
189265
  const merged = [];
@@ -188797,6 +189287,24 @@ function readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession) {
188797
189287
  const kept = truncated ? rows.slice(0, limit) : rows;
188798
189288
  return { messages: normalizeOpenCodeRows(db, sessionId, kept), truncated };
188799
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
+ }
188800
189308
  function readOpenCodeUserMessagesBefore(db, sessionId, beforeMs, count) {
188801
189309
  const want = Math.max(1, Math.floor(count));
188802
189310
  const window = Math.max(want * 5, 32);
@@ -189415,6 +189923,39 @@ function hasShareabilitySensitiveText(text) {
189415
189923
  init_memory();
189416
189924
  init_subagent_token_capture();
189417
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
+
189418
189959
  // src/features/magic-context/dreamer/classify-prompt.ts
189419
189960
  var SCORING_GUIDANCE = `### How to score importance (1-100)
189420
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.
@@ -189481,14 +190022,15 @@ ${renderPool(args.memories)}`;
189481
190022
  var SCOPES = new Set(["project", "ecosystem", "universe"]);
189482
190023
  function parseClassifyManifest(text) {
189483
190024
  const out = [];
189484
- 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)) {
189485
190027
  const attrs = m[1];
189486
190028
  const idMatch = attrs.match(/\bid\s*=\s*"(\d+)"/);
189487
190029
  if (!idMatch)
189488
- continue;
190030
+ throw new Error("classify manifest entry missing numeric id");
189489
190031
  const id = Number.parseInt(idMatch[1], 10);
189490
190032
  if (!Number.isInteger(id))
189491
- continue;
190033
+ throw new Error("classify manifest entry missing numeric id");
189492
190034
  const entry = { id };
189493
190035
  const impMatch = attrs.match(/\bimportance\s*=\s*"(\d+)"/);
189494
190036
  if (impMatch) {
@@ -189497,18 +190039,23 @@ function parseClassifyManifest(text) {
189497
190039
  entry.importance = Math.max(1, Math.min(100, imp));
189498
190040
  }
189499
190041
  const scopeMatch = attrs.match(/\bscope\s*=\s*"([a-z]+)"/i);
189500
- if (scopeMatch && SCOPES.has(scopeMatch[1].toLowerCase())) {
189501
- 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;
189502
190047
  }
189503
190048
  const shareMatch = attrs.match(/\bshareable\s*=\s*"(true|false|1|0)"/i);
189504
190049
  if (shareMatch) {
189505
190050
  const v = shareMatch[1].toLowerCase();
189506
190051
  entry.shareable = v === "true" || v === "1";
189507
190052
  }
189508
- if (entry.importance !== undefined || entry.scope || entry.shareable !== undefined) {
189509
- out.push(entry);
190053
+ if (entry.importance === undefined && !entry.scope && entry.shareable === undefined) {
190054
+ throw new Error(`classify manifest entry ${id} missing classification fields`);
189510
190055
  }
190056
+ out.push(entry);
189511
190057
  }
190058
+ assertNoDuplicateManifestIds(out.map((entry) => entry.id), "classify");
189512
190059
  return out;
189513
190060
  }
189514
190061
 
@@ -189643,9 +190190,13 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
189643
190190
  });
189644
190191
  },
189645
190192
  validateOutput: (messages) => {
190193
+ if (hasLengthCappedOutput(messages)) {
190194
+ throw new Error("classify returned length-capped output");
190195
+ }
189646
190196
  const text = extractLatestAssistantText(messages);
189647
190197
  if (!text)
189648
190198
  throw new Error("classify returned no output");
190199
+ parseClassifyManifest(text);
189649
190200
  return text;
189650
190201
  }
189651
190202
  });
@@ -189671,17 +190222,13 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
189671
190222
  }
189672
190223
  function applyClassifications(args, chunk, manifestText) {
189673
190224
  const byId = new Map(chunk.map((m) => [m.id, m]));
189674
- 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");
189675
190227
  if (parsed.length === 0)
189676
190228
  return { classified: 0, changed: 0 };
189677
190229
  let classified = 0;
189678
190230
  let changed = 0;
189679
- let leaseLost = false;
189680
- args.db.transaction(() => {
189681
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
189682
- leaseLost = true;
189683
- return;
189684
- }
190231
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
189685
190232
  for (const p of parsed) {
189686
190233
  const memory = byId.get(p.id);
189687
190234
  if (!memory)
@@ -189696,9 +190243,7 @@ function applyClassifications(args, chunk, manifestText) {
189696
190243
  if (didChange)
189697
190244
  changed += 1;
189698
190245
  }
189699
- })();
189700
- if (leaseLost)
189701
- throw new Error("Dream lease lost during classify commit");
190246
+ });
189702
190247
  return { classified, changed };
189703
190248
  }
189704
190249
  function recordInvocation(args, startedAt, params) {
@@ -189748,9 +190293,11 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
189748
190293
 
189749
190294
  class SmartNoteNetworkError extends Error {
189750
190295
  isSmartNoteNetworkError = true;
189751
- constructor(message) {
190296
+ terminal;
190297
+ constructor(message, options = {}) {
189752
190298
  super(message);
189753
190299
  this.name = "SmartNoteNetworkError";
190300
+ this.terminal = options.terminal ?? false;
189754
190301
  }
189755
190302
  }
189756
190303
 
@@ -189764,6 +190311,9 @@ class SmartNoteSecurityError extends Error {
189764
190311
  function isSmartNoteNetworkError(error51) {
189765
190312
  return error51 instanceof SmartNoteNetworkError || error51 instanceof Error && (error51.name === "SmartNoteNetworkError" || error51.message.includes("SmartNoteNetworkError") || error51.message.includes("SMART_NOTE_NETWORK"));
189766
190313
  }
190314
+ function isTerminalSmartNoteNetworkError(error51) {
190315
+ return error51 instanceof SmartNoteNetworkError && error51.terminal;
190316
+ }
189767
190317
  function parseSmartNoteManifest(json2) {
189768
190318
  if (!json2)
189769
190319
  return { capabilities: [] };
@@ -189793,6 +190343,7 @@ function stringArray(value) {
189793
190343
  var DNS_TIMEOUT_MS = 3000;
189794
190344
  var DEFAULT_HTTP_TIMEOUT_MS = 5000;
189795
190345
  var DEFAULT_HTTP_BODY_LIMIT_BYTES = 64 * 1024;
190346
+ var MAX_HTTP_ADDRESS_CANDIDATES = 4;
189796
190347
  var defaultResolver = {
189797
190348
  async lookup(hostname3, signal) {
189798
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 })));
@@ -189829,18 +190380,21 @@ async function guardedSmartNoteHttpGet(input, options) {
189829
190380
  });
189830
190381
  const timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
189831
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);
189832
190385
  let lastError;
189833
- for (const candidate of validation.addresses) {
190386
+ for (const candidate of candidates) {
189834
190387
  try {
189835
- return await requestValidatedAddress(validation, candidate, {
190388
+ return await requestAddress(validation, candidate, {
189836
190389
  signal: options.signal,
189837
190390
  timeoutMs,
189838
190391
  bodyLimitBytes
189839
190392
  });
189840
190393
  } catch (error51) {
189841
190394
  lastError = error51;
189842
- if (error51 instanceof SmartNoteSecurityError || options.signal.aborted)
190395
+ if (error51 instanceof SmartNoteSecurityError || options.signal.aborted || isTerminalSmartNoteNetworkError(error51)) {
189843
190396
  throw error51;
190397
+ }
189844
190398
  }
189845
190399
  }
189846
190400
  throw toNetworkError(lastError, "all validated addresses failed");
@@ -189910,7 +190464,9 @@ function requestValidatedAddress(validation, candidate, options) {
189910
190464
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
189911
190465
  bytes += buf.byteLength;
189912
190466
  if (bytes > options.bodyLimitBytes) {
189913
- 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();
189914
190470
  return;
189915
190471
  }
189916
190472
  chunks.push(buf);
@@ -189927,10 +190483,16 @@ function requestValidatedAddress(validation, candidate, options) {
189927
190483
  resolve4({ status, body: Buffer.concat(chunks).toString("utf8") });
189928
190484
  });
189929
190485
  });
189930
- 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
+ };
189931
190490
  options.signal.addEventListener("abort", onAbort, { once: true });
189932
190491
  request2.on("timeout", () => {
189933
- 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();
189934
190496
  });
189935
190497
  request2.on("error", (error51) => {
189936
190498
  options.signal.removeEventListener("abort", onAbort);
@@ -190251,6 +190813,20 @@ function withSandboxLock(fn) {
190251
190813
  var DEFAULT_TIMEOUT_MS = 2000;
190252
190814
  var DEFAULT_HEAP_LIMIT_BYTES = 8 * 1024 * 1024;
190253
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
+ }
190254
190830
  async function runCompiledSmartNoteCheck(options) {
190255
190831
  return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options));
190256
190832
  }
@@ -190258,17 +190834,24 @@ async function runCompiledSmartNoteCheckLocked(options) {
190258
190834
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
190259
190835
  const controller = new AbortController;
190260
190836
  const externalAbort = () => controller.abort(options.signal?.reason);
190261
- 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
+ }
190262
190842
  const timer = setTimeout(() => controller.abort(new Error("smart-note check timed out")), timeoutMs);
190263
190843
  try {
190844
+ throwIfRunAborted(controller.signal);
190845
+ const capabilities = resolveCapabilitiesForRun(options, controller.signal);
190264
190846
  const deadline = Date.now() + timeoutMs;
190265
190847
  const quickjs = await getAsyncModule();
190848
+ throwIfRunAborted(controller.signal);
190266
190849
  const context = quickjs.newContext();
190267
190850
  try {
190268
190851
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
190269
190852
  context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
190270
190853
  context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
190271
- installCapabilityObject(context, options.capabilities);
190854
+ installCapabilityObject(context, capabilities);
190272
190855
  disableAmbientDynamicCode(context);
190273
190856
  const result = await evalCheck(context, options.compiledCheck);
190274
190857
  const checkResult = result;
@@ -190421,6 +191004,7 @@ Remember: output only the JSON object described by the system prompt.`;
190421
191004
  }
190422
191005
  }, {
190423
191006
  timeoutMs: remainingMs,
191007
+ signal: args.signal,
190424
191008
  fallbackModels: args.fallbackModels,
190425
191009
  callContext: "dreamer:smart-note-compiler",
190426
191010
  fetchOutput: async () => {
@@ -190445,7 +191029,8 @@ Remember: output only the JSON object described by the system prompt.`;
190445
191029
  }
190446
191030
  const dryRun = await runCompiledSmartNoteCheck({
190447
191031
  compiledCheck,
190448
- capabilities: args.capabilities,
191032
+ capabilityFactory: args.capabilityFactory,
191033
+ signal: args.signal,
190449
191034
  timeoutMs: 2000
190450
191035
  });
190451
191036
  if (!dryRun.ok) {
@@ -190598,7 +191183,9 @@ function nextSmartNoteCheckDueAt(cron, options = {}) {
190598
191183
  const rawNext = cron?.trim() ? nextDueAtMs(cron, now) : null;
190599
191184
  const rawDelta = rawNext ? rawNext - now : SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS;
190600
191185
  const clamped = Math.min(ceilingMs, Math.max(floorMs, rawDelta));
190601
- 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;
190602
191189
  }
190603
191190
  function deterministicJitterMs(intervalMs, noteId, hash2) {
190604
191191
  const max = Math.min(60000, Math.floor(intervalMs * 0.1));
@@ -190768,9 +191355,9 @@ async function runDueCompiledSmartNoteChecks(args) {
190768
191355
  try {
190769
191356
  const result = await runCompiledSmartNoteCheck({
190770
191357
  compiledCheck: note.compiledCheck,
190771
- capabilities: createSmartNoteCapabilities({
191358
+ capabilityFactory: (signal) => createSmartNoteCapabilities({
190772
191359
  projectRoot: args.projectRoot,
190773
- signal: controller.signal
191360
+ signal
190774
191361
  }),
190775
191362
  signal: controller.signal,
190776
191363
  timeoutMs: Math.min(2000, remaining)
@@ -190835,6 +191422,24 @@ var MAX_COMPILE_PER_RUN = 5;
190835
191422
  var MAX_FALLBACK_PER_RUN = 3;
190836
191423
  var MAX_COMPILATION_FAILURES = 3;
190837
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
+ }
190838
191443
  async function evaluateSmartNotes(args) {
190839
191444
  const projectRoot = args.sessionDirectory ?? args.projectIdentity;
190840
191445
  const pendingAtStart = getPendingSmartNotes(args.db, args.projectIdentity).length;
@@ -190843,6 +191448,7 @@ async function evaluateSmartNotes(args) {
190843
191448
  return { surfaced: 0, pending: 0, ran: false };
190844
191449
  }
190845
191450
  let leaseLost = false;
191451
+ const leaseAbortController = new AbortController;
190846
191452
  const leaseHeld = () => !leaseLost && peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey);
190847
191453
  const assertLeaseHeld = (phase) => {
190848
191454
  if (!leaseHeld()) {
@@ -190852,6 +191458,7 @@ async function evaluateSmartNotes(args) {
190852
191458
  };
190853
191459
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => {
190854
191460
  leaseLost = true;
191461
+ leaseAbortController.abort(new Error("Dream lease lost during smart notes"));
190855
191462
  log("[dreamer] smart notes: lease lost — aborting");
190856
191463
  args.onLeaseLost?.("smart notes");
190857
191464
  });
@@ -190874,7 +191481,7 @@ async function evaluateSmartNotes(args) {
190874
191481
  break;
190875
191482
  assertLeaseHeld("compile start");
190876
191483
  didWork = true;
190877
- const compiled = await compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld);
191484
+ const compiled = await compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld, leaseAbortController.signal);
190878
191485
  if (compiled)
190879
191486
  surfaced += 1;
190880
191487
  }
@@ -190894,7 +191501,7 @@ async function evaluateSmartNotes(args) {
190894
191501
  break;
190895
191502
  assertLeaseHeld("fallback start");
190896
191503
  didWork = true;
190897
- 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);
190898
191505
  const now = Date.now();
190899
191506
  assertLeaseHeld("fallback commit");
190900
191507
  commitSmartNoteState(args.db, {
@@ -190920,9 +191527,8 @@ async function evaluateSmartNotes(args) {
190920
191527
  heartbeat.stop();
190921
191528
  }
190922
191529
  }
190923
- async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld) {
190924
- const controller = new AbortController;
190925
- 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");
190926
191532
  try {
190927
191533
  const result = await compileSmartNoteCheck({
190928
191534
  client: args.client,
@@ -190931,7 +191537,8 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld)
190931
191537
  sessionDirectory: args.sessionDirectory,
190932
191538
  projectIdentity: args.projectIdentity,
190933
191539
  note,
190934
- capabilities: createSmartNoteCapabilities({ projectRoot, signal: controller.signal }),
191540
+ capabilityFactory: (signal) => createSmartNoteCapabilities({ projectRoot, signal }),
191541
+ signal: promptSignal.signal,
190935
191542
  deadline: args.deadline,
190936
191543
  model: args.model,
190937
191544
  fallbackModels: args.fallbackModels
@@ -190976,7 +191583,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld)
190976
191583
  });
190977
191584
  return result.dryRun.met;
190978
191585
  } finally {
190979
- clearTimeout(timer);
191586
+ promptSignal.cleanup();
190980
191587
  }
190981
191588
  }
190982
191589
  async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseHeld) {
@@ -190987,7 +191594,7 @@ async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseH
190987
191594
  try {
190988
191595
  const result = await runCompiledSmartNoteCheck({
190989
191596
  compiledCheck: note.compiledCheck,
190990
- capabilities: createSmartNoteCapabilities({ projectRoot, signal: controller.signal }),
191597
+ capabilityFactory: (signal) => createSmartNoteCapabilities({ projectRoot, signal }),
190991
191598
  signal: controller.signal,
190992
191599
  timeoutMs: 2000
190993
191600
  });
@@ -191016,7 +191623,7 @@ async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseH
191016
191623
  clearTimeout(timer);
191017
191624
  }
191018
191625
  }
191019
- async function confirmReadOnly(args, noteId, content, surfaceCondition) {
191626
+ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSignal) {
191020
191627
  let childSessionId = null;
191021
191628
  const startedAt = Date.now();
191022
191629
  let invocationRecorded = false;
@@ -191059,42 +191666,49 @@ Note content: ${JSON.stringify(content)}
191059
191666
  Surface condition: ${JSON.stringify(surfaceCondition ?? "")}
191060
191667
 
191061
191668
  Output exactly JSON: {"met": false}`;
191062
- const run = await promptSyncWithValidatedOutputRetry(args.client, {
191063
- path: { id: childSessionId },
191064
- query: { directory: args.sessionDirectory ?? args.projectIdentity },
191065
- body: {
191066
- agent: SMART_NOTE_COMPILER_AGENT,
191067
- system: SMART_NOTE_CONFIRMATION_SYSTEM_PROMPT,
191068
- ...modelBodyField(args.model),
191069
- parts: [{ type: "text", text: prompt, synthetic: true }]
191070
- }
191071
- }, {
191072
- timeoutMs: Math.max(1000, args.deadline - Date.now()),
191073
- fallbackModels: args.fallbackModels,
191074
- callContext: "dreamer:smart-note-read-only-confirm",
191075
- fetchOutput: async () => {
191076
- const messagesResponse = await args.client.session.messages({
191077
- path: { id: childSessionId },
191078
- query: {
191079
- directory: args.sessionDirectory ?? args.projectIdentity,
191080
- limit: 20
191081
- }
191082
- });
191083
- return normalizeSDKResponse(messagesResponse, [], {
191084
- preferResponseOnMissingData: true
191085
- });
191086
- },
191087
- validateOutput: (messages) => {
191088
- const text = extractLatestAssistantText(messages) ?? "";
191089
- const match = text.match(/\{[\s\S]*\}/);
191090
- if (!match)
191091
- throw new Error("confirmation evaluator returned no JSON");
191092
- const parsed = JSON.parse(match[0]);
191093
- if (typeof parsed.met !== "boolean")
191094
- throw new Error("confirmation met missing");
191095
- return parsed.met;
191096
- }
191097
- });
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
+ }
191098
191712
  recordInvocation2({ status: "completed", messages: run.output });
191099
191713
  return run.validated;
191100
191714
  } catch (error51) {
@@ -191313,19 +191927,21 @@ ${list}
191313
191927
  }
191314
191928
  function parseMapMemoriesManifest(text) {
191315
191929
  const out = [];
191316
- 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)) {
191317
191932
  const attrs = m[1];
191318
191933
  const idMatch = attrs.match(/\bid\s*=\s*"(\d+)"/);
191319
191934
  if (!idMatch)
191320
- continue;
191935
+ throw new Error("mappings manifest entry missing numeric id");
191321
191936
  const id = Number.parseInt(idMatch[1], 10);
191322
191937
  if (!Number.isInteger(id))
191323
- continue;
191938
+ throw new Error("mappings manifest entry missing numeric id");
191324
191939
  const independent = /\bindependent\s*=\s*"(?:true|1)"/i.test(attrs);
191325
191940
  const filesMatch = attrs.match(/\bfiles\s*=\s*"([^"]*)"/);
191326
191941
  const files = filesMatch ? filesMatch[1].split(",").map((f) => f.trim()).filter(Boolean) : [];
191327
191942
  out.push({ id, files, independent: independent || files.length === 0 });
191328
191943
  }
191944
+ assertNoDuplicateManifestIds(out.map((entry) => entry.id), "mappings");
191329
191945
  return out;
191330
191946
  }
191331
191947
 
@@ -191414,9 +192030,13 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
191414
192030
  });
191415
192031
  },
191416
192032
  validateOutput: (messages) => {
192033
+ if (hasLengthCappedOutput(messages)) {
192034
+ throw new Error("map-memories returned length-capped output");
192035
+ }
191417
192036
  const text = extractLatestAssistantText(messages);
191418
192037
  if (!text)
191419
192038
  throw new Error("map-memories returned no output");
192039
+ parseMapMemoriesManifest(text);
191420
192040
  return text;
191421
192041
  }
191422
192042
  });
@@ -191442,7 +192062,8 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
191442
192062
  }
191443
192063
  async function applyBatchMappings(args, batch, manifestText) {
191444
192064
  const batchIds = new Set(batch.map((m) => m.id));
191445
- const parsed = parseMapMemoriesManifest(manifestText).filter((p) => batchIds.has(p.id));
192065
+ const parsed = parseMapMemoriesManifest(manifestText);
192066
+ assertManifestCoversExactly(parsed.map((entry) => entry.id), batchIds, "mappings");
191446
192067
  if (parsed.length === 0)
191447
192068
  return { mapped: 0, independent: 0 };
191448
192069
  const planned = [];
@@ -191464,12 +192085,7 @@ async function applyBatchMappings(args, batch, manifestText) {
191464
192085
  const now = Date.now();
191465
192086
  let mapped = 0;
191466
192087
  let independent = 0;
191467
- let leaseLost = false;
191468
- args.db.transaction(() => {
191469
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
191470
- leaseLost = true;
191471
- return;
191472
- }
192088
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
191473
192089
  for (const item of planned) {
191474
192090
  recordMemoryMapping(args.db, item.id, item.files, now);
191475
192091
  if (item.independent)
@@ -191477,9 +192093,7 @@ async function applyBatchMappings(args, batch, manifestText) {
191477
192093
  else
191478
192094
  mapped += 1;
191479
192095
  }
191480
- })();
191481
- if (leaseLost)
191482
- throw new Error("Dream lease lost during map-memories commit");
192096
+ });
191483
192097
  return { mapped, independent };
191484
192098
  }
191485
192099
  function recordInvocation2(args, startedAt, params) {
@@ -191710,11 +192324,7 @@ async function promotePrimers(args) {
191710
192324
  activePrimers: primers,
191711
192325
  threshold: PRIMER_CLUSTER_THRESHOLD
191712
192326
  });
191713
- args.db.transaction(() => {
191714
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
191715
- leaseLost = true;
191716
- return;
191717
- }
192327
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
191718
192328
  for (const cluster of clusters) {
191719
192329
  if (cluster.candidates.length === 0)
191720
192330
  continue;
@@ -191745,7 +192355,7 @@ async function promotePrimers(args) {
191745
192355
  });
191746
192356
  result.promoted += 1;
191747
192357
  }
191748
- })();
192358
+ });
191749
192359
  if (leaseLost)
191750
192360
  throw new Error("Dream lease lost during promote-primers commit");
191751
192361
  log(`[dreamer] primers: candidates=${result.candidates} promoted=${result.promoted} updated=${result.updated}`);
@@ -192023,16 +192633,9 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
192023
192633
  log(`[dreamer] refresh-primers: primer #${primer.id} answer not committed (no investigation tool calls)`);
192024
192634
  return false;
192025
192635
  }
192026
- let leaseLost = false;
192027
- args.db.transaction(() => {
192028
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
192029
- leaseLost = true;
192030
- return;
192031
- }
192636
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
192032
192637
  updatePrimerAnswer(args.db, primer.id, answer);
192033
- })();
192034
- if (leaseLost)
192035
- throw new Error("Dream lease lost during refresh-primers commit");
192638
+ });
192036
192639
  return true;
192037
192640
  } catch (error51) {
192038
192641
  const desc = describeError(error51);
@@ -192301,7 +192904,7 @@ async function partitionVerifyScope(args) {
192301
192904
  continue;
192302
192905
  }
192303
192906
  const files = v?.files ?? [];
192304
- 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);
192305
192908
  if (needs)
192306
192909
  inScope.push(toPrompt(m));
192307
192910
  else
@@ -192367,22 +192970,26 @@ function filesOf(s) {
192367
192970
  }
192368
192971
  function parseVerifyManifest(text) {
192369
192972
  const out = { verified: [], updated: [], archived: [] };
192370
- 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)) {
192371
192975
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
192372
- if (Number.isInteger(id))
192373
- 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]) });
192374
192979
  }
192375
- 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)) {
192376
192981
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
192377
- if (Number.isInteger(id)) {
192378
- out.updated.push({ id, files: filesOf(m[1]), content: (m[2] ?? "").trim() });
192379
- }
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() });
192380
192985
  }
192381
- for (const m of text.matchAll(/<archive\b([^>]*)\/?>/g)) {
192986
+ for (const m of body.matchAll(/<archive\b([^>]*)\/?>/g)) {
192382
192987
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
192383
- if (Number.isInteger(id))
192384
- 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") ?? "" });
192385
192991
  }
192992
+ assertNoDuplicateManifestIds([...out.verified, ...out.updated, ...out.archived].map((entry) => entry.id), "verify");
192386
192993
  return out;
192387
192994
  }
192388
192995
 
@@ -192475,9 +193082,13 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
192475
193082
  });
192476
193083
  },
192477
193084
  validateOutput: (messages) => {
193085
+ if (hasLengthCappedOutput(messages)) {
193086
+ throw new Error("verify returned length-capped output");
193087
+ }
192478
193088
  const text = extractLatestAssistantText(messages);
192479
193089
  if (!text)
192480
193090
  throw new Error("verify returned no output");
193091
+ parseVerifyManifest(text);
192481
193092
  return text;
192482
193093
  }
192483
193094
  });
@@ -192504,24 +193115,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
192504
193115
  async function applyVerifyManifest(args, batch, manifestText) {
192505
193116
  const batchIds = new Set(batch.map((m) => m.id));
192506
193117
  const parsed = parseVerifyManifest(manifestText);
193118
+ assertManifestCoversExactly([...parsed.verified, ...parsed.updated, ...parsed.archived].map((entry) => entry.id), batchIds, "verify");
192507
193119
  const now = Date.now();
192508
193120
  const writes = [];
192509
- const verdictCounts = new Map;
192510
- for (const verdict of [...parsed.verified, ...parsed.updated, ...parsed.archived]) {
192511
- if (!batchIds.has(verdict.id))
192512
- continue;
192513
- verdictCounts.set(verdict.id, (verdictCounts.get(verdict.id) ?? 0) + 1);
192514
- }
192515
- const conflictingIds = new Set(Array.from(verdictCounts.entries()).filter(([, count]) => count !== 1).map(([id]) => id));
192516
193121
  for (const v of parsed.verified) {
192517
- if (!batchIds.has(v.id) || conflictingIds.has(v.id))
192518
- continue;
192519
193122
  const files = await normalizeFiles(args, v.files);
192520
193123
  writes.push({ kind: "verify", id: v.id, files });
192521
193124
  }
192522
193125
  for (const u of parsed.updated) {
192523
- if (!batchIds.has(u.id) || conflictingIds.has(u.id))
192524
- continue;
192525
193126
  const content = u.content.trim();
192526
193127
  if (!content || content.length > 20000) {
192527
193128
  const files2 = await normalizeFiles(args, u.files);
@@ -192538,8 +193139,6 @@ async function applyVerifyManifest(args, batch, manifestText) {
192538
193139
  });
192539
193140
  }
192540
193141
  for (const a of parsed.archived) {
192541
- if (!batchIds.has(a.id) || conflictingIds.has(a.id))
192542
- continue;
192543
193142
  writes.push({ kind: "archive", id: a.id, reason: a.reason });
192544
193143
  }
192545
193144
  if (writes.length === 0)
@@ -192547,12 +193146,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
192547
193146
  let verified = 0;
192548
193147
  let updated = 0;
192549
193148
  let archived = 0;
192550
- let leaseLost = false;
192551
- args.db.transaction(() => {
192552
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
192553
- leaseLost = true;
192554
- return;
192555
- }
193149
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
192556
193150
  for (const w of writes) {
192557
193151
  const memory = getMemoryById(args.db, w.id);
192558
193152
  if (!isPrimaryMutable(memory))
@@ -192580,9 +193174,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
192580
193174
  archived += 1;
192581
193175
  }
192582
193176
  }
192583
- })();
192584
- if (leaseLost)
192585
- throw new Error("Dream lease lost during verify commit");
193177
+ });
192586
193178
  return { verified, updated, archived };
192587
193179
  }
192588
193180
  async function normalizeFiles(args, rawFiles) {
@@ -193076,11 +193668,7 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
193076
193668
  throw new Error("Dream lease lost during retrospective");
193077
193669
  const sourceSessionId = flagged[0]?.sessionId ?? userMessages[0]?.sessionId ?? "retrospective";
193078
193670
  const learnings = parseRetrospectiveLearnings(deepenRun.validated);
193079
- const applied = db.transaction(() => {
193080
- if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
193081
- leaseLost = true;
193082
- return null;
193083
- }
193671
+ const applied = runLeaseGuardedWrite(db, holderId, leaseKey, () => {
193084
193672
  const result = applyRetrospectiveLearnings({
193085
193673
  db,
193086
193674
  projectIdentity,
@@ -193091,7 +193679,7 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
193091
193679
  });
193092
193680
  recordRetrospectiveWindowProcessed(db, projectIdentity, windowKey);
193093
193681
  return result;
193094
- })();
193682
+ });
193095
193683
  if (leaseLost || !applied)
193096
193684
  throw new Error("Dream lease lost during retrospective commit");
193097
193685
  log(`[dreamer] retrospective: flagged=${flagged.length} learnings=${learnings.length} memory=${applied.memoryWritten} observations=${applied.observationsInserted} dropped=${applied.observationsDropped} rejected=${applied.rejected.length}`);
@@ -194613,7 +195201,7 @@ function createTagger() {
194613
195201
  }
194614
195202
  return map2;
194615
195203
  }
194616
- function isUniqueConstraintError(error51) {
195204
+ function isUniqueConstraintError2(error51) {
194617
195205
  return error51 instanceof Error && "code" in error51 && error51.code === "SQLITE_CONSTRAINT_UNIQUE";
194618
195206
  }
194619
195207
  function syncCounterAtLeast(sessionId, db, value) {
@@ -194651,7 +195239,7 @@ function createTagger() {
194651
195239
  getUpsertCounterStatement(db).run(sessionId, next, getHarness());
194652
195240
  })();
194653
195241
  } catch (error51) {
194654
- if (!isUniqueConstraintError(error51)) {
195242
+ if (!isUniqueConstraintError2(error51)) {
194655
195243
  throw error51;
194656
195244
  }
194657
195245
  const racedRow = dbExistingLookup();
@@ -195818,8 +196406,9 @@ function scheduleReconciliation(db, sessionId, readMessages) {
195818
196406
  reconciliationScheduledSessions.add(sessionId);
195819
196407
  defer(() => {
195820
196408
  reconcileSessionIndex(db, sessionId, readMessages).catch((error51) => {
195821
- reconciliationScheduledSessions.delete(sessionId);
195822
196409
  logIndexingError(sessionId, "reconciliation", error51);
196410
+ }).finally(() => {
196411
+ reconciliationScheduledSessions.delete(sessionId);
195823
196412
  });
195824
196413
  });
195825
196414
  }
@@ -195831,13 +196420,17 @@ function scheduleIncrementalIndex(db, sessionId, messageId, readSingleMessage) {
195831
196420
  const timer = setTimeout(() => {
195832
196421
  incrementalTimers.delete(key);
195833
196422
  pendingIncrementalKeys.add(key);
196423
+ let attemptedOrdinal = null;
195834
196424
  runWithSessionLock(sessionId, () => {
195835
196425
  const message = readSingleMessage(sessionId, messageId);
195836
196426
  if (!message) {
195837
196427
  return;
195838
196428
  }
196429
+ attemptedOrdinal = message.ordinal;
195839
196430
  indexSingleMessage(db, sessionId, message);
195840
196431
  }).catch((error51) => {
196432
+ markMessageIndexDirty(db, sessionId, attemptedOrdinal ?? getLastIndexedOrdinal(db, sessionId) + 1);
196433
+ reconciledSessions.delete(sessionId);
195841
196434
  logIndexingError(sessionId, `incremental index for ${messageId}`, error51);
195842
196435
  }).finally(() => {
195843
196436
  pendingIncrementalKeys.delete(key);
@@ -196031,6 +196624,7 @@ init_models_dev_cache();
196031
196624
  // src/hooks/magic-context/channel2-delivery.ts
196032
196625
  init_storage_meta_persisted();
196033
196626
  init_logger();
196627
+ import { randomUUID as randomUUID2 } from "node:crypto";
196034
196628
 
196035
196629
  // src/hooks/magic-context/ctx-reduce-nudge.ts
196036
196630
  init_tag_content_primitives();
@@ -196211,19 +196805,6 @@ ${body}${hintText}
196211
196805
  }
196212
196806
 
196213
196807
  // src/hooks/magic-context/channel2-delivery.ts
196214
- function sealDeliveredAfterUnconfirmedSend(db, sessionId) {
196215
- try {
196216
- if (getChannel2NudgeState(db, sessionId) === "delivered") {
196217
- return "already-delivered";
196218
- }
196219
- } catch {}
196220
- try {
196221
- setChannel2NudgeState(db, sessionId, "delivered");
196222
- return "sealed";
196223
- } catch {
196224
- return "stuck-claimed";
196225
- }
196226
- }
196227
196808
  async function maybeDeliverChannel2(sessionId, deps) {
196228
196809
  let state;
196229
196810
  try {
@@ -196249,7 +196830,8 @@ async function maybeDeliverChannel2(sessionId, deps) {
196249
196830
  const client = deps.client;
196250
196831
  if (!client)
196251
196832
  return false;
196252
- if (!casChannel2NudgeState(deps.db, sessionId, "pending", "claimed")) {
196833
+ const claimToken = randomUUID2();
196834
+ if (!claimChannel2NudgeState(deps.db, sessionId, claimToken)) {
196253
196835
  return false;
196254
196836
  }
196255
196837
  try {
@@ -196273,40 +196855,36 @@ async function maybeDeliverChannel2(sessionId, deps) {
196273
196855
  if (typeof session?.promptAsync !== "function") {
196274
196856
  throw new Error("client has no session.promptAsync");
196275
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
+ }
196276
196863
  await session.promptAsync({ path: { id: sessionId }, body });
196277
196864
  } catch (error51) {
196278
196865
  try {
196279
- casChannel2NudgeState(deps.db, sessionId, "claimed", "pending");
196280
- 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
+ }
196281
196872
  } catch (revertError) {
196282
196873
  sessionLog(sessionId, "channel2 ceiling nudge delivery failed; pending restore was busy so the stale claim will heal later:", { deliveryError: error51, revertError });
196283
196874
  }
196284
196875
  return false;
196285
196876
  }
196286
196877
  try {
196287
- const confirmed = casChannel2NudgeState(deps.db, sessionId, "claimed", "delivered");
196878
+ const confirmed = casChannel2NudgeClaim(deps.db, sessionId, "delivered", claimToken);
196288
196879
  if (confirmed) {
196289
196880
  sessionLog(sessionId, "channel2 ceiling nudge delivered");
196290
196881
  return true;
196291
196882
  }
196292
- const outcome = sealDeliveredAfterUnconfirmedSend(deps.db, sessionId);
196293
- if (outcome === "already-delivered") {
196294
- sessionLog(sessionId, "channel2 ceiling nudge duplicate window: our send returned after a sibling reclaimed the stale lease and already delivered");
196295
- } else if (outcome === "sealed") {
196296
- sessionLog(sessionId, "channel2 ceiling nudge sent but claim confirmation was lost; sealed delivered without an authoritative confirm");
196297
- } else {
196298
- sessionLog(sessionId, "channel2 ceiling nudge sent but claim confirmation was lost; lease stayed claimed and will heal later");
196299
- }
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`);
196300
196885
  return false;
196301
196886
  } catch (error51) {
196302
- const outcome = sealDeliveredAfterUnconfirmedSend(deps.db, sessionId);
196303
- if (outcome === "already-delivered") {
196304
- sessionLog(sessionId, "channel2 ceiling nudge duplicate window: our send returned after a sibling reclaimed the stale lease and already delivered");
196305
- } else if (outcome === "sealed") {
196306
- sessionLog(sessionId, "channel2 ceiling nudge sent but confirm failed:", error51);
196307
- } else {
196308
- sessionLog(sessionId, "channel2 ceiling nudge sent but confirm failed; lease stayed claimed and will heal later:", error51);
196309
- }
196887
+ sessionLog(sessionId, "channel2 ceiling nudge sent but token-confirm failed; lease state left unchanged:", error51);
196310
196888
  return false;
196311
196889
  }
196312
196890
  }
@@ -197736,6 +198314,8 @@ function injectToolPartIntoLatestAssistant(messages, part) {
197736
198314
  continue;
197737
198315
  if (typeof message.info.id !== "string")
197738
198316
  continue;
198317
+ if (!isReplayableAssistantAnchor(message))
198318
+ continue;
197739
198319
  if (hasToolPartWithCallId(message, part.callID)) {
197740
198320
  return message.info.id;
197741
198321
  }
@@ -197750,6 +198330,8 @@ function injectToolPartIntoAssistantById(messages, messageId, part) {
197750
198330
  continue;
197751
198331
  if (message.info.role !== "assistant")
197752
198332
  continue;
198333
+ if (!isReplayableAssistantAnchor(message))
198334
+ return false;
197753
198335
  if (hasToolPartWithCallId(message, part.callID))
197754
198336
  return true;
197755
198337
  message.parts.push(part);
@@ -197769,6 +198351,9 @@ function hasToolPartWithCallId(message, callId) {
197769
198351
  }
197770
198352
  return false;
197771
198353
  }
198354
+ function isReplayableAssistantAnchor(message) {
198355
+ return message.info.error === undefined || message.info.error === null;
198356
+ }
197772
198357
  function appendReminderToUserMessage(message, reminder) {
197773
198358
  for (const part of message.parts) {
197774
198359
  if (!isTextPart(part)) {
@@ -198625,24 +199210,46 @@ function getMessageSearchStatementWithCutoff(db) {
198625
199210
  return stmt;
198626
199211
  }
198627
199212
  var ftsRowCountStatements = new WeakMap;
199213
+ var ftsRowCountStatementsWithCutoff = new WeakMap;
198628
199214
  var ftsMatchCountStatements = new WeakMap;
198629
- function getSessionFtsRowCount(db, sessionId) {
198630
- let stmt = ftsRowCountStatements.get(db);
198631
- if (!stmt) {
198632
- stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
198633
- ftsRowCountStatements.set(db, stmt);
198634
- }
198635
- 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);
198636
199233
  return typeof row?.n === "number" ? row.n : 0;
198637
199234
  }
198638
- function countSessionFtsMatches(db, sessionId, ftsQuery) {
198639
- let stmt = ftsMatchCountStatements.get(db);
198640
- if (!stmt) {
198641
- stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
198642
- ftsMatchCountStatements.set(db, stmt);
198643
- }
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
+ })();
198644
199251
  try {
198645
- const row = stmt.get(sessionId, ftsQuery);
199252
+ const row = cutoff === null ? stmt.get(sessionId, ftsQuery) : stmt.get(sessionId, ftsQuery, cutoff);
198646
199253
  return typeof row?.n === "number" ? row.n : 0;
198647
199254
  } catch {
198648
199255
  return 0;
@@ -198892,7 +199499,7 @@ function searchMessages(args) {
198892
199499
  role: row.role
198893
199500
  }));
198894
199501
  }
198895
- const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
199502
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId, cutoff);
198896
199503
  const queryLists = [];
198897
199504
  if (baseQuery.length > 0) {
198898
199505
  queryLists.push({
@@ -198905,7 +199512,7 @@ function searchMessages(args) {
198905
199512
  const probeQuery = sanitizeFtsQuery(probe);
198906
199513
  if (probeQuery.length === 0)
198907
199514
  continue;
198908
- const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
199515
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery, cutoff);
198909
199516
  const weight = probeDiscriminationWeight(df, corpusSize);
198910
199517
  probeWeights.set(probe, weight);
198911
199518
  queryLists.push({
@@ -200199,8 +200806,31 @@ function readFilePath(target) {
200199
200806
 
200200
200807
  // src/hooks/magic-context/todo-view.ts
200201
200808
  import { createHash as createHash12 } from "node:crypto";
200202
- var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
200203
- 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]);
200204
200834
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
200205
200835
  function normalizeTodoStateJson(todos) {
200206
200836
  if (!Array.isArray(todos))
@@ -200212,7 +200842,7 @@ function normalizeTodoStateJson(todos) {
200212
200842
  normalized.push({
200213
200843
  content: todo.content,
200214
200844
  status: todo.status,
200215
- priority: todo.priority ?? "medium"
200845
+ priority: todo.priority ?? TODO_PRIORITY_MEDIUM
200216
200846
  });
200217
200847
  }
200218
200848
  return JSON.stringify(normalized);
@@ -200256,11 +200886,11 @@ function parseTodoState(stateJson) {
200256
200886
  const result = [];
200257
200887
  for (const item of parsed) {
200258
200888
  if (!isTodoItem(item))
200259
- continue;
200889
+ return null;
200260
200890
  result.push({
200261
200891
  content: item.content,
200262
200892
  status: item.status,
200263
- priority: item.priority ?? "medium"
200893
+ priority: item.priority ?? TODO_PRIORITY_MEDIUM
200264
200894
  });
200265
200895
  }
200266
200896
  return result;
@@ -200268,11 +200898,17 @@ function parseTodoState(stateJson) {
200268
200898
  return null;
200269
200899
  }
200270
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
+ }
200271
200907
  function isTodoItem(value) {
200272
200908
  if (value === null || typeof value !== "object")
200273
200909
  return false;
200274
200910
  const todo = value;
200275
- 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));
200276
200912
  }
200277
200913
 
200278
200914
  // src/hooks/magic-context/tool-reclaim.ts
@@ -200462,7 +201098,7 @@ async function runPostTransformPhase(args) {
200462
201098
  if (args.schedulerDecision === "execute" && !materializationRequested) {
200463
201099
  updateSessionMeta(args.db, args.sessionId, { lastResponseTime: Date.now() });
200464
201100
  }
200465
- const toolReclaimExecutePass = args.schedulerDecision === "execute";
201101
+ const toolReclaimExecutePass = args.schedulerDecision === "execute" || m0HardFoldThisPass;
200466
201102
  const alreadyMutatingThisPass = pendingOpsDidMutate || heuristicOrReasoningDidMutate;
200467
201103
  let autoReclaimTargetCount = 0;
200468
201104
  let autoReclaimDidMutate = false;
@@ -200554,12 +201190,12 @@ async function runPostTransformPhase(args) {
200554
201190
  sessionLog(args.sessionId, "transform failed dropping stale ctx_reduce calls:", error51);
200555
201191
  }
200556
201192
  }
200557
- if (canUseEmptySentinels && args.watermark > 0) {
201193
+ if (canUseEmptySentinels) {
200558
201194
  try {
200559
201195
  const tImg = performance.now();
200560
201196
  const frozenImageIds = getProcessedImageStrippedIds(args.db, args.sessionId);
200561
201197
  const imageResult = stripProcessedImages(args.messages, frozenImageIds, {
200562
- detect: isCacheBustingPass,
201198
+ detect: isCacheBustingPass && args.watermark > 0,
200563
201199
  watermark: args.watermark,
200564
201200
  messageTagNumbers: args.messageTagNumbers
200565
201201
  });
@@ -202125,12 +202761,25 @@ await __promiseAll([
202125
202761
  ]);
202126
202762
  import * as crypto3 from "node:crypto";
202127
202763
  var WAIT_FOR_LEASE_MS = 1000;
202764
+ var MAX_WRAPUP_LEASE_WAIT_MS = 10 * 60 * 1000;
202128
202765
  function sleep2(ms) {
202129
202766
  return new Promise((resolve4) => setTimeout(resolve4, ms));
202130
202767
  }
202131
202768
  function plural(value, word) {
202132
202769
  return `${value} ${word}${value === 1 ? "" : "s"}`;
202133
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
+ }
202134
202783
  function formatAlreadyRunningMessage(state) {
202135
202784
  if (!state)
202136
202785
  return "/ctx-wrapup is already running for this session.";
@@ -202173,44 +202822,55 @@ function emitWrapupProgress(ctx, sessionId, progress) {
202173
202822
  note: progress.note
202174
202823
  });
202175
202824
  }
202176
- async function waitForExistingIncrementalRun(sessionId) {
202825
+ async function waitForExistingIncrementalRun(sessionId, maxWaitMs) {
202177
202826
  const active = getActiveCompartmentRun(sessionId);
202178
202827
  if (!active)
202179
202828
  return "ok";
202180
202829
  if (active.kind === "recomp" || active.kind === "wrapup")
202181
202830
  return "busy";
202182
- try {
202183
- await active.promise;
202184
- } catch {}
202831
+ const outcome = await waitForActiveRunWithin(active.promise, maxWaitMs);
202832
+ if (outcome === "timeout")
202833
+ return "timeout";
202185
202834
  return "ok";
202186
202835
  }
202187
202836
  async function acquireCompartmentLeaseForWrapup(ctx, sessionId, renewWrapupMarker) {
202188
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());
202189
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
+ }
202190
202846
  const active = getActiveCompartmentRun(sessionId);
202191
- if (active?.kind === "recomp" || active?.kind === "wrapup")
202192
- return null;
202847
+ if (active?.kind === "recomp" || active?.kind === "wrapup") {
202848
+ return { ok: false, reason: "busy" };
202849
+ }
202193
202850
  if (active) {
202194
202851
  emitWrapupProgress(ctx, sessionId, { note: "Waiting for the active historian run…" });
202195
- try {
202196
- await active.promise;
202197
- } 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
+ }
202198
202857
  continue;
202199
202858
  }
202200
202859
  const lease = acquireCompartmentLease(ctx.db, sessionId, holderId);
202201
202860
  if (lease)
202202
- return holderId;
202861
+ return { ok: true, holderId };
202203
202862
  emitWrapupProgress(ctx, sessionId, { note: "Waiting for the compartment-state lease…" });
202204
202863
  if (!renewWrapupMarker({}))
202205
- return null;
202206
- await sleep2(WAIT_FOR_LEASE_MS);
202864
+ return { ok: false, reason: "ownership_lost" };
202865
+ await sleep2(Math.min(WAIT_FOR_LEASE_MS, remainingMs()));
202207
202866
  }
202208
202867
  }
202209
202868
  async function runOneWrapupIteration(args) {
202210
202869
  const { ctx, sessionId, plan, messagesToKeep, anchorRawMessageCount } = args;
202211
- const leaseHolderId = await acquireCompartmentLeaseForWrapup(ctx, sessionId, args.renewWrapupMarker);
202212
- if (!leaseHolderId)
202213
- 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;
202214
202874
  const renewal = setInterval(() => {
202215
202875
  try {
202216
202876
  if (!renewCompartmentLease(ctx.db, sessionId, leaseHolderId)) {
@@ -202255,7 +202915,7 @@ async function runOneWrapupIteration(args) {
202255
202915
  registerActiveCompartmentRun(sessionId, runnerPromise, "wrapup");
202256
202916
  try {
202257
202917
  await runnerPromise;
202258
- return true;
202918
+ return { ran: true };
202259
202919
  } finally {
202260
202920
  clearInterval(renewal);
202261
202921
  releaseCompartmentLease(ctx.db, sessionId, leaseHolderId);
@@ -202330,7 +202990,7 @@ ${message2}`;
202330
202990
  }, 60000);
202331
202991
  markerRenewal.unref?.();
202332
202992
  try {
202333
- const activeAtStart = await waitForExistingIncrementalRun(sessionId);
202993
+ const activeAtStart = await waitForExistingIncrementalRun(sessionId, resolveWrapupLeaseWaitTimeout(ctx));
202334
202994
  if (activeAtStart === "busy") {
202335
202995
  const message2 = "Another Magic Context rebuild is already running for this session.";
202336
202996
  setRecompTerminal(ctx.liveSessionState, sessionId, "skipped", message2);
@@ -202338,16 +202998,22 @@ ${message2}`;
202338
202998
 
202339
202999
  ${message2}`;
202340
203000
  }
202341
- try {
202342
- 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));
202343
- } catch {}
202344
- try {
202345
- pushNotification("action", { action: "wrapup-progress-kick" }, sessionId);
202346
- } catch {}
202347
- 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) {
202348
203014
  stoppedForFailure = true;
202349
203015
  stoppedReason = ownershipLostReason;
202350
- } else {
203016
+ } else if (!stoppedForFailure) {
202351
203017
  emitWrapupProgress(ctx, sessionId, {
202352
203018
  processedMessages: Math.max(0, lastEnd),
202353
203019
  totalMessages: Math.max(0, initialPlan.targetEligibleEndOrdinal - 1),
@@ -202394,7 +203060,7 @@ ${message2}`;
202394
203060
  }
202395
203061
  const beforeEnd = lastEnd;
202396
203062
  const beforeFailures = getHistorianFailureState(ctx.db, sessionId).failureCount;
202397
- const ran = await runOneWrapupIteration({
203063
+ const iteration = await runOneWrapupIteration({
202398
203064
  ctx,
202399
203065
  sessionId,
202400
203066
  plan,
@@ -202402,9 +203068,9 @@ ${message2}`;
202402
203068
  anchorRawMessageCount: initialPlan.anchorRawMessageCount,
202403
203069
  renewWrapupMarker
202404
203070
  });
202405
- if (!ran) {
203071
+ if (!iteration.ran) {
202406
203072
  stoppedForFailure = true;
202407
- 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.";
202408
203074
  break;
202409
203075
  }
202410
203076
  const afterEnd = getLastCompartmentEndMessage(ctx.db, sessionId);
@@ -204962,13 +205628,18 @@ function queueMemoryEmbedding(args) {
204962
205628
  if (!snapshot?.enabled) {
204963
205629
  return;
204964
205630
  }
205631
+ const normalizedHash = computeNormalizedHash(args.content);
204965
205632
  (async () => {
204966
205633
  const result = await embedTextForProject(args.projectPath, args.content);
204967
205634
  if (!result) {
204968
205635
  sessionLog(args.sessionId, `memory embedding skipped for memory ${args.memoryId}: provider unavailable or embedding generation failed.`);
204969
205636
  return;
204970
205637
  }
204971
- 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
+ }
204972
205643
  sessionLog(args.sessionId, `proactively embedded memory ${args.memoryId}.`);
204973
205644
  })().catch((error51) => {
204974
205645
  sessionLog(args.sessionId, `memory embedding failed for memory ${args.memoryId}:`, error51);
@@ -205053,8 +205724,9 @@ function createCtxMemoryTool(deps) {
205053
205724
  const isOwn = targetIdentityForStoredPath(memory.projectPath) === projectPath;
205054
205725
  if (isOwn)
205055
205726
  return true;
205056
- return toolShareCategories === null || toolShareCategories.includes(memory.category);
205727
+ return toolShareCategories?.includes(memory.category) ?? false;
205057
205728
  };
205729
+ const memoryOwnedByTool = (memory) => workspaceIdentitySet.identities.length > 1 ? targetIdentityForStoredPath(memory.projectPath) === projectPath : memoryBelongsToProject(memory, projectPath);
205058
205730
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
205059
205731
  if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
205060
205732
  return getDisabledMessage();
@@ -205077,21 +205749,24 @@ function createCtxMemoryTool(deps) {
205077
205749
  updateMemorySeenCount(deps.db, existingMemory.id);
205078
205750
  return `Memory already exists [ID: ${existingMemory.id}] in ${category} (seen count incremented).`;
205079
205751
  }
205080
- const memory = insertMemory(deps.db, {
205752
+ const insertResult = insertMemoryIdempotent(deps.db, {
205081
205753
  projectPath,
205082
205754
  category,
205083
205755
  content,
205084
205756
  sourceSessionId: toolContext.sessionID,
205085
205757
  sourceType: toolContext.agent === DREAMER_AGENT ? "dreamer" : getSourceType(deps)
205086
205758
  });
205759
+ if (!insertResult.inserted) {
205760
+ return `Memory already exists [ID: ${insertResult.memory.id}] in ${category} (seen count incremented).`;
205761
+ }
205087
205762
  queueMemoryEmbedding({
205088
205763
  deps,
205089
205764
  sessionId: toolContext.sessionID,
205090
205765
  projectPath,
205091
- memoryId: memory.id,
205766
+ memoryId: insertResult.memory.id,
205092
205767
  content
205093
205768
  });
205094
- return `Saved memory [ID: ${memory.id}] in ${category}.`;
205769
+ return `Saved memory [ID: ${insertResult.memory.id}] in ${category}.`;
205095
205770
  }
205096
205771
  if (args.action === "list") {
205097
205772
  const limit = normalizeLimit2(args.limit);
@@ -205111,7 +205786,8 @@ function createCtxMemoryTool(deps) {
205111
205786
  }
205112
205787
  const rawProjectPath = projectPathForMemoryId(deps.db, updateId);
205113
205788
  const memory = getMemoryById(deps.db, updateId);
205114
- if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
205789
+ const updateAllowed = memory ? toolContext.agent === DREAMER_AGENT ? memoryVisibleToTool(memory) : memoryOwnedByTool(memory) : false;
205790
+ if (!memory || !rawProjectPath || !updateAllowed) {
205115
205791
  return `Error: Memory with ID ${updateId} was not found.`;
205116
205792
  }
205117
205793
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -205159,7 +205835,7 @@ function createCtxMemoryTool(deps) {
205159
205835
  return "Error: One or more source memories were not found.";
205160
205836
  }
205161
205837
  if (toolContext.agent !== DREAMER_AGENT) {
205162
- const foreign = sourceMemories.find((memory) => !memoryVisibleToTool(memory));
205838
+ const foreign = sourceMemories.find((memory) => !memoryOwnedByTool(memory));
205163
205839
  if (foreign) {
205164
205840
  return `Error: Memory with ID ${foreign.id} was not found.`;
205165
205841
  }
@@ -205182,11 +205858,6 @@ function createCtxMemoryTool(deps) {
205182
205858
  return "Error: A valid category is required when action is 'merge'.";
205183
205859
  }
205184
205860
  const normalizedHash = computeNormalizedHash(content);
205185
- const duplicate = getMemoryByHash(deps.db, projectPath, category, normalizedHash);
205186
- const canonicalExisting = duplicate && ids.includes(duplicate.id) ? duplicate : null;
205187
- if (duplicate && !canonicalExisting) {
205188
- return `Error: Memory content already exists as ID ${duplicate.id}; update or archive existing duplicates instead.`;
205189
- }
205190
205861
  const mergedFrom = JSON.stringify(Array.from(new Set(sourceMemories.flatMap((memory) => {
205191
205862
  let parsed;
205192
205863
  try {
@@ -205202,14 +205873,21 @@ function createCtxMemoryTool(deps) {
205202
205873
  const mergedSeenCount = sourceMemories.reduce((sum, memory) => sum + memory.seenCount, 0);
205203
205874
  const mergedRetrievalCount = sourceMemories.reduce((sum, memory) => sum + memory.retrievalCount, 0);
205204
205875
  const mergedStatus = sourceMemories.some((memory) => memory.status === "permanent") ? "permanent" : "active";
205205
- const canonicalMemory = deps.db.transaction(() => {
205206
- 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, {
205207
205885
  projectPath,
205208
205886
  category,
205209
205887
  content,
205210
205888
  sourceSessionId: toolContext.sessionID,
205211
205889
  sourceType: toolContext.agent === DREAMER_AGENT ? "dreamer" : getSourceType(deps)
205212
- });
205890
+ }).memory;
205213
205891
  const canonicalContentChanged = nextCanonical.content !== content || nextCanonical.normalizedHash !== normalizedHash;
205214
205892
  if (canonicalContentChanged) {
205215
205893
  updateMemoryContentInCurrentTransaction(deps.db, nextCanonical, content, normalizedHash);
@@ -205237,7 +205915,10 @@ function createCtxMemoryTool(deps) {
205237
205915
  });
205238
205916
  }
205239
205917
  return nextCanonical;
205240
- })();
205918
+ });
205919
+ if (mergeConflict || !canonicalMemory) {
205920
+ return mergeConflict ?? "Error: Failed to merge memories.";
205921
+ }
205241
205922
  queueMemoryEmbedding({
205242
205923
  deps,
205243
205924
  sessionId: toolContext.sessionID,
@@ -205258,7 +205939,8 @@ function createCtxMemoryTool(deps) {
205258
205939
  for (const memoryId of archiveIds) {
205259
205940
  const rawProjectPath = projectPathForMemoryId(deps.db, memoryId);
205260
205941
  const memory = getMemoryById(deps.db, memoryId);
205261
- if (!memory || !rawProjectPath || !memoryVisibleToTool(memory)) {
205942
+ const archiveAllowed = memory ? toolContext.agent === DREAMER_AGENT ? memoryVisibleToTool(memory) : memoryOwnedByTool(memory) : false;
205943
+ if (!memory || !rawProjectPath || !archiveAllowed) {
205262
205944
  return `Error: Memory with ID ${memoryId} was not found.`;
205263
205945
  }
205264
205946
  if (toolContext.agent !== DREAMER_AGENT && !isPrimaryMutableMemory(memory)) {
@@ -205713,7 +206395,7 @@ function normalizeLimit3(limit) {
205713
206395
  return Math.max(1, Math.floor(limit));
205714
206396
  }
205715
206397
  function normalizeSources(sources) {
205716
- if (!sources || sources.length === 0)
206398
+ if (sources === undefined)
205717
206399
  return;
205718
206400
  const result = [];
205719
206401
  const seen = new Set;
@@ -205726,7 +206408,7 @@ function normalizeSources(sources) {
205726
206408
  }
205727
206409
  }
205728
206410
  }
205729
- return result.length > 0 ? result : undefined;
206411
+ return result;
205730
206412
  }
205731
206413
  function formatAge2(committedAtMs) {
205732
206414
  const ageMs = Date.now() - committedAtMs;
@@ -205818,7 +206500,7 @@ function createCtxSearchTool(deps) {
205818
206500
  args: {
205819
206501
  query: tool5.schema.string().describe("Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."),
205820
206502
  limit: tool5.schema.number().optional().describe("Maximum results to return (default: 10)"),
205821
- 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.')
205822
206504
  },
205823
206505
  async execute(args, toolContext) {
205824
206506
  const query = args.query?.trim();
@@ -205993,8 +206675,9 @@ function projectHash(directory) {
205993
206675
  function rpcPortDir(storageDir, directory) {
205994
206676
  return join20(storageDir, "rpc", projectHash(directory));
205995
206677
  }
205996
- function rpcPortFilePath(storageDir, directory, pid = process.pid) {
205997
- 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`);
205998
206681
  }
205999
206682
  function isPidAlive(pid) {
206000
206683
  if (!Number.isInteger(pid) || pid <= 0)
@@ -206022,7 +206705,8 @@ function parseRpcPortFile(content, fallbackPid = 0) {
206022
206705
  port: port2,
206023
206706
  pid,
206024
206707
  started_at: Number.isFinite(startedAt) ? startedAt : 0,
206025
- 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
206026
206710
  };
206027
206711
  } catch {
206028
206712
  return null;
@@ -206048,6 +206732,13 @@ function tokensMatch(presented, expected) {
206048
206732
  return false;
206049
206733
  return timingSafeEqual(a, b);
206050
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
+ }
206051
206742
 
206052
206743
  class MagicContextRpcServer {
206053
206744
  server = null;
@@ -206056,16 +206747,18 @@ class MagicContextRpcServer {
206056
206747
  portFilePath;
206057
206748
  portDir;
206058
206749
  startedAt = Date.now();
206750
+ instanceId = randomBytes(8).toString("hex");
206059
206751
  sockets = new Set;
206060
206752
  token = randomBytes(32).toString("hex");
206061
206753
  constructor(storageDir, directory) {
206062
- this.portFilePath = rpcPortFilePath(storageDir, directory);
206754
+ this.portFilePath = rpcPortFilePath(storageDir, directory, process.pid, this.instanceId);
206063
206755
  this.portDir = rpcPortDir(storageDir, directory);
206064
206756
  }
206065
206757
  handle(method, handler) {
206066
206758
  this.handlers.set(method, handler);
206067
206759
  }
206068
206760
  async start() {
206761
+ this.startedAt = Date.now();
206069
206762
  const self2 = this;
206070
206763
  const server = Bun.serve({
206071
206764
  port: 0,
@@ -206108,7 +206801,8 @@ class MagicContextRpcServer {
206108
206801
  port: this.port,
206109
206802
  pid: process.pid,
206110
206803
  started_at: this.startedAt,
206111
- token: this.token
206804
+ token: this.token,
206805
+ instance_id: this.instanceId
206112
206806
  }), { encoding: "utf-8", mode: 384 });
206113
206807
  renameSync5(tmpPath, this.portFilePath);
206114
206808
  try {
@@ -206154,6 +206848,9 @@ class MagicContextRpcServer {
206154
206848
  async handleFetch(req, srv) {
206155
206849
  const url2 = new URL(req.url);
206156
206850
  if (url2.pathname === "/ws") {
206851
+ if (!tokensMatch(websocketToken(req, url2), this.token)) {
206852
+ return new Response("Unauthorized", { status: 401 });
206853
+ }
206157
206854
  const ok = srv.upgrade(req, { data: { authed: false } });
206158
206855
  if (ok)
206159
206856
  return;
@@ -206165,9 +206862,7 @@ class MagicContextRpcServer {
206165
206862
  if (req.method !== "POST" || !url2.pathname.startsWith("/rpc/")) {
206166
206863
  return new Response("Not Found", { status: 404 });
206167
206864
  }
206168
- const auth = req.headers.get("authorization");
206169
- const presented = typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
206170
- if (!tokensMatch(presented, this.token)) {
206865
+ if (!tokensMatch(bearerToken(req), this.token)) {
206171
206866
  return json2({ error: "Unauthorized" }, 401);
206172
206867
  }
206173
206868
  const method = url2.pathname.slice(5);
@@ -206202,6 +206897,8 @@ class MagicContextRpcServer {
206202
206897
  } catch {
206203
206898
  return;
206204
206899
  }
206900
+ if (msg.type !== "hello" && !ws.data.authed)
206901
+ return;
206205
206902
  if (msg.type === "hello") {
206206
206903
  if (!tokensMatch(typeof msg.token === "string" ? msg.token : "", this.token)) {
206207
206904
  ws.send(JSON.stringify({ type: "error", error: "unauthorized" }));
@@ -206214,6 +206911,8 @@ class MagicContextRpcServer {
206214
206911
  }
206215
206912
  ws.data.authed = true;
206216
206913
  ws.data.sessionId = typeof msg.sessionId === "string" && msg.sessionId.length > 0 ? msg.sessionId : undefined;
206914
+ ws.data.unregister?.();
206915
+ ws.data.unregister = undefined;
206217
206916
  const sink = {
206218
206917
  sessionId: ws.data.sessionId,
206219
206918
  send: (notification) => {
@@ -206223,7 +206922,10 @@ class MagicContextRpcServer {
206223
206922
  ws.data.unregister = registerNotificationSink(sink);
206224
206923
  this.sockets.add(ws);
206225
206924
  const lastReceivedId = Number(msg.lastReceivedId ?? 0);
206226
- 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);
206227
206929
  for (const notification of backlog) {
206228
206930
  ws.send(JSON.stringify({ type: "notification", notification }));
206229
206931
  }
@@ -206233,7 +206935,13 @@ class MagicContextRpcServer {
206233
206935
  if (msg.type === "ack") {
206234
206936
  const lastReceivedId = Number(msg.lastReceivedId ?? 0);
206235
206937
  if (Number.isFinite(lastReceivedId) && lastReceivedId > 0) {
206236
- 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
+ }
206237
206945
  }
206238
206946
  }
206239
206947
  }