@cortexkit/pi-magic-context 0.31.3 → 0.31.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -157432,18 +157588,48 @@ function inferAccountingSubagent(agent) {
157432
157588
  return "recomp";
157433
157589
  return "historian";
157434
157590
  }
157591
+ var ALREADY_PROCESSING_PREFIX = "Agent is already processing";
157592
+ var ISOLATED_RETRY_MODEL_UNAVAILABLE_MESSAGE = "model unavailable in isolated retry: it is provided by a disabled extension; configure it through models.json or add a built-in/provider-configured fallback";
157593
+ var MODEL_RESOLUTION_ERROR_PATTERNS = [
157594
+ /unknown model/i,
157595
+ /unknown provider/i,
157596
+ /model.+not found/i,
157597
+ /provider.+not found/i,
157598
+ /could not resolve model/i,
157599
+ /no models? (matched|available|configured)/i,
157600
+ /model.+not configured/i
157601
+ ];
157435
157602
 
157436
157603
  class PiSubagentRunner {
157437
157604
  harness = "pi";
157438
157605
  invocation;
157439
157606
  spawnImpl;
157440
157607
  platform;
157608
+ extraArgs;
157441
157609
  constructor(options = {}) {
157442
157610
  this.invocation = options.piBinary ? { command: options.piBinary, prefixArgs: [] } : resolvePiInvocation();
157443
157611
  this.spawnImpl = options.spawnImpl ?? childProcess.spawn;
157444
157612
  this.platform = options.platform ?? process.platform;
157613
+ this.extraArgs = options.extraArgs ?? [];
157445
157614
  }
157446
157615
  async run(options) {
157616
+ const primaryRunMode = { disableDiscoveredExtensions: false };
157617
+ const primaryResult = await this.runModelChain(options, primaryRunMode);
157618
+ if (this.spawnUsesNoExtensions(primaryRunMode) || !isPiExtensionCollisionFailure(primaryResult)) {
157619
+ return primaryResult;
157620
+ }
157621
+ const sessionId = options.accountingSessionId ?? "pi-subagent";
157622
+ sessionLog(sessionId, "pi subagent: a loaded Pi extension started an agent turn before the child's prompt could run; retrying with an isolated extension set (user extensions disabled for this run)");
157623
+ const isolatedResult = await this.runModelChain(options, {
157624
+ disableDiscoveredExtensions: true
157625
+ });
157626
+ if (!isolatedResult.ok && isIsolatedRetryModelUnavailable(isolatedResult)) {
157627
+ sessionLog(sessionId, ISOLATED_RETRY_MODEL_UNAVAILABLE_MESSAGE);
157628
+ return annotateIsolatedRetryModelUnavailable(isolatedResult);
157629
+ }
157630
+ return isolatedResult;
157631
+ }
157632
+ async runModelChain(options, runMode) {
157447
157633
  const models = [options.model, ...options.fallbackModels ?? []].filter((model) => typeof model === "string" && model.length > 0);
157448
157634
  const attempts = models.length > 0 ? models : [undefined];
157449
157635
  let lastResult = null;
@@ -157454,17 +157640,23 @@ class PiSubagentRunner {
157454
157640
  model,
157455
157641
  fallbackModels: undefined
157456
157642
  };
157457
- const result = await this.runOnce(attemptOptions);
157643
+ const result = await this.runOnce(attemptOptions, runMode);
157458
157644
  if (result.ok)
157459
157645
  return result;
157460
157646
  lastResult = result;
157647
+ if (!this.spawnUsesNoExtensions(runMode) && isPiExtensionCollisionFailure(result)) {
157648
+ return result;
157649
+ }
157461
157650
  if (index >= attempts.length - 1 || !isFallbackEligible(result.reason)) {
157462
157651
  return result;
157463
157652
  }
157464
157653
  }
157465
- return lastResult ?? this.runOnce({ ...options, fallbackModels: undefined });
157654
+ return lastResult ?? this.runOnce({ ...options, fallbackModels: undefined }, runMode);
157655
+ }
157656
+ spawnUsesNoExtensions(runMode) {
157657
+ return runMode.disableDiscoveredExtensions || hasNoExtensionsArg([...this.invocation.prefixArgs, ...this.extraArgs]);
157466
157658
  }
157467
- async runOnce(options) {
157659
+ async runOnce(options, runMode) {
157468
157660
  const startTime = Date.now();
157469
157661
  let recordedAccounting = false;
157470
157662
  const recordAccounting = (result, messages = []) => {
@@ -157539,6 +157731,7 @@ class PiSubagentRunner {
157539
157731
  }
157540
157732
  }
157541
157733
  const args = buildArgs(options, {
157734
+ disableDiscoveredExtensions: runMode.disableDiscoveredExtensions,
157542
157735
  omitPositionalMessage: deliverViaStdin,
157543
157736
  systemPromptPath
157544
157737
  });
@@ -157566,7 +157759,7 @@ class PiSubagentRunner {
157566
157759
  };
157567
157760
  let child;
157568
157761
  try {
157569
- child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...args], {
157762
+ child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...this.extraArgs, ...args], {
157570
157763
  cwd: options.cwd,
157571
157764
  env: {
157572
157765
  ...process.env,
@@ -157860,6 +158053,32 @@ class PiSubagentRunner {
157860
158053
  });
157861
158054
  }
157862
158055
  }
158056
+ function getResultStderr(result) {
158057
+ const stderr = result.meta?.stderr;
158058
+ return typeof stderr === "string" ? stderr : "";
158059
+ }
158060
+ function hasNoExtensionsArg(args) {
158061
+ return args.includes("--no-extensions");
158062
+ }
158063
+ function isPiExtensionCollisionFailure(result) {
158064
+ return !result.ok && result.reason === "non_zero_exit" && getResultStderr(result).includes(ALREADY_PROCESSING_PREFIX);
158065
+ }
158066
+ function isIsolatedRetryModelUnavailable(result) {
158067
+ if (result.ok)
158068
+ return false;
158069
+ const diagnosticText = `${result.error}
158070
+ ${getResultStderr(result)}`;
158071
+ return MODEL_RESOLUTION_ERROR_PATTERNS.some((pattern) => pattern.test(diagnosticText));
158072
+ }
158073
+ function annotateIsolatedRetryModelUnavailable(result) {
158074
+ if (result.error.startsWith(ISOLATED_RETRY_MODEL_UNAVAILABLE_MESSAGE)) {
158075
+ return result;
158076
+ }
158077
+ return {
158078
+ ...result,
158079
+ error: `${ISOLATED_RETRY_MODEL_UNAVAILABLE_MESSAGE}. Original failure: ${result.error}`
158080
+ };
158081
+ }
157863
158082
  function isFallbackEligible(reason) {
157864
158083
  return reason === "model_failed" || reason === "truncated" || reason === "non_zero_exit" || reason === "no_assistant";
157865
158084
  }
@@ -157874,9 +158093,13 @@ function buildArgs(options, opts) {
157874
158093
  "--no-prompt-templates",
157875
158094
  "--no-context-files"
157876
158095
  ];
157877
- const shouldLoadSubagentExtension = SUBAGENT_ENTRY_PATH && (SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
158096
+ if (opts?.disableDiscoveredExtensions) {
158097
+ args.push("--no-extensions");
158098
+ }
158099
+ const subagentEntryPath = opts?.subagentEntryPath ?? SUBAGENT_ENTRY_PATH;
158100
+ const shouldLoadSubagentExtension = subagentEntryPath && (SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
157878
158101
  if (shouldLoadSubagentExtension) {
157879
- args.push("--extension", SUBAGENT_ENTRY_PATH);
158102
+ args.push("--extension", subagentEntryPath);
157880
158103
  if (DREAMER_ACTION_AGENTS.has(options.agent)) {
157881
158104
  args.push("--magic-context-dreamer-actions");
157882
158105
  }
@@ -158585,6 +158808,22 @@ function extractLatestAssistantText(messages) {
158585
158808
  return getTextParts(latest).map((part) => part.text).join(`
158586
158809
  `) || null;
158587
158810
  }
158811
+ function hasLengthCappedOutput(value) {
158812
+ if (Array.isArray(value))
158813
+ return value.some((item) => hasLengthCappedOutput(item));
158814
+ if (!isRecord(value))
158815
+ return false;
158816
+ if (value.length_capped === true || value.lengthCapped === true)
158817
+ return true;
158818
+ const finishReason = value.finish_reason ?? value.finishReason;
158819
+ if (typeof finishReason === "string") {
158820
+ const normalized = finishReason.toLowerCase();
158821
+ if (normalized === "length" || normalized === "max_tokens" || normalized === "max_output_tokens") {
158822
+ return true;
158823
+ }
158824
+ }
158825
+ return Object.values(value).some((item) => hasLengthCappedOutput(item));
158826
+ }
158588
158827
 
158589
158828
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
158590
158829
  init_logger();
@@ -174637,6 +174876,7 @@ class OpenAICompatibleEmbeddingProvider {
174637
174876
 
174638
174877
  // ../plugin/src/features/magic-context/memory/storage-memory-embeddings.ts
174639
174878
  var saveEmbeddingStatements = new WeakMap;
174879
+ var saveEmbeddingIfHashMatchesStatements = new WeakMap;
174640
174880
  var loadAllEmbeddingsStatements = new WeakMap;
174641
174881
  var deleteEmbeddingStatements = new WeakMap;
174642
174882
  var getStoredModelIdStatements = new WeakMap;
@@ -174667,6 +174907,14 @@ function getSaveEmbeddingStatement(db) {
174667
174907
  }
174668
174908
  return stmt;
174669
174909
  }
174910
+ function getSaveEmbeddingIfHashMatchesStatement(db) {
174911
+ let stmt = saveEmbeddingIfHashMatchesStatements.get(db);
174912
+ if (!stmt) {
174913
+ 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");
174914
+ saveEmbeddingIfHashMatchesStatements.set(db, stmt);
174915
+ }
174916
+ return stmt;
174917
+ }
174670
174918
  function getLoadAllEmbeddingsStatement(db) {
174671
174919
  let stmt = loadAllEmbeddingsStatements.get(db);
174672
174920
  if (!stmt) {
@@ -174695,6 +174943,10 @@ function saveEmbedding(db, memoryId, embedding, modelId) {
174695
174943
  const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
174696
174944
  getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
174697
174945
  }
174946
+ function saveEmbeddingIfHashMatches(db, memoryId, embedding, modelId, normalizedHash) {
174947
+ const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
174948
+ return getSaveEmbeddingIfHashMatchesStatement(db).run(memoryId, blob, modelId, memoryId, normalizedHash).changes > 0;
174949
+ }
174698
174950
  function loadAllEmbeddings(db, projectPath, modelId) {
174699
174951
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath, modelId).filter(isEmbeddingRow);
174700
174952
  const embeddings = new Map;
@@ -175555,12 +175807,12 @@ function isUnembeddedMemoryRow(row) {
175555
175807
  if (row === null || typeof row !== "object")
175556
175808
  return false;
175557
175809
  const candidate = row;
175558
- return typeof candidate.id === "number" && typeof candidate.content === "string";
175810
+ return typeof candidate.id === "number" && typeof candidate.content === "string" && typeof candidate.normalized_hash === "string";
175559
175811
  }
175560
175812
  function getLoadUnembeddedMemoriesStatement(db) {
175561
175813
  let stmt = loadUnembeddedMemoriesStatements.get(db);
175562
175814
  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 ?");
175815
+ 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
175816
  loadUnembeddedMemoriesStatements.set(db, stmt);
175565
175817
  }
175566
175818
  return stmt;
@@ -175583,8 +175835,9 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
175583
175835
  const embedding = result.vectors[index];
175584
175836
  if (!embedding)
175585
175837
  continue;
175586
- saveEmbedding(db, memory.id, embedding, result.modelId);
175587
- embeddedCount += 1;
175838
+ if (saveEmbeddingIfHashMatches(db, memory.id, embedding, result.modelId, memory.normalized_hash)) {
175839
+ embeddedCount += 1;
175840
+ }
175588
175841
  }
175589
175842
  })();
175590
175843
  return embeddedCount;
@@ -176286,25 +176539,33 @@ function getMemoryCountsByStatusStatement(db) {
176286
176539
  }
176287
176540
  return stmt;
176288
176541
  }
176289
- function insertMemory(db, input) {
176290
- const now = Date.now();
176291
- const normalizedHash = computeNormalizedHash(input.content);
176542
+ function buildInsertMemoryValues(input, normalizedHash, now, includeImportance) {
176292
176543
  const insertValues = [
176293
176544
  input.projectPath,
176294
176545
  input.category,
176295
176546
  input.content,
176296
176547
  normalizedHash
176297
176548
  ];
176298
- if (hasMemoryImportanceColumn(db)) {
176549
+ if (includeImportance) {
176299
176550
  insertValues.push(input.importance ?? 50);
176300
176551
  }
176301
176552
  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));
176553
+ return insertValues;
176554
+ }
176555
+ function loadInsertedMemory(db, rowid) {
176556
+ const inserted = getMemoryById(db, Number(rowid));
176305
176557
  if (!inserted) {
176306
176558
  throw new Error("Failed to load inserted memory row");
176307
176559
  }
176560
+ return inserted;
176561
+ }
176562
+ function insertMemory(db, input) {
176563
+ const now = Date.now();
176564
+ const normalizedHash = computeNormalizedHash(input.content);
176565
+ const insertValues = buildInsertMemoryValues(input, normalizedHash, now, hasMemoryImportanceColumn(db));
176566
+ const result = getInsertMemoryStatement(db).run(...insertValues);
176567
+ const insertedResult = result;
176568
+ const inserted = loadInsertedMemory(db, insertedResult.lastInsertRowid);
176308
176569
  invalidateProject(input.projectPath);
176309
176570
  return inserted;
176310
176571
  }
@@ -177098,6 +177359,14 @@ function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177098
177359
  return true;
177099
177360
  });
