@adhdev/daemon-core 0.9.82-rc.400 → 0.9.82-rc.401

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.
@@ -85,8 +85,9 @@ export interface CliAdapter {
85
85
  spawn(): Promise<void>;
86
86
  sendMessage(text: string, options?: {
87
87
  force?: boolean;
88
+ meshTaskId?: string;
88
89
  }): Promise<void>;
89
- forceSendMessage?(text: string): Promise<void>;
90
+ forceSendMessage?(text: string, meshTaskId?: string): Promise<void>;
90
91
  getStatus(options?: {
91
92
  allowParse?: boolean;
92
93
  }): CliAdapterStatus;
@@ -58,6 +58,7 @@ export declare class CliStateEngine {
58
58
  currentStatus: CliSessionStatus['status'];
59
59
  isWaitingForResponse: boolean;
60
60
  currentTurnScope: TurnParseScope | null;
61
+ currentTurnTaskId: string | null;
61
62
  activeModal: {
62
63
  message: string;
63
64
  buttons: string[];
@@ -187,8 +187,9 @@ export declare class ProviderCliAdapter implements CliAdapter {
187
187
  private waitForEchoAndSubmit;
188
188
  sendMessage(text: string, options?: {
189
189
  force?: boolean;
190
+ meshTaskId?: string;
190
191
  }): Promise<void>;
191
- forceSendMessage(text: string): Promise<void>;
192
+ forceSendMessage(text: string, meshTaskId?: string): Promise<void>;
192
193
  private waitForForceSubmitSettle;
193
194
  private enqueuePendingOutboundMessage;
194
195
  private shouldQueuePendingOutboundMessage;
@@ -229,6 +230,7 @@ export declare class ProviderCliAdapter implements CliAdapter {
229
230
  } | null);
230
231
  get currentTurnScope(): TurnParseScope | null;
231
232
  set currentTurnScope(v: TurnParseScope | null);
233
+ get currentTurnTaskId(): string | null;
232
234
  get responseEpoch(): number;
233
235
  set responseEpoch(v: number);
234
236
  get submitRetryUsed(): boolean;
@@ -4,6 +4,7 @@ export interface TurnParseScope {
4
4
  startedAt: number;
5
5
  bufferStart: number;
6
6
  rawBufferStart: number;
7
+ taskId?: string;
7
8
  }
8
9
  export declare function normalizeCliParsedMessages(parsedMessages: any[], _options: {
9
10
  scope?: TurnParseScope | null;
@@ -19,11 +19,54 @@ export interface WorktreeCreateOptions {
19
19
  meshName: string;
20
20
  /** Override the auto-resolved target directory */
21
21
  targetDir?: string;
22
+ /**
23
+ * Remote to fetch+compare the base branch against before branching.
24
+ * Default: 'origin'.
25
+ */
26
+ remote?: string;
27
+ /**
28
+ * When true (default) and `baseBranch` is given, fetch the base branch from
29
+ * `remote` and, if the local base branch is strictly behind the remote
30
+ * (no divergence), branch the worktree from the remote-tracking ref instead
31
+ * of the stale local ref. The decision is always surfaced via `baseSync`.
32
+ * Set false to preserve the legacy "branch from local ref, never fetch"
33
+ * behavior.
34
+ */
35
+ syncBaseFromRemote?: boolean;
36
+ }
37
+ /**
38
+ * How the worktree's start point was resolved relative to the remote base.
39
+ * Surfaced so the coordinator can detect a stale base node before dispatching
40
+ * work onto a worktree built on a behind/diverged base.
41
+ */
42
+ export interface WorktreeBaseSync {
43
+ /** The base branch name requested (e.g. 'main'). */
44
+ branch: string;
45
+ /** The remote compared against (e.g. 'origin'). */
46
+ remote: string;
47
+ /** The actual ref/commit-ish used as the worktree branch start point. */
48
+ startRef: string;
49
+ /** Whether `git fetch <remote> <branch>` succeeded. */
50
+ fetched: boolean;
51
+ /** Local base-branch SHA before clone, if the local ref exists. */
52
+ localSha?: string;
53
+ /** Remote-tracking base-branch SHA after fetch, if it exists. */
54
+ remoteSha?: string;
55
+ /** Commits the local base ref is behind the remote (0 when up-to-date/ahead). */
56
+ behindBy: number;
57
+ /** Commits the local base ref is ahead of the remote. */
58
+ aheadBy: number;
59
+ /** What was done with the base ref. */
60
+ action: 'up_to_date' | 'local_behind_used_remote' | 'local_ahead_used_local' | 'diverged_used_local' | 'no_remote_ref_used_local' | 'no_local_ref_used_remote';
61
+ /** Human-readable warning when the base was stale/diverged. */
62
+ warning?: string;
22
63
  }
23
64
  export interface WorktreeCreateResult {
24
65
  success: true;
25
66
  worktreePath: string;
26
67
  branch: string;
68
+ /** Present when `baseBranch` was given and base sync resolution ran. */
69
+ baseSync?: WorktreeBaseSync;
27
70
  }
28
71
  export interface WorktreeEntry {
29
72
  path: string;
@@ -56,7 +99,12 @@ export declare function resolveWorktreePath(repoRoot: string, meshName: string,
56
99
  /**
57
100
  * Create a new git worktree with a fresh branch.
58
101
  *
59
- * Runs: git worktree add <targetDir> -b <branch> [baseBranch]
102
+ * Runs: git worktree add <targetDir> -b <branch> [startRef]
103
+ *
104
+ * When `baseBranch` is given and `syncBaseFromRemote` is not disabled, the
105
+ * start point is resolved against the remote first (see
106
+ * resolveWorktreeBaseStartPoint) so a stale local base does not produce a stale
107
+ * worktree. The resolution is returned as `baseSync`.
60
108
  */
61
109
  export declare function createWorktree(opts: WorktreeCreateOptions): Promise<WorktreeCreateResult>;
62
110
  /**
@@ -17,4 +17,4 @@ export type { GitCommandResult, GitCommandServices, GitFileDiff, GitLogEntry, Gi
17
17
  export { TurnSnapshotTracker } from './turn-snapshot-tracker.js';
18
18
  export type { TurnCompletedCallback } from './turn-snapshot-tracker.js';
19
19
  export { createWorktree, listWorktrees, parseWorktreeListOutput, removeWorktree, resolveWorktreePath, } from './git-worktree.js';
20
- export type { WorktreeCreateOptions, WorktreeCreateResult, WorktreeEntry, WorktreeRemoveResult, } from './git-worktree.js';
20
+ export type { WorktreeBaseSync, WorktreeCreateOptions, WorktreeCreateResult, WorktreeEntry, WorktreeRemoveResult, } from './git-worktree.js';
package/dist/index.js CHANGED
@@ -399,10 +399,10 @@ function readInjected(value) {
399
399
  }
400
400
  function getDaemonBuildInfo() {
401
401
  if (cached) return cached;
402
- const commit = readInjected(true ? "2350563490670c1c560cc5cf6f863a9e61ef3f89" : void 0) ?? "unknown";
403
- const commitShort = readInjected(true ? "23505634" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
404
- const version = readInjected(true ? "0.9.82-rc.400" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
405
- const builtAt = readInjected(true ? "2026-06-27T13:15:45.410Z" : void 0);
402
+ const commit = readInjected(true ? "c31eb449e8febc28e378bd25394830a96a67594e" : void 0) ?? "unknown";
403
+ const commitShort = readInjected(true ? "c31eb449" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
404
+ const version = readInjected(true ? "0.9.82-rc.401" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
405
+ const builtAt = readInjected(true ? "2026-06-27T16:14:32.200Z" : void 0);
406
406
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
407
407
  return cached;
408
408
  }
@@ -1441,16 +1441,103 @@ function resolveWorktreePath(repoRoot, meshName, branch) {
1441
1441
  const parentDir = path4.dirname(repoRoot);
1442
1442
  return path4.join(parentDir, WORKTREE_DIR_NAME, safeMeshName, safeBranch);
1443
1443
  }
1444
+ async function tryGit(cwd, args) {
1445
+ try {
1446
+ const { stdout, stderr } = await execFileAsync2("git", args, {
1447
+ cwd,
1448
+ encoding: "utf8",
1449
+ timeout: GIT_TIMEOUT_MS,
1450
+ maxBuffer: GIT_MAX_BUFFER,
1451
+ windowsHide: true
1452
+ });
1453
+ return { ok: true, stdout: (stdout || "").trim(), stderr: (stderr || "").trim() };
1454
+ } catch (error) {
1455
+ return {
1456
+ ok: false,
1457
+ stdout: typeof error?.stdout === "string" ? error.stdout.trim() : "",
1458
+ stderr: typeof error?.stderr === "string" ? error.stderr.trim() : error?.message || ""
1459
+ };
1460
+ }
1461
+ }
1462
+ async function resolveWorktreeBaseStartPoint(repoRoot, baseBranch, remote) {
1463
+ const fetchResult = await tryGit(repoRoot, ["fetch", remote, baseBranch]);
1464
+ const fetched = fetchResult.ok;
1465
+ const localRev = await tryGit(repoRoot, ["rev-parse", "--verify", "--quiet", `refs/heads/${baseBranch}`]);
1466
+ const remoteRev = await tryGit(repoRoot, ["rev-parse", "--verify", "--quiet", `refs/remotes/${remote}/${baseBranch}`]);
1467
+ const localSha = localRev.ok && localRev.stdout ? localRev.stdout : void 0;
1468
+ const remoteSha = remoteRev.ok && remoteRev.stdout ? remoteRev.stdout : void 0;
1469
+ const remoteRef = `${remote}/${baseBranch}`;
1470
+ const base = {
1471
+ branch: baseBranch,
1472
+ remote,
1473
+ startRef: baseBranch,
1474
+ fetched,
1475
+ localSha,
1476
+ remoteSha,
1477
+ behindBy: 0,
1478
+ aheadBy: 0,
1479
+ action: "up_to_date"
1480
+ };
1481
+ const fetchWarn = fetched ? "" : ` (warning: git fetch ${remote} ${baseBranch} failed: ${fetchResult.stderr || "unknown error"})`;
1482
+ if (!remoteSha) {
1483
+ return {
1484
+ ...base,
1485
+ action: "no_remote_ref_used_local",
1486
+ ...fetched ? {} : { warning: `Could not fetch ${remoteRef}${fetchWarn}; worktree branched from local ${baseBranch}.` }
1487
+ };
1488
+ }
1489
+ if (!localSha) {
1490
+ return {
1491
+ ...base,
1492
+ startRef: remoteRef,
1493
+ action: "no_local_ref_used_remote"
1494
+ };
1495
+ }
1496
+ if (localSha === remoteSha) {
1497
+ return base;
1498
+ }
1499
+ const localIsAncestor = (await tryGit(repoRoot, ["merge-base", "--is-ancestor", localSha, remoteSha])).ok;
1500
+ const remoteIsAncestor = (await tryGit(repoRoot, ["merge-base", "--is-ancestor", remoteSha, localSha])).ok;
1501
+ const behindBy = Number((await tryGit(repoRoot, ["rev-list", "--count", `${localSha}..${remoteSha}`])).stdout) || 0;
1502
+ const aheadBy = Number((await tryGit(repoRoot, ["rev-list", "--count", `${remoteSha}..${localSha}`])).stdout) || 0;
1503
+ if (localIsAncestor && !remoteIsAncestor) {
1504
+ return {
1505
+ ...base,
1506
+ startRef: remoteRef,
1507
+ behindBy,
1508
+ aheadBy,
1509
+ action: "local_behind_used_remote",
1510
+ warning: `Base node local ${baseBranch} was behind ${remoteRef} by ${behindBy} commit(s); worktree branched from ${remoteRef} (${remoteSha.slice(0, 8)}) instead of stale local ${localSha.slice(0, 8)}.${fetchWarn}`
1511
+ };
1512
+ }
1513
+ if (remoteIsAncestor) {
1514
+ return { ...base, behindBy, aheadBy, action: "local_ahead_used_local" };
1515
+ }
1516
+ return {
1517
+ ...base,
1518
+ behindBy,
1519
+ aheadBy,
1520
+ action: "diverged_used_local",
1521
+ warning: `Base node local ${baseBranch} (${localSha.slice(0, 8)}) has DIVERGED from ${remoteRef} (${remoteSha.slice(0, 8)}): behind ${behindBy}, ahead ${aheadBy}. Worktree branched from local; a rebase onto ${remoteRef} will be required before its push can fast-forward.${fetchWarn}`
1522
+ };
1523
+ }
1444
1524
  async function createWorktree(opts) {
1445
1525
  const { repoRoot, branch, baseBranch, meshName } = opts;
1526
+ const remote = (opts.remote || "origin").trim() || "origin";
1446
1527
  const targetDir = opts.targetDir || resolveWorktreePath(repoRoot, meshName, branch);
1447
1528
  if ((0, import_node_fs2.existsSync)(targetDir)) {
1448
1529
  throw new Error(`Worktree target directory already exists: ${targetDir}`);
1449
1530
  }
1450
1531
  await (0, import_promises3.mkdir)(path4.dirname(targetDir), { recursive: true });
1532
+ let baseSync;
1533
+ let startRef = baseBranch;
1534
+ if (baseBranch && opts.syncBaseFromRemote !== false) {
1535
+ baseSync = await resolveWorktreeBaseStartPoint(repoRoot, baseBranch, remote);
1536
+ startRef = baseSync.startRef;
1537
+ }
1451
1538
  const args = ["worktree", "add", targetDir, "-b", branch];
1452
- if (baseBranch) {
1453
- args.push(baseBranch);
1539
+ if (startRef) {
1540
+ args.push(startRef);
1454
1541
  }
1455
1542
  try {
1456
1543
  await execFileAsync2("git", args, {
@@ -1473,7 +1560,8 @@ async function createWorktree(opts) {
1473
1560
  return {
1474
1561
  success: true,
1475
1562
  worktreePath: targetDir,
1476
- branch
1563
+ branch,
1564
+ ...baseSync ? { baseSync } : {}
1477
1565
  };
1478
1566
  }
1479
1567
  async function removeWorktree(repoRoot, worktreePath, opts = {}) {
@@ -5654,11 +5742,14 @@ var init_mesh_runtime_store = __esm({
5654
5742
  }
5655
5743
  /** A node may only execute one write task at a time (worktree isolation). */
5656
5744
  hasActiveNodeAssignment(meshId, nodeId) {
5745
+ const nodeIdForms = expandDaemonIdForms(nodeId);
5746
+ if (nodeIdForms.length === 0) return false;
5747
+ const placeholders = nodeIdForms.map(() => "?").join(", ");
5657
5748
  const row = this.db.prepare(`
5658
5749
  SELECT 1 FROM mesh_queue
5659
- WHERE mesh_id = ? AND status = 'assigned' AND assigned_node_id = ?
5750
+ WHERE mesh_id = ? AND status = 'assigned' AND assigned_node_id IN (${placeholders})
5660
5751
  LIMIT 1
5661
- `).get(meshId, nodeId);
5752
+ `).get(meshId, ...nodeIdForms);
5662
5753
  return row !== void 0;
5663
5754
  }
5664
5755
  /**
@@ -11874,7 +11965,14 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
11874
11965
  targetSessionId: sessionId,
11875
11966
  cliType: providerType,
11876
11967
  action: "send_chat",
11877
- message: task.message
11968
+ message: task.message,
11969
+ meshContext: {
11970
+ meshId,
11971
+ nodeId,
11972
+ taskId: task.id,
11973
+ ...readNonEmptyString2(loadConfig().machineId) ? { coordinatorDaemonId: readNonEmptyString2(loadConfig().machineId) } : {},
11974
+ ...readNonEmptyString2(task.sourceCoordinatorSessionId) ? { coordinatorSessionId: readNonEmptyString2(task.sourceCoordinatorSessionId) } : {}
11975
+ }
11878
11976
  }),
11879
11977
  {
11880
11978
  meshId,
@@ -19544,6 +19642,15 @@ var init_cli_state_engine = __esm({
19544
19642
  currentStatus = "starting";
19545
19643
  isWaitingForResponse = false;
19546
19644
  currentTurnScope = null;
19645
+ // ARCH-REFACTOR R1 (per-turn task identity): the mesh taskId bound to the most
19646
+ // recently STARTED turn. Unlike currentTurnScope (nulled the moment the turn
19647
+ // settles, before the completion event is even built), this persists past
19648
+ // completion and is only overwritten when the NEXT turn starts. That window is
19649
+ // exactly what the completion path needs: when a turn settles to idle, this still
19650
+ // holds THAT turn's taskId (the next task's turn cannot have started yet — it is
19651
+ // queued in pendingOutbound and only flushed asynchronously after idle), so the
19652
+ // completion event carries the correct id instead of the racy session scalar.
19653
+ currentTurnTaskId = null;
19547
19654
  activeModal = null;
19548
19655
  // ── Approval ─────────────────────────────────────
19549
19656
  lastApprovalResolvedAt = 0;
@@ -19653,6 +19760,7 @@ var init_cli_state_engine = __esm({
19653
19760
  this.finishRetryCount = 0;
19654
19761
  this.clearIdleFinishCandidate("send_message");
19655
19762
  this.currentTurnScope = turnScope;
19763
+ this.currentTurnTaskId = typeof turnScope.taskId === "string" && turnScope.taskId.trim() ? turnScope.taskId : null;
19656
19764
  this.responseEpoch += 1;
19657
19765
  }
19658
19766
  /** Called when PTY exits */
@@ -21605,15 +21713,18 @@ ${lastSnapshot}`;
21605
21713
  }
21606
21714
  async sendMessage(text, options = {}) {
21607
21715
  if (options.force === true) {
21608
- await this.forceSendMessage(text);
21716
+ await this.forceSendMessage(text, options.meshTaskId);
21609
21717
  return;
21610
21718
  }
21611
- await this.sendMessageNow(text, true);
21719
+ await this.sendMessageNow(text, true, options.meshTaskId);
21612
21720
  }
21613
- async forceSendMessage(text) {
21721
+ async forceSendMessage(text, meshTaskId) {
21614
21722
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
21615
21723
  const content = String(text || "");
21616
21724
  if (!content.trim()) return;
21725
+ if (typeof meshTaskId === "string" && meshTaskId.trim()) {
21726
+ this.engine.currentTurnTaskId = meshTaskId;
21727
+ }
21617
21728
  if (this.engine.currentStatus === "waiting_approval" || this.engine.hasActionableApproval()) {
21618
21729
  LOG.info("CLI", `[${this.cliType}] force-send held \u2014 session parked on approval modal (status=${this.engine.currentStatus})`);
21619
21730
  return;
@@ -21626,7 +21737,7 @@ ${lastSnapshot}`;
21626
21737
  async waitForForceSubmitSettle() {
21627
21738
  await new Promise((resolve24) => setTimeout(resolve24, FORCE_SUBMIT_SETTLE_MS));
21628
21739
  }
21629
- enqueuePendingOutboundMessage(text, reason) {
21740
+ enqueuePendingOutboundMessage(text, reason, meshTaskId) {
21630
21741
  const content = String(text || "");
21631
21742
  const duplicate = this.pendingOutboundQueue.some((message2) => message2.content === content);
21632
21743
  if (duplicate) {
@@ -21638,7 +21749,8 @@ ${lastSnapshot}`;
21638
21749
  role: "user",
21639
21750
  content,
21640
21751
  queuedAt,
21641
- source: "sendMessage"
21752
+ source: "sendMessage",
21753
+ ...typeof meshTaskId === "string" && meshTaskId.trim() ? { meshTaskId } : {}
21642
21754
  };
21643
21755
  this.pendingOutboundQueue.push(message);
21644
21756
  LOG.info("CLI", `[${this.cliType}] queued outbound message while busy (${reason}); queue=${this.pendingOutboundQueue.length}`);
@@ -21681,7 +21793,7 @@ ${lastSnapshot}`;
21681
21793
  if (this.engine.currentStatus !== "idle" || this.engine.isWaitingForResponse || this.engine.hasActionableApproval()) break;
21682
21794
  const next = this.pendingOutboundQueue[0];
21683
21795
  try {
21684
- await this.sendMessageNow(next.content, false);
21796
+ await this.sendMessageNow(next.content, false, next.meshTaskId);
21685
21797
  this.pendingOutboundQueue.shift();
21686
21798
  this.onStatusChange?.();
21687
21799
  } catch (error) {
@@ -21694,7 +21806,7 @@ ${lastSnapshot}`;
21694
21806
  this.pendingOutboundFlushInFlight = false;
21695
21807
  }
21696
21808
  }
21697
- async sendMessageNow(text, allowQueue) {
21809
+ async sendMessageNow(text, allowQueue, meshTaskId) {
21698
21810
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
21699
21811
  const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
21700
21812
  const allowInterventionPrompt = allowInputDuringGeneration && this.engine.isWaitingForResponse && !this.engine.hasActionableApproval();
@@ -21714,7 +21826,7 @@ ${lastSnapshot}`;
21714
21826
  })() : null;
21715
21827
  const queueReason = this.shouldQueuePendingOutboundMessage(parsedStatusBeforeSend);
21716
21828
  if (allowQueue && queueReason) {
21717
- this.enqueuePendingOutboundMessage(text, queueReason);
21829
+ this.enqueuePendingOutboundMessage(text, queueReason, meshTaskId);
21718
21830
  return;
21719
21831
  }
21720
21832
  if (!allowInterventionPrompt) {
@@ -21731,7 +21843,7 @@ ${lastSnapshot}`;
21731
21843
  }
21732
21844
  if (!this.ready) {
21733
21845
  if (allowQueue) {
21734
- this.enqueuePendingOutboundMessage(text, "not_ready_pending_prompt");
21846
+ this.enqueuePendingOutboundMessage(text, "not_ready_pending_prompt", meshTaskId);
21735
21847
  return;
21736
21848
  }
21737
21849
  throw new Error(`${this.cliName} not ready (status: ${this.engine.currentStatus})`);
@@ -21745,7 +21857,7 @@ ${lastSnapshot}`;
21745
21857
  const terminalLooksIdle = this.engine.currentStatus === "idle" && this.runDetectStatus(this.recentOutputBuffer) === "idle" && !this.engine.isWaitingForResponse && !this.engine.currentTurnScope && !this.engine.hasActionableApproval() && !parsedHasActionableModal;
21746
21858
  if (!terminalLooksIdle) {
21747
21859
  if (allowQueue) {
21748
- this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`);
21860
+ this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`, meshTaskId);
21749
21861
  return;
21750
21862
  }
21751
21863
  throw new Error(`${this.cliName} is still processing the previous prompt`);
@@ -21755,7 +21867,7 @@ ${lastSnapshot}`;
21755
21867
  const snap = this.getSnapshot();
21756
21868
  if (!this.engine.clearStaleIdleResponseGuard("send_message_guard", snap) && !this.engine.clearParsedIdleResponseGuard("send_message_parsed_idle_guard", parsedStatusBeforeSend, snap)) {
21757
21869
  if (allowQueue) {
21758
- this.enqueuePendingOutboundMessage(text, "waiting_for_response");
21870
+ this.enqueuePendingOutboundMessage(text, "waiting_for_response", meshTaskId);
21759
21871
  return;
21760
21872
  }
21761
21873
  throw new Error(`${this.cliName} is still processing the previous prompt`);
@@ -21766,7 +21878,11 @@ ${lastSnapshot}`;
21766
21878
  prompt: text,
21767
21879
  startedAt: Date.now(),
21768
21880
  bufferStart: this.accumulatedBuffer.length,
21769
- rawBufferStart: this.accumulatedRawBuffer.length
21881
+ rawBufferStart: this.accumulatedRawBuffer.length,
21882
+ // ARCH-REFACTOR R1: bind this turn to its mesh task. engine.onTurnStarted
21883
+ // copies this into currentTurnTaskId so the turn's completion event carries
21884
+ // the right id even if a later task overwrites the session scalar meanwhile.
21885
+ ...typeof meshTaskId === "string" && meshTaskId.trim() ? { taskId: meshTaskId } : {}
21770
21886
  };
21771
21887
  LOG.info("CLI", `[${this.cliType}] sendMessage turn scope buffer=${turnScope.bufferStart} raw=${turnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
21772
21888
  if (this.submitRetryTimer) {
@@ -22116,6 +22232,13 @@ ${lastSnapshot}`;
22116
22232
  set currentTurnScope(v) {
22117
22233
  this.engine.currentTurnScope = v;
22118
22234
  }
22235
+ // ARCH-REFACTOR R1: the mesh taskId bound to the most recently started turn,
22236
+ // surviving past turn settle until the next turn starts. The provider instance
22237
+ // reads this when stamping completion events so they carry the completing turn's
22238
+ // task rather than the racy last-write-wins session scalar.
22239
+ get currentTurnTaskId() {
22240
+ return this.engine.currentTurnTaskId;
22241
+ }
22119
22242
  get responseEpoch() {
22120
22243
  return this.engine.responseEpoch;
22121
22244
  }
@@ -40705,11 +40828,28 @@ var CliProviderInstance = class _CliProviderInstance {
40705
40828
  isMeshWorkerSession() {
40706
40829
  return !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.meshNodeId || this.settings.launchedByCoordinator);
40707
40830
  }
40831
+ /**
40832
+ * ARCH-REFACTOR R1: the taskId to attribute the CURRENTLY-completing turn to.
40833
+ * Prefers the per-turn binding (engine.currentTurnTaskId, set when the turn was
40834
+ * submitted and surviving until the next turn starts) over the last-write-wins
40835
+ * session scalar (settings.meshActiveTaskId). The scalar is retained only as a
40836
+ * backward-compat alias for the "current/last assignment" and is the source of the
40837
+ * NOTIF-MISDELIVER / TASK-MSG-MISROUTE race: a second task attaching before this
40838
+ * turn completes overwrites it. Returns undefined for a non-task ad-hoc turn.
40839
+ */
40840
+ completingTurnTaskId() {
40841
+ const turnTaskId = this.adapter?.currentTurnTaskId;
40842
+ if (typeof turnTaskId === "string" && turnTaskId.trim()) return turnTaskId;
40843
+ const scalar = this.settings.meshActiveTaskId;
40844
+ return typeof scalar === "string" && scalar.trim() ? scalar : void 0;
40845
+ }
40708
40846
  // EVTTRACE correlation context for this session's completion lifecycle. taskId is
40709
40847
  // the primary grep anchor; instanceId is the session fallback.
40710
40848
  meshTraceCtx(event = "agent:generating_completed") {
40711
40849
  return {
40712
- taskId: this.settings.meshActiveTaskId,
40850
+ // ARCH-REFACTOR R1: trace the per-turn taskId (falling back to the scalar) so
40851
+ // EvtTrace anchors on the same id the completion event actually carries.
40852
+ taskId: this.completingTurnTaskId(),
40713
40853
  sessionId: this.instanceId,
40714
40854
  nodeId: this.settings.meshNodeId,
40715
40855
  meshId: this.settings.meshNodeFor,
@@ -40768,6 +40908,8 @@ var CliProviderInstance = class _CliProviderInstance {
40768
40908
  chatTitle: pending.chatTitle,
40769
40909
  duration: pending.duration,
40770
40910
  timestamp: pending.timestamp,
40911
+ // ARCH-REFACTOR R1: attribute to the turn captured at idle-transition.
40912
+ ...pending.taskId ? { taskId: pending.taskId } : {},
40771
40913
  // When finalization is forced past the timeout on a `parsed_status:` block
40772
40914
  // (the parser never confirmed a final assistant turn) we previously rode an
40773
40915
  // empty `finalSummary` unconditionally. That empty value propagates to the
@@ -40793,6 +40935,8 @@ var CliProviderInstance = class _CliProviderInstance {
40793
40935
  chatTitle: pending.chatTitle,
40794
40936
  duration: pending.duration,
40795
40937
  timestamp: pending.timestamp,
40938
+ // ARCH-REFACTOR R1: attribute to the turn captured at idle-transition.
40939
+ ...pending.taskId ? { taskId: pending.taskId } : {},
40796
40940
  finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages)
40797
40941
  });
40798
40942
  this.completedDebouncePending = null;
@@ -41069,7 +41213,11 @@ var CliProviderInstance = class _CliProviderInstance {
41069
41213
  duration,
41070
41214
  timestamp: now,
41071
41215
  firstObservedAt: now,
41072
- previousStatus: this.lastStatus
41216
+ previousStatus: this.lastStatus,
41217
+ // ARCH-REFACTOR R1: snapshot the completing turn's taskId NOW (sync),
41218
+ // before any follow-up task's flush can start a new turn and move
41219
+ // engine.currentTurnTaskId.
41220
+ ...this.completingTurnTaskId() ? { taskId: this.completingTurnTaskId() } : {}
41073
41221
  };
41074
41222
  const ownsExternalHistory = !!this.adapter?.chatMessagesOwnedExternally;
41075
41223
  const meshWorkerSession = this.isMeshWorkerSession();
@@ -41172,10 +41320,11 @@ var CliProviderInstance = class _CliProviderInstance {
41172
41320
  workspace: typeof event.workspace === "string" && event.workspace.trim() ? event.workspace : this.workingDir,
41173
41321
  providerSessionId: typeof event.providerSessionId === "string" && event.providerSessionId.trim() ? event.providerSessionId : this.providerSessionId
41174
41322
  };
41175
- if (this.isMeshWorkerSession() && this.settings.meshActiveTaskId) {
41323
+ if (this.isMeshWorkerSession()) {
41176
41324
  const existingTaskId = typeof enrichedEvent.taskId === "string" && enrichedEvent.taskId.trim() ? enrichedEvent.taskId : void 0;
41177
41325
  if (!existingTaskId) {
41178
- enrichedEvent.taskId = this.settings.meshActiveTaskId;
41326
+ const resolved = this.completingTurnTaskId();
41327
+ if (resolved) enrichedEvent.taskId = resolved;
41179
41328
  }
41180
41329
  }
41181
41330
  if (this.context?.emitProviderEvent) {
@@ -44055,11 +44204,15 @@ Run 'adhdev doctor' for detailed diagnostics.`
44055
44204
  }
44056
44205
  const message = input.textFallback;
44057
44206
  if (!message) throw new Error("message required for send_chat");
44207
+ const meshTaskId = meshContext && typeof meshContext === "object" && typeof meshContext.taskId === "string" && meshContext.taskId.trim() ? meshContext.taskId : void 0;
44058
44208
  const forceSend = args?.force === true || args?.forceSend === true;
44059
44209
  if (forceSend && typeof adapter.forceSendMessage === "function") {
44060
- await adapter.forceSendMessage(message);
44210
+ if (meshTaskId) await adapter.forceSendMessage(message, meshTaskId);
44211
+ else await adapter.forceSendMessage(message);
44061
44212
  } else if (forceSend) {
44062
- await adapter.sendMessage(message, { force: true });
44213
+ await adapter.sendMessage(message, meshTaskId ? { force: true, meshTaskId } : { force: true });
44214
+ } else if (meshTaskId) {
44215
+ await adapter.sendMessage(message, { meshTaskId });
44063
44216
  } else {
44064
44217
  await adapter.sendMessage(message);
44065
44218
  }
@@ -48587,6 +48740,11 @@ var meshCrudHandlers = {
48587
48740
  baseBranch,
48588
48741
  meshName: mesh.name
48589
48742
  });
48743
+ if (result.baseSync?.warning) {
48744
+ console.warn(`[mesh] clone_mesh_node base sync (${result.baseSync.action}): ${result.baseSync.warning}`);
48745
+ } else if (result.baseSync && result.baseSync.action !== "up_to_date") {
48746
+ console.log(`[mesh] clone_mesh_node base sync: ${result.baseSync.action} (startRef=${result.baseSync.startRef})`);
48747
+ }
48590
48748
  let node;
48591
48749
  if (meshRecord.inline) {
48592
48750
  const { randomUUID: randomUUID15 } = await import("crypto");
@@ -48777,6 +48935,8 @@ var meshCrudHandlers = {
48777
48935
  node,
48778
48936
  worktreePath: result.worktreePath,
48779
48937
  branch: result.branch,
48938
+ ...result.baseSync ? { baseSync: result.baseSync } : {},
48939
+ ...result.baseSync?.warning ? { baseStaleWarning: result.baseSync.warning } : {},
48780
48940
  worktreeBootstrap: runningBootstrapState,
48781
48941
  worktreeSetup: {
48782
48942
  status: "running",
@@ -48792,6 +48952,8 @@ var meshCrudHandlers = {
48792
48952
  node,
48793
48953
  worktreePath: result.worktreePath,
48794
48954
  branch: result.branch,
48955
+ ...result.baseSync ? { baseSync: result.baseSync } : {},
48956
+ ...result.baseSync?.warning ? { baseStaleWarning: result.baseSync.warning } : {},
48795
48957
  submodulesInitialized,
48796
48958
  worktreeBootstrap: bootstrapState
48797
48959
  };