@adhdev/daemon-core 0.9.82-rc.385 → 0.9.82-rc.387

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
@@ -378,10 +378,10 @@ function readInjected(value) {
378
378
  }
379
379
  function getDaemonBuildInfo() {
380
380
  if (cached) return cached;
381
- const commit = readInjected(true ? "3aa3e12839017da30b44a64ca6c20f0c9730fae2" : void 0) ?? "unknown";
382
- const commitShort = readInjected(true ? "3aa3e128" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
383
- const version = readInjected(true ? "0.9.82-rc.385" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
384
- const builtAt = readInjected(true ? "2026-06-25T16:15:39.207Z" : void 0);
381
+ const commit = readInjected(true ? "057d5def5d55af124dfe910ee9accdf244576c14" : void 0) ?? "unknown";
382
+ const commitShort = readInjected(true ? "057d5def" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
383
+ const version = readInjected(true ? "0.9.82-rc.387" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
384
+ const builtAt = readInjected(true ? "2026-06-26T02:21:27.575Z" : void 0);
385
385
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
386
386
  return cached;
387
387
  }
@@ -1408,6 +1408,7 @@ var init_git_diff = __esm({
1408
1408
  var git_worktree_exports = {};
1409
1409
  __export(git_worktree_exports, {
1410
1410
  createWorktree: () => createWorktree,
1411
+ deleteBranchRef: () => deleteBranchRef,
1411
1412
  listWorktrees: () => listWorktrees,
1412
1413
  parseWorktreeListOutput: () => parseWorktreeListOutput,
1413
1414
  removeWorktree: () => removeWorktree,
@@ -1555,6 +1556,53 @@ function parseWorktreeListOutput(output) {
1555
1556
  }
1556
1557
  return entries;
1557
1558
  }
1559
+ async function deleteBranchRef(repoRoot, branch, opts = {}) {
1560
+ const name = (branch || "").trim();
1561
+ if (!name) return { deleted: false, reason: "empty_branch_name" };
1562
+ try {
1563
+ await execFileAsync2("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${name}`], {
1564
+ cwd: repoRoot,
1565
+ encoding: "utf8",
1566
+ timeout: GIT_TIMEOUT_MS,
1567
+ maxBuffer: GIT_MAX_BUFFER,
1568
+ windowsHide: true
1569
+ });
1570
+ } catch {
1571
+ return { deleted: true, reason: "branch_ref_absent" };
1572
+ }
1573
+ try {
1574
+ await execFileAsync2("git", ["branch", "-d", name], {
1575
+ cwd: repoRoot,
1576
+ encoding: "utf8",
1577
+ timeout: GIT_TIMEOUT_MS,
1578
+ maxBuffer: GIT_MAX_BUFFER,
1579
+ windowsHide: true
1580
+ });
1581
+ return { deleted: true, reason: "safe_deleted_merged_branch" };
1582
+ } catch (error) {
1583
+ const stderr = typeof error?.stderr === "string" ? error.stderr : "";
1584
+ const notMerged = /not fully merged/i.test(stderr) || /not fully merged/i.test(String(error?.message || ""));
1585
+ if (!notMerged) {
1586
+ return { deleted: false, reason: `branch_delete_failed: ${stderr.trim() || error?.message || "unknown error"}` };
1587
+ }
1588
+ if (opts.safeDeleteOnly) {
1589
+ return { deleted: false, reason: "branch_not_merged_per_git_safe_delete_only" };
1590
+ }
1591
+ try {
1592
+ await execFileAsync2("git", ["branch", "-D", name], {
1593
+ cwd: repoRoot,
1594
+ encoding: "utf8",
1595
+ timeout: GIT_TIMEOUT_MS,
1596
+ maxBuffer: GIT_MAX_BUFFER,
1597
+ windowsHide: true
1598
+ });
1599
+ return { deleted: true, reason: "force_deleted_patch_equivalent_branch", forced: true };
1600
+ } catch (forceError) {
1601
+ const fErr = typeof forceError?.stderr === "string" ? forceError.stderr : forceError?.message;
1602
+ return { deleted: false, reason: `branch_force_delete_failed: ${String(fErr || "unknown error").trim()}` };
1603
+ }
1604
+ }
1605
+ }
1558
1606
  async function pruneWorktrees(repoRoot) {
1559
1607
  try {
1560
1608
  await execFileAsync2("git", ["worktree", "prune"], {
@@ -4415,6 +4463,12 @@ function isMutationKeywordInCommandContext(text, matchStart, matchEnd) {
4415
4463
  const linePrefix = text.slice(lineStart, matchStart);
4416
4464
  if (/^\s*[$>]\s/.test(text.slice(lineStart))) return true;
4417
4465
  if (/^\s*$/.test(linePrefix)) return true;
4466
+ let tokStart = matchStart;
4467
+ while (tokStart > lineStart && !/\s/.test(text[tokStart - 1])) tokStart--;
4468
+ const beforeToken = text.slice(lineStart, tokStart);
4469
+ const tokenLead = text.slice(tokStart, matchStart);
4470
+ const atCmdPos = /^\s*$/.test(beforeToken) || /(?:&&|\|\||\||;)\s*$/.test(beforeToken);
4471
+ if (atCmdPos && /^(?:\.\/|\.\.\/|~\/|\/)/.test(tokenLead)) return true;
4418
4472
  if (/(?:&&|\|\||\||;)\s*$/.test(linePrefix)) return true;
4419
4473
  if (/(?:^|[\s,])(?:then|run|first)\s+$/i.test(linePrefix)) return true;
4420
4474
  if (/,\s*$/.test(linePrefix)) return true;
@@ -4441,9 +4495,65 @@ function isInsideBackticksOrFence(text, matchStart, matchEnd) {
4441
4495
  }
4442
4496
  return inlineCount % 2 === 1;
4443
4497
  }
4498
+ function isInsidePathSegment(text, matchStart, matchEnd) {
4499
+ const prev = matchStart > 0 ? text[matchStart - 1] : "";
4500
+ const next = matchEnd < text.length ? text[matchEnd] : "";
4501
+ const isSep = (c) => c === "/" || c === "\\";
4502
+ const segChar = (c) => /[A-Za-z0-9._~-]/.test(c);
4503
+ const inPath = isSep(prev) && (next === "" || isSep(next) || segChar(next) || /\s/.test(next)) || isSep(next) && (prev === "" || isSep(prev) || segChar(prev) || /\s/.test(prev));
4504
+ if (!inPath) return false;
4505
+ const lineStart = text.lastIndexOf("\n", matchStart - 1) + 1;
4506
+ let tokStart = matchStart;
4507
+ while (tokStart > lineStart && !/\s/.test(text[tokStart - 1])) tokStart--;
4508
+ const linePrefixBeforeToken = text.slice(lineStart, tokStart);
4509
+ const tokenPrefix = text.slice(tokStart, matchStart);
4510
+ const atLineStart = /^\s*$/.test(linePrefixBeforeToken);
4511
+ const afterConnective = /(?:&&|\|\||\||;)\s*$/.test(linePrefixBeforeToken);
4512
+ const isRunPrefixPath = /^(?:\.\/|\.\.\/|~\/|\/)/.test(tokenPrefix);
4513
+ if ((atLineStart || afterConnective) && isRunPrefixPath) return false;
4514
+ return true;
4515
+ }
4516
+ function isInsideQuotedSpan(text, matchStart, matchEnd) {
4517
+ const pairs = [
4518
+ ["\u201C", "\u201D"],
4519
+ ["\u2018", "\u2019"],
4520
+ ["\u300C", "\u300D"],
4521
+ ["\u300E", "\u300F"],
4522
+ ["\u300A", "\u300B"]
4523
+ ];
4524
+ for (const [open, close] of pairs) {
4525
+ const openIdx = text.lastIndexOf(open, matchStart - 1);
4526
+ if (openIdx < 0) continue;
4527
+ if (text.indexOf(close, openIdx + open.length) >= matchEnd) return true;
4528
+ }
4529
+ const lineStart = text.lastIndexOf("\n", matchStart - 1) + 1;
4530
+ const lineEnd = (() => {
4531
+ const i = text.indexOf("\n", matchEnd);
4532
+ return i < 0 ? text.length : i;
4533
+ })();
4534
+ for (const q of ['"', "'"]) {
4535
+ let count = 0;
4536
+ for (let i = lineStart; i < matchStart; i++) if (text[i] === q) count++;
4537
+ if (count % 2 === 1 && text.indexOf(q, matchEnd) >= 0 && text.indexOf(q, matchEnd) < lineEnd) {
4538
+ if (q === '"') return true;
4539
+ let openPos = -1, c = 0;
4540
+ for (let i = lineStart; i < matchStart; i++) {
4541
+ if (text[i] === q) {
4542
+ c++;
4543
+ if (c % 2 === 1) openPos = i;
4544
+ }
4545
+ }
4546
+ const beforeOpen = openPos > lineStart ? text[openPos - 1] : " ";
4547
+ if (/[\s(\[{>]/.test(beforeOpen) || openPos === lineStart) return true;
4548
+ }
4549
+ }
4550
+ return false;
4551
+ }
4444
4552
  function isRealMutationMatch(text, matchStart, matchEnd) {
4445
4553
  if (hasNegationBefore(text, matchStart)) return false;
4446
4554
  if (hasTrailingNegation(text, matchEnd)) return false;
4555
+ if (isInsidePathSegment(text, matchStart, matchEnd)) return false;
4556
+ if (isInsideQuotedSpan(text, matchStart, matchEnd)) return false;
4447
4557
  return isMutationKeywordInCommandContext(text, matchStart, matchEnd);
4448
4558
  }
4449
4559
  function patternHasRealMutation(pattern, text) {
@@ -35833,6 +35943,17 @@ var FsmDriver = class {
35833
35943
  hasIdleHoldPending() {
35834
35944
  return (this.lastFsmEval?.transitions ?? []).some((t) => !t.holdSatisfied && t.condResult);
35835
35945
  }
35946
+ /**
35947
+ * True once the machine has reached its first non-initial idle state (the
35948
+ * prompt is genuinely drawn — see maybeMarkReady). The cli-adapter surfaces
35949
+ * this on its idle status so CliProviderInstance can re-arm the queue-claim
35950
+ * agent:ready on the first genuine ready, independent of the boot-time
35951
+ * starting→idle one-shot (which is consumed too early for specs whose
35952
+ * initial state already reports idle).
35953
+ */
35954
+ hasSeenReady() {
35955
+ return this.readySeenOnce;
35956
+ }
35836
35957
  getCompletionIdleDebounceState() {
35837
35958
  const out = outgoingTransitions(this.spec, this.currentStateId);
35838
35959
  const toReady = this.lastFsmEval?.transitions.find((t, i) => {
@@ -37499,7 +37620,7 @@ var SpecCliAdapter = class _SpecCliAdapter {
37499
37620
  if (state.status === "generating") {
37500
37621
  return { status: "generating", messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
37501
37622
  }
37502
- return { status: "idle", messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
37623
+ return { status: "idle", messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, fsmReadySeen: this.driver.hasSeenReady?.() ?? false, ...sessionFields };
37503
37624
  }
37504
37625
  maybeRefreshNativeHistory() {
37505
37626
  }
@@ -38544,6 +38665,14 @@ var CliProviderInstance = class _CliProviderInstance {
38544
38665
  context = null;
38545
38666
  events = [];
38546
38667
  lastStatus = "starting";
38668
+ // Idempotency guard for the queue-claim agent:ready event. agent:ready is the
38669
+ // sole signal the mesh coordinator's tryAssignQueueTask waits on to hand a
38670
+ // queued task to this worker. It is emitted in two places: the boot-time
38671
+ // starting→idle one-shot, and the readySeen re-arm below. This flag makes the
38672
+ // event fire AT MOST ONCE per session so a worker is never claimed twice and a
38673
+ // queued task is never double-dispatched/double-injected. Whichever path fires
38674
+ // first sets it; the other becomes a no-op.
38675
+ agentReadyEmitted = false;
38547
38676
  generatingStartedAt = 0;
38548
38677
  settings = {};
38549
38678
  monitor;
@@ -39624,6 +39753,17 @@ var CliProviderInstance = class _CliProviderInstance {
39624
39753
  } catch {
39625
39754
  }
39626
39755
  }
39756
+ /**
39757
+ * Emit the queue-claim agent:ready event at most once per session. Both the
39758
+ * boot-time starting→idle one-shot and the fsmReadySeen re-arm call this; the
39759
+ * agentReadyEmitted guard ensures the second caller is a no-op so a worker is
39760
+ * never claimed twice and a queued task is never double-dispatched.
39761
+ */
39762
+ emitAgentReadyOnce(chatTitle, now) {
39763
+ if (this.agentReadyEmitted) return;
39764
+ this.agentReadyEmitted = true;
39765
+ this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
39766
+ }
39627
39767
  detectStatusTransition() {
39628
39768
  const now = Date.now();
39629
39769
  const adapterStatus = this.adapter.getStatus({ allowParse: false });
@@ -39776,7 +39916,7 @@ var CliProviderInstance = class _CliProviderInstance {
39776
39916
  this.scheduleCompletedDebounceFlush(flushDelay);
39777
39917
  }
39778
39918
  } else if (newStatus === "idle" && this.lastStatus === "starting") {
39779
- this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
39919
+ this.emitAgentReadyOnce(chatTitle, now);
39780
39920
  } else if (newStatus === "error") {
39781
39921
  if (this.generatingDebounceTimer) {
39782
39922
  clearTimeout(this.generatingDebounceTimer);
@@ -39815,6 +39955,9 @@ var CliProviderInstance = class _CliProviderInstance {
39815
39955
  }
39816
39956
  this.lastStatus = newStatus;
39817
39957
  }
39958
+ if (newStatus === "idle" && adapterStatus.fsmReadySeen === true && !this.agentReadyEmitted) {
39959
+ this.emitAgentReadyOnce(chatTitle, now);
39960
+ }
39818
39961
  this.applyProviderResponse(parsedStatus, {
39819
39962
  phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
39820
39963
  });
@@ -47188,13 +47331,16 @@ var meshCrudHandlers = {
47188
47331
  worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
47189
47332
  worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
47190
47333
  forced: worktreeCleanup?.forced === true ? true : void 0,
47191
- forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
47334
+ forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0,
47335
+ branchRefDeleted: typeof worktreeCleanup?.branchRefDeleted === "boolean" ? worktreeCleanup.branchRefDeleted : void 0,
47336
+ branchRefReason: typeof worktreeCleanup?.branchRefReason === "string" ? worktreeCleanup.branchRefReason : void 0
47192
47337
  }
47193
47338
  });
47194
47339
  } catch {
47195
47340
  }
47196
47341
  }
47197
47342
  const residueWarning = worktreeCleanup?.residue === true && typeof worktreeCleanup?.residueWarning === "string" ? worktreeCleanup.residueWarning : void 0;
47343
+ const branchRefWarning = typeof worktreeCleanup?.branchRefWarning === "string" ? worktreeCleanup.branchRefWarning : void 0;
47198
47344
  const skippedLiveSessionIds = Array.isArray(sessionCleanup?.skippedLiveSessionIds) ? sessionCleanup.skippedLiveSessionIds.filter((v) => typeof v === "string") : [];
47199
47345
  const orphanedSessionsRemaining = skippedLiveSessionIds.length > 0;
47200
47346
  const orphanNextAction = orphanedSessionsRemaining ? `Live session(s) [${skippedLiveSessionIds.join(", ")}] were skipped and still survive this node removal. Run mesh_cleanup_sessions with mode:'stop_and_delete' and sessionIds:[${skippedLiveSessionIds.map((id) => `'${id}'`).join(", ")}] to release them.` : void 0;
@@ -47202,6 +47348,7 @@ var meshCrudHandlers = {
47202
47348
  success: true,
47203
47349
  removed,
47204
47350
  ...residueWarning ? { residueWarning } : {},
47351
+ ...branchRefWarning ? { branchRefWarning } : {},
47205
47352
  ...sessionCleanup ? { sessionCleanup } : {},
47206
47353
  ...worktreeCleanup ? { worktreeCleanup } : {},
47207
47354
  ...orphanedSessionsRemaining ? { orphanedSessionsRemaining: true, nextAction: orphanNextAction } : {}
@@ -52471,16 +52618,50 @@ var DaemonCommandRouter = class {
52471
52618
  recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
52472
52619
  };
52473
52620
  }
52474
- const forceFallbackConvergence = args.force ? { allow: true, status: "force_override", source: "caller_force_flag" } : await this.getWorktreeForceCleanupConvergence({ repoRoot, workspace, node: args.node });
52621
+ const mergeConvergence = await this.getWorktreeForceCleanupConvergence({ repoRoot, workspace, node: args.node });
52622
+ const forceFallbackConvergence = args.force ? { allow: true, status: "force_override", source: "caller_force_flag" } : mergeConvergence;
52623
+ const deleteBranchIfMerged = async () => {
52624
+ const branch = String(args.node.worktreeBranch).trim();
52625
+ const status = mergeConvergence.allow ? mergeConvergence.status || "" : "";
52626
+ const MERGED_STATUSES = /* @__PURE__ */ new Set([
52627
+ "merged_to_main",
52628
+ "merged_pushed",
52629
+ "merged_to_default_ref",
52630
+ "cleanup_candidate"
52631
+ ]);
52632
+ const PATCH_EQUIV_STATUS = "patch_equivalent_to_default_ref";
52633
+ if (!branch) {
52634
+ return { branchRefDeleted: false, branchRefReason: "empty_branch_name" };
52635
+ }
52636
+ if (!mergeConvergence.allow || !MERGED_STATUSES.has(status) && status !== PATCH_EQUIV_STATUS) {
52637
+ return {
52638
+ branchRefDeleted: false,
52639
+ branchRefReason: `branch_not_merged_preserved: ${mergeConvergence.error || mergeConvergence.status || "convergence_unverified"}`,
52640
+ branchRefWarning: `Branch ref '${branch}' was preserved (not deleted) because it is not confirmed merged into the default ref \u2014 no work was lost. Merge it (or pass a verified branchConvergence final state) and re-run cleanup, or delete it manually after confirming.`
52641
+ };
52642
+ }
52643
+ const { deleteBranchRef: deleteBranchRef2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
52644
+ const res = await deleteBranchRef2(repoRoot, branch, { safeDeleteOnly: status !== PATCH_EQUIV_STATUS });
52645
+ return {
52646
+ branchRefDeleted: res.deleted,
52647
+ branchRefReason: res.reason,
52648
+ ...res.forced ? { branchRefForced: true } : {},
52649
+ ...res.deleted ? {} : {
52650
+ branchRefWarning: `Branch ref '${branch}' could not be deleted (${res.reason}); it was preserved so no work is lost.`
52651
+ }
52652
+ };
52653
+ };
52475
52654
  try {
52476
52655
  const result = await removeWorktree2(repoRoot, workspace, {
52477
52656
  requireClean: !args.force,
52478
52657
  allowSubmoduleForceFallback: forceFallbackConvergence.allow
52479
52658
  });
52659
+ const branchOutcome = await deleteBranchIfMerged();
52480
52660
  return {
52481
52661
  success: true,
52482
52662
  removedPath: result.removedPath,
52483
52663
  repoRoot,
52664
+ ...branchOutcome,
52484
52665
  ...result.fallback ? {
52485
52666
  fallback: result.fallback,
52486
52667
  forced: result.forced,
@@ -52513,10 +52694,12 @@ var DaemonCommandRouter = class {
52513
52694
  maxBuffer: GIT_MAX_BUFFER_CLEANUP,
52514
52695
  windowsHide: true
52515
52696
  });
52697
+ const branchOutcome = await deleteBranchIfMerged();
52516
52698
  return {
52517
52699
  success: true,
52518
52700
  removedPath: workspace,
52519
52701
  repoRoot,
52702
+ ...branchOutcome,
52520
52703
  fallback: "git_worktree_remove_submodule_deinit",
52521
52704
  forced: true,
52522
52705
  reason: "working_trees_containing_submodules",
@@ -52534,10 +52717,12 @@ var DaemonCommandRouter = class {
52534
52717
  });
52535
52718
  } catch {
52536
52719
  }
52720
+ const branchOutcome = await deleteBranchIfMerged();
52537
52721
  return {
52538
52722
  success: true,
52539
52723
  removedPath: workspace,
52540
52724
  repoRoot,
52725
+ ...branchOutcome,
52541
52726
  fallback: "fs_rm_worktree_prune",
52542
52727
  forced: true,
52543
52728
  reason: "working_trees_containing_submodules",