177100
177361
  }
177362
+ function runLeaseGuardedWrite(db, holderId, leaseKey, fn) {
177363
+ return runImmediate2(db, () => {
177364
+ if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
177365
+ throw new Error("Dream lease lost before guarded write");
177366
+ }
177367
+ return fn();
177368
+ });
177369
+ }
177101
177370
  var LEASE_HEARTBEAT_INTERVAL_MS = 60 * 1000;
177102
177371
  function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalMs = LEASE_HEARTBEAT_INTERVAL_MS) {
177103
177372
  let lost = false;
@@ -177892,6 +178161,39 @@ function hasShareabilitySensitiveText(text) {
177892
178161
  }
177893
178162
  }
177894
178163
 
178164
+ // ../plugin/src/features/magic-context/dreamer/manifest-parser.ts
178165
+ function extractCompleteManifestBody(text, rootName) {
178166
+ const escapedRoot = rootName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
178167
+ const rootMatch = new RegExp(`<${escapedRoot}\\b[^>]*>([\\s\\S]*?)<\\/${escapedRoot}>`, "i").exec(text);
178168
+ if (rootMatch)
178169
+ return rootMatch[1];
178170
+ const hasOpenRoot = new RegExp(`<${escapedRoot}\\b`, "i").test(text);
178171
+ const hasCloseRoot = new RegExp(`<\\/${escapedRoot}>`, "i").test(text);
178172
+ if (hasOpenRoot && !hasCloseRoot) {
178173
+ throw new Error(`${rootName} manifest missing closing root tag`);
178174
+ }
178175
+ throw new Error(`${rootName} manifest missing complete root element`);
178176
+ }
178177
+ function assertNoDuplicateManifestIds(ids, rootName) {
178178
+ const seen = new Set;
178179
+ for (const id of ids) {
178180
+ if (seen.has(id))
178181
+ throw new Error(`${rootName} manifest contains duplicate id ${id}`);
178182
+ seen.add(id);
178183
+ }
178184
+ }
178185
+ function assertManifestCoversExactly(ids, expectedIds, rootName) {
178186
+ assertNoDuplicateManifestIds(ids, rootName);
178187
+ for (const id of ids) {
178188
+ if (!expectedIds.has(id))
178189
+ throw new Error(`${rootName} manifest contains unknown id ${id}`);
178190
+ }
178191
+ for (const id of expectedIds) {
178192
+ if (!ids.includes(id))
178193
+ throw new Error(`${rootName} manifest missing id ${id}`);
178194
+ }
178195
+ }
178196
+
177895
178197
  // ../plugin/src/features/magic-context/dreamer/classify-prompt.ts
177896
178198
  var SCORING_GUIDANCE = `### How to score importance (1-100)
177897
178199
  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 +178260,15 @@ ${renderPool(args.memories)}`;
177958
178260
  var SCOPES = new Set(["project", "ecosystem", "universe"]);
177959
178261
  function parseClassifyManifest(text) {
177960
178262
  const out = [];
177961
- for (const m of text.matchAll(/<memory\b([^>]*)\/?>/g)) {
178263
+ const body = extractCompleteManifestBody(text, "classify");
178264
+ for (const m of body.matchAll(/<memory\b([^>]*)\/?>/g)) {
177962
178265
  const attrs = m[1];
177963
178266
  const idMatch = attrs.match(/\bid\s*=\s*"(\d+)"/);
177964
178267
  if (!idMatch)
177965
- continue;
178268
+ throw new Error("classify manifest entry missing numeric id");
177966
178269
  const id = Number.parseInt(idMatch[1], 10);
177967
178270
  if (!Number.isInteger(id))
177968
- continue;
178271
+ throw new Error("classify manifest entry missing numeric id");
177969
178272
  const entry = { id };
177970
178273
  const impMatch = attrs.match(/\bimportance\s*=\s*"(\d+)"/);
177971
178274
  if (impMatch) {
@@ -177974,18 +178277,23 @@ function parseClassifyManifest(text) {
177974
178277
  entry.importance = Math.max(1, Math.min(100, imp));
177975
178278
  }
177976
178279
  const scopeMatch = attrs.match(/\bscope\s*=\s*"([a-z]+)"/i);
177977
- if (scopeMatch && SCOPES.has(scopeMatch[1].toLowerCase())) {
177978
- entry.scope = scopeMatch[1].toLowerCase();
178280
+ if (scopeMatch) {
178281
+ const scope = scopeMatch[1].toLowerCase();
178282
+ if (!SCOPES.has(scope))
178283
+ throw new Error(`classify manifest invalid scope ${scope}`);
178284
+ entry.scope = scope;
177979
178285
  }
177980
178286
  const shareMatch = attrs.match(/\bshareable\s*=\s*"(true|false|1|0)"/i);
177981
178287
  if (shareMatch) {
177982
178288
  const v = shareMatch[1].toLowerCase();
177983
178289
  entry.shareable = v === "true" || v === "1";
177984
178290
  }
177985
- if (entry.importance !== undefined || entry.scope || entry.shareable !== undefined) {
177986
- out.push(entry);
178291
+ if (entry.importance === undefined && !entry.scope && entry.shareable === undefined) {
178292
+ throw new Error(`classify manifest entry ${id} missing classification fields`);
177987
178293
  }
178294
+ out.push(entry);
177988
178295
  }
178296
+ assertNoDuplicateManifestIds(out.map((entry) => entry.id), "classify");
177989
178297
  return out;
177990
178298
  }
177991
178299
 
@@ -178120,9 +178428,13 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
178120
178428
  });
178121
178429
  },
178122
178430
  validateOutput: (messages) => {
178431
+ if (hasLengthCappedOutput(messages)) {
178432
+ throw new Error("classify returned length-capped output");
178433
+ }
178123
178434
  const text = extractLatestAssistantText(messages);
178124
178435
  if (!text)
178125
178436
  throw new Error("classify returned no output");
178437
+ parseClassifyManifest(text);
178126
178438
  return text;
178127
178439
  }
178128
178440
  });
@@ -178148,17 +178460,13 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
178148
178460
  }
178149
178461
  function applyClassifications(args, chunk, manifestText) {
178150
178462
  const byId = new Map(chunk.map((m) => [m.id, m]));
178151
- const parsed = parseClassifyManifest(manifestText).filter((p) => byId.has(p.id));
178463
+ const parsed = parseClassifyManifest(manifestText);
178464
+ assertManifestCoversExactly(parsed.map((entry) => entry.id), new Set(byId.keys()), "classify");
178152
178465
  if (parsed.length === 0)
178153
178466
  return { classified: 0, changed: 0 };
178154
178467
  let classified = 0;
178155
178468
  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
- }
178469
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
178162
178470
  for (const p of parsed) {
178163
178471
  const memory = byId.get(p.id);
178164
178472
  if (!memory)
@@ -178173,9 +178481,7 @@ function applyClassifications(args, chunk, manifestText) {
178173
178481
  if (didChange)
178174
178482
  changed += 1;
178175
178483
  }
178176
- })();
178177
- if (leaseLost)
178178
- throw new Error("Dream lease lost during classify commit");
178484
+ });
178179
178485
  return { classified, changed };
178180
178486
  }
