@cortexkit/pi-magic-context 0.31.3 → 0.31.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14990,12 +14990,41 @@ function pushNotification(type, payload, sessionId) {
14990
14990
  queue = kept.sort((a, b) => a.id - b.id);
14991
14991
  }
14992
14992
  }
14993
- function drainNotifications(lastReceivedId = 0, sessionId) {
14993
+ function cursor(value) {
14994
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
14995
+ }
14996
+ function drainNotifications(lastReceivedId = 0, sessionId, options = {}) {
14997
+ const sessionCursor = cursor(lastReceivedId);
14998
+ if (options.globalOnly) {
14999
+ queue = queue.filter((notification) => notification.sessionId !== undefined || notification.id > sessionCursor);
15000
+ return queue.filter((notification) => notification.sessionId === undefined && notification.id > sessionCursor);
15001
+ }
15002
+ if (options.sessionOnly) {
15003
+ if (sessionId === undefined)
15004
+ return [];
15005
+ queue = queue.filter((notification) => notification.sessionId !== sessionId || notification.id > sessionCursor);
15006
+ return queue.filter((notification) => notification.sessionId === sessionId && notification.id > sessionCursor);
15007
+ }
15008
+ if (sessionId !== undefined && options.globalLastReceivedId !== undefined) {
15009
+ const globalCursor = cursor(options.globalLastReceivedId);
15010
+ queue = queue.filter((notification) => {
15011
+ if (notification.sessionId === undefined)
15012
+ return notification.id > globalCursor;
15013
+ if (notification.sessionId === sessionId)
15014
+ return notification.id > sessionCursor;
15015
+ return true;
15016
+ });
15017
+ return queue.filter((notification) => {
15018
+ if (notification.sessionId === undefined)
15019
+ return notification.id > globalCursor;
15020
+ return notification.sessionId === sessionId && notification.id > sessionCursor;
15021
+ });
15022
+ }
14994
15023
  const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
14995
- if (lastReceivedId > 0) {
14996
- queue = queue.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
15024
+ if (sessionCursor > 0) {
15025
+ queue = queue.filter((notification) => !(notification.id <= sessionCursor && matchesClient(notification)));
14997
15026
  }
14998
- return queue.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
15027
+ return queue.filter((notification) => notification.id > sessionCursor && matchesClient(notification));
14999
15028
  }
15000
15029
  function isTuiConnected(sessionId) {
15001
15030
  if (sinks.size === 0)
@@ -15138,7 +15167,7 @@ var PROMPT_CONTEXT_MESSAGE_LIMIT = 50;
15138
15167
 
15139
15168
  // src/index.ts
15140
15169
  import { createRequire as createRequire3 } from "node:module";
15141
- import { join as join13 } from "node:path";
15170
+ import { join as join13, resolve as resolve3 } from "node:path";
15142
15171
 
15143
15172
  // ../plugin/src/config/agent-disable.ts
15144
15173
  function isDreamerRunnable(config) {
@@ -149215,12 +149244,19 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149215
149244
  let totalTokens = 0;
149216
149245
  let messagesProcessed = 0;
149217
149246
  let lastOrdinal = startOrdinal - 1;
149247
+ let highestScannedOrdinal = startOrdinal - 1;
149218
149248
  let lastMessageId = "";
149219
149249
  let firstMessageId = "";
149220
149250
  let currentBlock = null;
149221
149251
  let pendingNoiseMeta = [];
149222
149252
  let commitClusters = 0;
149223
149253
  let lastFlushedRole = "";
149254
+ function recordFilteredNoise(meta) {
149255
+ pendingNoiseMeta.push(meta);
149256
+ if (!currentBlock) {
149257
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, meta.ordinal);
149258
+ }
149259
+ }
149224
149260
  function flushCurrentBlock() {
149225
149261
  if (!currentBlock)
149226
149262
  return true;
@@ -149236,6 +149272,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149236
149272
  if (!firstMessageId)
149237
149273
  firstMessageId = currentBlock.meta[0]?.messageId ?? "";
149238
149274
  lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
149275
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, lastOrdinal);
149239
149276
  lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
149240
149277
  messagesProcessed += currentBlock.meta.length;
149241
149278
  lines.push(blockText);
@@ -149259,7 +149296,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149259
149296
  if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
149260
149297
  const tcSummaries = extractToolCallSummaries(msg.parts);
149261
149298
  if (tcSummaries.length === 0) {
149262
- pendingNoiseMeta.push(meta);
149299
+ recordFilteredNoise(meta);
149263
149300
  continue;
149264
149301
  }
149265
149302
  const tcText = tcSummaries.join(" / ");
@@ -149291,7 +149328,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149291
149328
  const compacted = compactTextForSummary(allParts.join(" / "), msg.role);
149292
149329
  const text = compacted.text;
149293
149330
  if (!text) {
149294
- pendingNoiseMeta.push(meta);
149331
+ recordFilteredNoise(meta);
149295
149332
  continue;
149296
149333
  }
149297
149334
  const msgHasNarrative = textParts.length > 0;
@@ -149318,7 +149355,9 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149318
149355
  };
149319
149356
  pendingNoiseMeta = [];
149320
149357
  }
149321
- flushCurrentBlock();
149358
+ if (flushCurrentBlock() && pendingNoiseMeta.length > 0) {
149359
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, pendingNoiseMeta[pendingNoiseMeta.length - 1]?.ordinal ?? highestScannedOrdinal);
149360
+ }
149322
149361
  const toolOnlyRanges = [];
