@adhdev/daemon-core 0.9.82-rc.293 → 0.9.82-rc.295

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.mjs CHANGED
@@ -270,10 +270,10 @@ function readInjected(value) {
270
270
  }
271
271
  function getDaemonBuildInfo() {
272
272
  if (cached) return cached;
273
- const commit = readInjected(true ? "88f97abcc85d83fe5d5313b229d32a7d76b1208a" : void 0) ?? "unknown";
274
- const commitShort = readInjected(true ? "88f97abc" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
275
- const version = readInjected(true ? "0.9.82-rc.293" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
276
- const builtAt = readInjected(true ? "2026-06-16T11:13:51.625Z" : void 0);
273
+ const commit = readInjected(true ? "dc26917ced80beb84689897738b6edb82208c146" : void 0) ?? "unknown";
274
+ const commitShort = readInjected(true ? "dc26917c" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
275
+ const version = readInjected(true ? "0.9.82-rc.295" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
276
+ const builtAt = readInjected(true ? "2026-06-16T13:44:28.663Z" : void 0);
277
277
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
278
278
  return cached;
279
279
  }
@@ -2007,6 +2007,7 @@ function buildRulesSection(coordinatorCliType) {
2007
2007
  - **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
2008
2008
  - **Limit parallelism.** Start with 1\u20132 tasks; scale only on success. Never duplicate a session because \`mesh_read_chat\` shows no final message while tool/terminal activity is ongoing. This caps *concurrent* load \u2014 it does not mean serialize independent work: when a new, independent request arrives and there is headroom under \`maxParallelTasks\`, dispatch it right away rather than waiting for an in-flight task or a user nudge (read-only diagnosis especially, since it has no merge cost).
2009
2009
  - **Check history first.** Call \`mesh_task_history\` at session start to avoid duplicate work and inform recovery. On failure, read task history before retrying.
2010
+ - **Sequence shared-base-moving merges.** Parallel dispatch is encouraged, but merging one worktree can advance another in-flight worktree's base \u2014 especially the oss submodule pointer \u2014 turning a clean fast-forward into a diverged rebase (patch-equivalence correctly blocks this). Before merging an in-flight worktree while siblings are also in flight, land in an intentional order, re-clone long-running worktrees from the advanced base, or expect to manually rebase + ff-only the laggards; merging an independent fix mid-flight can strand siblings into a rebase.
2010
2011
  - **Converge branches.** After worktree tasks: refine/fast-forward, or classify as \`pushed_feature_branch_needs_merge\` / \`blocked_review\` / \`cleanup_candidate\` / \`not_mergeable\`. Clean up with \`mesh_remove_node\`.
2011
2012
  - **Refinery is config-driven.** \`mesh_refine_node\` must run validation from \`.adhdev/refine.{json,yaml,yml}\` or \`repo-mesh.refine.*\`. Heuristics are scaffolding only.
2012
2013
  - **Submodule reachability = publish-needed.** \`submodule_reachability_failed\` \u2192 classify as \`blocked_review\`, request user approval to push to submodule main, then rerun \`mesh_refine_node\`.
@@ -15302,10 +15303,12 @@ function detectClaudeTuiMultiSelect(screenText) {
15302
15303
  if (/Space to (?:select|toggle)|toggle selection|select multiple|select all that apply/i.test(screenText)) {
15303
15304
  return true;
15304
15305
  }
15305
- const optionCheckbox = /^\s*(?:[❯›>]\s*)?(?:\[[ xX]\]|[☐☒◻◼])\s*\d+\.\s+\S/;
15306
+ const optionCheckbox = `(?:\\[[ xX]\\]|[\u2610\u2612\u25FB\u25FC])`;
15307
+ const beforeNumber = new RegExp(`^\\s*(?:[\u276F\u203A>]\\s*)?${optionCheckbox}\\s*\\d+\\.\\s+\\S`);
15308
+ const afterNumber = new RegExp(`^\\s*(?:[\u276F\u203A>]\\s*)?\\d+\\.\\s*${optionCheckbox}\\s+\\S`);
15306
15309
  for (const line of screenText.split(/\r?\n/)) {
15307
15310
  if (line.includes("\u2714 Submit")) continue;
15308
- if (optionCheckbox.test(line)) return true;
15311
+ if (beforeNumber.test(line) || afterNumber.test(line)) return true;
15309
15312
  }
15310
15313
  return false;
15311
15314
  }
@@ -15445,6 +15448,16 @@ function parseClaudeInteractiveTuiQuestion(page, index) {
15445
15448
  ...allowFreeform ? { allowFreeform: true } : {}
15446
15449
  };
15447
15450
  }
15451
+ function readFocusedClaudeTuiQuestion(screenText) {
15452
+ if (!screenText.includes("Enter to select")) return null;
15453
+ const parsed = parseClaudeInteractiveTuiQuestion({ screenText }, 0);
15454
+ if (!parsed) return null;
15455
+ return {
15456
+ question: parsed.question,
15457
+ ...parsed.header ? { header: parsed.header } : {},
15458
+ multiSelect: parsed.multiSelect
15459
+ };
15460
+ }
15448
15461
  function detectClaudeAskUserQuestionPromptFromTuiPages(pages, options) {
15449
15462
  if (pages.length === 0) return null;
15450
15463
  const headers = claudeTuiQuestionHeaders(pages[0].screenText);
@@ -21571,22 +21584,160 @@ function isBoundedTailRequest(limit, offset, excludeRecentCount) {
21571
21584
  if (!Number.isFinite(numericOffset) || !Number.isFinite(numericExclude)) return false;
21572
21585
  return true;
21573
21586
  }
21587
+ var REVERSE_TAIL_SMALL_FILE_BYTES = 64 * 1024;
21588
+ var REVERSE_TAIL_CHUNK_BYTES = 64 * 1024;
21589
+ var TAIL_LINES_RETAINED = BOUNDED_TAIL_MAX_LIMIT + 2 * BOUNDED_TAIL_SLACK;
21590
+ var INCREMENTAL_TAIL_CACHE_MAX_ENTRIES = 64;
21591
+ var incrementalTailCache = /* @__PURE__ */ new Map();
21592
+ function evictIncrementalTailCache() {
21593
+ while (incrementalTailCache.size > INCREMENTAL_TAIL_CACHE_MAX_ENTRIES) {
21594
+ const oldest = incrementalTailCache.keys().next().value;
21595
+ if (oldest === void 0) break;
21596
+ incrementalTailCache.delete(oldest);
21597
+ }
21598
+ }
21599
+ function splitBufferLines(buf) {
21600
+ const lines = [];
21601
+ let lineEnd = buf.length;
21602
+ let firstNewline = -1;
21603
+ for (let i = buf.length - 1; i >= 0; i--) {
21604
+ if (buf[i] !== 10) continue;
21605
+ if (i + 1 < lineEnd) {
21606
+ lines.push(buf.toString("utf-8", i + 1, lineEnd));
21607
+ }
21608
+ lineEnd = i;
21609
+ firstNewline = i;
21610
+ }
21611
+ lines.reverse();
21612
+ const head = firstNewline >= 0 ? buf.subarray(0, firstNewline) : buf;
21613
+ return { head, lines };
21614
+ }
21615
+ function readReverseTailLines(filePath, needed) {
21616
+ const fd = fs5.openSync(filePath, "r");
21617
+ try {
21618
+ const stat2 = fs5.fstatSync(fd);
21619
+ const size = stat2.size;
21620
+ let position = size;
21621
+ let carry = Buffer.alloc(0);
21622
+ const collected = [];
21623
+ while (position > 0 && collected.length < needed) {
21624
+ const chunkSize = Math.min(REVERSE_TAIL_CHUNK_BYTES, position);
21625
+ position -= chunkSize;
21626
+ const chunk = Buffer.alloc(chunkSize);
21627
+ fs5.readSync(fd, chunk, 0, chunkSize, position);
21628
+ const combined = carry.length ? Buffer.concat([chunk, carry]) : chunk;
21629
+ const { head, lines } = splitBufferLines(combined);
21630
+ carry = head;
21631
+ for (let i = lines.length - 1; i >= 0; i--) {
21632
+ collected.push(lines[i]);
21633
+ }
21634
+ }
21635
+ const reachedStart = position <= 0;
21636
+ if (reachedStart && carry.length) {
21637
+ collected.push(carry.toString("utf-8"));
21638
+ }
21639
+ collected.reverse();
21640
+ return { lines: collected, coversWholeFile: reachedStart, size, mtimeMs: stat2.mtimeMs };
21641
+ } finally {
21642
+ fs5.closeSync(fd);
21643
+ }
21644
+ }
21645
+ function readFileTailLines(filePath, needed) {
21646
+ let stat2;
21647
+ try {
21648
+ stat2 = fs5.statSync(filePath);
21649
+ } catch {
21650
+ return { lines: [], coversWholeFile: true };
21651
+ }
21652
+ const size = stat2.size;
21653
+ const mtimeMs = stat2.mtimeMs;
21654
+ if (size === 0) {
21655
+ incrementalTailCache.delete(filePath);
21656
+ return { lines: [], coversWholeFile: true };
21657
+ }
21658
+ const cached2 = incrementalTailCache.get(filePath);
21659
+ if (cached2) {
21660
+ if (cached2.size === size && cached2.mtimeMs === mtimeMs) {
21661
+ incrementalTailCache.delete(filePath);
21662
+ incrementalTailCache.set(filePath, cached2);
21663
+ if (cached2.coversWholeFile || cached2.lines.length >= needed) {
21664
+ return { lines: cached2.lines, coversWholeFile: cached2.coversWholeFile };
21665
+ }
21666
+ } else if (size > cached2.size) {
21667
+ const incremental = tryIncrementalTailGrowth(filePath, cached2, size, mtimeMs, needed);
21668
+ if (incremental) return { lines: incremental.lines, coversWholeFile: incremental.coversWholeFile };
21669
+ }
21670
+ incrementalTailCache.delete(filePath);
21671
+ }
21672
+ if (size <= REVERSE_TAIL_SMALL_FILE_BYTES) {
21673
+ let content;
21674
+ try {
21675
+ content = fs5.readFileSync(filePath, "utf-8");
21676
+ } catch {
21677
+ return { lines: [], coversWholeFile: true };
21678
+ }
21679
+ const lines = content.split("\n");
21680
+ if (lines.length && lines[lines.length - 1] === "") lines.pop();
21681
+ storeIncrementalTailCache(filePath, size, mtimeMs, lines, true);
21682
+ return { lines, coversWholeFile: true };
21683
+ }
21684
+ let result;
21685
+ try {
21686
+ result = readReverseTailLines(filePath, needed);
21687
+ } catch {
21688
+ return { lines: [], coversWholeFile: true };
21689
+ }
21690
+ storeIncrementalTailCache(filePath, result.size, result.mtimeMs, result.lines, result.coversWholeFile);
21691
+ return { lines: result.lines, coversWholeFile: result.coversWholeFile };
21692
+ }
21693
+ function tryIncrementalTailGrowth(filePath, cached2, size, mtimeMs, needed) {
21694
+ const fd = fs5.openSync(filePath, "r");
21695
+ try {
21696
+ if (cached2.size > 0) {
21697
+ const boundary = Buffer.alloc(1);
21698
+ fs5.readSync(fd, boundary, 0, 1, cached2.size - 1);
21699
+ if (boundary[0] !== 10) return null;
21700
+ }
21701
+ const appendedLength = size - cached2.size;
21702
+ const appended = Buffer.alloc(appendedLength);
21703
+ fs5.readSync(fd, appended, 0, appendedLength, cached2.size);
21704
+ const newLines = appended.toString("utf-8").split("\n");
21705
+ if (newLines.length && newLines[newLines.length - 1] === "") newLines.pop();
21706
+ const merged = cached2.lines.concat(newLines);
21707
+ const trimmed = merged.length > TAIL_LINES_RETAINED ? merged.slice(merged.length - TAIL_LINES_RETAINED) : merged;
21708
+ const coversWholeFile = cached2.coversWholeFile && trimmed.length === merged.length;
21709
+ storeIncrementalTailCache(filePath, size, mtimeMs, trimmed, coversWholeFile);
21710
+ if (coversWholeFile || trimmed.length >= needed) {
21711
+ return { lines: trimmed, coversWholeFile };
21712
+ }
21713
+ return { lines: trimmed, coversWholeFile };
21714
+ } catch {
21715
+ return null;
21716
+ } finally {
21717
+ fs5.closeSync(fd);
21718
+ }
21719
+ }
21720
+ function storeIncrementalTailCache(filePath, size, mtimeMs, lines, coversWholeFile) {
21721
+ const retained = lines.length > TAIL_LINES_RETAINED ? lines.slice(lines.length - TAIL_LINES_RETAINED) : lines;
21722
+ const covers = coversWholeFile && retained.length === lines.length;
21723
+ incrementalTailCache.delete(filePath);
21724
+ incrementalTailCache.set(filePath, { size, mtimeMs, lines: retained, coversWholeFile: covers });
21725
+ evictIncrementalTailCache();
21726
+ }
21574
21727
  function readBoundedTailRecords(agentType, dir, files, needed) {
21575
21728
  const collected = [];
21576
21729
  const seen = /* @__PURE__ */ new Set();
21577
21730
  let readAllFiles = true;
21578
21731
  for (let f = 0; f < files.length; f++) {
21579
21732
  const filePath = path12.join(dir, files[f]);
21580
- let content;
21581
- try {
21582
- content = fs5.readFileSync(filePath, "utf-8");
21583
- } catch {
21584
- continue;
21585
- }
21586
- const lines = content.trim().split("\n").filter(Boolean);
21733
+ const remaining = Math.max(0, needed - collected.length);
21734
+ const perFileNeeded = Math.min(needed, remaining + BOUNDED_TAIL_SLACK);
21735
+ const { lines, coversWholeFile } = readFileTailLines(filePath, perFileNeeded);
21587
21736
  for (let i = lines.length - 1; i >= 0; i--) {
21737
+ const line = lines[i];
21738
+ if (!line) continue;
21588
21739
  try {
21589
- const parsed = JSON.parse(lines[i]);
21740
+ const parsed = JSON.parse(line);
21590
21741
  const sanitizedMessage = sanitizeHistoryMessage(agentType, parsed);
21591
21742
  if (!sanitizedMessage) continue;
21592
21743
  const hash = buildHistoryMessageHash(agentType, sanitizedMessage);
@@ -21596,6 +21747,10 @@ function readBoundedTailRecords(agentType, dir, files, needed) {
21596
21747
  } catch {
21597
21748
  }
21598
21749
  }
21750
+ if (!coversWholeFile) {
21751
+ readAllFiles = false;
21752
+ break;
21753
+ }
21599
21754
  if (collected.length >= needed && f < files.length - 1) {
21600
21755
  readAllFiles = false;
21601
21756
  break;
@@ -31552,12 +31707,14 @@ var SpecCliAdapter = class _SpecCliAdapter {
31552
31707
  }
31553
31708
  this.maybeClearResolvedClaudeTuiPrompt();
31554
31709
  this.maybeCaptureClaudeTuiPrompt();
31710
+ this.maybeUpgradeClaudeTuiMultiSelect();
31555
31711
  this.statusCallback?.();
31556
31712
  return;
31557
31713
  case "pty_data":
31558
31714
  this.detectInteractivePromptFromPtyChunk(ev.chunk);
31559
31715
  this.maybeClearResolvedClaudeTuiPrompt();
31560
31716
  this.maybeCaptureClaudeTuiPrompt();
31717
+ this.maybeUpgradeClaudeTuiMultiSelect();
31561
31718
  try {
31562
31719
  this.ptyDataCallback?.(ev.chunk);
31563
31720
  } catch {
@@ -31706,6 +31863,53 @@ var SpecCliAdapter = class _SpecCliAdapter {
31706
31863
  this.claudeTuiPromptCaptureInFlight = false;
31707
31864
  });
31708
31865
  }
31866
+ /**
31867
+ * The TUI prompt is captured on the FIRST frame that renders the
31868
+ * "Enter to select" footer. At that instant the option rows' checkbox
31869
+ * column may not have drawn yet, so `detectClaudeTuiMultiSelect` returns
31870
+ * false and the prompt is frozen as single-select — the dashboard then
31871
+ * renders radio buttons even though the picker is multi-select.
31872
+ *
31873
+ * While the same TUI prompt is still on screen, re-check the live snapshot:
31874
+ * if checkbox glyphs have since appeared, promote any single-select
31875
+ * question to multi-select and re-emit status. Promotion is one-way
31876
+ * (false→true only) — once a question is known multi-select we never demote
31877
+ * it, since the glyph column can scroll out of view on later frames.
31878
+ *
31879
+ * For MULTI-question prompts the per-page Tab capture is the actual source
31880
+ * of the bug: pages 2..N are snapshotted ~120ms after the Tab keypress,
31881
+ * before their option-row glyph column has redrawn, so those questions
31882
+ * freeze as single-select while page 1 (already settled) is correct. We
31883
+ * cannot upgrade blindly — the live snapshot shows only ONE focused page —
31884
+ * but we CAN read that page's question text/header and upgrade the matching
31885
+ * question. As the user navigates the picker (or it settles), each page is
31886
+ * eventually re-read and repaired.
31887
+ */
31888
+ maybeUpgradeClaudeTuiMultiSelect() {
31889
+ if (this.cliType !== "claude-cli" || this.interactivePromptTransport !== "tui" || !this.activeInteractivePrompt) return;
31890
+ const questions = this.activeInteractivePrompt.questions;
31891
+ if (questions.every((q) => q.multiSelect)) return;
31892
+ let screenText = "";
31893
+ try {
31894
+ screenText = this.driver.snapshot();
31895
+ } catch {
31896
+ return;
31897
+ }
31898
+ if (!screenText.includes("Enter to select")) return;
31899
+ if (questions.length === 1) {
31900
+ if (questions[0].multiSelect) return;
31901
+ if (!detectClaudeTuiMultiSelect(screenText)) return;
31902
+ questions[0].multiSelect = true;
31903
+ this.statusCallback?.();
31904
+ return;
31905
+ }
31906
+ const focused = readFocusedClaudeTuiQuestion(screenText);
31907
+ if (!focused || !focused.multiSelect) return;
31908
+ const match = questions.find((q) => focused.header && q.header && q.header === focused.header || q.question === focused.question);
31909
+ if (!match || match.multiSelect) return;
31910
+ match.multiSelect = true;
31911
+ this.statusCallback?.();
31912
+ }
31709
31913
  readClaudeTuiHeaders(screenText) {
31710
31914
  const navLine = screenText.split(/\r?\n/).find((line) => line.includes("\u2714 Submit") && /[☐☒]/.test(line));
31711
31915
  if (!navLine) return [];
@@ -31855,6 +32059,7 @@ function normalizeProviderSessionId(provider, providerSessionId) {
31855
32059
  }
31856
32060
 
31857
32061
  // src/providers/cli-provider-instance.ts
32062
+ var STATUS_HYDRATION_TAIL_LIMIT = 200;
31858
32063
  function isIdleStatus(value) {
31859
32064
  const status = typeof value === "string" ? value.trim().toLowerCase() : "";
31860
32065
  return !status || status === "idle" || status === "ready";
@@ -33517,12 +33722,14 @@ ${effect.notification.body || ""}`.trim();
33517
33722
  const newestMessageAt = parsedMessages.reduce((newest, message) => Math.max(newest, getMessageTime(message)), 0);
33518
33723
  return newestMessageAt === 0;
33519
33724
  }
33520
- syncCanonicalSavedHistoryIfNeeded() {
33725
+ syncCanonicalSavedHistoryIfNeeded(options = {}) {
33521
33726
  if (!this.providerSessionId) return false;
33522
33727
  const canonicalHistory = this.provider.nativeHistory;
33523
33728
  if (!canonicalHistory) return false;
33729
+ const limit = options.full ? Number.MAX_SAFE_INTEGER : STATUS_HYDRATION_TAIL_LIMIT;
33730
+ const windowTag = options.full ? "full" : `tail:${STATUS_HYDRATION_TAIL_LIMIT}`;
33524
33731
  if (isNativeSourceCanonicalHistory(canonicalHistory)) {
33525
- const cacheKey = [this.type, this.providerSessionId, this.workingDir].join("\0");
33732
+ const cacheKey = [this.type, this.providerSessionId, this.workingDir, windowTag].join("\0");
33526
33733
  const now = Date.now();
33527
33734
  if (cacheKey === this.lastNativeSourceCanonicalCacheKey && now - this.lastNativeSourceCanonicalCheckAt < 2e3) {
33528
33735
  return true;
@@ -33534,7 +33741,7 @@ ${effect.notification.body || ""}`.trim();
33534
33741
  historySessionId: this.providerSessionId,
33535
33742
  workspace: this.workingDir,
33536
33743
  offset: 0,
33537
- limit: Number.MAX_SAFE_INTEGER,
33744
+ limit,
33538
33745
  historyBehavior: this.provider.historyBehavior,
33539
33746
  scripts: this.provider.scripts
33540
33747
  });
@@ -33550,7 +33757,7 @@ ${effect.notification.body || ""}`.trim();
33550
33757
  return true;
33551
33758
  }
33552
33759
  try {
33553
- const cacheKey = [this.type, this.providerSessionId, this.workingDir, canonicalHistory.mode || "materialized-mirror"].join("\0");
33760
+ const cacheKey = [this.type, this.providerSessionId, this.workingDir, canonicalHistory.mode || "materialized-mirror", windowTag].join("\0");
33554
33761
  const now = Date.now();
33555
33762
  if (cacheKey === this.lastNativeSourceCanonicalCacheKey && now - this.lastNativeSourceCanonicalCheckAt < 2e3) {
33556
33763
  return true;
@@ -33560,7 +33767,7 @@ ${effect.notification.body || ""}`.trim();
33560
33767
  if (!materializeProviderNativeHistory(this.type, canonicalHistory, this.providerSessionId, this.workingDir, this.provider.scripts)) {
33561
33768
  return false;
33562
33769
  }
33563
- const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId, 0, this.provider.historyBehavior);
33770
+ const restoredHistory = readChatHistory(this.type, 0, limit, this.providerSessionId, 0, this.provider.historyBehavior);
33564
33771
  this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
33565
33772
  role: message.role,
33566
33773
  content: message.content,
@@ -33575,7 +33782,7 @@ ${effect.notification.body || ""}`.trim();
33575
33782
  }
33576
33783
  restorePersistedHistoryFromCurrentSession() {
33577
33784
  if (!this.providerSessionId) return;
33578
- this.syncCanonicalSavedHistoryIfNeeded();
33785
+ this.syncCanonicalSavedHistoryIfNeeded({ full: true });
33579
33786
  const restoredHistory = isNativeSourceCanonicalHistory(this.provider.nativeHistory) ? readProviderChatHistory(this.type, {
33580
33787
  canonicalHistory: this.provider.nativeHistory,
33581
33788
  historySessionId: this.providerSessionId,