178181
178487
  function recordInvocation(args, startedAt, params) {
@@ -178223,9 +178529,11 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
178223
178529
 
178224
178530
  class SmartNoteNetworkError extends Error {
178225
178531
  isSmartNoteNetworkError = true;
178226
- constructor(message) {
178532
+ terminal;
178533
+ constructor(message, options = {}) {
178227
178534
  super(message);
178228
178535
  this.name = "SmartNoteNetworkError";
178536
+ this.terminal = options.terminal ?? false;
178229
178537
  }
178230
178538
  }
178231
178539
 
@@ -178239,6 +178547,9 @@ class SmartNoteSecurityError extends Error {
178239
178547
  function isSmartNoteNetworkError(error51) {
178240
178548
  return error51 instanceof SmartNoteNetworkError || error51 instanceof Error && (error51.name === "SmartNoteNetworkError" || error51.message.includes("SmartNoteNetworkError") || error51.message.includes("SMART_NOTE_NETWORK"));
178241
178549
  }
178550
+ function isTerminalSmartNoteNetworkError(error51) {
178551
+ return error51 instanceof SmartNoteNetworkError && error51.terminal;
178552
+ }
178242
178553
  function parseSmartNoteManifest(json2) {
178243
178554
  if (!json2)
178244
178555
  return { capabilities: [] };
@@ -178268,6 +178579,7 @@ function stringArray(value) {
178268
178579
  var DNS_TIMEOUT_MS = 3000;
178269
178580
  var DEFAULT_HTTP_TIMEOUT_MS = 5000;
178270
178581
  var DEFAULT_HTTP_BODY_LIMIT_BYTES = 64 * 1024;
178582
+ var MAX_HTTP_ADDRESS_CANDIDATES = 4;
178271
178583
  var defaultResolver = {
178272
178584
  async lookup(hostname3, signal) {
178273
178585
  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 +178616,21 @@ async function guardedSmartNoteHttpGet(input, options) {
178304
178616
  });
178305
178617
  const timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
178306
178618
  const bodyLimitBytes = options.bodyLimitBytes ?? DEFAULT_HTTP_BODY_LIMIT_BYTES;
178619
+ const requestAddress = options.requestAddress ?? requestValidatedAddress;
178620
+ const candidates = validation.addresses.slice(0, MAX_HTTP_ADDRESS_CANDIDATES);
178307
178621
  let lastError;
178308
- for (const candidate of validation.addresses) {
178622
+ for (const candidate of candidates) {
178309
178623
  try {
178310
- return await requestValidatedAddress(validation, candidate, {
178624
+ return await requestAddress(validation, candidate, {
178311
178625
  signal: options.signal,
178312
178626
  timeoutMs,
178313
178627
  bodyLimitBytes
178314
178628
  });
178315
178629
  } catch (error51) {
178316
178630
  lastError = error51;
178317
- if (error51 instanceof SmartNoteSecurityError || options.signal.aborted)
178631
+ if (error51 instanceof SmartNoteSecurityError || options.signal.aborted || isTerminalSmartNoteNetworkError(error51)) {
178318
178632
  throw error51;
178633
+ }
178319
178634
  }
178320
178635
  }
178321
178636
  throw toNetworkError(lastError, "all validated addresses failed");
@@ -178385,7 +178700,9 @@ function requestValidatedAddress(validation, candidate, options) {
178385
178700
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
178386
178701
  bytes += buf.byteLength;
178387
178702
  if (bytes > options.bodyLimitBytes) {
178388
- request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large"));
178703
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large", { terminal: true }));
178704
+ response.destroy();
178705
+ request2.destroy();
178389
178706
  return;
178390
178707
  }
178391
178708
  chunks.push(buf);
@@ -178402,10 +178719,16 @@ function requestValidatedAddress(validation, candidate, options) {
178402
178719
  resolve({ status, body: Buffer.concat(chunks).toString("utf8") });
178403
178720
  });
178404
178721
  });
178405
- const onAbort = () => request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted"));
178722
+ const onAbort = () => {
178723
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted"));
178724
+ request2.destroy();
178725
+ };
178406
178726
  options.signal.addEventListener("abort", onAbort, { once: true });
178407
178727
  request2.on("timeout", () => {
178408
- request2.destroy(new SmartNoteNetworkError("SMART_NOTE_NETWORK: request timed out"));
178728
+ reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: request timed out", {
178729
+ terminal: true
178730
+ }));
178731
+ request2.destroy();
178409
178732
  });
178410
178733
  request2.on("error", (error51) => {
178411
178734
  options.signal.removeEventListener("abort", onAbort);
@@ -178753,6 +179076,20 @@ function withSandboxLock(fn) {
178753
179076
  var DEFAULT_TIMEOUT_MS = 2000;
178754
179077
  var DEFAULT_HEAP_LIMIT_BYTES = 8 * 1024 * 1024;
178755
179078
  var DEFAULT_STACK_LIMIT_BYTES = 512 * 1024;
179079
+ function resolveCapabilitiesForRun(options, signal) {
179080
+ if (options.capabilityFactory) {
179081
+ return options.capabilityFactory(signal);
179082
+ }
179083
+ if (options.capabilities) {
179084
+ return options.capabilities;
179085
+ }
179086
+ throw new Error("smart-note check requires capabilities");
179087
+ }
179088
+ function throwIfRunAborted(signal) {
179089
+ if (signal.aborted) {
179090
+ throw signal.reason ?? new Error("smart-note check aborted");
179091
+ }
179092
+ }
178756
179093
  async function runCompiledSmartNoteCheck(options) {
178757
179094
  return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options));
178758
179095
  }
@@ -178760,17 +179097,24 @@ async function runCompiledSmartNoteCheckLocked(options) {
178760
179097
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
178761
179098
  const controller = new AbortController;
178762
179099
  const externalAbort = () => controller.abort(options.signal?.reason);
178763
- options.signal?.addEventListener("abort", externalAbort, { once: true });
179100
+ if (options.signal?.aborted) {
179101
+ externalAbort();
179102
+ } else {
179103
+ options.signal?.addEventListener("abort", externalAbort, { once: true });
179104
+ }
178764
179105
  const timer = setTimeout(() => controller.abort(new Error("smart-note check timed out")), timeoutMs);
178765
179106
  try {
179107
+ throwIfRunAborted(controller.signal);
179108
+ const capabilities = resolveCapabilitiesForRun(options, controller.signal);
178766
179109
  const deadline = Date.now() + timeoutMs;
178767
179110
  const quickjs = await getAsyncModule();
179111
+ throwIfRunAborted(controller.signal);
178768
179112
  const context = quickjs.newContext();
178769
179113
  try {
178770
179114
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
178771
179115
  context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
178772
179116
  context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
178773
- installCapabilityObject(context, options.capabilities);
179117
+ installCapabilityObject(context, capabilities);
178774
179118
  disableAmbientDynamicCode(context);
178775
179119
  const result = await evalCheck(context, options.compiledCheck);
178776
179120
  const checkResult = result;
@@ -178923,6 +179267,7 @@ Remember: output only the JSON object described by the system prompt.`;
178923
179267
  }
178924
179268
  }, {
178925
179269
  timeoutMs: remainingMs,
179270
+ signal: args.signal,
178926
179271
  fallbackModels: args.fallbackModels,
178927
179272
  callContext: "dreamer:smart-note-compiler",
178928
179273
  fetchOutput: async () => {
@@ -178947,7 +179292,8 @@ Remember: output only the JSON object described by the system prompt.`;
178947
179292
  }
178948
179293
  const dryRun = await runCompiledSmartNoteCheck({
178949
179294
  compiledCheck,
178950
- capabilities: args.capabilities,
179295
+ capabilityFactory: args.capabilityFactory,
179296
+ signal: args.signal,
178951
179297
  timeoutMs: 2000
178952
179298
  });
178953
179299
  if (!dryRun.ok) {
@@ -179095,7 +179441,9 @@ function nextSmartNoteCheckDueAt(cron, options = {}) {
179095
179441
  const rawNext = cron?.trim() ? nextDueAtMs(cron, now) : null;
179096
179442
  const rawDelta = rawNext ? rawNext - now : SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS;
179097
179443
  const clamped = Math.min(ceilingMs, Math.max(floorMs, rawDelta));
179098
- return now + clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
179444
+ const jittered = clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
179445
+ const bounded = Math.min(ceilingMs, Math.max(floorMs, jittered));
179446
+ return now + bounded;
179099
179447
  }
179100
179448
  function deterministicJitterMs(intervalMs, noteId, hash2) {
179101
179449
  const max = Math.min(60000, Math.floor(intervalMs * 0.1));
@@ -179264,9 +179612,9 @@ async function runDueCompiledSmartNoteChecks(args) {
179264
179612
  try {
179265
179613
  const result = await runCompiledSmartNoteCheck({
179266
179614
  compiledCheck: note.compiledCheck,
179267
- capabilities: createSmartNoteCapabilities({
179615
+ capabilityFactory: (signal) => createSmartNoteCapabilities({
179268
179616
  projectRoot: args.projectRoot,
179269
- signal: controller.signal
179617
+ signal
179270
179618
  }),
179271
179619
  signal: controller.signal,
179272
179620
  timeoutMs: Math.min(2000, remaining)
@@ -179329,6 +179677,24 @@ var MAX_COMPILE_PER_RUN = 5;
179329
179677
  var MAX_FALLBACK_PER_RUN = 3;
179330
179678
  var MAX_COMPILATION_FAILURES = 3;
179331
179679
  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.';
179680
+ function createPromptAbortSignal(parent, timeoutMs, timeoutMessage) {
179681
+ const controller = new AbortController;
179682
+ const abortFromParent = () => {
179683
+ controller.abort(parent.reason ?? new Error("smart-note prompt aborted by lease loss"));
179684
+ };
179685
+ if (parent.aborted)
179686
+ abortFromParent();
179687
+ else
179688
+ parent.addEventListener("abort", abortFromParent, { once: true });
179689
+ const timer = setTimeout(() => controller.abort(new Error(timeoutMessage)), timeoutMs);
179690
+ return {
179691
+ signal: controller.signal,
179692
+ cleanup: () => {
179693
+ clearTimeout(timer);
179694
+ parent.removeEventListener("abort", abortFromParent);
179695
+ }
179696
+ };
179697
+ }
179332
179698
  async function evaluateSmartNotes(args) {
179333
179699
  const projectRoot = args.sessionDirectory ?? args.projectIdentity;
179334
179700
  const pendingAtStart = getPendingSmartNotes(args.db, args.projectIdentity).length;
@@ -179337,6 +179703,7 @@ async function evaluateSmartNotes(args) {
179337
179703
  return { surfaced: 0, pending: 0, ran: false };
179338
179704
  }
179339
179705
  let leaseLost = false;
179706
+ const leaseAbortController = new AbortController;
179340
179707
  const leaseHeld = () => !leaseLost && peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey);
179341
179708
  const assertLeaseHeld = (phase) => {
179342
179709
  if (!leaseHeld()) {
@@ -179346,6 +179713,7 @@ async function evaluateSmartNotes(args) {
179346
179713
  };
179347
179714
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => {
179348
179715
  leaseLost = true;
179716
+ leaseAbortController.abort(new Error("Dream lease lost during smart notes"));
179349
179717
  log("[dreamer] smart notes: lease lost — aborting");
179350
179718
  args.onLeaseLost?.("smart notes");
179351
179719
  });
@@ -179368,7 +179736,7 @@ async function evaluateSmartNotes(args) {
179368
179736
  break;
179369
179737
  assertLeaseHeld("compile start");
179370
179738
  didWork = true;
179371
- const compiled = await compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld);
179739
+ const compiled = await compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld, leaseAbortController.signal);
179372
179740
  if (compiled)
179373
179741
  surfaced += 1;
179374
179742
  }
@@ -179388,7 +179756,7 @@ async function evaluateSmartNotes(args) {
179388
179756
  break;
179389
179757
  assertLeaseHeld("fallback start");
179390
179758
  didWork = true;
179391
- const met = await confirmReadOnly(args, note.id, note.content, note.surfaceCondition);
179759
+ const met = await confirmReadOnly(args, note.id, note.content, note.surfaceCondition, leaseAbortController.signal);
179392
179760
  const now = Date.now();
179393
179761
  assertLeaseHeld("fallback commit");
179394
179762
  commitSmartNoteState(args.db, {
@@ -179414,9 +179782,8 @@ async function evaluateSmartNotes(args) {
179414
179782
  heartbeat.stop();
179415
179783
  }
179416
179784
  }
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()));
179785
+ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld, leaseSignal) {
179786
+ const promptSignal = createPromptAbortSignal(leaseSignal, Math.max(1000, args.deadline - Date.now()), "smart-note compile deadline");
179420
179787
  try {
179421
179788
  const result = await compileSmartNoteCheck({
179422
179789
  client: args.client,
@@ -179425,7 +179792,8 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld)
179425
179792
  sessionDirectory: args.sessionDirectory,
179426
179793
  projectIdentity: args.projectIdentity,
179427
179794
  note,
179428
- capabilities: createSmartNoteCapabilities({ projectRoot, signal: controller.signal }),
179795
+ capabilityFactory: (signal) => createSmartNoteCapabilities({ projectRoot, signal }),
179796
+ signal: promptSignal.signal,
179429
179797
  deadline: args.deadline,
179430
179798
  model: args.model,
179431
179799
  fallbackModels: args.fallbackModels
@@ -179470,7 +179838,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld)
179470
179838
  });
179471
179839
  return result.dryRun.met;
179472
179840
  } finally {
179473
- clearTimeout(timer);
179841
+ promptSignal.cleanup();
179474
179842
  }
179475
179843
  }
179476
179844
  async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseHeld) {
@@ -179481,7 +179849,7 @@ async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseH
179481
179849
  try {
179482
179850
  const result = await runCompiledSmartNoteCheck({
179483
179851
  compiledCheck: note.compiledCheck,
179484
- capabilities: createSmartNoteCapabilities({ projectRoot, signal: controller.signal }),
179852
+ capabilityFactory: (signal) => createSmartNoteCapabilities({ projectRoot, signal }),
179485
179853
  signal: controller.signal,
179486
179854
  timeoutMs: 2000
179487
179855
  });
@@ -179510,7 +179878,7 @@ async function runLivenessCheck(args, note, projectRoot, assertLeaseHeld, leaseH
179510
179878
  clearTimeout(timer);
179511
179879
  }
179512
179880
  }
179513
- async function confirmReadOnly(args, noteId, content, surfaceCondition) {
179881
+ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSignal) {
179514
179882
  let childSessionId = null;
179515
179883
  const startedAt = Date.now();
179516
179884
  let invocationRecorded = false;
@@ -179553,42 +179921,49 @@ Note content: ${JSON.stringify(content)}
179553
179921
  Surface condition: ${JSON.stringify(surfaceCondition ?? "")}
179554
179922
 
179555
179923
  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
- });
179924
+ const promptSignal = createPromptAbortSignal(leaseSignal, Math.max(1000, args.deadline - Date.now()), "smart-note confirmation deadline");
179925
+ let run;
179926
+ try {
179927
+ run = await promptSyncWithValidatedOutputRetry(args.client, {
179928
+ path: { id: childSessionId },
179929
+ query: { directory: args.sessionDirectory ?? args.projectIdentity },
179930
+ body: {
179931
+ agent: SMART_NOTE_COMPILER_AGENT,
179932
+ system: SMART_NOTE_CONFIRMATION_SYSTEM_PROMPT,
179933
+ ...modelBodyField(args.model),
179934
+ parts: [{ type: "text", text: prompt, synthetic: true }]
179935
+ }
179936
+ }, {
179937
+ timeoutMs: Math.max(1000, args.deadline - Date.now()),
179938
+ signal: promptSignal.signal,
179939
+ fallbackModels: args.fallbackModels,
179940
+ callContext: "dreamer:smart-note-read-only-confirm",
179941
+ fetchOutput: async () => {
179942
+ const messagesResponse = await args.client.session.messages({
179943
+ path: { id: childSessionId },
179944
+ query: {
179945
+ directory: args.sessionDirectory ?? args.projectIdentity,
179946
+ limit: 20
179947
+ }
179948
+ });
179949
+ return normalizeSDKResponse(messagesResponse, [], {
179950
+ preferResponseOnMissingData: true
179951
+ });
179952
+ },
179953
+ validateOutput: (messages) => {
179954
+ const text = extractLatestAssistantText(messages) ?? "";
179955
+ const match = text.match(/\{[\s\S]*\}/);
179956
+ if (!match)
179957
+ throw new Error("confirmation evaluator returned no JSON");
179958
+ const parsed = JSON.parse(match[0]);
179959
+ if (typeof parsed.met !== "boolean")
179960
+ throw new Error("confirmation met missing");
179961
+ return parsed.met;
179962
+ }
179963
+ });
179964
+ } finally {
179965
+ promptSignal.cleanup();
179966
+ }
179592
179967
  recordInvocation2({ status: "completed", messages: run.output });
179593
179968
  return run.validated;
179594
179969
  } catch (error51) {
@@ -179803,19 +180178,21 @@ ${list}
179803
180178
  }
179804
180179
  function parseMapMemoriesManifest(text) {
179805
180180
  const out = [];
179806
- for (const m of text.matchAll(/<memory\b([^>]*)\/?>/g)) {
180181
+ const body = extractCompleteManifestBody(text, "mappings");
180182
+ for (const m of body.matchAll(/<memory\b([^>]*)\/?>/g)) {
179807
180183
  const attrs = m[1];
179808
180184
  const idMatch = attrs.match(/\bid\s*=\s*"(\d+)"/);
179809
180185
  if (!idMatch)
179810
- continue;
180186
+ throw new Error("mappings manifest entry missing numeric id");
179811
180187
  const id = Number.parseInt(idMatch[1], 10);
179812
180188
  if (!Number.isInteger(id))
179813
- continue;
180189
+ throw new Error("mappings manifest entry missing numeric id");
179814
180190
  const independent = /\bindependent\s*=\s*"(?:true|1)"/i.test(attrs);
179815
180191
  const filesMatch = attrs.match(/\bfiles\s*=\s*"([^"]*)"/);
179816
180192
  const files = filesMatch ? filesMatch[1].split(",").map((f) => f.trim()).filter(Boolean) : [];
179817
180193
  out.push({ id, files, independent: independent || files.length === 0 });
179818
180194
  }
180195
+ assertNoDuplicateManifestIds(out.map((entry) => entry.id), "mappings");
179819
180196
  return out;
179820
180197
  }
179821
180198
 
@@ -179904,9 +180281,13 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
179904
180281
  });
179905
180282
  },
179906
180283
  validateOutput: (messages) => {
180284
+ if (hasLengthCappedOutput(messages)) {
180285
+ throw new Error("map-memories returned length-capped output");
180286
+ }
179907
180287
  const text = extractLatestAssistantText(messages);
179908
180288
  if (!text)
179909
180289
  throw new Error("map-memories returned no output");
180290
+ parseMapMemoriesManifest(text);
179910
180291
  return text;
179911
180292
  }
179912
180293
  });
@@ -179932,7 +180313,8 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
179932
180313
  }
179933
180314
  async function applyBatchMappings(args, batch, manifestText) {
179934
180315
  const batchIds = new Set(batch.map((m) => m.id));
179935
- const parsed = parseMapMemoriesManifest(manifestText).filter((p) => batchIds.has(p.id));
180316
+ const parsed = parseMapMemoriesManifest(manifestText);
180317
+ assertManifestCoversExactly(parsed.map((entry) => entry.id), batchIds, "mappings");
179936
180318
  if (parsed.length === 0)
179937
180319
  return { mapped: 0, independent: 0 };
179938
180320
  const planned = [];
@@ -179954,12 +180336,7 @@ async function applyBatchMappings(args, batch, manifestText) {
179954
180336
  const now = Date.now();
179955
180337
  let mapped = 0;
179956
180338
  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
- }
180339
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
179963
180340
  for (const item of planned) {
179964
180341
  recordMemoryMapping(args.db, item.id, item.files, now);
179965
180342
  if (item.independent)
@@ -179967,9 +180344,7 @@ async function applyBatchMappings(args, batch, manifestText) {
179967
180344
  else
179968
180345
  mapped += 1;
179969
180346
  }
179970
- })();
179971
- if (leaseLost)
179972
- throw new Error("Dream lease lost during map-memories commit");
180347
+ });
179973
180348
  return { mapped, independent };
179974
180349
  }