149323
149362
  for (const range of flushedToolOnlyBlocks) {
149324
149363
  const last = toolOnlyRanges[toolOnlyRanges.length - 1];
@@ -149335,7 +149374,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149335
149374
  endMessageId: lastMessageId,
149336
149375
  messageCount: messagesProcessed,
149337
149376
  tokenEstimate: totalTokens,
149338
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
149377
+ hasMore: Math.max(lastOrdinal, highestScannedOrdinal) < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
149339
149378
  text: lines.join(`
149340
149379
  `),
149341
149380
  lines: lineMeta,
@@ -149370,7 +149409,10 @@ function clearCompressionDepthRange(db, sessionId, startOrdinal, endOrdinal) {
149370
149409
  var lastIndexedStatements = new WeakMap;
149371
149410
  var insertMessageStatements = new WeakMap;
149372
149411
  var upsertIndexStatements = new WeakMap;
149412
+ var upsertCleanIndexStatements = new WeakMap;
149413
+ var upsertDirtyFloorStatements = new WeakMap;
149373
149414
  var deleteFtsStatements = new WeakMap;
149415
+ var deleteFtsFromOrdinalStatements = new WeakMap;
149374
149416
  var deleteIndexStatements = new WeakMap;
149375
149417
  var countIndexedMessageStatements = new WeakMap;
149376
149418
  function normalizeIndexText(text) {
@@ -149379,7 +149421,7 @@ function normalizeIndexText(text) {
149379
149421
  function getLastIndexedStatement(db) {
149380
149422
  let stmt = lastIndexedStatements.get(db);
149381
149423
  if (!stmt) {
149382
- stmt = db.prepare("SELECT last_indexed_ordinal FROM message_history_index WHERE session_id = ?");
149424
+ stmt = db.prepare("SELECT last_indexed_ordinal, dirty_floor_ordinal FROM message_history_index WHERE session_id = ?");
149383
149425
  lastIndexedStatements.set(db, stmt);
149384
149426
  }
149385
149427
  return stmt;
@@ -149400,6 +149442,22 @@ function getUpsertIndexStatement(db) {
149400
149442
  }
149401
149443
  return stmt;
149402
149444
  }
149445
+ function getUpsertCleanIndexStatement(db) {
149446
+ let stmt = upsertCleanIndexStatements.get(db);
149447
+ if (!stmt) {
149448
+ 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");
149449
+ upsertCleanIndexStatements.set(db, stmt);
149450
+ }
149451
+ return stmt;
149452
+ }
149453
+ function getUpsertDirtyFloorStatement(db) {
149454
+ let stmt = upsertDirtyFloorStatements.get(db);
149455
+ if (!stmt) {
149456
+ 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");
149457
+ upsertDirtyFloorStatements.set(db, stmt);
149458
+ }
149459
+ return stmt;
149460
+ }
149403
149461
  function getDeleteFtsStatement(db) {
149404
149462
  let stmt = deleteFtsStatements.get(db);
149405
149463
  if (!stmt) {
@@ -149408,6 +149466,14 @@ function getDeleteFtsStatement(db) {
149408
149466
  }
149409
149467
  return stmt;
149410
149468
  }
149469
+ function getDeleteFtsFromOrdinalStatement(db) {
149470
+ let stmt = deleteFtsFromOrdinalStatements.get(db);
149471
+ if (!stmt) {
149472
+ stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ? AND CAST(message_ordinal AS INTEGER) >= ?");
149473
+ deleteFtsFromOrdinalStatements.set(db, stmt);
149474
+ }
149475
+ return stmt;
149476
+ }
149411
149477
  function getDeleteIndexStatement(db) {
149412
149478
  let stmt = deleteIndexStatements.get(db);
149413
149479
  if (!stmt) {
@@ -149428,6 +149494,14 @@ function getLastIndexedOrdinal(db, sessionId) {
149428
149494
  const row = getLastIndexedStatement(db).get(sessionId);
149429
149495
  return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
149430
149496
  }
149497
+ function getDirtyIndexFloor(db, sessionId) {
149498
+ const row = getLastIndexedStatement(db).get(sessionId);
149499
+ return typeof row?.dirty_floor_ordinal === "number" && row.dirty_floor_ordinal > 0 ? row.dirty_floor_ordinal : null;
149500
+ }
149501
+ function markMessageIndexDirty(db, sessionId, floorOrdinal) {
149502
+ const dirtyFloor = Math.max(1, Math.floor(floorOrdinal));
149503
+ getUpsertDirtyFloorStatement(db).run(sessionId, getLastIndexedOrdinal(db, sessionId), dirtyFloor, Date.now(), getHarness());
149504
+ }
149431
149505
  function isMessageAlreadyIndexed(db, sessionId, messageId) {
149432
149506
  const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
149433
149507
  return (typeof row?.count === "number" ? row.count : 0) > 0;
@@ -149495,7 +149569,15 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
149495
149569
  db.exec("BEGIN IMMEDIATE");
149496
149570
  let committed = false;
149497
149571
  try {
149498
- const effectiveWatermark = Math.max(lastIndexedOrdinal, getLastIndexedOrdinal(db, sessionId));
149572
+ let effectiveWatermark = Math.max(lastIndexedOrdinal, getLastIndexedOrdinal(db, sessionId));
149573
+ const dirtyFloor = getDirtyIndexFloor(db, sessionId);
149574
+ if (dirtyFloor !== null) {
149575
+ const rewindOrdinal = Math.max(1, Math.min(dirtyFloor, finalWatermark + 1));
149576
+ if (rewindOrdinal <= finalWatermark) {
149577
+ getDeleteFtsFromOrdinalStatement(db).run(sessionId, rewindOrdinal);
149578
+ }
149579
+ effectiveWatermark = Math.min(effectiveWatermark, rewindOrdinal - 1);
149580
+ }
149499
149581
  const insertMessage = getInsertMessageStatement(db);
149500
149582
  for (const message of messages) {
149501
149583
  if (message.ordinal <= effectiveWatermark) {
@@ -149512,7 +149594,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
149512
149594
  inserted++;
149513
149595
  }
149514
149596
  const newWatermark = Math.max(effectiveWatermark, finalWatermark);
149515
- getUpsertIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
149597
+ getUpsertCleanIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
149516
149598
  db.exec("COMMIT");
149517
149599
  committed = true;
149518
149600
  } finally {
@@ -149609,8 +149691,9 @@ function scheduleReconciliation(db, sessionId, readMessages) {
149609
149691
  reconciliationScheduledSessions.add(sessionId);
149610
149692
  defer(() => {
149611
149693
  reconcileSessionIndex(db, sessionId, readMessages).catch((error) => {
149612
- reconciliationScheduledSessions.delete(sessionId);
149613
149694
  logIndexingError(sessionId, "reconciliation", error);
149695
+ }).finally(() => {
149696
+ reconciliationScheduledSessions.delete(sessionId);
149614
149697
  });
149615
149698
  });
149616
149699
  }
@@ -149622,13 +149705,17 @@ function scheduleIncrementalIndex(db, sessionId, messageId, readSingleMessage) {
149622
149705
  const timer = setTimeout(() => {
149623
149706
  incrementalTimers.delete(key);
149624
149707
  pendingIncrementalKeys.add(key);
149708
+ let attemptedOrdinal = null;
149625
149709
  runWithSessionLock(sessionId, () => {
149626
149710
  const message = readSingleMessage(sessionId, messageId);
149627
149711
  if (!message) {
149628
149712
  return;
149629
149713
  }
149714
+ attemptedOrdinal = message.ordinal;
149630
149715
  indexSingleMessage(db, sessionId, message);
149631
149716
  }).catch((error) => {
149717
+ markMessageIndexDirty(db, sessionId, attemptedOrdinal ?? getLastIndexedOrdinal(db, sessionId) + 1);
149718
+ reconciledSessions.delete(sessionId);
149632
149719
  logIndexingError(sessionId, `incremental index for ${messageId}`, error);
149633
149720
  }).finally(() => {
149634
149721
  pendingIncrementalKeys.delete(key);
@@ -150580,6 +150667,7 @@ function healNullTextColumns(db) {
150580
150667
  ["cache_ttl", ""],
150581
150668
  ["last_nudge_band", ""],
150582
150669
  ["last_nudge_level", ""],
150670
+ ["channel2_nudge_claim_token", ""],
150583
150671
  ["last_transform_error", ""],
150584
150672
  ["nudge_anchor_message_id", ""],
150585
150673
  ["nudge_anchor_text", ""],
@@ -150643,6 +150731,7 @@ function healNullIntegerColumns(db) {
150643
150731
  }
150644
150732
 
150645
150733
  // ../plugin/src/features/magic-context/workspaces.ts
150734
+ init_logger();
150646
150735
  import { createHash as createHash2 } from "node:crypto";
150647
150736
 
150648
150737
  // ../plugin/src/features/magic-context/memory/constants.ts
@@ -150693,6 +150782,7 @@ var CATEGORY_DEFAULT_TTL = {
150693
150782
  };
150694
150783
  // ../plugin/src/features/magic-context/workspaces.ts
150695
150784
  var VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
150785
+ var DEFAULT_WORKSPACE_SHARE_CATEGORIES = ["CONSTRAINTS"];
150696
150786
  function tableExists(db, tableName) {
150697
150787
  const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
150698
150788
  return Boolean(row);
@@ -150707,23 +150797,39 @@ function uniqueSorted(values) {
150707
150797
  function placeholders(values) {
150708
150798
  return values.map(() => "?").join(", ");
150709
150799
  }
150800
+ function defaultWorkspaceShareCategories() {
150801
+ return [...DEFAULT_WORKSPACE_SHARE_CATEGORIES];
150802
+ }
150803
+ function warnInvalidShareCategories(reason, raw) {
150804
+ log("[magic-context] WARN: invalid workspace share_categories; sharing no foreign memory categories", {
150805
+ reason,
150806
+ raw
150807
+ });
150808
+ }
150710
150809
  function normalizeShareCategories(raw) {
150711
- if (raw === null || raw === undefined)
150712
- return null;
150713
- if (typeof raw !== "string")
150714
- return null;
150810
+ if (raw === null || raw === undefined) {
150811
+ return defaultWorkspaceShareCategories();
150812
+ }
150813
+ if (typeof raw !== "string") {
150814
+ warnInvalidShareCategories("not a string", raw);
150815
+ return [];
150816
+ }
150715
150817
  let parsed;
150716
150818
  try {
150717
150819
  parsed = JSON.parse(raw);
150718
150820
  } catch {
150719
- return null;
150821
+ warnInvalidShareCategories("malformed JSON", raw);
150822
+ return [];
150823
+ }
150824
+ if (!Array.isArray(parsed)) {
150825
+ warnInvalidShareCategories("not a JSON array", raw);
150826
+ return [];
150720
150827
  }
150721
- if (!Array.isArray(parsed))
150722
- return null;
150723
150828
  const categories = [];
150724
150829
  for (const value of parsed) {
150725
150830
  if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
150726
- return null;
150831
+ warnInvalidShareCategories("unknown category", raw);
150832
+ return [];
150727
150833
  }
150728
150834
  if (!categories.includes(value))
150729
150835
  categories.push(value);
@@ -150732,16 +150838,33 @@ function normalizeShareCategories(raw) {
150732
150838
  }
150733
150839
  function selectWorkspaceShareCategories(db, identities) {
150734
150840
  const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
150735
- if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
150841
+ if (candidates.length === 0 || !tableExists(db, "workspace_members")) {
150736
150842
  return null;
150737
150843
  }
150844
+ const hasMembership = Boolean(db.prepare(`SELECT 1
150845
+ FROM workspace_members
150846
+ WHERE project_path IN (${placeholders(candidates)})
150847
+ LIMIT 1`).get(...candidates));
150848
+ if (!hasMembership)
150849
+ return null;
150850
+ if (!tableExists(db, "workspaces")) {
150851
+ log("[magic-context] WARN: workspace member has no workspaces table; sharing no foreign memory categories");
150852
+ return [];
150853
+ }
150854
+ if (!columnExists(db, "workspaces", "share_categories")) {
150855
+ return defaultWorkspaceShareCategories();
150856
+ }
150738
150857
  const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
150739
150858
  FROM workspace_members AS member
150740
150859
  JOIN workspaces AS workspace ON workspace.id = member.workspace_id
150741
150860
  WHERE member.project_path IN (${placeholders(candidates)})
150742
150861
  ORDER BY workspace.id ASC
150743
150862
  LIMIT 1`).get(...candidates);
150744
- return normalizeShareCategories(row?.shareCategories ?? null);
150863
+ if (!row) {
150864
+ log("[magic-context] WARN: workspace member has no workspace share_categories row; sharing no foreign memory categories");
150865
+ return [];
150866
+ }
150867
+ return normalizeShareCategories(row.shareCategories);
150745
150868
  }
150746
150869
  function resolveWorkspaceShareCategories(db, projectIdentity) {
150747
150870
  return selectWorkspaceShareCategories(db, [projectIdentity]);
@@ -150846,7 +150969,7 @@ function computeWorkspaceEpochFingerprint(db, identities) {
150846
150969
  const hash = createHash2("sha256");
150847
150970
  hash.update("share_categories", "utf8");
150848
150971
  hash.update("\x00");
150849
- hash.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
150972
+ hash.update(shareCategories === null ? "NO_WORKSPACE" : JSON.stringify(shareCategories), "utf8");
150850
150973
  hash.update(`
150851
150974
  `);
150852
150975
  for (const identity of canonical) {
@@ -153418,6 +153541,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153418
153541
  CREATE TABLE IF NOT EXISTS message_history_index (
153419
153542
  session_id TEXT PRIMARY KEY,
153420
153543
  last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
153544
+ dirty_floor_ordinal INTEGER NOT NULL DEFAULT 0,
153421
153545
  updated_at INTEGER NOT NULL,
153422
153546
  harness TEXT NOT NULL DEFAULT 'opencode'
153423
153547
  );
@@ -153447,6 +153571,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153447
153571
  last_nudge_level TEXT DEFAULT '',
153448
153572
  channel2_nudge_state TEXT DEFAULT '',
153449
153573
  channel2_nudge_claimed_at INTEGER DEFAULT 0,
153574
+ channel2_nudge_claim_token TEXT DEFAULT '',
153450
153575
  last_emergency_input_sample INTEGER DEFAULT 0,
153451
153576
  last_transform_error TEXT DEFAULT '',
153452
153577
  nudge_anchor_message_id TEXT DEFAULT '',
@@ -153693,6 +153818,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153693
153818
  ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
153694
153819
  ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
153695
153820
  ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
153821
+ ensureColumn(db, "session_meta", "channel2_nudge_claim_token", "TEXT DEFAULT ''");
153696
153822
  ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
153697
153823
  ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
153698
153824
  ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
@@ -153905,6 +154031,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153905
154031
  ON transform_decisions(session_id, harness);
153906
154032
  `);
153907
154033
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
154034
+ ensureColumn(db, "message_history_index", "dirty_floor_ordinal", "INTEGER NOT NULL DEFAULT 0");
153908
154035
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
153909
154036
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
153910
154037
  ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -153920,7 +154047,7 @@ var CHANNEL2_CLAIM_TTL_MS = 120000;
153920
154047
  function healWedgedChannel2Claims(db) {
153921
154048
  try {
153922
154049
  const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
153923
- 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);
154050
+ 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);
153924
154051
  } catch {}
153925
154052
  }
153926
154053
  function openDatabase(dbPathOrOptions) {
@@ -154613,7 +154740,7 @@ function setChannel2NudgeState(db, sessionId, state) {
154613
154740
  db.transaction(() => {
154614
154741
  ensureSessionMetaRow(db, sessionId);
154615
154742
  const claimedAt = state === "claimed" ? Date.now() : 0;
154616
- db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ?").run(state, claimedAt, sessionId);
154743
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ?, channel2_nudge_claim_token = '' WHERE session_id = ?").run(state, claimedAt, sessionId);
154617
154744
  })();
154618
154745
  }
154619
154746
  function casChannel2NudgeState(db, sessionId, from, to) {
@@ -154621,7 +154748,7 @@ function casChannel2NudgeState(db, sessionId, from, to) {
154621
154748
  db.transaction(() => {
154622
154749
  ensureSessionMetaRow(db, sessionId);
154623
154750
  const claimedAt = to === "claimed" ? Date.now() : 0;
154624
- 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);
154751
+ 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);
154625
154752
  changed = (result.changes ?? 0) > 0;
154626
154753
  })();
154627
154754
  return changed;
@@ -156833,8 +156960,31 @@ function clearNoteNudgeTriggerOnly(db, sessionId) {
156833
156960
 
156834
156961
  // ../plugin/src/hooks/magic-context/todo-view.ts
156835
156962
  import { createHash as createHash5 } from "node:crypto";
156836
- var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
156837
- var TITLE_DONE_STATUSES = new Set(["completed"]);
156963
+ var TODO_STATUS_PENDING = "pending";
156964
+ var TODO_STATUS_IN_PROGRESS = "in_progress";
156965
+ var TODO_STATUS_COMPLETED = "completed";
156966
+ var TODO_STATUS_CANCELLED = "cancelled";
156967
+ var TODO_PRIORITY_HIGH = "high";
156968
+ var TODO_PRIORITY_MEDIUM = "medium";
156969
+ var TODO_PRIORITY_LOW = "low";
156970
+ var TODO_STATUSES = [
156971
+ TODO_STATUS_PENDING,
156972
+ TODO_STATUS_IN_PROGRESS,
156973
+ TODO_STATUS_COMPLETED,
156974
+ TODO_STATUS_CANCELLED
156975
+ ];
156976
+ var TODO_PRIORITIES = [
156977
+ TODO_PRIORITY_HIGH,
156978
+ TODO_PRIORITY_MEDIUM,
156979
+ TODO_PRIORITY_LOW
156980
+ ];
156981
+ var TODO_STATUS_SET = new Set(TODO_STATUSES);
156982
+ var TODO_PRIORITY_SET = new Set(TODO_PRIORITIES);
156983
+ var TERMINAL_STATUSES = new Set([
156984
+ TODO_STATUS_COMPLETED,
156985
+ TODO_STATUS_CANCELLED
156986
+ ]);
156987
+ var TITLE_DONE_STATUSES = new Set([TODO_STATUS_COMPLETED]);
156838
156988
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
156839
156989
  function normalizeTodoStateJson(todos) {
156840
156990
  if (!Array.isArray(todos))
@@ -156846,7 +156996,7 @@ function normalizeTodoStateJson(todos) {
156846
156996
  normalized.push({
156847
156997
  content: todo.content,
156848
156998
  status: todo.status,
156849
- priority: todo.priority ?? "medium"
156999
+ priority: todo.priority ?? TODO_PRIORITY_MEDIUM
156850
157000
  });
156851
157001
  }
156852
157002
  return JSON.stringify(normalized);
@@ -156890,11 +157040,11 @@ function parseTodoState(stateJson) {
156890
157040
  const result = [];
156891
157041
  for (const item of parsed) {
156892
157042
  if (!isTodoItem(item))
156893
- continue;
157043
+ return null;
156894
157044
  result.push({
156895
157045
  content: item.content,
156896
157046
  status: item.status,
156897
- priority: item.priority ?? "medium"
157047
+ priority: item.priority ?? TODO_PRIORITY_MEDIUM
156898
157048
  });
156899
157049
  }
156900
157050
  return result;
@@ -156902,11 +157052,17 @@ function parseTodoState(stateJson) {
156902
157052
  return null;
156903
157053
  }
156904
157054
  }
157055
+ function isTodoStatus(value) {
157056
+ return typeof value === "string" && TODO_STATUS_SET.has(value);
157057
+ }
157058
+ function isTodoPriority(value) {
157059
+ return typeof value === "string" && TODO_PRIORITY_SET.has(value);
157060
+ }
156905
157061
  function isTodoItem(value) {
156906
157062
  if (value === null || typeof value !== "object")
156907
157063
  return false;
156908
157064
  const todo = value;
156909
- return typeof todo.content === "string" && typeof todo.status === "string" && (todo.priority === undefined || typeof todo.priority === "string");
157065
+ return typeof todo.content === "string" && isTodoStatus(todo.status) && (todo.priority === undefined || isTodoPriority(todo.priority));
156910
157066
  }
156911
157067
 
156912
157068
  // ../plugin/src/hooks/magic-context/upgrade-reminder.ts
@@ -158585,6 +158741,22 @@ function extractLatestAssistantText(messages) {
158585
158741
  return getTextParts(latest).map((part) => part.text).join(`
158586
158742
  `) || null;
158587
158743
  }
158744
+ function hasLengthCappedOutput(value) {
158745
+ if (Array.isArray(value))
158746
+ return value.some((item) => hasLengthCappedOutput(item));
158747
+ if (!isRecord(value))
158748
+ return false;
158749
+ if (value.length_capped === true || value.lengthCapped === true)
158750
+ return true;
158751
+ const finishReason = value.finish_reason ?? value.finishReason;
158752
+ if (typeof finishReason === "string") {
158753
+ const normalized = finishReason.toLowerCase();
158754
+ if (normalized === "length" || normalized === "max_tokens" || normalized === "max_output_tokens") {
158755
+ return true;
158756
+ }
158757
+ }
158758
+ return Object.values(value).some((item) => hasLengthCappedOutput(item));
158759
+ }
158588
158760
 
158589
158761
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
158590
158762
  init_logger();
@@ -174637,6 +174809,7 @@ class OpenAICompatibleEmbeddingProvider {
174637
174809
 
174638
174810
  // ../plugin/src/features/magic-context/memory/storage-memory-embeddings.ts
174639
174811
  var saveEmbeddingStatements = new WeakMap;
174812
+ var saveEmbeddingIfHashMatchesStatements = new WeakMap;
174640
174813
  var loadAllEmbeddingsStatements = new WeakMap;
174641
174814
  var deleteEmbeddingStatements = new WeakMap;
174642
174815
  var getStoredModelIdStatements = new WeakMap;
@@ -174667,6 +174840,14 @@ function getSaveEmbeddingStatement(db) {
174667
174840
  }
174668
174841
  return stmt;
174669
174842
  }
174843
+ function getSaveEmbeddingIfHashMatchesStatement(db) {
174844
+ let stmt = saveEmbeddingIfHashMatchesStatements.get(db);
174845
+ if (!stmt) {
174846
+ 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");
174847
+ saveEmbeddingIfHashMatchesStatements.set(db, stmt);
174848
+ }
174849
+ return stmt;
174850
+ }
174670
174851
  function getLoadAllEmbeddingsStatement(db) {
174671
174852
  let stmt = loadAllEmbeddingsStatements.get(db);
174672
174853
  if (!stmt) {
@@ -174695,6 +174876,10 @@ function saveEmbedding(db, memoryId, embedding, modelId) {
174695
174876
  const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
174696
174877
  getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
174697
174878
  }
174879
+ function saveEmbeddingIfHashMatches(db, memoryId, embedding, modelId, normalizedHash) {
174880
+ const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
174881
+ return getSaveEmbeddingIfHashMatchesStatement(db).run(memoryId, blob, modelId, memoryId, normalizedHash).changes > 0;
174882
+ }
174698
174883
  function loadAllEmbeddings(db, projectPath, modelId) {
174699
174884
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath, modelId).filter(isEmbeddingRow);
174700
174885
  const embeddings = new Map;
@@ -175555,12 +175740,12 @@ function isUnembeddedMemoryRow(row) {
175555
175740
  if (row === null || typeof row !== "object")
175556
175741
  return false;
175557
175742
  const candidate = row;
175558
- return typeof candidate.id === "number" && typeof candidate.content === "string";
175743
+ return typeof candidate.id === "number" && typeof candidate.content === "string" && typeof candidate.normalized_hash === "string";
175559
175744
  }
175560
175745
  function getLoadUnembeddedMemoriesStatement(db) {
175561
175746
  let stmt = loadUnembeddedMemoriesStatements.get(db);
175562
175747
  if (!stmt) {
175563
- 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 ?");
175748
+ 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 ?");
175564
175749
  loadUnembeddedMemoriesStatements.set(db, stmt);
175565
175750
  }
175566
175751
  return stmt;
@@ -175583,8 +175768,9 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
175583
175768
  const embedding = result.vectors[index];
175584
175769
  if (!embedding)
175585
175770
  continue;
175586
- saveEmbedding(db, memory.id, embedding, result.modelId);
175587
- embeddedCount += 1;
175771
+ if (saveEmbeddingIfHashMatches(db, memory.id, embedding, result.modelId, memory.normalized_hash)) {
175772
+ embeddedCount += 1;
175773
+ }
175588
175774
  }
175589
175775
  })();
175590
175776
  return embeddedCount;
@@ -176286,25 +176472,33 @@ function getMemoryCountsByStatusStatement(db) {
176286
176472
  }
176287
176473
  return stmt;
176288
176474
  }
176289
- function insertMemory(db, input) {
176290
- const now = Date.now();
176291
- const normalizedHash = computeNormalizedHash(input.content);
176475
+ function buildInsertMemoryValues(input, normalizedHash, now, includeImportance) {
176292
176476
  const insertValues = [
176293
176477
  input.projectPath,
176294
176478
  input.category,
176295
176479
  input.content,
176296
176480
  normalizedHash
176297
176481
  ];
176298
- if (hasMemoryImportanceColumn(db)) {
176482
+ if (includeImportance) {
176299
176483
  insertValues.push(input.importance ?? 50);
176300
176484
  }
176301
176485
  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);
176302
- const result = getInsertMemoryStatement(db).run(...insertValues);
176303
- const insertedResult = result;
176304
- const inserted = getMemoryById(db, Number(insertedResult.lastInsertRowid));
176486
+ return insertValues;
176487
+ }
176488
+ function loadInsertedMemory(db, rowid) {
176489
+ const inserted = getMemoryById(db, Number(rowid));
176305
176490
  if (!inserted) {
176306
176491
  throw new Error("Failed to load inserted memory row");
176307
176492
  }
176493
+ return inserted;
176494
+ }
176495
+ function insertMemory(db, input) {
176496
+ const now = Date.now();
176497
+ const normalizedHash = computeNormalizedHash(input.content);
176498
+ const insertValues = buildInsertMemoryValues(input, normalizedHash, now, hasMemoryImportanceColumn(db));
176499
+ const result = getInsertMemoryStatement(db).run(...insertValues);
176500
+ const insertedResult = result;
176501
+ const inserted = loadInsertedMemory(db, insertedResult.lastInsertRowid);
176308
176502
  invalidateProject(input.projectPath);
176309
176503
  return inserted;
176310
176504
  }
@@ -177098,6 +177292,14 @@ function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177098
177292
  return true;
177099
177293
  });
177100
177294
  }
177295
+ function runLeaseGuardedWrite(db, holderId, leaseKey, fn) {
177296
+ return runImmediate2(db, () => {
177297
+ if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
177298
+ throw new Error("Dream lease lost before guarded write");
177299
+ }
177300
+ return fn();
177301
+ });
177302
+ }
177101
177303
  var LEASE_HEARTBEAT_INTERVAL_MS = 60 * 1000;
177102
177304
  function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalMs = LEASE_HEARTBEAT_INTERVAL_MS) {
177103
177305
  let lost = false;
@@ -177892,6 +178094,39 @@ function hasShareabilitySensitiveText(text) {
177892
178094
  }
177893
178095
  }
177894
178096
 
178097
+ // ../plugin/src/features/magic-context/dreamer/manifest-parser.ts
178098
+ function extractCompleteManifestBody(text, rootName) {
178099
+ const escapedRoot = rootName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
178100
+ const rootMatch = new RegExp(`<${escapedRoot}\\b[^>]*>([\\s\\S]*?)<\\/${escapedRoot}>`, "i").exec(text);
178101
+ if (rootMatch)
178102
+ return rootMatch[1];
178103
+ const hasOpenRoot = new RegExp(`<${escapedRoot}\\b`, "i").test(text);
178104
+ const hasCloseRoot = new RegExp(`<\\/${escapedRoot}>`, "i").test(text);
178105
+ if (hasOpenRoot && !hasCloseRoot) {
178106
+ throw new Error(`${rootName} manifest missing closing root tag`);
178107
+ }
178108
+ throw new Error(`${rootName} manifest missing complete root element`);
178109
+ }
178110
+ function assertNoDuplicateManifestIds(ids, rootName) {
178111
+ const seen = new Set;
178112
+ for (const id of ids) {
178113
+ if (seen.has(id))
178114
+ throw new Error(`${rootName} manifest contains duplicate id ${id}`);
178115
+ seen.add(id);
178116
+ }
178117
+ }
178118
+ function assertManifestCoversExactly(ids, expectedIds, rootName) {
178119
+ assertNoDuplicateManifestIds(ids, rootName);
178120
+ for (const id of ids) {
178121
+ if (!expectedIds.has(id))
178122
+ throw new Error(`${rootName} manifest contains unknown id ${id}`);
178123
+ }
178124
+ for (const id of expectedIds) {
178125
+ if (!ids.includes(id))
178126
+ throw new Error(`${rootName} manifest missing id ${id}`);
178127
+ }
178128
+ }
178129
+
177895
178130
  // ../plugin/src/features/magic-context/dreamer/classify-prompt.ts
177896
178131
  var SCORING_GUIDANCE = `### How to score importance (1-100)
177897
178132
  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.
@@ -177958,14 +178193,15 @@ ${renderPool(args.memories)}`;
177958
178193
  var SCOPES = new Set(["project", "ecosystem", "universe"]);
177959
178194
  function parseClassifyManifest(text) {
177960
178195
  const out = [];
177961
- for (const m of text.matchAll(/<memory\b([^>]*)\/?>/g)) {
178196
+ const body = extractCompleteManifestBody(text, "classify");
178197
+ for (const m of body.matchAll(/<memory\b([^>]*)\/?>/g)) {
177962
178198
  const attrs = m[1];
177963
178199
  const idMatch = attrs.match(/\bid\s*=\s*"(\d+)"/);
177964
178200
  if (!idMatch)
177965
- continue;
178201
+ throw new Error("classify manifest entry missing numeric id");
177966
178202
  const id = Number.parseInt(idMatch[1], 10);
177967
178203
  if (!Number.isInteger(id))
177968
- continue;
178204
+ throw new Error("classify manifest entry missing numeric id");
177969
178205
  const entry = { id };
177970
178206
  const impMatch = attrs.match(/\bimportance\s*=\s*"(\d+)"/);
177971
178207
  if (impMatch) {
@@ -177974,18 +178210,23 @@ function parseClassifyManifest(text) {
177974
178210
  entry.importance = Math.max(1, Math.min(100, imp));
177975
178211
  }
177976
178212
  const scopeMatch = attrs.match(/\bscope\s*=\s*"([a-z]+)"/i);
177977
- if (scopeMatch && SCOPES.has(scopeMatch[1].toLowerCase())) {
177978
- entry.scope = scopeMatch[1].toLowerCase();
178213
+ if (scopeMatch) {
178214
+ const scope = scopeMatch[1].toLowerCase();
178215
+ if (!SCOPES.has(scope))
178216
+ throw new Error(`classify manifest invalid scope ${scope}`);
178217
+ entry.scope = scope;
177979
178218
  }
177980
178219
  const shareMatch = attrs.match(/\bshareable\s*=\s*"(true|false|1|0)"/i);
177981
178220
  if (shareMatch) {
177982
178221
  const v = shareMatch[1].toLowerCase();
177983
178222
  entry.shareable = v === "true" || v === "1";
177984
178223
  }
177985
- if (entry.importance !== undefined || entry.scope || entry.shareable !== undefined) {
177986
- out.push(entry);
178224
+ if (entry.importance === undefined && !entry.scope && entry.shareable === undefined) {
178225
+ throw new Error(`classify manifest entry ${id} missing classification fields`);
177987
178226
  }
178227
+ out.push(entry);
177988
178228
  }
178229
+ assertNoDuplicateManifestIds(out.map((entry) => entry.id), "classify");
177989
178230
  return out;
177990
178231
  }
177991
178232
 
@@ -178120,9 +178361,13 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
178120
178361
  });
178121
178362
  },
178122
178363
  validateOutput: (messages) => {
178364
+ if (hasLengthCappedOutput(messages)) {
178365
+ throw new Error("classify returned length-capped output");
178366
+ }
178123
178367
  const text = extractLatestAssistantText(messages);
178124
178368
  if (!text)
178125
178369
  throw new Error("classify returned no output");
178370
+ parseClassifyManifest(text);
178126
178371
  return text;
178127
178372
  }
178128
178373
  });
@@ -178148,17 +178393,13 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
178148
178393
  }
178149
178394
  function applyClassifications(args, chunk, manifestText) {
178150
178395
  const byId = new Map(chunk.map((m) => [m.id, m]));
178151
- const parsed = parseClassifyManifest(manifestText).filter((p) => byId.has(p.id));
178396
+ const parsed = parseClassifyManifest(manifestText);
178397
+ assertManifestCoversExactly(parsed.map((entry) => entry.id), new Set(byId.keys()), "classify");
178152
178398
  if (parsed.length === 0)
178153
178399
  return { classified: 0, changed: 0 };
178154
178400
  let classified = 0;
178155
178401
  let changed = 0;
178156
- let leaseLost = false;
178157
- args.db.transaction(() => {
178158
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
178159
- leaseLost = true;
178160
- return;
178161
- }
178402
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
178162
178403
  for (const p of parsed) {
178163
178404
  const memory = byId.get(p.id);
178164
178405
  if (!memory)
@@ -178173,9 +178414,7 @@ function applyClassifications(args, chunk, manifestText) {
178173
178414
  if (didChange)
178174
178415
  changed += 1;
178175
178416
  }
178176
- })();
178177
- if (leaseLost)
178178
- throw new Error("Dream lease lost during classify commit");
178417
+ });
178179
178418
  return { classified, changed };
178180
178419
  }
178181
178420
  function recordInvocation(args, startedAt, params) {
@@ -178223,9 +178462,11 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
178223
178462
 
178224
178463
  class SmartNoteNetworkError extends Error {
178225
178464
  isSmartNoteNetworkError = true;
178226
- constructor(message) {
178465
+ terminal;
178466
+ constructor(message, options = {}) {
178227
178467
  super(message);
178228
178468
  this.name = "SmartNoteNetworkError";
178469
+ this.terminal = options.terminal ?? false;
178229
178470
  }
178230
178471
  }
178231
178472
 
@@ -178239,6 +178480,9 @@ class SmartNoteSecurityError extends Error {
178239
178480
  function isSmartNoteNetworkError(error51) {
178240
178481
  return error51 instanceof SmartNoteNetworkError || error51 instanceof Error && (error51.name === "SmartNoteNetworkError" || error51.message.includes("SmartNoteNetworkError") || error51.message.includes("SMART_NOTE_NETWORK"));
178241
178482
  }
178483
+ function isTerminalSmartNoteNetworkError(error51) {
178484
+ return error51 instanceof SmartNoteNetworkError && error51.terminal;
178485
+ }
178242
178486
  function parseSmartNoteManifest(json2) {
178243
178487
  if (!json2)
178244
178488
  return { capabilities: [] };
@@ -178268,6 +178512,7 @@ function stringArray(value) {
178268
178512
  var DNS_TIMEOUT_MS = 3000;
178269
178513
  var DEFAULT_HTTP_TIMEOUT_MS = 5000;
178270
178514
  var DEFAULT_HTTP_BODY_LIMIT_BYTES = 64 * 1024;
178515
+ var MAX_HTTP_ADDRESS_CANDIDATES = 4;
178271
178516
  var defaultResolver = {
178272
178517
  async lookup(hostname3, signal) {
178273
178518
  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 })));
@@ -178304,18 +178549,21 @@ async function guardedSmartNoteHttpGet(input, options) {
178304
178549
  });
178305
178550
  const timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
178306
178551
  const bodyLimitBytes = options.bodyLimitBytes ?? DEFAULT_HTTP_BODY_LIMIT_BYTES;
178552
+ const requestAddress = options.requestAddress ?? requestValidatedAddress;
178553
+ const candidates = validation.addresses.slice(0, MAX_HTTP_ADDRESS_CANDIDATES);
178307
178554
  let lastError;
178308
- for (const candidate of validation.addresses) {
178555
+ for (const candidate of candidates) {
178309
178556
  try {
178310
- return await requestValidatedAddress(validation, candidate, {
178557
+ return await requestAddress(validation, candidate, {
178311
178558
  signal: options.signal,
178312
178559
  timeoutMs,
178313
178560
  bodyLimitBytes
178314
178561
  });
178315
178562
  } catch (error51) {
178316
178563
  lastError = error51;
178317
- if (error51 instanceof SmartNoteSecurityError || options.signal.aborted)
178564
+ if (error51 instanceof SmartNoteSecurityError || options.signal.aborted || isTerminalSmartNoteNetworkError(error51)) {
178318
178565
  throw error51;
178566
+ }
178319
178567
  }
178320
178568
  }
178321
178569
  throw toNetworkError(lastError, "all validated addresses failed");
@@ -178385,7 +178633,9 @@ function requestValidatedAddress(validation, candidate, options) {
178385
178633
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
178386
178634
  bytes += buf.byteLength;
178387
178635
  if (bytes > options.bodyLimitBytes) {
178388
- request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large"));
178636
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large", { terminal: true }));
178637
+ response.destroy();
178638
+ request2.destroy();
178389
178639
  return;
178390
178640
  }
178391
178641
  chunks.push(buf);
@@ -178402,10 +178652,16 @@ function requestValidatedAddress(validation, candidate, options) {
178402
178652
  resolve({ status, body: Buffer.concat(chunks).toString("utf8") });
178403
178653
  });
178404
178654
  });
178405
- const onAbort = () => request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted"));
178655
+ const onAbort = () => {
178656
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted"));
178657
+ request2.destroy();
178658
+ };
178406
178659
  options.signal.addEventListener("abort", onAbort, { once: true });
178407
178660
  request2.on("timeout", () => {
178408
- request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: request timed out"));
178661
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: request timed out", {
178662
+ terminal: true
178663
+ }));
178664
+ request2.destroy();
178409
178665
  });
178410
178666
  request2.on("error", (error51) => {
178411
178667
  options.signal.removeEventListener("abort", onAbort);
@@ -178753,6 +179009,20 @@ function withSandboxLock(fn) {
178753
179009
  var DEFAULT_TIMEOUT_MS = 2000;
178754
179010
  var DEFAULT_HEAP_LIMIT_BYTES = 8 * 1024 * 1024;
178755
179011
  var DEFAULT_STACK_LIMIT_BYTES = 512 * 1024;
179012
+ function resolveCapabilitiesForRun(options, signal) {
179013
+ if (options.capabilityFactory) {
179014
+ return options.capabilityFactory(signal);
179015
+ }
179016
+ if (options.capabilities) {
179017
+ return options.capabilities;
179018
+ }
179019
+ throw new Error("smart-note check requires capabilities");
179020
+ }
179021
+ function throwIfRunAborted(signal) {
179022
+ if (signal.aborted) {
179023
+ throw signal.reason ?? new Error("smart-note check aborted");
179024
+ }
179025
+ }
178756
179026
  async function runCompiledSmartNoteCheck(options) {
178757
179027
  return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options));
178758
179028
  }
@@ -178760,17 +179030,24 @@ async function runCompiledSmartNoteCheckLocked(options) {
178760
179030
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
178761
179031
  const controller = new AbortController;
178762
179032
  const externalAbort = () => controller.abort(options.signal?.reason);
178763
- options.signal?.addEventListener("abort", externalAbort, { once: true });
179033
+ if (options.signal?.aborted) {
179034
+ externalAbort();
179035
+ } else {
179036
+ options.signal?.addEventListener("abort", externalAbort, { once: true });
179037
+ }
178764
179038
  const timer = setTimeout(() => controller.abort(new Error("smart-note check timed out")), timeoutMs);
178765
179039
  try {
179040
+ throwIfRunAborted(controller.signal);
179041
+ const capabilities = resolveCapabilitiesForRun(options, controller.signal);
178766
179042
  const deadline = Date.now() + timeoutMs;
178767
179043
  const quickjs = await getAsyncModule();
179044
+ throwIfRunAborted(controller.signal);
178768
179045
  const context = quickjs.newContext();
178769
179046
  try {
178770
179047
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
178771
179048
  context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
178772
179049
  context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
178773
- installCapabilityObject(context, options.capabilities);
179050
+ installCapabilityObject(context, capabilities);
178774
179051
  disableAmbientDynamicCode(context);
178775
179052
  const result = await evalCheck(context, options.compiledCheck);
178776
179053
  const checkResult = result;
@@ -178923,6 +179200,7 @@ Remember: output only the JSON object described by the system prompt.`;
178923
179200
  }
178924
179201
  }, {
178925
179202
  timeoutMs: remainingMs,
179203
+ signal: args.signal,
178926
179204
  fallbackModels: args.fallbackModels,
178927
179205
  callContext: "dreamer:smart-note-compiler",
178928
179206
  fetchOutput: async () => {
@@ -178947,7 +179225,8 @@ Remember: output only the JSON object described by the system prompt.`;
178947
179225
  }
178948
179226
  const dryRun = await runCompiledSmartNoteCheck({
178949
179227
  compiledCheck,
178950
- capabilities: args.capabilities,
179228
+ capabilityFactory: args.capabilityFactory,
179229
+ signal: args.signal,
178951
179230
  timeoutMs: 2000
178952
179231
  });
178953
179232
  if (!dryRun.ok) {
@@ -179095,7 +179374,9 @@ function nextSmartNoteCheckDueAt(cron, options = {}) {
179095
179374
  const rawNext = cron?.trim() ? nextDueAtMs(cron, now) : null;
179096
179375
  const rawDelta = rawNext ? rawNext - now : SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS;
179097
179376
  const clamped = Math.min(ceilingMs, Math.max(floorMs, rawDelta));
179098
- return now + clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
179377
+ const jittered = clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
179378
+ const bounded = Math.min(ceilingMs, Math.max(floorMs, jittered));
179379
+ return now + bounded;
179099
179380
  }
179100
179381
  function deterministicJitterMs(intervalMs, noteId, hash2) {
179101
179382
  const max = Math.min(60000, Math.floor(intervalMs * 0.1));
@@ -179264,9 +179545,9 @@ async function runDueCompiledSmartNoteChecks(args) {
179264
179545
  try {
179265
179546
  const result = await runCompiledSmartNoteCheck({
179266
179547
  compiledCheck: note.compiledCheck,
179267
- capabilities: createSmartNoteCapabilities({
179548
+ capabilityFactory: (signal) => createSmartNoteCapabilities({
179268
179549
  projectRoot: args.projectRoot,
179269
- signal: controller.signal
179550
+ signal
179270
179551
  }),
179271
179552
  signal: controller.signal,
179272
179553
  timeoutMs: Math.min(2000, remaining)
@@ -179329,6 +179610,24 @@ var MAX_COMPILE_PER_RUN = 5;
179329
179610
  var MAX_FALLBACK_PER_RUN = 3;
179330
179611
  var MAX_COMPILATION_FAILURES = 3;
179331
179612
  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.';
179613
+ function createPromptAbortSignal(parent, timeoutMs, timeoutMessage) {
179614
+ const controller = new AbortController;
179615
+ const abortFromParent = () => {
179616
+ controller.abort(parent.reason ?? new Error("smart-note prompt aborted by lease loss"));
179617
+ };
179618
+ if (parent.aborted)
179619
+ abortFromParent();
179620
+ else
179621
+ parent.addEventListener("abort", abortFromParent, { once: true });
179622
+ const timer = setTimeout(() => controller.abort(new Error(timeoutMessage)), timeoutMs);
179623
+ return {
179624
+ signal: controller.signal,
179625
+ cleanup: () => {
179626
+ clearTimeout(timer);
179627
+ parent.removeEventListener("abort", abortFromParent);
179628
+ }
179629
+ };
179630
+ }
179332
179631
  async function evaluateSmartNotes(args) {
179333
179632
  const projectRoot = args.sessionDirectory ?? args.projectIdentity;
179334
179633
  const pendingAtStart = getPendingSmartNotes(args.db, args.projectIdentity).length;
@@ -179337,6 +179636,7 @@ async function evaluateSmartNotes(args) {
179337
179636
  return { surfaced: 0, pending: 0, ran: false };
179338
179637
  }
179339
179638
  let leaseLost = false;
179639
+ const leaseAbortController = new AbortController;
179340
179640
  const leaseHeld = () => !leaseLost && peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey);
179341
179641
  const assertLeaseHeld = (phase) => {
179342
179642
  if (!leaseHeld()) {
@@ -179346,6 +179646,7 @@ async function evaluateSmartNotes(args) {
179346
179646
  };
179347
179647
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => {
179348
179648
  leaseLost = true;
179649
+ leaseAbortController.abort(new Error("Dream lease lost during smart notes"));
179349
179650
  log("[dreamer] smart notes: lease lost — aborting");
179350
179651
  args.onLeaseLost?.("smart notes");
179351
179652
  });
@@ -179368,7 +179669,7 @@ async function evaluateSmartNotes(args) {
179368
179669
  break;
179369
179670
  assertLeaseHeld("compile start");
179370
179671
  didWork = true;
179371
- const compiled = await compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld);
179672
+ const compiled = await compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld, leaseAbortController.signal);
179372
179673
  if (compiled)
179373
179674
  surfaced += 1;
179374
179675
  }
@@ -179388,7 +179689,7 @@ async function evaluateSmartNotes(args) {
179388
179689
  break;
179389
179690
  assertLeaseHeld("fallback start");
179390
179691
  didWork = true;
179391
- const met = await confirmReadOnly(args, note.id, note.content, note.surfaceCondition);
179692
+ const met = await confirmReadOnly(args, note.id, note.content, note.surfaceCondition, leaseAbortController.signal);
179392
179693
  const now = Date.now();
179393
179694
  assertLeaseHeld("fallback commit");
179394
179695
  commitSmartNoteState(args.db, {
@@ -179414,9 +179715,8 @@ async function evaluateSmartNotes(args) {
179414
179715
  heartbeat.stop();
179415
179716
  }
179416
179717
  }
179417
- async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld) {
179418
- const controller = new AbortController;
179419
- const timer = setTimeout(() => controller.abort(new Error("smart-note compile deadline")), Math.max(1000, args.deadline - Date.now()));
179718
+ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld, leaseSignal) {
179719
+ const promptSignal = createPromptAbortSignal(leaseSignal, Math.max(1000, args.deadline - Date.now()), "smart-note compile deadline");
179420
179720
  try {
179421
179721
  const result = await compileSmartNoteCheck({
179422
179722
  client: args.client,
@@ -179425,7 +179725,8 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld)
179425
179725
  sessionDirectory: args.sessionDirectory,
179426
179726
  projectIdentity: args.projectIdentity,
179427
179727
  note,
179428
- capabilities: createSmartNoteCapabilities({ projectRoot, signal: controller.signal }),
179728
+ capabilityFactory: (signal) => createSmartNoteCapabilities({ projectRoot, signal }),
179729
+ signal: promptSignal.signal,
179429
179730
  deadline: args.deadline,
179430
179731
  model: args.model,
179431
179732
  fallbackModels: args.fallbackModels
@@ -179470,7 +179771,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld)
179470
179771
  });
179471
179772
  return result.dryRun.met;
179472
179773
  } finally {
179473
- clearTimeout(timer);
179774
+ promptSignal.cleanup();
179474
179775
  }
179475
179776
  }
179476
179777
  async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseHeld) {
@@ -179481,7 +179782,7 @@ async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseH
179481
179782
  try {
179482
179783
  const result = await runCompiledSmartNoteCheck({
179483
179784
  compiledCheck: note.compiledCheck,
179484
- capabilities: createSmartNoteCapabilities({ projectRoot, signal: controller.signal }),
179785
+ capabilityFactory: (signal) => createSmartNoteCapabilities({ projectRoot, signal }),
179485
179786
  signal: controller.signal,
179486
179787
  timeoutMs: 2000
179487
179788
  });
@@ -179510,7 +179811,7 @@ async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseH
179510
179811
  clearTimeout(timer);
179511
179812
  }
179512
179813
  }
179513
- async function confirmReadOnly(args, noteId, content, surfaceCondition) {
179814
+ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSignal) {
179514
179815
  let childSessionId = null;
179515
179816
  const startedAt = Date.now();
179516
179817
  let invocationRecorded = false;
@@ -179553,42 +179854,49 @@ Note content: ${JSON.stringify(content)}
179553
179854
  Surface condition: ${JSON.stringify(surfaceCondition ?? "")}
179554
179855
 
179555
179856
  Output exactly JSON: {"met": false}`;
179556
- const run = await promptSyncWithValidatedOutputRetry(args.client, {
179557
- path: { id: childSessionId },
179558
- query: { directory: args.sessionDirectory ?? args.projectIdentity },
179559
- body: {
179560
- agent: SMART_NOTE_COMPILER_AGENT,
179561
- system: SMART_NOTE_CONFIRMATION_SYSTEM_PROMPT,
179562
- ...modelBodyField(args.model),
179563
- parts: [{ type: "text", text: prompt, synthetic: true }]
179564
- }
179565
- }, {
179566
- timeoutMs: Math.max(1000, args.deadline - Date.now()),
179567
- fallbackModels: args.fallbackModels,
179568
- callContext: "dreamer:smart-note-read-only-confirm",
179569
- fetchOutput: async () => {
179570
- const messagesResponse = await args.client.session.messages({
179571
- path: { id: childSessionId },
179572
- query: {
179573
- directory: args.sessionDirectory ?? args.projectIdentity,
179574
- limit: 20
179575
- }
179576
- });
179577
- return normalizeSDKResponse(messagesResponse, [], {
179578
- preferResponseOnMissingData: true
179579
- });
179580
- },
179581
- validateOutput: (messages) => {
179582
- const text = extractLatestAssistantText(messages) ?? "";
179583
- const match = text.match(/\{[\s\S]*\}/);
179584
- if (!match)
179585
- throw new Error("confirmation evaluator returned no JSON");
179586
- const parsed = JSON.parse(match[0]);
179587
- if (typeof parsed.met !== "boolean")
179588
- throw new Error("confirmation met missing");
179589
- return parsed.met;
179590
- }
179591
- });
179857
+ const promptSignal = createPromptAbortSignal(leaseSignal, Math.max(1000, args.deadline - Date.now()), "smart-note confirmation deadline");
179858
+ let run;
179859
+ try {
179860
+ run = await promptSyncWithValidatedOutputRetry(args.client, {
179861
+ path: { id: childSessionId },
179862
+ query: { directory: args.sessionDirectory ?? args.projectIdentity },
179863
+ body: {
179864
+ agent: SMART_NOTE_COMPILER_AGENT,
179865
+ system: SMART_NOTE_CONFIRMATION_SYSTEM_PROMPT,
179866
+ ...modelBodyField(args.model),
179867
+ parts: [{ type: "text", text: prompt, synthetic: true }]
179868
+ }
179869
+ }, {
179870
+ timeoutMs: Math.max(1000, args.deadline - Date.now()),
179871
+ signal: promptSignal.signal,
179872
+ fallbackModels: args.fallbackModels,
179873
+ callContext: "dreamer:smart-note-read-only-confirm",
179874
+ fetchOutput: async () => {
179875
+ const messagesResponse = await args.client.session.messages({
179876
+ path: { id: childSessionId },
179877
+ query: {
179878
+ directory: args.sessionDirectory ?? args.projectIdentity,
179879
+ limit: 20
179880
+ }
179881
+ });
179882
+ return normalizeSDKResponse(messagesResponse, [], {
179883
+ preferResponseOnMissingData: true
179884
+ });
179885
+ },
179886
+ validateOutput: (messages) => {
179887
+ const text = extractLatestAssistantText(messages) ?? "";
179888
+ const match = text.match(/\{[\s\S]*\}/);
179889
+ if (!match)
179890
+ throw new Error("confirmation evaluator returned no JSON");
179891
+ const parsed = JSON.parse(match[0]);
179892
+ if (typeof parsed.met !== "boolean")
179893
+ throw new Error("confirmation met missing");
179894
+ return parsed.met;
179895
+ }
179896
+ });
179897
+ } finally {
179898
+ promptSignal.cleanup();
179899
+ }
179592
179900
  recordInvocation2({ status: "completed", messages: run.output });
179593
179901
  return run.validated;
179594
179902
  } catch (error51) {
@@ -179803,19 +180111,21 @@ ${list}
179803
180111
  }
179804
180112
  function parseMapMemoriesManifest(text) {
179805
180113
  const out = [];
179806
- for (const m of text.matchAll(/<memory\b([^>]*)\/?>/g)) {
180114
+ const body = extractCompleteManifestBody(text, "mappings");
180115
+ for (const m of body.matchAll(/<memory\b([^>]*)\/?>/g)) {
179807
180116
  const attrs = m[1];
179808
180117
  const idMatch = attrs.match(/\bid\s*=\s*"(\d+)"/);
179809
180118
  if (!idMatch)
179810
- continue;
180119
+ throw new Error("mappings manifest entry missing numeric id");
179811
180120
  const id = Number.parseInt(idMatch[1], 10);
179812
180121
  if (!Number.isInteger(id))
179813
- continue;
180122
+ throw new Error("mappings manifest entry missing numeric id");
179814
180123
  const independent = /\bindependent\s*=\s*"(?:true|1)"/i.test(attrs);
179815
180124
  const filesMatch = attrs.match(/\bfiles\s*=\s*"([^"]*)"/);
179816
180125
  const files = filesMatch ? filesMatch[1].split(",").map((f) => f.trim()).filter(Boolean) : [];
179817
180126
  out.push({ id, files, independent: independent || files.length === 0 });
179818
180127
  }
180128
+ assertNoDuplicateManifestIds(out.map((entry) => entry.id), "mappings");
179819
180129
  return out;
179820
180130
  }
179821
180131
 
@@ -179904,9 +180214,13 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
179904
180214
  });
179905
180215
  },
179906
180216
  validateOutput: (messages) => {
180217
+ if (hasLengthCappedOutput(messages)) {
180218
+ throw new Error("map-memories returned length-capped output");
180219
+ }
179907
180220
  const text = extractLatestAssistantText(messages);
179908
180221
  if (!text)
179909
180222
  throw new Error("map-memories returned no output");
180223
+ parseMapMemoriesManifest(text);
179910
180224
  return text;
179911
180225
  }
179912
180226
  });
@@ -179932,7 +180246,8 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
179932
180246
  }
179933
180247
  async function applyBatchMappings(args, batch, manifestText) {
179934
180248
  const batchIds = new Set(batch.map((m) => m.id));
179935
- const parsed = parseMapMemoriesManifest(manifestText).filter((p) => batchIds.has(p.id));
180249
+ const parsed = parseMapMemoriesManifest(manifestText);
180250
+ assertManifestCoversExactly(parsed.map((entry) => entry.id), batchIds, "mappings");
179936
180251
  if (parsed.length === 0)
179937
180252
  return { mapped: 0, independent: 0 };
179938
180253
  const planned = [];
@@ -179954,12 +180269,7 @@ async function applyBatchMappings(args, batch, manifestText) {
179954
180269
  const now = Date.now();
179955
180270
  let mapped = 0;
179956
180271
  let independent = 0;
179957
- let leaseLost = false;
179958
- args.db.transaction(() => {
179959
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
179960
- leaseLost = true;
179961
- return;
179962
- }
180272
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
179963
180273
  for (const item of planned) {
179964
180274
  recordMemoryMapping(args.db, item.id, item.files, now);
179965
180275
  if (item.independent)
@@ -179967,9 +180277,7 @@ async function applyBatchMappings(args, batch, manifestText) {
179967
180277
  else
179968
180278
  mapped += 1;
179969
180279
  }
179970
- })();
179971
- if (leaseLost)
179972
- throw new Error("Dream lease lost during map-memories commit");
180280
+ });
179973
180281
  return { mapped, independent };
179974
180282
  }
179975
180283
  function recordInvocation2(args, startedAt, params) {
@@ -180197,11 +180505,7 @@ async function promotePrimers(args) {
180197
180505
  activePrimers: primers,
180198
180506
  threshold: PRIMER_CLUSTER_THRESHOLD
180199
180507
  });
180200
- args.db.transaction(() => {
180201
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
180202
- leaseLost = true;
180203
- return;
180204
- }
180508
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
180205
180509
  for (const cluster of clusters) {
180206
180510
  if (cluster.candidates.length === 0)
180207
180511
  continue;
@@ -180232,7 +180536,7 @@ async function promotePrimers(args) {
180232
180536
  });
180233
180537
  result.promoted += 1;
180234
180538
  }
180235
- })();
180539
+ });
180236
180540
  if (leaseLost)
180237
180541
  throw new Error("Dream lease lost during promote-primers commit");
180238
180542
  log(`[dreamer] primers: candidates=${result.candidates} promoted=${result.promoted} updated=${result.updated}`);
@@ -180500,16 +180804,9 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
180500
180804
  log(`[dreamer] refresh-primers: primer #${primer.id} answer not committed (no investigation tool calls)`);
180501
180805
  return false;
180502
180806
  }
180503
- let leaseLost = false;
180504
- args.db.transaction(() => {
180505
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
180506
- leaseLost = true;
180507
- return;
180508
- }
180807
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
180509
180808
  updatePrimerAnswer(args.db, primer.id, answer);
180510
- })();
180511
- if (leaseLost)
180512
- throw new Error("Dream lease lost during refresh-primers commit");
180809
+ });
180513
180810
  return true;
180514
180811
  } catch (error51) {
180515
180812
  const desc = describeError(error51);
@@ -180706,8 +181003,7 @@ class OpenCodeRetrospectiveRawProvider {
180706
181003
  LEFT JOIN session_meta m ON m.session_id = sp.session_id
180707
181004
  WHERE sp.project_path = ? AND sp.harness = 'opencode'
180708
181005
  AND COALESCE(m.is_subagent, 0) = 0
180709
- ORDER BY sp.updated_at DESC, sp.session_id DESC
180710
- LIMIT ?`).all(projectIdentity, RETROSPECTIVE_MAX_SESSIONS_PER_RUN);
181006
+ ORDER BY sp.updated_at ASC, sp.session_id ASC`).all(projectIdentity);
180711
181007
  return rows.map((row) => ({
180712
181008
  sessionId: row.session_id,
180713
181009
  updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
@@ -180732,6 +181028,12 @@ class OpenCodeRetrospectiveRawProvider {
180732
181028
  return { messages: [], truncated: false };
180733
181029
  }
180734
181030
  }
181031
+ readOldestMessageTimesSince(sessionIds, sinceMs) {
181032
+ const db = this.resolveDb();
181033
+ if (!db || sessionIds.length === 0)
181034
+ return new Map;
181035
+ return readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs);
181036
+ }
180735
181037
  readUserMessagesBefore(sessionId, beforeMs, count) {
180736
181038
  const db = this.resolveDb();
180737
181039
  if (!db)
@@ -180753,13 +181055,28 @@ class OpenCodeRetrospectiveRawProvider {
180753
181055
  async function readRetrospectiveScanWindow(provider2, projectIdentity, watermarkMs, overlapUserCount, options) {
180754
181056
  const maxMessages = options?.maxMessagesPerRun ?? RETROSPECTIVE_MAX_MESSAGES_PER_RUN;
180755
181057
  const capPerSession = options?.capPerSession ?? RETROSPECTIVE_MAX_MESSAGES_PER_SESSION;
180756
- const sessionReadPageSize = Math.max(1, Math.floor(options?.maxSessionsPerRun ?? RETROSPECTIVE_MAX_SESSIONS_PER_RUN));
181058
+ const sessionLimit = Math.max(1, Math.floor(options?.maxSessionsPerRun ?? RETROSPECTIVE_MAX_SESSIONS_PER_RUN));
180757
181059
  try {
180758
- const sessions = await provider2.listProjectSessions(projectIdentity);
181060
+ const allSessions = await provider2.listProjectSessions(projectIdentity);
181061
+ const eligibleSessions = allSessions.map((session, index) => ({ session, index })).filter(({ session }) => (session.updatedAt ?? Number.POSITIVE_INFINITY) > watermarkMs);
181062
+ const oldestBySession = provider2.readOldestMessageTimesSince ? await provider2.readOldestMessageTimesSince(eligibleSessions.map(({ session }) => session.sessionId), watermarkMs) : null;
181063
+ const sessions = (oldestBySession ? eligibleSessions.filter(({ session }) => oldestBySession.has(session.sessionId)) : eligibleSessions).sort((a, b) => {
181064
+ const aFrontier = oldestBySession?.get(a.session.sessionId);
181065
+ const bFrontier = oldestBySession?.get(b.session.sessionId);
181066
+ if (aFrontier !== undefined || bFrontier !== undefined) {
181067
+ return (aFrontier ?? Number.POSITIVE_INFINITY) - (bFrontier ?? Number.POSITIVE_INFINITY) || a.index - b.index;
181068
+ }
181069
+ const aUpdated = a.session.updatedAt ?? Number.POSITIVE_INFINITY;
181070
+ const bUpdated = b.session.updatedAt ?? Number.POSITIVE_INFINITY;
181071
+ const byUpdated = aUpdated - bUpdated;
181072
+ return byUpdated || a.index - b.index;
181073
+ });
181074
+ const sessionsToRead = sessions.slice(0, sessionLimit).map(({ session }) => session);
181075
+ const firstExcludedSession = sessions[sessionLimit]?.session;
181076
+ const firstExcludedPendingTs = firstExcludedSession ? oldestBySession?.get(firstExcludedSession.sessionId) : undefined;
180759
181077
  const sinceReads = [];
180760
- for (let i = 0;i < sessions.length; i += sessionReadPageSize) {
180761
- const page = sessions.slice(i, i + sessionReadPageSize);
180762
- sinceReads.push(...await Promise.all(page.map((session) => provider2.readUserMessagesSince(session.sessionId, watermarkMs, capPerSession))));
181078
+ if (sessionsToRead.length > 0) {
181079
+ sinceReads.push(...await Promise.all(sessionsToRead.map((session) => provider2.readUserMessagesSince(session.sessionId, watermarkMs, capPerSession))));
180763
181080
  }
180764
181081
  let saturatedFrontier = Number.POSITIVE_INFINITY;
180765
181082
  for (const read of sinceReads) {
@@ -180781,9 +181098,14 @@ async function readRetrospectiveScanWindow(provider2, projectIdentity, watermark
180781
181098
  if (firstDropped) {
180782
181099
  frontier = Math.min(frontier, firstDropped.ts - 1);
180783
181100
  }
181101
+ if (typeof firstExcludedPendingTs === "number") {
181102
+ frontier = Math.min(frontier, firstExcludedPendingTs - 1);
181103
+ } else if (typeof firstExcludedSession?.updatedAt === "number") {
181104
+ frontier = Math.min(frontier, firstExcludedSession.updatedAt - 1);
181105
+ }
180784
181106
  maxScannedTs = Math.max(watermarkMs, Math.min(maxScannedTs, frontier));
180785
181107
  const keptSessionIds = new Set(keptSince.map((message) => message.sessionId));
180786
- const overlapSessions = sessions.filter((session) => keptSessionIds.has(session.sessionId));
181108
+ const overlapSessions = sessionsToRead.filter((session) => keptSessionIds.has(session.sessionId));
180787
181109
  const overlapBatches = overlapUserCount > 0 && watermarkMs > 0 ? await Promise.all(overlapSessions.map((session) => provider2.readUserMessagesBefore(session.sessionId, watermarkMs, overlapUserCount))) : [];
180788
181110
  const seen = new Set;
180789
181111
  const merged = [];
@@ -180811,6 +181133,24 @@ function readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession) {
180811
181133
  const kept = truncated ? rows.slice(0, limit) : rows;
180812
181134
  return { messages: normalizeOpenCodeRows(db, sessionId, kept), truncated };
180813
181135
  }
181136
+ function readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs) {
181137
+ const result = new Map;
181138
+ const uniqueIds = Array.from(new Set(sessionIds.filter((id) => id.length > 0)));
181139
+ const chunkSize = 500;
181140
+ for (let i = 0;i < uniqueIds.length; i += chunkSize) {
181141
+ const chunk = uniqueIds.slice(i, i + chunkSize);
181142
+ const placeholders4 = chunk.map(() => "?").join(", ");
181143
+ const rows = db.prepare(`SELECT session_id, MIN(time_created) AS oldest_ts
181144
+ FROM message
181145
+ WHERE time_created > ? AND session_id IN (${placeholders4})
181146
+ GROUP BY session_id`).all(sinceMs, ...chunk);
181147
+ for (const row of rows) {
181148
+ if (typeof row.oldest_ts === "number")
181149
+ result.set(row.session_id, row.oldest_ts);
181150
+ }
181151
+ }
181152
+ return result;
181153
+ }
180814
181154
  function readOpenCodeUserMessagesBefore(db, sessionId, beforeMs, count) {
180815
181155
  const want = Math.max(1, Math.floor(count));
180816
181156
  const window = Math.max(want * 5, 32);
@@ -181103,7 +181443,7 @@ async function partitionVerifyScope(args) {
181103
181443
  continue;
181104
181444
  }
181105
181445
  const files = v?.files ?? [];
181106
- const needs = files.some((file2) => !verificationFileExists(gitRoot, file2) || uncommitted.has(file2) || (changeTimes.get(file2) ?? 0) > verifiedAt);
181446
+ const needs = files.some((file2) => !verificationFileExists(gitRoot, file2) || uncommitted.has(file2) || (changeTimes.get(file2) ?? 0) >= verifiedAt - 1000);
181107
181447
  if (needs)
181108
181448
  inScope.push(toPrompt(m));
181109
181449
  else
@@ -181169,22 +181509,26 @@ function filesOf(s) {
181169
181509
  }
181170
181510
  function parseVerifyManifest(text) {
181171
181511
  const out = { verified: [], updated: [], archived: [] };
181172
- for (const m of text.matchAll(/<verified\b([^>]*)\/?>/g)) {
181512
+ const body = extractCompleteManifestBody(text, "verify");
181513
+ for (const m of body.matchAll(/<verified\b([^>]*)\/?>/g)) {
181173
181514
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
181174
- if (Number.isInteger(id))
181175
- out.verified.push({ id, files: filesOf(m[1]) });
181515
+ if (!Number.isInteger(id))
181516
+ throw new Error("verify manifest entry missing numeric id");
181517
+ out.verified.push({ id, files: filesOf(m[1]) });
181176
181518
  }
181177
- for (const m of text.matchAll(/<update\b([^>]*?)(?:\/>|>([\s\S]*?)<\/update>)/g)) {
181519
+ for (const m of body.matchAll(/<update\b([^>]*?)(?:\/>|>([\s\S]*?)<\/update>)/g)) {
181178
181520
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
181179
- if (Number.isInteger(id)) {
181180
- out.updated.push({ id, files: filesOf(m[1]), content: (m[2] ?? "").trim() });
181181
- }
181521
+ if (!Number.isInteger(id))
181522
+ throw new Error("verify manifest entry missing numeric id");
181523
+ out.updated.push({ id, files: filesOf(m[1]), content: (m[2] ?? "").trim() });
181182
181524
  }
181183
- for (const m of text.matchAll(/<archive\b([^>]*)\/?>/g)) {
181525
+ for (const m of body.matchAll(/<archive\b([^>]*)\/?>/g)) {
181184
181526
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
181185
- if (Number.isInteger(id))
181186
- out.archived.push({ id, reason: attrOf(m[1], "reason") ?? "" });
181527
+ if (!Number.isInteger(id))
181528
+ throw new Error("verify manifest entry missing numeric id");
181529
+ out.archived.push({ id, reason: attrOf(m[1], "reason") ?? "" });
181187
181530
  }
181531
+ assertNoDuplicateManifestIds([...out.verified, ...out.updated, ...out.archived].map((entry) => entry.id), "verify");
181188
181532
  return out;
181189
181533
  }
181190
181534
 
@@ -181277,9 +181621,13 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
181277
181621
  });
181278
181622
  },
181279
181623
  validateOutput: (messages) => {
181624
+ if (hasLengthCappedOutput(messages)) {
181625
+ throw new Error("verify returned length-capped output");
181626
+ }
181280
181627
  const text = extractLatestAssistantText(messages);
181281
181628
  if (!text)
181282
181629
  throw new Error("verify returned no output");
181630
+ parseVerifyManifest(text);
181283
181631
  return text;
181284
181632
  }
181285
181633
  });
@@ -181306,24 +181654,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
181306
181654
  async function applyVerifyManifest(args, batch, manifestText) {
181307
181655
  const batchIds = new Set(batch.map((m) => m.id));
181308
181656
  const parsed = parseVerifyManifest(manifestText);
181657
+ assertManifestCoversExactly([...parsed.verified, ...parsed.updated, ...parsed.archived].map((entry) => entry.id), batchIds, "verify");
181309
181658
  const now = Date.now();
181310
181659
  const writes = [];
181311
- const verdictCounts = new Map;
181312
- for (const verdict of [...parsed.verified, ...parsed.updated, ...parsed.archived]) {
181313
- if (!batchIds.has(verdict.id))
181314
- continue;
181315
- verdictCounts.set(verdict.id, (verdictCounts.get(verdict.id) ?? 0) + 1);
181316
- }
181317
- const conflictingIds = new Set(Array.from(verdictCounts.entries()).filter(([, count]) => count !== 1).map(([id]) => id));
181318
181660
  for (const v of parsed.verified) {
181319
- if (!batchIds.has(v.id) || conflictingIds.has(v.id))
181320
- continue;
181321
181661
  const files = await normalizeFiles(args, v.files);
181322
181662
  writes.push({ kind: "verify", id: v.id, files });
181323
181663
  }
181324
181664
  for (const u of parsed.updated) {
181325
- if (!batchIds.has(u.id) || conflictingIds.has(u.id))
181326
- continue;
181327
181665
  const content = u.content.trim();
181328
181666
  if (!content || content.length > 20000) {
181329
181667
  const files2 = await normalizeFiles(args, u.files);
@@ -181340,8 +181678,6 @@ async function applyVerifyManifest(args, batch, manifestText) {
181340
181678
  });
181341
181679
  }
181342
181680
  for (const a of parsed.archived) {
181343
- if (!batchIds.has(a.id) || conflictingIds.has(a.id))
181344
- continue;
181345
181681
  writes.push({ kind: "archive", id: a.id, reason: a.reason });
181346
181682
  }
181347
181683
  if (writes.length === 0)
@@ -181349,12 +181685,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
181349
181685
  let verified = 0;
181350
181686
  let updated = 0;
181351
181687
  let archived = 0;
181352
- let leaseLost = false;
181353
- args.db.transaction(() => {
181354
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
181355
- leaseLost = true;
181356
- return;
181357
- }
181688
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
181358
181689
  for (const w of writes) {
181359
181690
  const memory = getMemoryById(args.db, w.id);
181360
181691
  if (!isPrimaryMutable(memory))
@@ -181382,9 +181713,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
181382
181713
  archived += 1;
181383
181714
  }
181384
181715
  }
181385
- })();
181386
- if (leaseLost)
181387
- throw new Error("Dream lease lost during verify commit");
181716
+ });
181388
181717
  return { verified, updated, archived };
181389
181718
  }
181390
181719
  async function normalizeFiles(args, rawFiles) {
@@ -181878,11 +182207,7 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
181878
182207
  throw new Error("Dream lease lost during retrospective");
181879
182208
  const sourceSessionId = flagged[0]?.sessionId ?? userMessages[0]?.sessionId ?? "retrospective";
181880
182209
  const learnings = parseRetrospectiveLearnings(deepenRun.validated);
181881
- const applied = db.transaction(() => {
181882
- if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
181883
- leaseLost = true;
181884
- return null;
181885
- }
182210
+ const applied = runLeaseGuardedWrite(db, holderId, leaseKey, () => {
181886
182211
  const result = applyRetrospectiveLearnings({
181887
182212
  db,
181888
182213
  projectIdentity,
@@ -181893,7 +182218,7 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
181893
182218
  });
181894
182219
  recordRetrospectiveWindowProcessed(db, projectIdentity, windowKey);
181895
182220
  return result;
181896
- })();
182221
+ });
181897
182222
  if (leaseLost || !applied)
181898
182223
  throw new Error("Dream lease lost during retrospective commit");
181899
182224
  log(`[dreamer] retrospective: flagged=${flagged.length} learnings=${learnings.length} memory=${applied.memoryWritten} observations=${applied.observationsInserted} dropped=${applied.observationsDropped} rejected=${applied.rejected.length}`);
@@ -183428,11 +183753,106 @@ function migrateLegacyExperimental(rawConfig, warnings) {
183428
183753
 
183429
183754
  // ../plugin/src/config/project-security.ts
183430
183755
  var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
183756
+ var HISTORIAN_USER_ONLY_FIELDS = ["model", "fallback_models"];
183431
183757
  var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
183432
183758
  var EMBEDDING_DESTINATION_FIELDS = ["endpoint", "provider"];
183759
+ 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.";
183760
+ 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.";
183761
+ 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.";
183433
183762
  function isPlainObject3(value) {
183434
183763
  return typeof value === "object" && value !== null && !Array.isArray(value);
183435
183764
  }
183765
+ function isValidPercentageThreshold(value) {
183766
+ return typeof value === "number" && Number.isFinite(value) && value >= 20 && value <= 80;
183767
+ }
183768
+ function isValidTokenThreshold(value) {
183769
+ return typeof value === "number" && Number.isFinite(value) && value >= 5000 && value <= 2000000;
183770
+ }
183771
+ function normalizeTrustedPercentageThresholds(value) {
183772
+ if (typeof value === "number" && Number.isFinite(value)) {
183773
+ return { defaultValue: value, overrides: new Map };
183774
+ }
183775
+ if (isPlainObject3(value) && typeof value.default === "number" && Number.isFinite(value.default)) {
183776
+ const overrides = new Map;
183777
+ for (const [key, child] of Object.entries(value)) {
183778
+ if (key === "default")
183779
+ continue;
183780
+ if (typeof child === "number" && Number.isFinite(child)) {
183781
+ overrides.set(key, child);
183782
+ }
183783
+ }
183784
+ return { defaultValue: value.default, overrides };
183785
+ }
183786
+ return { defaultValue: DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, overrides: new Map };
183787
+ }
183788
+ function normalizeTrustedTokenThresholds(value) {
183789
+ if (!isPlainObject3(value)) {
183790
+ return { defaultValue: undefined, overrides: new Map };
183791
+ }
183792
+ const overrides = new Map;
183793
+ for (const [key, child] of Object.entries(value)) {
183794
+ if (key === "default")
183795
+ continue;
183796
+ if (typeof child === "number" && Number.isFinite(child)) {
183797
+ overrides.set(key, child);
183798
+ }
183799
+ }
183800
+ return {
183801
+ defaultValue: typeof value.default === "number" && Number.isFinite(value.default) ? value.default : undefined,
183802
+ overrides
183803
+ };
183804
+ }
183805
+ function clonePercentageThresholds(value) {
183806
+ return {
183807
+ defaultValue: value.defaultValue,
183808
+ overrides: new Map(value.overrides)
183809
+ };
183810
+ }
183811
+ function cloneTokenThresholds(value) {
183812
+ return {
183813
+ defaultValue: value.defaultValue,
183814
+ overrides: new Map(value.overrides)
183815
+ };
183816
+ }
183817
+ function percentageThresholdsEqual(left, right) {
183818
+ if (left.defaultValue !== right.defaultValue)
183819
+ return false;
183820
+ if (left.overrides.size !== right.overrides.size)
183821
+ return false;
183822
+ for (const [key, value] of left.overrides) {
183823
+ if (right.overrides.get(key) !== value)
183824
+ return false;
183825
+ }
183826
+ return true;
183827
+ }
183828
+ function setMergedPercentageThreshold(mergedRaw, value) {
183829
+ if (value.overrides.size === 0) {
183830
+ mergedRaw.execute_threshold_percentage = value.defaultValue;
183831
+ return;
183832
+ }
183833
+ const serialized = { default: value.defaultValue };
183834
+ for (const [key, threshold] of value.overrides) {
183835
+ serialized[key] = threshold;
183836
+ }
183837
+ mergedRaw.execute_threshold_percentage = serialized;
183838
+ }
183839
+ function setMergedTokenThreshold(mergedRaw, value) {
183840
+ if (value.defaultValue === undefined && value.overrides.size === 0) {
183841
+ delete mergedRaw.execute_threshold_tokens;
183842
+ return;
183843
+ }
183844
+ const serialized = {};
183845
+ if (value.defaultValue !== undefined) {
183846
+ serialized.default = value.defaultValue;
183847
+ }
183848
+ for (const [key, threshold] of value.overrides) {
183849
+ serialized[key] = threshold;
183850
+ }
183851
+ mergedRaw.execute_threshold_tokens = serialized;
183852
+ }
183853
+ function makeProjectThresholdWarning(field, reason) {
183854
+ return `Ignoring ${field} from project config (${reason})`;
183855
+ }
183436
183856
  function stripUnsafeProjectConfigFields(projectRaw) {
183437
183857
  const warnings = [];
183438
183858
  if ("auto_update" in projectRaw) {
@@ -183460,6 +183880,19 @@ function stripUnsafeProjectConfigFields(projectRaw) {
183460
183880
  warnings.push(`Ignoring embedding.${removed.join("/")} from project config ` + "(security: a repository cannot choose where private text is embedded).");
183461
183881
  }
183462
183882
  }
183883
+ const historian = projectRaw.historian;
183884
+ if (isPlainObject3(historian)) {
183885
+ const removed = [];
183886
+ for (const field of HISTORIAN_USER_ONLY_FIELDS) {
183887
+ if (field in historian) {
183888
+ delete historian[field];
183889
+ removed.push(field);
183890
+ }
183891
+ }
183892
+ if (removed.length > 0) {
183893
+ warnings.push(`Ignoring historian.${removed.join("/")} from project config ` + "(security: historian model selection is user-level only; a repository cannot force extra compaction cost).");
183894
+ }
183895
+ }
183463
183896
  for (const agentKey of HIDDEN_AGENT_KEYS) {
183464
183897
  const block = projectRaw[agentKey];
183465
183898
  if (!isPlainObject3(block))
@@ -183477,6 +183910,101 @@ function stripUnsafeProjectConfigFields(projectRaw) {
183477
183910
  }
183478
183911
  return warnings;
183479
183912
  }
183913
+ function constrainProjectThresholdOverrides(args) {
183914
+ const warnings = [];
183915
+ const basePercentage = normalizeTrustedPercentageThresholds(args.trustedBaseConfig.execute_threshold_percentage);
183916
+ const baseTokens = normalizeTrustedTokenThresholds(args.trustedBaseConfig.execute_threshold_tokens);
183917
+ if ("execute_threshold_percentage" in args.projectRaw) {
183918
+ const projectValue = args.projectRaw.execute_threshold_percentage;
183919
+ if (isValidPercentageThreshold(projectValue)) {
183920
+ const constrained = clonePercentageThresholds(basePercentage);
183921
+ constrained.defaultValue = Math.max(basePercentage.defaultValue, projectValue);
183922
+ for (const [modelKey, threshold] of basePercentage.overrides) {
183923
+ const raisedThreshold = Math.max(threshold, projectValue);
183924
+ if (raisedThreshold === constrained.defaultValue) {
183925
+ constrained.overrides.delete(modelKey);
183926
+ } else {
183927
+ constrained.overrides.set(modelKey, raisedThreshold);
183928
+ }
183929
+ }
183930
+ setMergedPercentageThreshold(args.mergedRaw, constrained);
183931
+ if (percentageThresholdsEqual(constrained, basePercentage)) {
183932
+ warnings.push(makeProjectThresholdWarning("execute_threshold_percentage", PERCENTAGE_THRESHOLD_REASON));
183933
+ }
183934
+ } else if (isPlainObject3(projectValue)) {
183935
+ const constrained = clonePercentageThresholds(basePercentage);
183936
+ let touchedValidEntry = false;
183937
+ if (isValidPercentageThreshold(projectValue.default)) {
183938
+ touchedValidEntry = true;
183939
+ if (projectValue.default > basePercentage.defaultValue) {
183940
+ constrained.defaultValue = projectValue.default;
183941
+ } else {
183942
+ warnings.push(makeProjectThresholdWarning("execute_threshold_percentage.default", PERCENTAGE_THRESHOLD_REASON));
183943
+ }
183944
+ }
183945
+ for (const [modelKey, rawValue] of Object.entries(projectValue)) {
183946
+ if (modelKey === "default")
183947
+ continue;
183948
+ if (!isValidPercentageThreshold(rawValue))
183949
+ continue;
183950
+ touchedValidEntry = true;
183951
+ const baseValue = basePercentage.overrides.get(modelKey) ?? basePercentage.defaultValue;
183952
+ if (rawValue > baseValue) {
183953
+ if (rawValue === constrained.defaultValue) {
183954
+ constrained.overrides.delete(modelKey);
183955
+ } else {
183956
+ constrained.overrides.set(modelKey, rawValue);
183957
+ }
183958
+ } else {
183959
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_percentage.${modelKey}`, PERCENTAGE_THRESHOLD_REASON));
183960
+ }
183961
+ }
183962
+ if (touchedValidEntry) {
183963
+ setMergedPercentageThreshold(args.mergedRaw, constrained);
183964
+ }
183965
+ }
183966
+ }
183967
+ if ("execute_threshold_tokens" in args.projectRaw && isPlainObject3(args.projectRaw.execute_threshold_tokens)) {
183968
+ const projectValue = args.projectRaw.execute_threshold_tokens;
183969
+ const constrained = cloneTokenThresholds(baseTokens);
183970
+ let touchedValidEntry = false;
183971
+ if (isValidTokenThreshold(projectValue.default)) {
183972
+ touchedValidEntry = true;
183973
+ if (baseTokens.defaultValue === undefined) {
183974
+ warnings.push(makeProjectThresholdWarning("execute_threshold_tokens.default", TOKEN_THRESHOLD_INTRODUCTION_REASON));
183975
+ } else if (projectValue.default > baseTokens.defaultValue) {
183976
+ constrained.defaultValue = projectValue.default;
183977
+ } else {
183978
+ warnings.push(makeProjectThresholdWarning("execute_threshold_tokens.default", TOKEN_THRESHOLD_REASON));
183979
+ }
183980
+ }
183981
+ for (const [modelKey, rawValue] of Object.entries(projectValue)) {
183982
+ if (modelKey === "default")
183983
+ continue;
183984
+ if (!isValidTokenThreshold(rawValue))
183985
+ continue;
183986
+ touchedValidEntry = true;
183987
+ const baseValue = baseTokens.overrides.get(modelKey) ?? baseTokens.defaultValue;
183988
+ if (baseValue === undefined) {
183989
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_tokens.${modelKey}`, TOKEN_THRESHOLD_INTRODUCTION_REASON));
183990
+ continue;
183991
+ }
183992
+ if (rawValue > baseValue) {
183993
+ if (rawValue === constrained.defaultValue) {
183994
+ constrained.overrides.delete(modelKey);
183995
+ } else {
183996
+ constrained.overrides.set(modelKey, rawValue);
183997
+ }
183998
+ } else {
183999
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_tokens.${modelKey}`, TOKEN_THRESHOLD_REASON));
184000
+ }
184001
+ }
184002
+ if (touchedValidEntry) {
184003
+ setMergedTokenThreshold(args.mergedRaw, constrained);
184004
+ }
184005
+ }
184006
+ return warnings;
184007
+ }
183480
184008
  function normalizeEndpoint3(value) {
183481
184009
  if (typeof value !== "string")
183482
184010
  return;
@@ -183844,6 +184372,7 @@ function loadPiConfig(opts = {}) {
183844
184372
  return a.scope === "user" ? -1 : 1;
183845
184373
  });
183846
184374
  const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
184375
+ const trustedBaseConfig = parsePiConfig(userRaw ?? {}).config;
183847
184376
  for (const loaded of mergeFiles) {
183848
184377
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
183849
184378
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
@@ -183856,6 +184385,13 @@ function loadPiConfig(opts = {}) {
183856
184385
  for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
183857
184386
  warnings.push(`${prefix} ${warning}`);
183858
184387
  }
184388
+ for (const warning of constrainProjectThresholdOverrides({
184389
+ mergedRaw: rawConfig,
184390
+ projectRaw,
184391
+ trustedBaseConfig
184392
+ })) {
184393
+ warnings.push(`${prefix} ${warning}`);
184394
+ }
183859
184395
  } else {
183860
184396
  rawConfig = mergeRawConfigs(rawConfig, loaded.config);
183861
184397
  }
@@ -183954,6 +184490,7 @@ function loadPiConfigDetailed(opts = {}) {
183954
184490
  return a.scope === "user" ? -1 : 1;
183955
184491
  });
183956
184492
  const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
184493
+ const trustedBaseConfig = parsePiConfig(userRaw ?? {}).config;
183957
184494
  for (const loaded of mergeFiles) {
183958
184495
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
183959
184496
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
@@ -183966,6 +184503,13 @@ function loadPiConfigDetailed(opts = {}) {
183966
184503
  for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
183967
184504
  warnings.push(`${prefix} ${warning}`);
183968
184505
  }
184506
+ for (const warning of constrainProjectThresholdOverrides({
184507
+ mergedRaw: rawConfig,
184508
+ projectRaw,
184509
+ trustedBaseConfig
184510
+ })) {
184511
+ warnings.push(`${prefix} ${warning}`);
184512
+ }
183969
184513
  } else {
183970
184514
  rawConfig = mergeRawConfigs(rawConfig, loaded.config);
183971
184515
  }
@@ -184044,6 +184588,8 @@ function isMidTurnPi(event, _sessionId) {
184044
184588
  }
184045
184589
  if (latestAssistant === null)
184046
184590
  return false;
184591
+ if (hasRealUserAfter(messages, latestAssistantIndex))
184592
+ return false;
184047
184593
  if (latestAssistant.stopReason === "toolUse")
184048
184594
  return true;
184049
184595
  const toolCallIds = getToolCallIds(latestAssistant.content);
@@ -184066,6 +184612,15 @@ function isMidTurnPi(event, _sessionId) {
184066
184612
  }
184067
184613
  return false;
184068
184614
  }
184615
+ function hasRealUserAfter(messages, latestAssistantIndex) {
184616
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
184617
+ if (msg === null || typeof msg !== "object")
184618
+ continue;
184619
+ if (msg.role === "user")
184620
+ return true;
184621
+ }
184622
+ return false;
184623
+ }
184069
184624
  function getToolCallIds(content) {
184070
184625
  const ids = new Set;
184071
184626
  if (!Array.isArray(content))
@@ -184388,7 +184943,7 @@ class PiRetrospectiveRawProvider {
184388
184943
  updatedAt: typeof info.modified === "number" ? info.modified : undefined
184389
184944
  });
184390
184945
  }
184391
- return result.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
184946
+ return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
184392
184947
  }
184393
184948
  async readUserMessagesSince(sessionId, sinceMs, capPerSession) {
184394
184949
  const all = await this.loadUserEntries(sessionId);
@@ -184399,6 +184954,15 @@ class PiRetrospectiveRawProvider {
184399
184954
  truncated: eligible.length > limit
184400
184955
  };
184401
184956
  }
184957
+ async readOldestMessageTimesSince(sessionIds, sinceMs) {
184958
+ const out = new Map;
184959
+ for (const sessionId of sessionIds) {
184960
+ const oldest = (await this.loadUserEntries(sessionId)).filter((message) => message.ts > sinceMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal)[0];
184961
+ if (oldest)
184962
+ out.set(sessionId, oldest.ts);
184963
+ }
184964
+ return out;
184965
+ }
184402
184966
  async readUserMessagesBefore(sessionId, beforeMs, count) {
184403
184967
  const all = await this.loadUserEntries(sessionId);
184404
184968
  return all.filter((entry) => entry.ts <= beforeMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal).slice(-Math.max(1, Math.floor(count)));
@@ -186158,6 +186722,8 @@ init_logger();
186158
186722
  // ../plugin/src/hooks/magic-context/read-session-true-raw-tokens.ts
186159
186723
  var MAX_MESSAGE_CACHE_ENTRIES = 1e5;
186160
186724
  var MAX_MESSAGE_CACHE_KEY_BYTES = 64 * 1024 * 1024;
186725
+ var FNV1A_32_OFFSET = 2166136261;
186726
+ var FNV1A_32_PRIME = 16777619;
186161
186727
  var messageEstimateCache = new Map;
186162
186728
  var messageEstimateCacheBytes = 0;
186163
186729
  var EMPTY_BREAKDOWN = {
@@ -186260,6 +186826,23 @@ function recursiveByteLength(value) {
186260
186826
  }
186261
186827
  return String(value).length;
186262
186828
  }
186829
+ function updateFnv1a32(hash2, text) {
186830
+ let next = hash2;
186831
+ for (let index = 0;index < text.length; index += 1) {
186832
+ next ^= text.charCodeAt(index);
186833
+ next = Math.imul(next, FNV1A_32_PRIME) >>> 0;
186834
+ }
186835
+ return next;
186836
+ }
186837
+ function contentStringsHash(fields) {
186838
+ let hash2 = FNV1A_32_OFFSET;
186839
+ for (const field of fields) {
186840
+ hash2 = updateFnv1a32(hash2, `${field.length}:`);
186841
+ hash2 = updateFnv1a32(hash2, field);
186842
+ hash2 = updateFnv1a32(hash2, "\x00");
186843
+ }
186844
+ return hash2.toString(16).padStart(8, "0");
186845
+ }
186263
186846
  function rawPartVersion(part) {
186264
186847
  return part.__magicContextPartUpdatedAt ?? part.updated_at ?? part.updatedAt ?? part.version ?? part.revision ?? "";
186265
186848
  }
@@ -186607,13 +187190,12 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
186607
187190
  function partContentFingerprint(part) {
186608
187191
  if (!isRecord(part))
186609
187192
  return `${typeof part}:${recursiveByteLength(part)}`;
186610
- const type = partType(part);
186611
187193
  const tool = toolSignalFromPart(part);
186612
187194
  if (tool) {
186613
- return `${type}:${tool.callId}:${tool.inputText.length}:${tool.outputText.length}`;
187195
+ return contentStringsHash([tool.inputText, tool.outputText]);
186614
187196
  }
186615
187197
  const text = firstStringField(part, ["text", "thinking", "reasoning", "content", "url"]) ?? "";
186616
- return `${type}:${text.length}`;
187198
+ return contentStringsHash([text]);
186617
187199
  }
186618
187200
  function computeRawRangeFingerprint(messages, startInclusive, endExclusive) {
186619
187201
  const pieces = [];
@@ -188870,24 +189452,46 @@ function getMessageSearchStatementWithCutoff(db) {
188870
189452
  return stmt;
188871
189453
  }
188872
189454
  var ftsRowCountStatements = new WeakMap;
189455
+ var ftsRowCountStatementsWithCutoff = new WeakMap;
188873
189456
  var ftsMatchCountStatements = new WeakMap;
188874
- function getSessionFtsRowCount(db, sessionId) {
188875
- let stmt = ftsRowCountStatements.get(db);
188876
- if (!stmt) {
188877
- stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
188878
- ftsRowCountStatements.set(db, stmt);
188879
- }
188880
- const row = stmt.get(sessionId);
189457
+ var ftsMatchCountStatementsWithCutoff = new WeakMap;
189458
+ function getSessionFtsRowCount(db, sessionId, cutoff) {
189459
+ const stmt = cutoff === null ? (() => {
189460
+ let cached2 = ftsRowCountStatements.get(db);
189461
+ if (!cached2) {
189462
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
189463
+ ftsRowCountStatements.set(db, cached2);
189464
+ }
189465
+ return cached2;
189466
+ })() : (() => {
189467
+ let cached2 = ftsRowCountStatementsWithCutoff.get(db);
189468
+ if (!cached2) {
189469
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND CAST(message_ordinal AS INTEGER) <= ?");
189470
+ ftsRowCountStatementsWithCutoff.set(db, cached2);
189471
+ }
189472
+ return cached2;
189473
+ })();
189474
+ const row = cutoff === null ? stmt.get(sessionId) : stmt.get(sessionId, cutoff);
188881
189475
  return typeof row?.n === "number" ? row.n : 0;
188882
189476
  }
188883
- function countSessionFtsMatches(db, sessionId, ftsQuery) {
188884
- let stmt = ftsMatchCountStatements.get(db);
188885
- if (!stmt) {
188886
- stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
188887
- ftsMatchCountStatements.set(db, stmt);
188888
- }
189477
+ function countSessionFtsMatches(db, sessionId, ftsQuery, cutoff) {
189478
+ const stmt = cutoff === null ? (() => {
189479
+ let cached2 = ftsMatchCountStatements.get(db);
189480
+ if (!cached2) {
189481
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
189482
+ ftsMatchCountStatements.set(db, cached2);
189483
+ }
189484
+ return cached2;
189485
+ })() : (() => {
189486
+ let cached2 = ftsMatchCountStatementsWithCutoff.get(db);
189487
+ if (!cached2) {
189488
+ 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) <= ?");
189489
+ ftsMatchCountStatementsWithCutoff.set(db, cached2);
189490
+ }
189491
+ return cached2;
189492
+ })();
188889
189493
  try {
188890
- const row = stmt.get(sessionId, ftsQuery);
189494
+ const row = cutoff === null ? stmt.get(sessionId, ftsQuery) : stmt.get(sessionId, ftsQuery, cutoff);
188891
189495
  return typeof row?.n === "number" ? row.n : 0;
188892
189496
  } catch {
188893
189497
  return 0;
@@ -189137,7 +189741,7 @@ function searchMessages(args) {
189137
189741
  role: row.role
189138
189742
  }));
189139
189743
  }
189140
- const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
189744
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId, cutoff);
189141
189745
  const queryLists = [];
189142
189746
  if (baseQuery.length > 0) {
189143
189747
  queryLists.push({
@@ -189150,7 +189754,7 @@ function searchMessages(args) {
189150
189754
  const probeQuery = sanitizeFtsQuery(probe);
189151
189755
  if (probeQuery.length === 0)
189152
189756
  continue;
189153
- const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
189757
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery, cutoff);
189154
189758
  const weight = probeDiscriminationWeight(df, corpusSize);
189155
189759
  probeWeights.set(probe, weight);
189156
189760
  queryLists.push({
@@ -200185,6 +200789,15 @@ async function executeContextRecompWithResult(deps, options = {}) {
200185
200789
  published: false
200186
200790
  };
200187
200791
  }
200792
+ options.onLeaseAcquired?.();
200793
+ if (isWrapupInProgress(deps.db, sessionId)) {
200794
+ sessionLog(sessionId, "recomp skipped: /ctx-wrapup became active");
200795
+ releaseCompartmentLease(deps.db, sessionId, holderId);
200796
+ return {
200797
+ message: "## Magic Recomp — Skipped\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
200798
+ published: false
200799
+ };
200800
+ }
200188
200801
  const renewal = startLeaseRenewal(deps, holderId);
200189
200802
  const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
200190
200803
  const promise2 = options.range ? executePartialRecompInternal(runnerDeps, options.range) : executeContextRecompInternal(runnerDeps);
@@ -201310,7 +201923,7 @@ function formatThresholdPercent(value) {
201310
201923
  // package.json
201311
201924
  var package_default = {
201312
201925
  name: "@cortexkit/pi-magic-context",
201313
- version: "0.31.3",
201926
+ version: "0.31.4",
201314
201927
  type: "module",
201315
201928
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
201316
201929
  main: "dist/index.js",
@@ -201860,6 +202473,11 @@ function readHistorianState(db, sessionId, meta3) {
201860
202473
  import * as crypto4 from "node:crypto";
201861
202474
  var DEFAULT_MESSAGES_TO_KEEP = 20;
201862
202475
  var LEASE_WAIT_MS = 1000;
202476
+ var MAX_WRAPUP_LEASE_WAIT_MS = 10 * 60 * 1000;
202477
+ function resolveWrapupLeaseWaitTimeout(deps) {
202478
+ const configured = deps.wrapupLeaseWaitTimeoutMs ?? MAX_WRAPUP_LEASE_WAIT_MS;
202479
+ return Number.isFinite(configured) && configured >= 0 ? configured : MAX_WRAPUP_LEASE_WAIT_MS;
202480
+ }
201863
202481
  function parseWrapupArgs(raw) {
201864
202482
  const trimmed = raw.trim();
201865
202483
  if (trimmed === "")
@@ -202077,11 +202695,12 @@ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleS
202077
202695
  Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
202078
202696
  level: "info"
202079
202697
  });
202080
- const leaseHolder = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker);
202081
- if (!leaseHolder) {
202082
- failure = ownershipLost ? `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : "Another Magic Context rebuild started while wrapup was waiting. Run /ctx-wrapup again to continue.";
202698
+ const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps));
202699
+ if (!leaseResult.ok) {
202700
+ failure = ownershipLost ? `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : leaseResult.reason === "timeout" ? `Timed out waiting for another process to release the compartment-state lease; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : "Another Magic Context rebuild started while wrapup was waiting. Run /ctx-wrapup again to continue.";
202083
202701
  break;
202084
202702
  }
202703
+ const leaseHolder = leaseResult.holderId;
202085
202704
  const leaseRenewal = setInterval(() => {
202086
202705
  try {
202087
202706
  renewCompartmentLease(deps.db, sessionId, leaseHolder);
@@ -202185,15 +202804,19 @@ function resolvePiContextLimit(ctx) {
202185
202804
  }
202186
202805
  return 128000;
202187
202806
  }
202188
- async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker) {
202807
+ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
202808
+ const waitStartedAt = Date.now();
202809
+ const remainingMs = () => Math.max(0, waitStartedAt + maxWaitMs - Date.now());
202189
202810
  for (;; ) {
202811
+ if (remainingMs() <= 0)
202812
+ return { ok: false, reason: "timeout" };
202190
202813
  const holderId = crypto4.randomUUID();
202191
202814
  const lease = acquireCompartmentLease(db, sessionId, holderId);
202192
202815
  if (lease)
202193
- return holderId;
202816
+ return { ok: true, holderId };
202194
202817
  if (!renewWrapupMarker({}))
202195
- return null;
202196
- await new Promise((resolve3) => setTimeout(resolve3, LEASE_WAIT_MS));
202818
+ return { ok: false, reason: "ownership_lost" };
202819
+ await new Promise((resolve3) => setTimeout(resolve3, Math.min(LEASE_WAIT_MS, remainingMs())));
202197
202820
  }
202198
202821
  }
202199
202822
  function estimateChunks(tokens, chunkTokens) {
@@ -206949,8 +207572,9 @@ function createCtxMemoryTool(deps) {
206949
207572
  const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
206950
207573
  if (isOwn)
206951
207574
  return true;
206952
- return toolShareCategories === null || toolShareCategories.includes(memory2.category);
207575
+ return toolShareCategories?.includes(memory2.category) ?? false;
206953
207576
  };
207577
+ const memoryOwnedByTool = (memory2) => workspaceIdentitySet.identities.length > 1 ? targetIdentityForStoredPath(memory2.projectPath) === projectIdentity : storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
206954
207578
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
206955
207579
  if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
206956
207580
  return err2("Cross-session memory is disabled for this project.");
@@ -206997,7 +207621,8 @@ function createCtxMemoryTool(deps) {
206997
207621
  return err2("Error: 'content' is required when action is 'update'.");
206998
207622
  }
206999
207623
  const memory2 = getMemoryById(deps.db, updateId);
207000
- if (!memory2 || !memoryVisibleToTool(memory2)) {
207624
+ const updateAllowed = memory2 ? dreamerAllowed ? memoryVisibleToTool(memory2) : memoryOwnedByTool(memory2) : false;
207625
+ if (!memory2 || !updateAllowed) {
207001
207626
  return err2(`Error: Memory with ID ${updateId} was not found.`);
207002
207627
  }
207003
207628
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
@@ -207044,7 +207669,7 @@ function createCtxMemoryTool(deps) {
207044
207669
  return err2("Error: One or more source memories were not found.");
207045
207670
  }
207046
207671
  if (!dreamerAllowed) {
207047
- const foreign = sourceMemories.find((memory2) => !memoryVisibleToTool(memory2));
207672
+ const foreign = sourceMemories.find((memory2) => !memoryOwnedByTool(memory2));
207048
207673
  if (foreign) {
207049
207674
  return err2(`Error: Memory with ID ${foreign.id} was not found.`);
207050
207675
  }
@@ -207146,7 +207771,8 @@ function createCtxMemoryTool(deps) {
207146
207771
  const archiveIds = [...new Set(rawArchiveIds)];
207147
207772
  for (const memoryId of archiveIds) {
207148
207773
  const memory2 = getMemoryById(deps.db, memoryId);
207149
- if (!memory2 || !memoryVisibleToTool(memory2)) {
207774
+ const archiveAllowed = memory2 ? dreamerAllowed ? memoryVisibleToTool(memory2) : memoryOwnedByTool(memory2) : false;
207775
+ if (!memory2 || !archiveAllowed) {
207150
207776
  return err2(`Error: Memory with ID ${memoryId} was not found.`);
207151
207777
  }
207152
207778
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
@@ -207807,14 +208433,7 @@ import {
207807
208433
  var TODO_TOOL_NAME = "todowrite";
207808
208434
  var TODOS_COMMAND_NAME = "todos";
207809
208435
  var WIDGET_KEY = "magic-context-todos";
207810
- var MAX_OVERLAY_CONTENT_ROWS = 12;
207811
- var TODO_STATUSES = [
207812
- "pending",
207813
- "in_progress",
207814
- "completed",
207815
- "cancelled"
207816
- ];
207817
- var TODO_PRIORITIES = ["high", "medium", "low"];
208436
+ var MAX_TODO_CONTENT_ROWS = 12;
207818
208437
  var STATUS_GLYPH = {
207819
208438
  pending: "○",
207820
208439
  in_progress: "◐",
@@ -207828,10 +208447,10 @@ var STATUS_COLOR = {
207828
208447
  cancelled: "error"
207829
208448
  };
207830
208449
  var snapshotsBySession = new Map;
207831
- function isTodoStatus(value) {
208450
+ function isTodoStatus2(value) {
207832
208451
  return TODO_STATUSES.includes(value);
207833
208452
  }
207834
- function isTodoPriority(value) {
208453
+ function isTodoPriority2(value) {
207835
208454
  return TODO_PRIORITIES.includes(value);
207836
208455
  }
207837
208456
  function cloneTodos(todos) {
@@ -207845,14 +208464,14 @@ function parseTodos(input) {
207845
208464
  if (item === null || typeof item !== "object")
207846
208465
  return null;
207847
208466
  const raw = item;
207848
- if (typeof raw.content !== "string" || !isTodoStatus(raw.status)) {
208467
+ if (typeof raw.content !== "string" || !isTodoStatus2(raw.status)) {
207849
208468
  return null;
207850
208469
  }
207851
208470
  const todo = {
207852
208471
  content: raw.content,
207853
208472
  status: raw.status
207854
208473
  };
207855
- if (isTodoPriority(raw.priority))
208474
+ if (isTodoPriority2(raw.priority))
207856
208475
  todo.priority = raw.priority;
207857
208476
  if (typeof raw.id === "string" && raw.id.length > 0)
207858
208477
  todo.id = raw.id;
@@ -207929,7 +208548,7 @@ function countTodos(todos) {
207929
208548
  return counts;
207930
208549
  }
207931
208550
  function activeTitleCount(todos) {
207932
- return todos.filter((todo) => todo.status !== "completed").length;
208551
+ return todos.filter((todo) => !TITLE_DONE_STATUSES.has(todo.status)).length;
207933
208552
  }
207934
208553
  function formatCounts(counts) {
207935
208554
  if (counts.total === 0)
@@ -207963,6 +208582,16 @@ function lineComponent(renderLines) {
207963
208582
  invalidate() {}
207964
208583
  };
207965
208584
  }
208585
+ function capTodoRows(rows) {
208586
+ if (rows.length <= MAX_TODO_CONTENT_ROWS) {
208587
+ return { visible: [...rows], hiddenCount: 0 };
208588
+ }
208589
+ const visibleCount = Math.max(0, MAX_TODO_CONTENT_ROWS - 1);
208590
+ return {
208591
+ visible: rows.slice(0, visibleCount),
208592
+ hiddenCount: rows.length - visibleCount
208593
+ };
208594
+ }
207966
208595
  function renderTodowriteCall(args, theme) {
207967
208596
  const todos = parseTodos(args.todos) ?? [];
207968
208597
  const active = activeTitleCount(todos);
@@ -207985,7 +208614,12 @@ function renderTodowriteResult(result, theme) {
207985
208614
  return lineComponent((width) => {
207986
208615
  if (renderedTodos.length === 0)
207987
208616
  return [theme.fg("dim", "No todos")];
207988
- return renderedTodos.map((todo) => truncateToWidth2(formatTodoLine(todo, theme, { showId: true }), width, "…"));
208617
+ const { visible, hiddenCount } = capTodoRows(renderedTodos);
208618
+ const lines = visible.map((todo) => truncateToWidth2(formatTodoLine(todo, theme, { showId: true }), width, "…"));
208619
+ if (hiddenCount > 0) {
208620
+ lines.push(truncateToWidth2(theme.fg("dim", `+${hiddenCount} more`), width, "…"));
208621
+ }
208622
+ return lines;
207989
208623
  });
207990
208624
  }
207991
208625
  function registerTodosCommand(pi) {
@@ -208015,8 +208649,11 @@ function registerTodosCommand(pi) {
208015
208649
  if (group.length === 0)
208016
208650
  continue;
208017
208651
  lines.push(heading);
208018
- for (const todo of group)
208652
+ const { visible, hiddenCount } = capTodoRows(group);
208653
+ for (const todo of visible)
208019
208654
  lines.push(formatCommandLine(todo));
208655
+ if (hiddenCount > 0)
208656
+ lines.push(` +${hiddenCount} more`);
208020
208657
  }
208021
208658
  ctx.ui.notify(lines.join(`
208022
208659
  `), "info");
@@ -208144,19 +208781,16 @@ class TodoOverlay {
208144
208781
  const heading = `${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, "Todos")} ${theme.fg("muted", "—")} ${theme.fg("dim", formatCounts(counts))}`;
208145
208782
  const truncate3 = (line) => truncateToWidth2(line, width, "…");
208146
208783
  const lines = [truncate3(heading)];
208147
- const hasTruncatedTail = overlayTodos.length > MAX_OVERLAY_CONTENT_ROWS;
208148
- const visibleRows = hasTruncatedTail ? MAX_OVERLAY_CONTENT_ROWS - 1 : MAX_OVERLAY_CONTENT_ROWS;
208149
- const truncatedTail = Math.max(0, overlayTodos.length - visibleRows);
208150
- const visible = overlayTodos.slice(0, visibleRows);
208784
+ const { visible, hiddenCount } = capTodoRows(overlayTodos);
208151
208785
  for (const [index, { todo, key }] of visible.entries()) {
208152
- const isLast = index === visible.length - 1 && truncatedTail === 0;
208786
+ const isLast = index === visible.length - 1 && hiddenCount === 0;
208153
208787
  const branch = theme.fg("dim", isLast ? "└─" : "├─");
208154
208788
  lines.push(truncate3(`${branch} ${formatTodoLine(todo, theme, { showId: true })}`));
208155
208789
  if (todo.status === "completed")
208156
208790
  this.completedTaskIdsPendingHide.add(key);
208157
208791
  }
208158
- if (truncatedTail > 0) {
208159
- lines.push(truncate3(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${truncatedTail} more`)}`));
208792
+ if (hiddenCount > 0) {
208793
+ lines.push(truncate3(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${hiddenCount} more`)}`));
208160
208794
  }
208161
208795
  return lines;
208162
208796
  }
@@ -208212,17 +208846,10 @@ function registerTodoStateLifecycle(pi, deps) {
208212
208846
  }
208213
208847
 
208214
208848
  // src/tools/todowrite.ts
208215
- var STATUS_VALUES = [
208216
- "pending",
208217
- "in_progress",
208218
- "completed",
208219
- "cancelled"
208220
- ];
208221
- var PRIORITY_VALUES = ["high", "medium", "low"];
208222
208849
  var TodoItem = exports_typebox.Object({
208223
208850
  content: exports_typebox.String({ description: "Brief description of the task" }),
208224
- status: exports_typebox.Union(STATUS_VALUES.map((v) => exports_typebox.Literal(v))),
208225
- priority: exports_typebox.Optional(exports_typebox.Union(PRIORITY_VALUES.map((v) => exports_typebox.Literal(v)))),
208851
+ status: exports_typebox.Union(TODO_STATUSES.map((v) => exports_typebox.Literal(v))),
208852
+ priority: exports_typebox.Optional(exports_typebox.Union(TODO_PRIORITIES.map((v) => exports_typebox.Literal(v)))),
208226
208853
  id: exports_typebox.Optional(exports_typebox.String({ description: "Optional stable id for the todo" }))
208227
208854
  });
208228
208855
  var TodowriteParams = exports_typebox.Object({
@@ -208247,8 +208874,7 @@ function createTodowriteTool() {
208247
208874
  parameters: TodowriteParams,
208248
208875
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
208249
208876
  const todos = params.todos ?? [];
208250
- const completed = todos.filter((t) => t.status === "completed").length;
208251
- const active = todos.length - completed;
208877
+ const active = todos.filter((todo) => !TITLE_DONE_STATUSES.has(todo.status)).length;
208252
208878
  return {
208253
208879
  content: [
208254
208880
  {
@@ -208339,6 +208965,55 @@ function persistPiMessageEndModelMeta(args) {
208339
208965
  updateSessionMeta(args.db, args.sessionId, { cacheTtl });
208340
208966
  }
208341
208967
  }
208968
+ function getCompatiblePiTodoCapture(todos) {
208969
+ if (!Array.isArray(todos))
208970
+ return null;
208971
+ const normalized = normalizeTodoStateJson(todos);
208972
+ if (normalized === null)
208973
+ return null;
208974
+ return { normalized, todos };
208975
+ }
208976
+ function applyCompatiblePiTodoCapture(args) {
208977
+ if (args.todowriteEnabled) {
208978
+ setTodoSnapshot(args.sessionId, args.capture.todos);
208979
+ args.todoOverlay?.update(args.sessionId);
208980
+ }
208981
+ if (args.persist) {
208982
+ updateSessionMeta(args.db, args.sessionId, {
208983
+ lastTodoState: args.capture.normalized
208984
+ });
208985
+ }
208986
+ }
208987
+ function capturePiTodowriteArgsIfCompatible(args) {
208988
+ const capture = getCompatiblePiTodoCapture(args.todos);
208989
+ if (capture === null)
208990
+ return false;
208991
+ applyCompatiblePiTodoCapture({ ...args, capture });
208992
+ return true;
208993
+ }
208994
+ function capturePiTodowriteMessageIfCompatible(args) {
208995
+ const msg = args.message;
208996
+ if (msg?.role !== "assistant" || !Array.isArray(msg.content)) {
208997
+ return false;
208998
+ }
208999
+ for (const block of msg.content) {
209000
+ if (!block || typeof block !== "object")
209001
+ continue;
209002
+ const b = block;
209003
+ if (b.type !== "toolCall")
209004
+ continue;
209005
+ if (typeof b.name !== "string")
209006
+ continue;
209007
+ if (b.name !== "todowrite")
209008
+ continue;
209009
+ const capture = getCompatiblePiTodoCapture(b.arguments?.todos);
209010
+ if (capture === null)
209011
+ continue;
209012
+ applyCompatiblePiTodoCapture({ ...args, capture });
209013
+ return true;
209014
+ }
209015
+ return false;
209016
+ }
208342
209017
  function info(message, data) {
208343
209018
  log(`${PREFIX} ${message}`, data);
208344
209019
  }
@@ -208346,6 +209021,7 @@ function warn(message, data) {
208346
209021
  log(`${PREFIX} WARN ${message}`, data);
208347
209022
  }
208348
209023
  var migratedConfigDirs = new Set;
209024
+ var loggedPiConfigDirs = new Set;
208349
209025
  function ensureConfigLocationsMigrated(dir) {
208350
209026
  if (migratedConfigDirs.has(dir))
208351
209027
  return;
@@ -208355,6 +209031,28 @@ function ensureConfigLocationsMigrated(dir) {
208355
209031
  info: (m) => info(m)
208356
209032
  });
208357
209033
  }
209034
+ function logPiConfigLoad(args) {
209035
+ const key = resolve3(args.dir);
209036
+ if (args.dedupe && loggedPiConfigDirs.has(key))
209037
+ return;
209038
+ if (args.dedupe) {
209039
+ loggedPiConfigDirs.add(key);
209040
+ }
209041
+ if (args.loadedFromPaths.length > 0) {
209042
+ info(`config loaded from: ${args.loadedFromPaths.join(", ")}`);
209043
+ } else {
209044
+ info("config: no magic-context.jsonc found, using schema defaults");
209045
+ }
209046
+ for (const warning of args.warnings) {
209047
+ warn(`config: ${warning}`);
209048
+ }
209049
+ }
209050
+ var __test = {
209051
+ logPiConfigLoad,
209052
+ resetLoggedPiConfigDirs() {
209053
+ loggedPiConfigDirs.clear();
209054
+ }
209055
+ };
208358
209056
  function formatTokens(value) {
208359
209057
  return value.toLocaleString();
208360
209058
  }
@@ -208541,14 +209239,12 @@ async function src_default2(pi) {
208541
209239
  const { config: config2, warnings, loadedFromPaths } = loadPiConfig({
208542
209240
  cwd: projectDir
208543
209241
  });
208544
- if (loadedFromPaths.length > 0) {
208545
- info(`config loaded from: ${loadedFromPaths.join(", ")}`);
208546
- } else {
208547
- info("config: no magic-context.jsonc found, using schema defaults");
208548
- }
208549
- for (const w of warnings) {
208550
- warn(`config: ${w}`);
208551
- }
209242
+ logPiConfigLoad({
209243
+ dir: projectDir,
209244
+ loadedFromPaths,
209245
+ warnings,
209246
+ dedupe: true
209247
+ });
208552
209248
  setSqlitePragmaConfig({
208553
209249
  cacheSizeMb: config2.sqlite.cache_size_mb,
208554
209250
  mmapSizeMb: config2.sqlite.mmap_size_mb
@@ -208630,7 +209326,14 @@ async function src_default2(pi) {
208630
209326
  if (cached2)
208631
209327
  return cached2;
208632
209328
  ensureConfigLocationsMigrated(dir);
208633
- const switchedConfig = loadPiConfig({ cwd: dir }).config;
209329
+ const switchedLoad = loadPiConfig({ cwd: dir });
209330
+ logPiConfigLoad({
209331
+ dir,
209332
+ loadedFromPaths: switchedLoad.loadedFromPaths,
209333
+ warnings: switchedLoad.warnings,
209334
+ dedupe: true
209335
+ });
209336
+ const switchedConfig = switchedLoad.config;
208634
209337
  const switchedIdentity = identityOverride ?? resolveProjectIdentityOrFallback(dir);
208635
209338
  const built = buildProjectDeps(dir, switchedIdentity, switchedConfig);
208636
209339
  projectDepsByDir.set(dir, built);
@@ -209001,18 +209704,14 @@ ${block}` : event.systemPrompt;
209001
209704
  const todoArgs = event.args;
209002
209705
  const todos = todoArgs?.todos;
209003
209706
  const sessionMeta = Array.isArray(todos) ? getOrCreateSessionMeta(db, sessionId) : null;
209004
- if (todowriteEnabled && Array.isArray(todos)) {
209005
- setTodoSnapshot(sessionId, todos);
209006
- todoOverlay?.update(sessionId);
209007
- }
209008
- if (sessionMeta && !sessionMeta.isSubagent) {
209009
- const normalizedTodos = normalizeTodoStateJson(todos);
209010
- if (normalizedTodos !== null) {
209011
- updateSessionMeta(db, sessionId, {
209012
- lastTodoState: normalizedTodos
209013
- });
209014
- }
209015
- }
209707
+ capturePiTodowriteArgsIfCompatible({
209708
+ db,
209709
+ sessionId,
209710
+ todos,
209711
+ todowriteEnabled,
209712
+ todoOverlay,
209713
+ persist: Boolean(sessionMeta && !sessionMeta.isSubagent)
209714
+ });
209016
209715
  if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
209017
209716
  if (sessionMeta && !sessionMeta.isSubagent) {
209018
209717
  onNoteTrigger(db, sessionId, "todos_complete");
@@ -209115,36 +209814,14 @@ ${block}` : event.systemPrompt;
209115
209814
  try {
209116
209815
  const sessionMetaForTodo = getOrCreateSessionMeta(db, sessionId);
209117
209816
  if (!sessionMetaForTodo.isSubagent) {
209118
- const msg = event.message;
209119
- if (msg && msg.role === "assistant" && Array.isArray(msg.content)) {
209120
- for (const block of msg.content) {
209121
- if (!block || typeof block !== "object")
209122
- continue;
209123
- const b = block;
209124
- if (b.type !== "toolCall")
209125
- continue;
209126
- if (typeof b.name !== "string")
209127
- continue;
209128
- if (b.name !== "todowrite") {
209129
- continue;
209130
- }
209131
- const args = b.arguments;
209132
- const todos = args?.todos;
209133
- if (!Array.isArray(todos))
209134
- continue;
209135
- const normalized = normalizeTodoStateJson(todos);
209136
- if (normalized === null)
209137
- continue;
209138
- if (todowriteEnabled) {
209139
- setTodoSnapshot(sessionId, todos);
209140
- todoOverlay?.update(sessionId);
209141
- }
209142
- updateSessionMeta(db, sessionId, {
209143
- lastTodoState: normalized
209144
- });
209145
- break;
209146
- }
209147
- }
209817
+ capturePiTodowriteMessageIfCompatible({
209818
+ db,
209819
+ sessionId,
209820
+ message: event.message,
209821
+ todowriteEnabled,
209822
+ todoOverlay,
209823
+ persist: true
209824
+ });
209148
209825
  }
209149
209826
  } catch (err5) {
209150
209827
  warn("message_end: synthetic todowrite capture failed:", err5);
@@ -209236,5 +209913,8 @@ export {
209236
209913
  resolveDreamerFromConfig,
209237
209914
  persistPiPressureFromMessageEnd,
209238
209915
  persistPiMessageEndModelMeta,
209239
- src_default2 as default
209916
+ src_default2 as default,
209917
+ capturePiTodowriteMessageIfCompatible,
209918
+ capturePiTodowriteArgsIfCompatible,
209919
+ __test
209240
209920
  };