179975
180350
  function recordInvocation2(args, startedAt, params) {
@@ -180197,11 +180572,7 @@ async function promotePrimers(args) {
180197
180572
  activePrimers: primers,
180198
180573
  threshold: PRIMER_CLUSTER_THRESHOLD
180199
180574
  });
180200
- args.db.transaction(() => {
180201
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
180202
- leaseLost = true;
180203
- return;
180204
- }
180575
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
180205
180576
  for (const cluster of clusters) {
180206
180577
  if (cluster.candidates.length === 0)
180207
180578
  continue;
@@ -180232,7 +180603,7 @@ async function promotePrimers(args) {
180232
180603
  });
180233
180604
  result.promoted += 1;
180234
180605
  }
180235
- })();
180606
+ });
180236
180607
  if (leaseLost)
180237
180608
  throw new Error("Dream lease lost during promote-primers commit");
180238
180609
  log(`[dreamer] primers: candidates=${result.candidates} promoted=${result.promoted} updated=${result.updated}`);
@@ -180500,16 +180871,9 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
180500
180871
  log(`[dreamer] refresh-primers: primer #${primer.id} answer not committed (no investigation tool calls)`);
180501
180872
  return false;
180502
180873
  }
180503
- let leaseLost = false;
180504
- args.db.transaction(() => {
180505
- if (!peekLeaseHolderAndExpiry(args.db, args.holderId, args.leaseKey)) {
180506
- leaseLost = true;
180507
- return;
180508
- }
180874
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
180509
180875
  updatePrimerAnswer(args.db, primer.id, answer);
180510
- })();
180511
- if (leaseLost)
180512
- throw new Error("Dream lease lost during refresh-primers commit");
180876
+ });
180513
180877
  return true;
180514
180878
  } catch (error51) {
180515
180879
  const desc = describeError(error51);
@@ -180706,8 +181070,7 @@ class OpenCodeRetrospectiveRawProvider {
180706
181070
  LEFT JOIN session_meta m ON m.session_id = sp.session_id
180707
181071
  WHERE sp.project_path = ? AND sp.harness = 'opencode'
180708
181072
  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);
181073
+ ORDER BY sp.updated_at ASC, sp.session_id ASC`).all(projectIdentity);
180711
181074
  return rows.map((row) => ({
180712
181075
  sessionId: row.session_id,
180713
181076
  updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
@@ -180732,6 +181095,12 @@ class OpenCodeRetrospectiveRawProvider {
180732
181095
  return { messages: [], truncated: false };
180733
181096
  }
180734
181097
  }
181098
+ readOldestMessageTimesSince(sessionIds, sinceMs) {
181099
+ const db = this.resolveDb();
181100
+ if (!db || sessionIds.length === 0)
181101
+ return new Map;
181102
+ return readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs);
181103
+ }
180735
181104
  readUserMessagesBefore(sessionId, beforeMs, count) {
180736
181105
  const db = this.resolveDb();
180737
181106
  if (!db)
@@ -180753,13 +181122,28 @@ class OpenCodeRetrospectiveRawProvider {
180753
181122
  async function readRetrospectiveScanWindow(provider2, projectIdentity, watermarkMs, overlapUserCount, options) {
180754
181123
  const maxMessages = options?.maxMessagesPerRun ?? RETROSPECTIVE_MAX_MESSAGES_PER_RUN;
180755
181124
  const capPerSession = options?.capPerSession ?? RETROSPECTIVE_MAX_MESSAGES_PER_SESSION;
180756
- const sessionReadPageSize = Math.max(1, Math.floor(options?.maxSessionsPerRun ?? RETROSPECTIVE_MAX_SESSIONS_PER_RUN));
181125
+ const sessionLimit = Math.max(1, Math.floor(options?.maxSessionsPerRun ?? RETROSPECTIVE_MAX_SESSIONS_PER_RUN));
180757
181126
  try {
180758
- const sessions = await provider2.listProjectSessions(projectIdentity);
181127
+ const allSessions = await provider2.listProjectSessions(projectIdentity);
181128
+ const eligibleSessions = allSessions.map((session, index) => ({ session, index })).filter(({ session }) => (session.updatedAt ?? Number.POSITIVE_INFINITY) > watermarkMs);
181129
+ const oldestBySession = provider2.readOldestMessageTimesSince ? await provider2.readOldestMessageTimesSince(eligibleSessions.map(({ session }) => session.sessionId), watermarkMs) : null;
181130
+ const sessions = (oldestBySession ? eligibleSessions.filter(({ session }) => oldestBySession.has(session.sessionId)) : eligibleSessions).sort((a, b) => {
181131
+ const aFrontier = oldestBySession?.get(a.session.sessionId);
181132
+ const bFrontier = oldestBySession?.get(b.session.sessionId);
181133
+ if (aFrontier !== undefined || bFrontier !== undefined) {
181134
+ return (aFrontier ?? Number.POSITIVE_INFINITY) - (bFrontier ?? Number.POSITIVE_INFINITY) || a.index - b.index;
181135
+ }
181136
+ const aUpdated = a.session.updatedAt ?? Number.POSITIVE_INFINITY;
181137
+ const bUpdated = b.session.updatedAt ?? Number.POSITIVE_INFINITY;
181138
+ const byUpdated = aUpdated - bUpdated;
181139
+ return byUpdated || a.index - b.index;
181140
+ });
181141
+ const sessionsToRead = sessions.slice(0, sessionLimit).map(({ session }) => session);
181142
+ const firstExcludedSession = sessions[sessionLimit]?.session;
181143
+ const firstExcludedPendingTs = firstExcludedSession ? oldestBySession?.get(firstExcludedSession.sessionId) : undefined;
180759
181144
  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))));
181145
+ if (sessionsToRead.length > 0) {
181146
+ sinceReads.push(...await Promise.all(sessionsToRead.map((session) => provider2.readUserMessagesSince(session.sessionId, watermarkMs, capPerSession))));
180763
181147
  }
180764
181148
  let saturatedFrontier = Number.POSITIVE_INFINITY;
180765
181149
  for (const read of sinceReads) {
@@ -180781,9 +181165,14 @@ async function readRetrospectiveScanWindow(provider2, projectIdentity, watermark
180781
181165
  if (firstDropped) {
180782
181166
  frontier = Math.min(frontier, firstDropped.ts - 1);
180783
181167
  }
181168
+ if (typeof firstExcludedPendingTs === "number") {
181169
+ frontier = Math.min(frontier, firstExcludedPendingTs - 1);
181170
+ } else if (typeof firstExcludedSession?.updatedAt === "number") {
181171
+ frontier = Math.min(frontier, firstExcludedSession.updatedAt - 1);
181172
+ }
180784
181173
  maxScannedTs = Math.max(watermarkMs, Math.min(maxScannedTs, frontier));
180785
181174
  const keptSessionIds = new Set(keptSince.map((message) => message.sessionId));
180786
- const overlapSessions = sessions.filter((session) => keptSessionIds.has(session.sessionId));
181175
+ const overlapSessions = sessionsToRead.filter((session) => keptSessionIds.has(session.sessionId));
180787
181176
  const overlapBatches = overlapUserCount > 0 && watermarkMs > 0 ? await Promise.all(overlapSessions.map((session) => provider2.readUserMessagesBefore(session.sessionId, watermarkMs, overlapUserCount))) : [];
180788
181177
  const seen = new Set;
180789
181178
  const merged = [];
@@ -180811,6 +181200,24 @@ function readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession) {
180811
181200
  const kept = truncated ? rows.slice(0, limit) : rows;
180812
181201
  return { messages: normalizeOpenCodeRows(db, sessionId, kept), truncated };
180813
181202
  }
181203
+ function readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs) {
181204
+ const result = new Map;
181205
+ const uniqueIds = Array.from(new Set(sessionIds.filter((id) => id.length > 0)));
181206
+ const chunkSize = 500;
181207
+ for (let i = 0;i < uniqueIds.length; i += chunkSize) {
181208
+ const chunk = uniqueIds.slice(i, i + chunkSize);
181209
+ const placeholders4 = chunk.map(() => "?").join(", ");
181210
+ const rows = db.prepare(`SELECT session_id, MIN(time_created) AS oldest_ts
181211
+ FROM message
181212
+ WHERE time_created > ? AND session_id IN (${placeholders4})
181213
+ GROUP BY session_id`).all(sinceMs, ...chunk);
181214
+ for (const row of rows) {
181215
+ if (typeof row.oldest_ts === "number")
181216
+ result.set(row.session_id, row.oldest_ts);
181217
+ }
181218
+ }
181219
+ return result;
181220
+ }
180814
181221
  function readOpenCodeUserMessagesBefore(db, sessionId, beforeMs, count) {
180815
181222
  const want = Math.max(1, Math.floor(count));
180816
181223
  const window = Math.max(want * 5, 32);
@@ -181103,7 +181510,7 @@ async function partitionVerifyScope(args) {
181103
181510
  continue;
181104
181511
  }
181105
181512
  const files = v?.files ?? [];
181106
- const needs = files.some((file2) => !verificationFileExists(gitRoot, file2) || uncommitted.has(file2) || (changeTimes.get(file2) ?? 0) > verifiedAt);
181513
+ const needs = files.some((file2) => !verificationFileExists(gitRoot, file2) || uncommitted.has(file2) || (changeTimes.get(file2) ?? 0) >= verifiedAt - 1000);
181107
181514
  if (needs)
181108
181515
  inScope.push(toPrompt(m));
181109
181516
  else
@@ -181169,22 +181576,26 @@ function filesOf(s) {
181169
181576
  }
181170
181577
  function parseVerifyManifest(text) {
181171
181578
  const out = { verified: [], updated: [], archived: [] };
181172
- for (const m of text.matchAll(/<verified\b([^>]*)\/?>/g)) {
181579
+ const body = extractCompleteManifestBody(text, "verify");
181580
+ for (const m of body.matchAll(/<verified\b([^>]*)\/?>/g)) {
181173
181581
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
181174
- if (Number.isInteger(id))
181175
- out.verified.push({ id, files: filesOf(m[1]) });
181582
+ if (!Number.isInteger(id))
181583
+ throw new Error("verify manifest entry missing numeric id");
181584
+ out.verified.push({ id, files: filesOf(m[1]) });
181176
181585
  }
181177
- for (const m of text.matchAll(/<update\b([^>]*?)(?:\/>|>([\s\S]*?)<\/update>)/g)) {
181586
+ for (const m of body.matchAll(/<update\b([^>]*?)(?:\/>|>([\s\S]*?)<\/update>)/g)) {
181178
181587
  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
- }
181588
+ if (!Number.isInteger(id))
181589
+ throw new Error("verify manifest entry missing numeric id");
181590
+ out.updated.push({ id, files: filesOf(m[1]), content: (m[2] ?? "").trim() });
181182
181591
  }
181183
- for (const m of text.matchAll(/<archive\b([^>]*)\/?>/g)) {
181592
+ for (const m of body.matchAll(/<archive\b([^>]*)\/?>/g)) {
181184
181593
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
181185
- if (Number.isInteger(id))
181186
- out.archived.push({ id, reason: attrOf(m[1], "reason") ?? "" });
181594
+ if (!Number.isInteger(id))
181595
+ throw new Error("verify manifest entry missing numeric id");
181596
+ out.archived.push({ id, reason: attrOf(m[1], "reason") ?? "" });
181187
181597
  }
181598
+ assertNoDuplicateManifestIds([...out.verified, ...out.updated, ...out.archived].map((entry) => entry.id), "verify");
181188
181599
  return out;
181189
181600
  }
181190
181601
 
@@ -181277,9 +181688,13 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
181277
181688
  });
181278
181689
  },
181279
181690
  validateOutput: (messages) => {
181691
+ if (hasLengthCappedOutput(messages)) {
181692
+ throw new Error("verify returned length-capped output");
181693
+ }
181280
181694
  const text = extractLatestAssistantText(messages);
181281
181695
  if (!text)
181282
181696
  throw new Error("verify returned no output");
181697
+ parseVerifyManifest(text);
181283
181698
  return text;
181284
181699
  }
181285
181700
  });
@@ -181306,24 +181721,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
181306
181721
  async function applyVerifyManifest(args, batch, manifestText) {
181307
181722
  const batchIds = new Set(batch.map((m) => m.id));
181308
181723
  const parsed = parseVerifyManifest(manifestText);
181724
+ assertManifestCoversExactly([...parsed.verified, ...parsed.updated, ...parsed.archived].map((entry) => entry.id), batchIds, "verify");
181309
181725
  const now = Date.now();
181310
181726
  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
181727
  for (const v of parsed.verified) {
181319
- if (!batchIds.has(v.id) || conflictingIds.has(v.id))
181320
- continue;
181321
181728
  const files = await normalizeFiles(args, v.files);
181322
181729
  writes.push({ kind: "verify", id: v.id, files });
181323
181730
  }
181324
181731
  for (const u of parsed.updated) {
181325
- if (!batchIds.has(u.id) || conflictingIds.has(u.id))
181326
- continue;
181327
181732
  const content = u.content.trim();
181328
181733
  if (!content || content.length > 20000) {
181329
181734
  const files2 = await normalizeFiles(args, u.files);
@@ -181340,8 +181745,6 @@ async function applyVerifyManifest(args, batch, manifestText) {
181340
181745
  });
181341
181746
  }
181342
181747
  for (const a of parsed.archived) {
181343
- if (!batchIds.has(a.id) || conflictingIds.has(a.id))
181344
- continue;
181345
181748
  writes.push({ kind: "archive", id: a.id, reason: a.reason });
181346
181749
  }
181347
181750
  if (writes.length === 0)
@@ -181349,12 +181752,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
181349
181752
  let verified = 0;
181350
181753
  let updated = 0;
181351
181754
  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
- }
181755
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
181358
181756
  for (const w of writes) {
181359
181757
  const memory = getMemoryById(args.db, w.id);
181360
181758
  if (!isPrimaryMutable(memory))
@@ -181382,9 +181780,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
181382
181780
  archived += 1;
181383
181781
  }
181384
181782
  }
181385
- })();
181386
- if (leaseLost)
181387
- throw new Error("Dream lease lost during verify commit");
181783
+ });
181388
181784
  return { verified, updated, archived };
181389
181785
  }
181390
181786
  async function normalizeFiles(args, rawFiles) {
@@ -181878,11 +182274,7 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
181878
182274
  throw new Error("Dream lease lost during retrospective");
181879
182275
  const sourceSessionId = flagged[0]?.sessionId ?? userMessages[0]?.sessionId ?? "retrospective";
181880
182276
  const learnings = parseRetrospectiveLearnings(deepenRun.validated);
181881
- const applied = db.transaction(() => {
181882
- if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
181883
- leaseLost = true;
181884
- return null;
181885
- }
182277
+ const applied = runLeaseGuardedWrite(db, holderId, leaseKey, () => {
181886
182278
  const result = applyRetrospectiveLearnings({
181887
182279
  db,
181888
182280
  projectIdentity,
@@ -181893,7 +182285,7 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
181893
182285
  });
181894
182286
  recordRetrospectiveWindowProcessed(db, projectIdentity, windowKey);
181895
182287
  return result;
181896
- })();
182288
+ });
181897
182289
  if (leaseLost || !applied)
181898
182290
  throw new Error("Dream lease lost during retrospective commit");
181899
182291
  log(`[dreamer] retrospective: flagged=${flagged.length} learnings=${learnings.length} memory=${applied.memoryWritten} observations=${applied.observationsInserted} dropped=${applied.observationsDropped} rejected=${applied.rejected.length}`);
@@ -183428,11 +183820,106 @@ function migrateLegacyExperimental(rawConfig, warnings) {
183428
183820
 
183429
183821
  // ../plugin/src/config/project-security.ts
183430
183822
  var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
183823
+ var HISTORIAN_USER_ONLY_FIELDS = ["model", "fallback_models"];
183431
183824
  var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
183432
183825
  var EMBEDDING_DESTINATION_FIELDS = ["endpoint", "provider"];
183826
+ 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.";
183827
+ 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.";
183828
+ 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
183829
  function isPlainObject3(value) {
183434
183830
  return typeof value === "object" && value !== null && !Array.isArray(value);
183435
183831
  }
183832
+ function isValidPercentageThreshold(value) {
183833
+ return typeof value === "number" && Number.isFinite(value) && value >= 20 && value <= 80;
183834
+ }
183835
+ function isValidTokenThreshold(value) {
183836
+ return typeof value === "number" && Number.isFinite(value) && value >= 5000 && value <= 2000000;
183837
+ }
183838
+ function normalizeTrustedPercentageThresholds(value) {
183839
+ if (typeof value === "number" && Number.isFinite(value)) {
183840
+ return { defaultValue: value, overrides: new Map };
183841
+ }
183842
+ if (isPlainObject3(value) && typeof value.default === "number" && Number.isFinite(value.default)) {
183843
+ const overrides = new Map;
183844
+ for (const [key, child] of Object.entries(value)) {
183845
+ if (key === "default")
183846
+ continue;
183847
+ if (typeof child === "number" && Number.isFinite(child)) {
183848
+ overrides.set(key, child);
183849
+ }
183850
+ }
183851
+ return { defaultValue: value.default, overrides };
183852
+ }
183853
+ return { defaultValue: DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, overrides: new Map };
183854
+ }
183855
+ function normalizeTrustedTokenThresholds(value) {
183856
+ if (!isPlainObject3(value)) {
183857
+ return { defaultValue: undefined, overrides: new Map };
183858
+ }
183859
+ const overrides = new Map;
183860
+ for (const [key, child] of Object.entries(value)) {
183861
+ if (key === "default")
183862
+ continue;
183863
+ if (typeof child === "number" && Number.isFinite(child)) {
183864
+ overrides.set(key, child);
183865
+ }
183866
+ }
183867
+ return {
183868
+ defaultValue: typeof value.default === "number" && Number.isFinite(value.default) ? value.default : undefined,
183869
+ overrides
183870
+ };
183871
+ }
183872
+ function clonePercentageThresholds(value) {
183873
+ return {
183874
+ defaultValue: value.defaultValue,
183875
+ overrides: new Map(value.overrides)
183876
+ };
183877
+ }
183878
+ function cloneTokenThresholds(value) {
183879
+ return {
183880
+ defaultValue: value.defaultValue,
183881
+ overrides: new Map(value.overrides)
183882
+ };
183883
+ }
183884
+ function percentageThresholdsEqual(left, right) {
183885
+ if (left.defaultValue !== right.defaultValue)
183886
+ return false;
183887
+ if (left.overrides.size !== right.overrides.size)
183888
+ return false;
183889
+ for (const [key, value] of left.overrides) {
183890
+ if (right.overrides.get(key) !== value)
183891
+ return false;
183892
+ }
183893
+ return true;
183894
+ }
183895
+ function setMergedPercentageThreshold(mergedRaw, value) {
183896
+ if (value.overrides.size === 0) {
183897
+ mergedRaw.execute_threshold_percentage = value.defaultValue;
183898
+ return;
183899
+ }
183900
+ const serialized = { default: value.defaultValue };
183901
+ for (const [key, threshold] of value.overrides) {
183902
+ serialized[key] = threshold;
183903
+ }
183904
+ mergedRaw.execute_threshold_percentage = serialized;
183905
+ }
183906
+ function setMergedTokenThreshold(mergedRaw, value) {
183907
+ if (value.defaultValue === undefined && value.overrides.size === 0) {
183908
+ delete mergedRaw.execute_threshold_tokens;
183909
+ return;
183910
+ }
183911
+ const serialized = {};
183912
+ if (value.defaultValue !== undefined) {
183913
+ serialized.default = value.defaultValue;
183914
+ }
183915
+ for (const [key, threshold] of value.overrides) {
183916
+ serialized[key] = threshold;
183917
+ }
183918
+ mergedRaw.execute_threshold_tokens = serialized;
183919
+ }
183920
+ function makeProjectThresholdWarning(field, reason) {
183921
+ return `Ignoring ${field} from project config (${reason})`;
183922
+ }
183436
183923
  function stripUnsafeProjectConfigFields(projectRaw) {
183437
183924
  const warnings = [];
183438
183925
  if ("auto_update" in projectRaw) {
@@ -183460,6 +183947,19 @@ function stripUnsafeProjectConfigFields(projectRaw) {
183460
183947
  warnings.push(`Ignoring embedding.${removed.join("/")} from project config ` + "(security: a repository cannot choose where private text is embedded).");
183461
183948
  }
183462
183949
  }
183950
+ const historian = projectRaw.historian;
183951
+ if (isPlainObject3(historian)) {
183952
+ const removed = [];
183953
+ for (const field of HISTORIAN_USER_ONLY_FIELDS) {
183954
+ if (field in historian) {
183955
+ delete historian[field];
183956
+ removed.push(field);
183957
+ }
183958
+ }
183959
+ if (removed.length > 0) {
183960
+ warnings.push(`Ignoring historian.${removed.join("/")} from project config ` + "(security: historian model selection is user-level only; a repository cannot force extra compaction cost).");
183961
+ }
183962
+ }
183463
183963
  for (const agentKey of HIDDEN_AGENT_KEYS) {
183464
183964
  const block = projectRaw[agentKey];
183465
183965
  if (!isPlainObject3(block))
@@ -183477,6 +183977,101 @@ function stripUnsafeProjectConfigFields(projectRaw) {
183477
183977
  }
183478
183978
  return warnings;
183479
183979
  }
183980
+ function constrainProjectThresholdOverrides(args) {
183981
+ const warnings = [];
183982
+ const basePercentage = normalizeTrustedPercentageThresholds(args.trustedBaseConfig.execute_threshold_percentage);
183983
+ const baseTokens = normalizeTrustedTokenThresholds(args.trustedBaseConfig.execute_threshold_tokens);
183984
+ if ("execute_threshold_percentage" in args.projectRaw) {
183985
+ const projectValue = args.projectRaw.execute_threshold_percentage;
183986
+ if (isValidPercentageThreshold(projectValue)) {
183987
+ const constrained = clonePercentageThresholds(basePercentage);
183988
+ constrained.defaultValue = Math.max(basePercentage.defaultValue, projectValue);
183989
+ for (const [modelKey, threshold] of basePercentage.overrides) {
183990
+ const raisedThreshold = Math.max(threshold, projectValue);
183991
+ if (raisedThreshold === constrained.defaultValue) {
183992
+ constrained.overrides.delete(modelKey);
183993
+ } else {
183994
+ constrained.overrides.set(modelKey, raisedThreshold);
183995
+ }
183996
+ }
183997
+ setMergedPercentageThreshold(args.mergedRaw, constrained);
183998
+ if (percentageThresholdsEqual(constrained, basePercentage)) {
183999
+ warnings.push(makeProjectThresholdWarning("execute_threshold_percentage", PERCENTAGE_THRESHOLD_REASON));
184000
+ }
184001
+ } else if (isPlainObject3(projectValue)) {
184002
+ const constrained = clonePercentageThresholds(basePercentage);
184003
+ let touchedValidEntry = false;
184004
+ if (isValidPercentageThreshold(projectValue.default)) {
184005
+ touchedValidEntry = true;
184006
+ if (projectValue.default > basePercentage.defaultValue) {
184007
+ constrained.defaultValue = projectValue.default;
184008
+ } else {
184009
+ warnings.push(makeProjectThresholdWarning("execute_threshold_percentage.default", PERCENTAGE_THRESHOLD_REASON));
184010
+ }
184011
+ }
184012
+ for (const [modelKey, rawValue] of Object.entries(projectValue)) {
184013
+ if (modelKey === "default")
184014
+ continue;
184015
+ if (!isValidPercentageThreshold(rawValue))
184016
+ continue;
184017
+ touchedValidEntry = true;
184018
+ const baseValue = basePercentage.overrides.get(modelKey) ?? basePercentage.defaultValue;
184019
+ if (rawValue > baseValue) {
184020
+ if (rawValue === constrained.defaultValue) {
184021
+ constrained.overrides.delete(modelKey);
184022
+ } else {
184023
+ constrained.overrides.set(modelKey, rawValue);
184024
+ }
184025
+ } else {
184026
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_percentage.${modelKey}`, PERCENTAGE_THRESHOLD_REASON));
184027
+ }
184028
+ }
184029
+ if (touchedValidEntry) {
184030
+ setMergedPercentageThreshold(args.mergedRaw, constrained);
184031
+ }
184032
+ }
184033
+ }
184034
+ if ("execute_threshold_tokens" in args.projectRaw && isPlainObject3(args.projectRaw.execute_threshold_tokens)) {
184035
+ const projectValue = args.projectRaw.execute_threshold_tokens;
184036
+ const constrained = cloneTokenThresholds(baseTokens);
184037
+ let touchedValidEntry = false;
184038
+ if (isValidTokenThreshold(projectValue.default)) {
184039
+ touchedValidEntry = true;
184040
+ if (baseTokens.defaultValue === undefined) {
184041
+ warnings.push(makeProjectThresholdWarning("execute_threshold_tokens.default", TOKEN_THRESHOLD_INTRODUCTION_REASON));
184042
+ } else if (projectValue.default > baseTokens.defaultValue) {
184043
+ constrained.defaultValue = projectValue.default;
184044
+ } else {
184045
+ warnings.push(makeProjectThresholdWarning("execute_threshold_tokens.default", TOKEN_THRESHOLD_REASON));
184046
+ }
184047
+ }
184048
+ for (const [modelKey, rawValue] of Object.entries(projectValue)) {
184049
+ if (modelKey === "default")
184050
+ continue;
184051
+ if (!isValidTokenThreshold(rawValue))
184052
+ continue;
184053
+ touchedValidEntry = true;
184054
+ const baseValue = baseTokens.overrides.get(modelKey) ?? baseTokens.defaultValue;
184055
+ if (baseValue === undefined) {
184056
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_tokens.${modelKey}`, TOKEN_THRESHOLD_INTRODUCTION_REASON));
184057
+ continue;
184058
+ }
184059
+ if (rawValue > baseValue) {
184060
+ if (rawValue === constrained.defaultValue) {
184061
+ constrained.overrides.delete(modelKey);
184062
+ } else {
184063
+ constrained.overrides.set(modelKey, rawValue);
184064
+ }
184065
+ } else {
184066
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_tokens.${modelKey}`, TOKEN_THRESHOLD_REASON));
184067
+ }
184068
+ }
184069
+ if (touchedValidEntry) {
184070
+ setMergedTokenThreshold(args.mergedRaw, constrained);
184071
+ }
184072
+ }
184073
+ return warnings;
184074
+ }
183480
184075
  function normalizeEndpoint3(value) {
183481
184076
  if (typeof value !== "string")
183482
184077
  return;
@@ -183844,6 +184439,7 @@ function loadPiConfig(opts = {}) {
183844
184439
  return a.scope === "user" ? -1 : 1;
183845
184440
  });
183846
184441
  const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
184442
+ const trustedBaseConfig = parsePiConfig(userRaw ?? {}).config;
183847
184443
  for (const loaded of mergeFiles) {
183848
184444
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
183849
184445
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
@@ -183856,6 +184452,13 @@ function loadPiConfig(opts = {}) {
183856
184452
  for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
183857
184453
  warnings.push(`${prefix} ${warning}`);
183858
184454
  }
184455
+ for (const warning of constrainProjectThresholdOverrides({
184456
+ mergedRaw: rawConfig,
184457
+ projectRaw,
184458
+ trustedBaseConfig
184459
+ })) {
184460
+ warnings.push(`${prefix} ${warning}`);
184461
+ }
183859
184462
  } else {
183860
184463
  rawConfig = mergeRawConfigs(rawConfig, loaded.config);
183861
184464
  }
@@ -183954,6 +184557,7 @@ function loadPiConfigDetailed(opts = {}) {
183954
184557
  return a.scope === "user" ? -1 : 1;
183955
184558
  });
183956
184559
  const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
184560
+ const trustedBaseConfig = parsePiConfig(userRaw ?? {}).config;
183957
184561
  for (const loaded of mergeFiles) {
183958
184562
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
183959
184563
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
@@ -183966,6 +184570,13 @@ function loadPiConfigDetailed(opts = {}) {
183966
184570
  for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
183967
184571
  warnings.push(`${prefix} ${warning}`);
183968
184572
  }
184573
+ for (const warning of constrainProjectThresholdOverrides({
184574
+ mergedRaw: rawConfig,
184575
+ projectRaw,
184576
+ trustedBaseConfig
184577
+ })) {
184578
+ warnings.push(`${prefix} ${warning}`);
184579
+ }
183969
184580
  } else {
183970
184581
  rawConfig = mergeRawConfigs(rawConfig, loaded.config);
183971
184582
  }
@@ -184044,6 +184655,8 @@ function isMidTurnPi(event, _sessionId) {
184044
184655
  }
184045
184656
  if (latestAssistant === null)
184046
184657
  return false;
184658
+ if (hasRealUserAfter(messages, latestAssistantIndex))
184659
+ return false;
184047
184660
  if (latestAssistant.stopReason === "toolUse")
184048
184661
  return true;
184049
184662
  const toolCallIds = getToolCallIds(latestAssistant.content);
@@ -184066,6 +184679,15 @@ function isMidTurnPi(event, _sessionId) {
184066
184679
  }
184067
184680
  return false;
184068
184681
  }
184682
+ function hasRealUserAfter(messages, latestAssistantIndex) {
184683
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
184684
+ if (msg === null || typeof msg !== "object")
184685
+ continue;
184686
+ if (msg.role === "user")
184687
+ return true;
184688
+ }
184689
+ return false;
184690
+ }
184069
184691
  function getToolCallIds(content) {
184070
184692
  const ids = new Set;
184071
184693
  if (!Array.isArray(content))
@@ -184388,7 +185010,7 @@ class PiRetrospectiveRawProvider {
184388
185010
  updatedAt: typeof info.modified === "number" ? info.modified : undefined
184389
185011
  });
184390
185012
  }
184391
- return result.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
185013
+ return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
184392
185014
  }
184393
185015
  async readUserMessagesSince(sessionId, sinceMs, capPerSession) {
184394
185016
  const all = await this.loadUserEntries(sessionId);
@@ -184399,6 +185021,15 @@ class PiRetrospectiveRawProvider {
184399
185021
  truncated: eligible.length > limit
184400
185022
  };
184401
185023
  }
185024
+ async readOldestMessageTimesSince(sessionIds, sinceMs) {
185025
+ const out = new Map;
185026
+ for (const sessionId of sessionIds) {
185027
+ const oldest = (await this.loadUserEntries(sessionId)).filter((message) => message.ts > sinceMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal)[0];
185028
+ if (oldest)
185029
+ out.set(sessionId, oldest.ts);
185030
+ }
185031
+ return out;
185032
+ }
184402
185033
  async readUserMessagesBefore(sessionId, beforeMs, count) {
184403
185034
  const all = await this.loadUserEntries(sessionId);
184404
185035
  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 +186789,8 @@ init_logger();
186158
186789
  // ../plugin/src/hooks/magic-context/read-session-true-raw-tokens.ts
186159
186790
  var MAX_MESSAGE_CACHE_ENTRIES = 1e5;
186160
186791
  var MAX_MESSAGE_CACHE_KEY_BYTES = 64 * 1024 * 1024;
186792
+ var FNV1A_32_OFFSET = 2166136261;
186793
+ var FNV1A_32_PRIME = 16777619;
186161
186794
  var messageEstimateCache = new Map;
186162
186795
  var messageEstimateCacheBytes = 0;
186163
186796
  var EMPTY_BREAKDOWN = {
@@ -186260,6 +186893,23 @@ function recursiveByteLength(value) {
186260
186893
  }
186261
186894
  return String(value).length;
186262
186895
  }
186896
+ function updateFnv1a32(hash2, text) {
186897
+ let next = hash2;
186898
+ for (let index = 0;index < text.length; index += 1) {
186899
+ next ^= text.charCodeAt(index);
186900
+ next = Math.imul(next, FNV1A_32_PRIME) >>> 0;
186901
+ }
186902
+ return next;
186903
+ }
186904
+ function contentStringsHash(fields) {
186905
+ let hash2 = FNV1A_32_OFFSET;
186906
+ for (const field of fields) {
186907
+ hash2 = updateFnv1a32(hash2, `${field.length}:`);
186908
+ hash2 = updateFnv1a32(hash2, field);
186909
+ hash2 = updateFnv1a32(hash2, "\x00");
186910
+ }
186911
+ return hash2.toString(16).padStart(8, "0");
186912
+ }
186263
186913
  function rawPartVersion(part) {
186264
186914
  return part.__magicContextPartUpdatedAt ?? part.updated_at ?? part.updatedAt ?? part.version ?? part.revision ?? "";
186265
186915
  }
@@ -186607,13 +187257,12 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
186607
187257
  function partContentFingerprint(part) {
186608
187258
  if (!isRecord(part))
186609
187259
  return `${typeof part}:${recursiveByteLength(part)}`;
186610
- const type = partType(part);
186611
187260
  const tool = toolSignalFromPart(part);
186612
187261
  if (tool) {
186613
- return `${type}:${tool.callId}:${tool.inputText.length}:${tool.outputText.length}`;
187262
+ return contentStringsHash([tool.inputText, tool.outputText]);
186614
187263
  }
186615
187264
  const text = firstStringField(part, ["text", "thinking", "reasoning", "content", "url"]) ?? "";
186616
- return `${type}:${text.length}`;
187265
+ return contentStringsHash([text]);
186617
187266
  }
186618
187267
  function computeRawRangeFingerprint(messages, startInclusive, endExclusive) {
186619
187268
  const pieces = [];
@@ -188870,24 +189519,46 @@ function getMessageSearchStatementWithCutoff(db) {
188870
189519
  return stmt;
188871
189520
  }
188872
189521
  var ftsRowCountStatements = new WeakMap;
189522
+ var ftsRowCountStatementsWithCutoff = new WeakMap;
188873
189523
  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);
189524
+ var ftsMatchCountStatementsWithCutoff = new WeakMap;
189525
+ function getSessionFtsRowCount(db, sessionId, cutoff) {
189526
+ const stmt = cutoff === null ? (() => {
189527
+ let cached2 = ftsRowCountStatements.get(db);
189528
+ if (!cached2) {
189529
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
189530
+ ftsRowCountStatements.set(db, cached2);
189531
+ }
189532
+ return cached2;
189533
+ })() : (() => {
189534
+ let cached2 = ftsRowCountStatementsWithCutoff.get(db);
189535
+ if (!cached2) {
189536
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND CAST(message_ordinal AS INTEGER) <= ?");
189537
+ ftsRowCountStatementsWithCutoff.set(db, cached2);
189538
+ }
189539
+ return cached2;
189540
+ })();
189541
+ const row = cutoff === null ? stmt.get(sessionId) : stmt.get(sessionId, cutoff);
188881
189542
  return typeof row?.n === "number" ? row.n : 0;
188882
189543
  }
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
- }
189544
+ function countSessionFtsMatches(db, sessionId, ftsQuery, cutoff) {
189545
+ const stmt = cutoff === null ? (() => {
189546
+ let cached2 = ftsMatchCountStatements.get(db);
189547
+ if (!cached2) {
189548
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
189549
+ ftsMatchCountStatements.set(db, cached2);
189550
+ }
189551
+ return cached2;
189552
+ })() : (() => {
189553
+ let cached2 = ftsMatchCountStatementsWithCutoff.get(db);
189554
+ if (!cached2) {
189555
+ 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) <= ?");
189556
+ ftsMatchCountStatementsWithCutoff.set(db, cached2);
189557
+ }
189558
+ return cached2;
189559
+ })();
188889
189560
  try {
188890
- const row = stmt.get(sessionId, ftsQuery);
189561
+ const row = cutoff === null ? stmt.get(sessionId, ftsQuery) : stmt.get(sessionId, ftsQuery, cutoff);
188891
189562
  return typeof row?.n === "number" ? row.n : 0;
188892
189563
  } catch {
188893
189564
  return 0;
@@ -189137,7 +189808,7 @@ function searchMessages(args) {
189137
189808
  role: row.role
189138
189809
  }));
189139
189810
  }
189140
- const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
189811
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId, cutoff);
189141
189812
  const queryLists = [];
189142
189813
  if (baseQuery.length > 0) {
189143
189814
  queryLists.push({
@@ -189150,7 +189821,7 @@ function searchMessages(args) {
189150
189821
  const probeQuery = sanitizeFtsQuery(probe);
189151
189822
  if (probeQuery.length === 0)
189152
189823
  continue;
189153
- const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
189824
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery, cutoff);
189154
189825
  const weight = probeDiscriminationWeight(df, corpusSize);
189155
189826
  probeWeights.set(probe, weight);
189156
189827
  queryLists.push({
@@ -200185,6 +200856,15 @@ async function executeContextRecompWithResult(deps, options = {}) {
200185
200856
  published: false
200186
200857
  };
200187
200858
  }
200859
+ options.onLeaseAcquired?.();
200860
+ if (isWrapupInProgress(deps.db, sessionId)) {
200861
+ sessionLog(sessionId, "recomp skipped: /ctx-wrapup became active");
200862
+ releaseCompartmentLease(deps.db, sessionId, holderId);
200863
+ return {
200864
+ message: "## Magic Recomp — Skipped\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
200865
+ published: false
200866
+ };
200867
+ }
200188
200868
  const renewal = startLeaseRenewal(deps, holderId);
200189
200869
  const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
200190
200870
  const promise2 = options.range ? executePartialRecompInternal(runnerDeps, options.range) : executeContextRecompInternal(runnerDeps);
@@ -201310,7 +201990,7 @@ function formatThresholdPercent(value) {
201310
201990
  // package.json
201311
201991
  var package_default = {
201312
201992
  name: "@cortexkit/pi-magic-context",
201313
- version: "0.31.3",
201993
+ version: "0.31.5",
201314
201994
  type: "module",
201315
201995
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
201316
201996
  main: "dist/index.js",
@@ -201860,6 +202540,11 @@ function readHistorianState(db, sessionId, meta3) {
201860
202540
  import * as crypto4 from "node:crypto";
201861
202541
  var DEFAULT_MESSAGES_TO_KEEP = 20;
201862
202542
  var LEASE_WAIT_MS = 1000;
202543
+ var MAX_WRAPUP_LEASE_WAIT_MS = 10 * 60 * 1000;
202544
+ function resolveWrapupLeaseWaitTimeout(deps) {
202545
+ const configured = deps.wrapupLeaseWaitTimeoutMs ?? MAX_WRAPUP_LEASE_WAIT_MS;
202546
+ return Number.isFinite(configured) && configured >= 0 ? configured : MAX_WRAPUP_LEASE_WAIT_MS;
202547
+ }
201863
202548
  function parseWrapupArgs(raw) {
201864
202549
  const trimmed = raw.trim();
201865
202550
  if (trimmed === "")
@@ -202077,11 +202762,12 @@ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleS
202077
202762
  Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
202078
202763
  level: "info"
202079
202764
  });
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.";
202765
+ const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps));
202766
+ if (!leaseResult.ok) {
202767
+ 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
202768
  break;
202084
202769
  }
202770
+ const leaseHolder = leaseResult.holderId;
202085
202771
  const leaseRenewal = setInterval(() => {
202086
202772
  try {
202087
202773
  renewCompartmentLease(deps.db, sessionId, leaseHolder);
@@ -202185,15 +202871,19 @@ function resolvePiContextLimit(ctx) {
202185
202871
  }
202186
202872
  return 128000;
202187
202873
  }
202188
- async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker) {
202874
+ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
202875
+ const waitStartedAt = Date.now();
202876
+ const remainingMs = () => Math.max(0, waitStartedAt + maxWaitMs - Date.now());
202189
202877
  for (;; ) {
202878
+ if (remainingMs() <= 0)
202879
+ return { ok: false, reason: "timeout" };
202190
202880
  const holderId = crypto4.randomUUID();
202191
202881
  const lease = acquireCompartmentLease(db, sessionId, holderId);
202192
202882
  if (lease)
202193
- return holderId;
202883
+ return { ok: true, holderId };
202194
202884
  if (!renewWrapupMarker({}))
202195
- return null;
202196
- await new Promise((resolve3) => setTimeout(resolve3, LEASE_WAIT_MS));
202885
+ return { ok: false, reason: "ownership_lost" };
202886
+ await new Promise((resolve3) => setTimeout(resolve3, Math.min(LEASE_WAIT_MS, remainingMs())));
202197
202887
  }
202198
202888
  }
202199
202889
  function estimateChunks(tokens, chunkTokens) {
@@ -206949,8 +207639,9 @@ function createCtxMemoryTool(deps) {
206949
207639
  const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
206950
207640
  if (isOwn)
206951
207641
  return true;
206952
- return toolShareCategories === null || toolShareCategories.includes(memory2.category);
207642
+ return toolShareCategories?.includes(memory2.category) ?? false;
206953
207643
  };
207644
+ const memoryOwnedByTool = (memory2) => workspaceIdentitySet.identities.length > 1 ? targetIdentityForStoredPath(memory2.projectPath) === projectIdentity : storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
206954
207645
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
206955
207646
  if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
206956
207647
  return err2("Cross-session memory is disabled for this project.");
@@ -206997,7 +207688,8 @@ function createCtxMemoryTool(deps) {
206997
207688
  return err2("Error: 'content' is required when action is 'update'.");
206998
207689
  }
206999
207690
  const memory2 = getMemoryById(deps.db, updateId);
207000
- if (!memory2 || !memoryVisibleToTool(memory2)) {
207691
+ const updateAllowed = memory2 ? dreamerAllowed ? memoryVisibleToTool(memory2) : memoryOwnedByTool(memory2) : false;
207692
+ if (!memory2 || !updateAllowed) {
207001
207693
  return err2(`Error: Memory with ID ${updateId} was not found.`);
207002
207694
  }
207003
207695
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
@@ -207044,7 +207736,7 @@ function createCtxMemoryTool(deps) {
207044
207736
  return err2("Error: One or more source memories were not found.");
207045
207737
  }
207046
207738
  if (!dreamerAllowed) {
207047
- const foreign = sourceMemories.find((memory2) => !memoryVisibleToTool(memory2));
207739
+ const foreign = sourceMemories.find((memory2) => !memoryOwnedByTool(memory2));
207048
207740
  if (foreign) {
207049
207741
  return err2(`Error: Memory with ID ${foreign.id} was not found.`);
207050
207742
  }
@@ -207146,7 +207838,8 @@ function createCtxMemoryTool(deps) {
207146
207838
  const archiveIds = [...new Set(rawArchiveIds)];
207147
207839
  for (const memoryId of archiveIds) {
207148
207840
  const memory2 = getMemoryById(deps.db, memoryId);
207149
- if (!memory2 || !memoryVisibleToTool(memory2)) {
207841
+ const archiveAllowed = memory2 ? dreamerAllowed ? memoryVisibleToTool(memory2) : memoryOwnedByTool(memory2) : false;
207842
+ if (!memory2 || !archiveAllowed) {
207150
207843
  return err2(`Error: Memory with ID ${memoryId} was not found.`);
207151
207844
  }
207152
207845
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
@@ -207807,14 +208500,7 @@ import {
207807
208500
  var TODO_TOOL_NAME = "todowrite";
207808
208501
  var TODOS_COMMAND_NAME = "todos";
207809
208502
  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"];
208503
+ var MAX_TODO_CONTENT_ROWS = 12;
207818
208504
  var STATUS_GLYPH = {
207819
208505
  pending: "○",
207820
208506
  in_progress: "◐",
@@ -207828,10 +208514,10 @@ var STATUS_COLOR = {
207828
208514
  cancelled: "error"
207829
208515
  };
207830
208516
  var snapshotsBySession = new Map;
207831
- function isTodoStatus(value) {
208517
+ function isTodoStatus2(value) {
207832
208518
  return TODO_STATUSES.includes(value);
207833
208519
  }
207834
- function isTodoPriority(value) {
208520
+ function isTodoPriority2(value) {
207835
208521
  return TODO_PRIORITIES.includes(value);
207836
208522
  }
207837
208523
  function cloneTodos(todos) {
@@ -207845,14 +208531,14 @@ function parseTodos(input) {
207845
208531
  if (item === null || typeof item !== "object")
207846
208532
  return null;
207847
208533
  const raw = item;
207848
- if (typeof raw.content !== "string" || !isTodoStatus(raw.status)) {
208534
+ if (typeof raw.content !== "string" || !isTodoStatus2(raw.status)) {
207849
208535
  return null;
207850
208536
  }
207851
208537
  const todo = {
207852
208538
  content: raw.content,
207853
208539
  status: raw.status
207854
208540
  };
207855
- if (isTodoPriority(raw.priority))
208541
+ if (isTodoPriority2(raw.priority))
207856
208542
  todo.priority = raw.priority;
207857
208543
  if (typeof raw.id === "string" && raw.id.length > 0)
207858
208544
  todo.id = raw.id;
@@ -207929,7 +208615,7 @@ function countTodos(todos) {
207929
208615
  return counts;
207930
208616
  }
207931
208617
  function activeTitleCount(todos) {
207932
- return todos.filter((todo) => todo.status !== "completed").length;
208618
+ return todos.filter((todo) => !TITLE_DONE_STATUSES.has(todo.status)).length;
207933
208619
  }
207934
208620
  function formatCounts(counts) {
207935
208621
  if (counts.total === 0)
@@ -207963,6 +208649,16 @@ function lineComponent(renderLines) {
207963
208649
  invalidate() {}
207964
208650
  };
207965
208651
  }
208652
+ function capTodoRows(rows) {
208653
+ if (rows.length <= MAX_TODO_CONTENT_ROWS) {
208654
+ return { visible: [...rows], hiddenCount: 0 };
208655
+ }
208656
+ const visibleCount = Math.max(0, MAX_TODO_CONTENT_ROWS - 1);
208657
+ return {
208658
+ visible: rows.slice(0, visibleCount),
208659
+ hiddenCount: rows.length - visibleCount
208660
+ };
208661
+ }
207966
208662
  function renderTodowriteCall(args, theme) {
207967
208663
  const todos = parseTodos(args.todos) ?? [];
207968
208664
  const active = activeTitleCount(todos);
@@ -207985,7 +208681,12 @@ function renderTodowriteResult(result, theme) {
207985
208681
  return lineComponent((width) => {
207986
208682
  if (renderedTodos.length === 0)
207987
208683
  return [theme.fg("dim", "No todos")];
207988
- return renderedTodos.map((todo) => truncateToWidth2(formatTodoLine(todo, theme, { showId: true }), width, "…"));
208684
+ const { visible, hiddenCount } = capTodoRows(renderedTodos);
208685
+ const lines = visible.map((todo) => truncateToWidth2(formatTodoLine(todo, theme, { showId: true }), width, "…"));
208686
+ if (hiddenCount > 0) {
208687
+ lines.push(truncateToWidth2(theme.fg("dim", `+${hiddenCount} more`), width, "…"));
208688
+ }
208689
+ return lines;
207989
208690
  });
207990
208691
  }
207991
208692
  function registerTodosCommand(pi) {
@@ -208015,8 +208716,11 @@ function registerTodosCommand(pi) {
208015
208716
  if (group.length === 0)
208016
208717
  continue;
208017
208718
  lines.push(heading);
208018
- for (const todo of group)
208719
+ const { visible, hiddenCount } = capTodoRows(group);
208720
+ for (const todo of visible)
208019
208721
  lines.push(formatCommandLine(todo));
208722
+ if (hiddenCount > 0)
208723
+ lines.push(` +${hiddenCount} more`);
208020
208724
  }
208021
208725
  ctx.ui.notify(lines.join(`
208022
208726
  `), "info");
@@ -208144,19 +208848,16 @@ class TodoOverlay {
208144
208848
  const heading = `${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, "Todos")} ${theme.fg("muted", "—")} ${theme.fg("dim", formatCounts(counts))}`;
208145
208849
  const truncate3 = (line) => truncateToWidth2(line, width, "…");
208146
208850
  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);
208851
+ const { visible, hiddenCount } = capTodoRows(overlayTodos);
208151
208852
  for (const [index, { todo, key }] of visible.entries()) {
208152
- const isLast = index === visible.length - 1 && truncatedTail === 0;
208853
+ const isLast = index === visible.length - 1 && hiddenCount === 0;
208153
208854
  const branch = theme.fg("dim", isLast ? "└─" : "├─");
208154
208855
  lines.push(truncate3(`${branch} ${formatTodoLine(todo, theme, { showId: true })}`));
208155
208856
  if (todo.status === "completed")
208156
208857
  this.completedTaskIdsPendingHide.add(key);
208157
208858
  }
208158
- if (truncatedTail > 0) {
208159
- lines.push(truncate3(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${truncatedTail} more`)}`));
208859
+ if (hiddenCount > 0) {
208860
+ lines.push(truncate3(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${hiddenCount} more`)}`));
208160
208861
  }
208161
208862
  return lines;
208162
208863
  }
@@ -208212,17 +208913,10 @@ function registerTodoStateLifecycle(pi, deps) {
208212
208913
  }
208213
208914
 
208214
208915
  // 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
208916
  var TodoItem = exports_typebox.Object({
208223
208917
  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)))),
208918
+ status: exports_typebox.Union(TODO_STATUSES.map((v) => exports_typebox.Literal(v))),
208919
+ priority: exports_typebox.Optional(exports_typebox.Union(TODO_PRIORITIES.map((v) => exports_typebox.Literal(v)))),
208226
208920
  id: exports_typebox.Optional(exports_typebox.String({ description: "Optional stable id for the todo" }))
208227
208921
  });
208228
208922
  var TodowriteParams = exports_typebox.Object({
@@ -208247,8 +208941,7 @@ function createTodowriteTool() {
208247
208941
  parameters: TodowriteParams,
208248
208942
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
208249
208943
  const todos = params.todos ?? [];
208250
- const completed = todos.filter((t) => t.status === "completed").length;
208251
- const active = todos.length - completed;
208944
+ const active = todos.filter((todo) => !TITLE_DONE_STATUSES.has(todo.status)).length;
208252
208945
  return {
208253
208946
  content: [
208254
208947
  {
@@ -208339,6 +209032,55 @@ function persistPiMessageEndModelMeta(args) {
208339
209032
  updateSessionMeta(args.db, args.sessionId, { cacheTtl });
208340
209033
  }
208341
209034
  }
209035
+ function getCompatiblePiTodoCapture(todos) {
209036
+ if (!Array.isArray(todos))
209037
+ return null;
209038
+ const normalized = normalizeTodoStateJson(todos);
209039
+ if (normalized === null)
209040
+ return null;
209041
+ return { normalized, todos };
209042
+ }
209043
+ function applyCompatiblePiTodoCapture(args) {
209044
+ if (args.todowriteEnabled) {
209045
+ setTodoSnapshot(args.sessionId, args.capture.todos);
209046
+ args.todoOverlay?.update(args.sessionId);
209047
+ }
209048
+ if (args.persist) {
209049
+ updateSessionMeta(args.db, args.sessionId, {
209050
+ lastTodoState: args.capture.normalized
209051
+ });
209052
+ }
209053
+ }
209054
+ function capturePiTodowriteArgsIfCompatible(args) {
209055
+ const capture = getCompatiblePiTodoCapture(args.todos);
209056
+ if (capture === null)
209057
+ return false;
209058
+ applyCompatiblePiTodoCapture({ ...args, capture });
209059
+ return true;
209060
+ }
209061
+ function capturePiTodowriteMessageIfCompatible(args) {
209062
+ const msg = args.message;
209063
+ if (msg?.role !== "assistant" || !Array.isArray(msg.content)) {
209064
+ return false;
209065
+ }
209066
+ for (const block of msg.content) {
209067
+ if (!block || typeof block !== "object")
209068
+ continue;
209069
+ const b = block;
209070
+ if (b.type !== "toolCall")
209071
+ continue;
209072
+ if (typeof b.name !== "string")
209073
+ continue;
209074
+ if (b.name !== "todowrite")
209075
+ continue;
209076
+ const capture = getCompatiblePiTodoCapture(b.arguments?.todos);
209077
+ if (capture === null)
209078
+ continue;
209079
+ applyCompatiblePiTodoCapture({ ...args, capture });
209080
+ return true;
209081
+ }
209082
+ return false;
209083
+ }
208342
209084
  function info(message, data) {
208343
209085
  log(`${PREFIX} ${message}`, data);
208344
209086
  }
@@ -208346,6 +209088,7 @@ function warn(message, data) {
208346
209088
  log(`${PREFIX} WARN ${message}`, data);
208347
209089
  }
208348
209090
  var migratedConfigDirs = new Set;
209091
+ var loggedPiConfigDirs = new Set;
208349
209092
  function ensureConfigLocationsMigrated(dir) {
208350
209093
  if (migratedConfigDirs.has(dir))
208351
209094
  return;
@@ -208355,6 +209098,28 @@ function ensureConfigLocationsMigrated(dir) {
208355
209098
  info: (m) => info(m)
208356
209099
  });
208357
209100
  }
209101
+ function logPiConfigLoad(args) {
209102
+ const key = resolve3(args.dir);
209103
+ if (args.dedupe && loggedPiConfigDirs.has(key))
209104
+ return;
209105
+ if (args.dedupe) {
209106
+ loggedPiConfigDirs.add(key);
209107
+ }
209108
+ if (args.loadedFromPaths.length > 0) {
209109
+ info(`config loaded from: ${args.loadedFromPaths.join(", ")}`);
209110
+ } else {
209111
+ info("config: no magic-context.jsonc found, using schema defaults");
209112
+ }
209113
+ for (const warning of args.warnings) {
209114
+ warn(`config: ${warning}`);
209115
+ }
209116
+ }
209117
+ var __test = {
209118
+ logPiConfigLoad,
209119
+ resetLoggedPiConfigDirs() {
209120
+ loggedPiConfigDirs.clear();
209121
+ }
209122
+ };
208358
209123
  function formatTokens(value) {
208359
209124
  return value.toLocaleString();
208360
209125
  }
@@ -208541,14 +209306,12 @@ async function src_default2(pi) {
208541
209306
  const { config: config2, warnings, loadedFromPaths } = loadPiConfig({
208542
209307
  cwd: projectDir
208543
209308
  });
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
- }
209309
+ logPiConfigLoad({
209310
+ dir: projectDir,
209311
+ loadedFromPaths,
209312
+ warnings,
209313
+ dedupe: true
209314
+ });
208552
209315
  setSqlitePragmaConfig({
208553
209316
  cacheSizeMb: config2.sqlite.cache_size_mb,
208554
209317
  mmapSizeMb: config2.sqlite.mmap_size_mb
@@ -208630,7 +209393,14 @@ async function src_default2(pi) {
208630
209393
  if (cached2)
208631
209394
  return cached2;
208632
209395
  ensureConfigLocationsMigrated(dir);
208633
- const switchedConfig = loadPiConfig({ cwd: dir }).config;
209396
+ const switchedLoad = loadPiConfig({ cwd: dir });
209397
+ logPiConfigLoad({
209398
+ dir,
209399
+ loadedFromPaths: switchedLoad.loadedFromPaths,
209400
+ warnings: switchedLoad.warnings,
209401
+ dedupe: true
209402
+ });
209403
+ const switchedConfig = switchedLoad.config;
208634
209404
  const switchedIdentity = identityOverride ?? resolveProjectIdentityOrFallback(dir);
208635
209405
  const built = buildProjectDeps(dir, switchedIdentity, switchedConfig);
208636
209406
  projectDepsByDir.set(dir, built);
@@ -209001,18 +209771,14 @@ ${block}` : event.systemPrompt;
209001
209771
  const todoArgs = event.args;
209002
209772
  const todos = todoArgs?.todos;
209003
209773
  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
- }
209774
+ capturePiTodowriteArgsIfCompatible({
209775
+ db,
209776
+ sessionId,
209777
+ todos,
209778
+ todowriteEnabled,
209779
+ todoOverlay,
209780
+ persist: Boolean(sessionMeta && !sessionMeta.isSubagent)
209781
+ });
209016
209782
  if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
209017
209783
  if (sessionMeta && !sessionMeta.isSubagent) {
209018
209784
  onNoteTrigger(db, sessionId, "todos_complete");
@@ -209115,36 +209881,14 @@ ${block}` : event.systemPrompt;
209115
209881
  try {
209116
209882
  const sessionMetaForTodo = getOrCreateSessionMeta(db, sessionId);
209117
209883
  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
- }
209884
+ capturePiTodowriteMessageIfCompatible({
209885
+ db,
209886
+ sessionId,
209887
+ message: event.message,
209888
+ todowriteEnabled,
209889
+ todoOverlay,
209890
+ persist: true
209891
+ });
209148
209892
  }
209149
209893
  } catch (err5) {
209150
209894
  warn("message_end: synthetic todowrite capture failed:", err5);
@@ -209236,5 +209980,8 @@ export {
209236
209980
  resolveDreamerFromConfig,
209237
209981
  persistPiPressureFromMessageEnd,
209238
209982
  persistPiMessageEndModelMeta,
209239
- src_default2 as default
209983
+ src_default2 as default,
209984
+ capturePiTodowriteMessageIfCompatible,
209985
+ capturePiTodowriteArgsIfCompatible,
209986
+ __test
209240
209987
  };