@adhdev/daemon-core 0.9.82-rc.366 → 0.9.82-rc.368

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.
@@ -6,6 +6,33 @@ import type { CommandResult, CommandHelpers } from './handler.js';
6
6
  import { type InputEnvelope } from '../providers/contracts.js';
7
7
  export declare const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25000;
8
8
  export declare function buildSendInputSignature(input: InputEnvelope): string;
9
+ /**
10
+ * read_chat node scope verdict. One physical daemon hosts a base node plus several
11
+ * worktree nodes; mesh_read_chat always dispatches read_chat with the requested
12
+ * node's workspace (`args.workspace`). When the resolved target session actually
13
+ * lives in a DIFFERENT worktree, returning its transcript — or worse, letting the
14
+ * native-history-by-workspace fallback splice sibling worktree turns into the
15
+ * reply — makes the coordinator believe one session received every worktree's
16
+ * work. This guard refuses a CONFIRMED cross-workspace read instead of mixing.
17
+ *
18
+ * Conservative by design (mirrors the WTCLAIM fix-B "unknown → allow" rule): only
19
+ * a session id that resolves to a known workspace which is unequal to a known
20
+ * intended workspace blocks. When either side is unknown — no targetSessionId, no
21
+ * args.workspace, an unregistered session, the coordinator self-session, or a
22
+ * plain dashboard read that never passes a node workspace — the read proceeds
23
+ * untouched, so base-node and same-daemon coordinator reads never regress.
24
+ */
25
+ export declare function evaluateReadChatNodeWorkspaceScope(args: {
26
+ targetSessionId?: string;
27
+ intendedWorkspace?: string;
28
+ sessionWorkspace?: string;
29
+ }): {
30
+ scoped: false;
31
+ } | {
32
+ scoped: true;
33
+ intended: string;
34
+ actual: string;
35
+ };
9
36
  interface DebugSanitizeOptions {
10
37
  maxDepth?: number;
11
38
  maxArrayLength?: number;
@@ -86,6 +86,7 @@ export declare function finalizeMeshNodeStatus(args: {
86
86
  }): void;
87
87
  export declare const MESH_DIRECT_PROBE_TIMEOUT_MS: number;
88
88
  export declare const MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS: number;
89
+ export declare const MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS: number;
89
90
  /**
90
91
  * De-duplicates and rate-limits per-peer git_status probes so a single mesh
91
92
  * refresh — or a burst of refreshes from the dashboard auto-retry loop — cannot
@@ -116,6 +117,37 @@ export declare class MeshGitProbeCache {
116
117
  */
117
118
  probe(daemonId: string, workspace: string, probe: () => Promise<Record<string, unknown> | null>): Promise<Record<string, unknown> | null>;
118
119
  }
120
+ /**
121
+ * Await `work` under a warmup-aware deadline so a cold-open DataChannel handshake
122
+ * is NOT charged against the command response budget — the root cause of the
123
+ * "first mesh probe to a cold peer false-times-out, the warm retry succeeds"
124
+ * signature. Two budgets, switched by the live peer connection state:
125
+ *
126
+ * - While `isConnected()` returns false the peer's channel is still opening; the
127
+ * cold-open `connectTimeoutMs` budget applies. This phase is deliberately
128
+ * generous because a TURN-relayed cross-machine handshake legitimately needs
129
+ * many seconds — but a genuine connect *failure* is surfaced by `work`
130
+ * rejecting on its own (the mesh manager fails the peer the instant its
131
+ * PeerConnection state goes terminal), so a real failure is never masked for
132
+ * the whole window.
133
+ * - The first time `isConnected()` returns true the channel is warm; from that
134
+ * instant the tight `responseTimeoutMs` governs how long the handler may take.
135
+ * Warm-channel callers therefore see behavior identical to the old single
136
+ * `Promise.race(work, responseTimeoutMs)`.
137
+ *
138
+ * Rejects with `Error('timeout')` when either budget is exhausted, mirroring the
139
+ * previous single-race contract. Pure except for timers + the injected
140
+ * `isConnected` probe, so it is unit-testable under fake timers without any real
141
+ * WebRTC. When no connection getter is wired `isConnected` should be `() => true`
142
+ * (the caller's choice) so the response deadline governs from t0 — the legacy
143
+ * single-budget behavior, never a combined connect+response window.
144
+ */
145
+ export declare function awaitWithWarmupDeadline<T>(work: Promise<T>, opts: {
146
+ isConnected: () => boolean;
147
+ connectTimeoutMs: number;
148
+ responseTimeoutMs: number;
149
+ pollIntervalMs?: number;
150
+ }): Promise<T>;
119
151
  /**
120
152
  * Probe a remote peer's git_status with a bounded retry budget, but only while
121
153
  * the peer is reported `connected`. A single slow (often TURN-relayed) peer can
@@ -136,6 +168,8 @@ export declare function probeRemoteMeshGitStatusWithRetry(args: {
136
168
  timeoutMs: number;
137
169
  /** Per-attempt timeout for retries (attempts > 0); defaults to timeoutMs. */
138
170
  retryTimeoutMs?: number;
171
+ /** Cold-open warmup budget per attempt; defaults to MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS. */
172
+ connectTimeoutMs?: number;
139
173
  getConnection?: (daemonId: string) => Record<string, unknown> | null;
140
174
  onConnection?: (connection: Record<string, unknown>) => void;
141
175
  }): Promise<Record<string, unknown> | null>;
package/dist/index.js CHANGED
@@ -316,10 +316,10 @@ function readInjected(value) {
316
316
  }
317
317
  function getDaemonBuildInfo() {
318
318
  if (cached) return cached;
319
- const commit = readInjected(true ? "c2224ab0c9d05e85b4cbf88e4fc696f3733ec397" : void 0) ?? "unknown";
320
- const commitShort = readInjected(true ? "c2224ab0" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
321
- const version = readInjected(true ? "0.9.82-rc.366" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
322
- const builtAt = readInjected(true ? "2026-06-24T02:22:36.214Z" : void 0);
319
+ const commit = readInjected(true ? "8cb6cfc5399f55c6b625095bacc9a27e1741df17" : void 0) ?? "unknown";
320
+ const commitShort = readInjected(true ? "8cb6cfc5" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
321
+ const version = readInjected(true ? "0.9.82-rc.368" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
322
+ const builtAt = readInjected(true ? "2026-06-24T06:14:32.255Z" : void 0);
323
323
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
324
324
  return cached;
325
325
  }
@@ -2903,6 +2903,16 @@ function meshNodeIdMatches(node, candidateId) {
2903
2903
  if (!trimmed) return false;
2904
2904
  return normalizeMeshNodeId(node) === trimmed;
2905
2905
  }
2906
+ function normalizeMeshWorkspaceForCompare(dir) {
2907
+ if (typeof dir !== "string") return "";
2908
+ return dir.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
2909
+ }
2910
+ function meshWorkspacesEquivalent(a, b) {
2911
+ const left = normalizeMeshWorkspaceForCompare(a);
2912
+ const right = normalizeMeshWorkspaceForCompare(b);
2913
+ if (!left || !right) return false;
2914
+ return left === right;
2915
+ }
2906
2916
  function machineCoreFromDaemonId(id) {
2907
2917
  const trimmed = readString3(id);
2908
2918
  if (!trimmed) return void 0;
@@ -12856,21 +12866,29 @@ function deliverTaskToSession(dispatchThunk, ctx) {
12856
12866
  }
12857
12867
  });
12858
12868
  }
12859
- function normalizeMeshWorkspaceForCompare(dir) {
12860
- if (typeof dir !== "string") return "";
12861
- return dir.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
12862
- }
12863
12869
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
12864
12870
  const mesh = getMeshWithCache(components, meshId);
12865
12871
  const node = mesh?.nodes.find((n) => readMeshNodeId(n) === nodeId);
12866
12872
  const localClaimAdapter = components.cliManager?.adapters?.get(sessionId);
12867
- if (localClaimAdapter) {
12868
- const sessionWorkspace = normalizeMeshWorkspaceForCompare(localClaimAdapter.workingDir);
12869
- const nodeWorkspace = normalizeMeshWorkspaceForCompare(readNonEmptyString2(node?.workspace));
12870
- if (sessionWorkspace && nodeWorkspace && sessionWorkspace !== nodeWorkspace) {
12871
- LOG.info("MeshQueue", `WTCLAIM: refusing claim for node ${nodeId} (${sessionId}) \u2014 session workspace "${sessionWorkspace}" \u2260 node workspace "${nodeWorkspace}" (cross-workspace dispatch blocked)`);
12873
+ let claimInstanceWorkspace = "";
12874
+ let claimStampedNodeId = "";
12875
+ try {
12876
+ const claimState = components.instanceManager?.getInstance?.(sessionId)?.getState?.();
12877
+ claimInstanceWorkspace = readNonEmptyString2(claimState?.workspace);
12878
+ const claimSettings = claimState?.settings || {};
12879
+ claimStampedNodeId = readNonEmptyString2(claimSettings.meshNodeId);
12880
+ } catch {
12881
+ }
12882
+ const nodeWorkspaceRaw = readNonEmptyString2(node?.workspace);
12883
+ const sessionWorkspaceRaw = readNonEmptyString2(localClaimAdapter?.workingDir) || claimInstanceWorkspace;
12884
+ if (claimStampedNodeId && nodeId) {
12885
+ if (!meshNodeIdMatches({ id: claimStampedNodeId }, nodeId)) {
12886
+ LOG.info("MeshQueue", `WTDISPATCH: refusing claim for node ${nodeId} (${sessionId}) \u2014 session is bound to node "${claimStampedNodeId}" (cross-node claim blocked)`);
12872
12887
  return false;
12873
12888
  }
12889
+ } else if (sessionWorkspaceRaw && nodeWorkspaceRaw && !meshWorkspacesEquivalent(sessionWorkspaceRaw, nodeWorkspaceRaw)) {
12890
+ LOG.info("MeshQueue", `WTCLAIM: refusing claim for node ${nodeId} (${sessionId}) \u2014 session workspace "${normalizeMeshWorkspaceForCompare(sessionWorkspaceRaw)}" \u2260 node workspace "${normalizeMeshWorkspaceForCompare(nodeWorkspaceRaw)}" (cross-workspace dispatch blocked)`);
12891
+ return false;
12874
12892
  }
12875
12893
  const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
12876
12894
  const providerMaxParallel = resolveProviderMaxParallel(node?.policy, providerType);
@@ -13711,7 +13729,10 @@ function injectMeshSystemMessage(components, args) {
13711
13729
  if (terminal?.kind === "task_completed" && !sessionHasActiveAssignment(args.meshId, eventSessionId)) {
13712
13730
  const newDispatchAfterTerminal = hasDispatchAfterTerminal(args.meshId, eventSessionId, terminal.id);
13713
13731
  const supersedesWeakTerminal = isWeakTerminalLedgerPayload(terminal.payload) && isGenuineCompletionEvidence(args.metadataEvent);
13714
- if (!newDispatchAfterTerminal && !supersedesWeakTerminal) {
13732
+ const terminalTaskId = readNonEmptyString2(terminal.payload.taskId);
13733
+ const eventTaskId = readNonEmptyString2(args.metadataEvent.taskId);
13734
+ const distinctTaskCompletion = !!eventTaskId && !!terminalTaskId && eventTaskId !== terminalTaskId;
13735
+ if (!newDispatchAfterTerminal && !supersedesWeakTerminal && !distinctTaskCompletion) {
13715
13736
  const terminalProviderSessionId = readNonEmptyString2(terminal.payload.providerSessionId);
13716
13737
  const terminalFinalSummary = readNonEmptyString2(terminal.payload.finalSummary);
13717
13738
  const eventProviderSessionId = readNonEmptyString2(args.metadataEvent.providerSessionId);
@@ -14145,6 +14166,25 @@ function handleMeshForwardEvent(components, payload) {
14145
14166
  metadataEvent: buildRelayMetadataEvent(payload)
14146
14167
  });
14147
14168
  }
14169
+ function enqueueCoordinatorForwardPush(coordinatorDaemonId, run) {
14170
+ let lane = coordinatorForwardLanes.get(coordinatorDaemonId);
14171
+ if (!lane) {
14172
+ lane = { tail: Promise.resolve(), depth: 0 };
14173
+ coordinatorForwardLanes.set(coordinatorDaemonId, lane);
14174
+ }
14175
+ const wasIdle = lane.depth === 0;
14176
+ lane.depth += 1;
14177
+ const dec = () => {
14178
+ lane.depth -= 1;
14179
+ };
14180
+ if (wasIdle) {
14181
+ lane.tail = Promise.resolve(run()).catch(() => {
14182
+ }).then(dec, dec);
14183
+ } else {
14184
+ lane.tail = lane.tail.then(() => run()).catch(() => {
14185
+ }).then(dec, dec);
14186
+ }
14187
+ }
14148
14188
  function forwardUnresolvedDelegateEvent(components, routing, event) {
14149
14189
  const coordinatorDaemonId = readNonEmptyString2(routing.coordinatorDaemonId);
14150
14190
  if (!coordinatorDaemonId) return false;
@@ -14157,6 +14197,16 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
14157
14197
  nodeId: readNonEmptyString2(routing.nodeId) || readNonEmptyString2(event.meshNodeId) || void 0,
14158
14198
  workspace: readNonEmptyString2(routing.workspace) || readNonEmptyString2(event.workspace) || void 0
14159
14199
  };
14200
+ const selfDaemonIds = resolveCoordinatorDrainDaemonIds(components);
14201
+ if (selfDaemonIds.some((self) => daemonIdsEquivalent(self, coordinatorDaemonId))) {
14202
+ try {
14203
+ handleMeshForwardEvent(components, payload);
14204
+ LOG.info("MeshEvents", `Self-addressed unresolved-delegate ${eventName} routed via local router (coordinator ${coordinatorDaemonId} is self) \u2014 outbox skipped`);
14205
+ } catch (e) {
14206
+ LOG.warn("MeshEvents", `Local route of self-addressed unresolved-delegate ${eventName} failed: ${e?.message || e}`);
14207
+ }
14208
+ return true;
14209
+ }
14160
14210
  const persisted = enqueueUnresolvedDelegateForward(coordinatorDaemonId, eventName, payload);
14161
14211
  const fwdTraceCtx = {
14162
14212
  taskId: payload.taskId,
@@ -14166,7 +14216,8 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
14166
14216
  };
14167
14217
  traceMeshEventStage("outbox_enqueue", fwdTraceCtx, `coordinatorDaemon=${coordinatorDaemonId} meshId=absent`);
14168
14218
  traceMeshEventStage("forward_send", fwdTraceCtx, "immediate push");
14169
- Promise.resolve(components.dispatchMeshCommand(coordinatorDaemonId, "mesh_forward_event", payload)).then((result) => {
14219
+ const dispatchMeshCommand = components.dispatchMeshCommand;
14220
+ enqueueCoordinatorForwardPush(coordinatorDaemonId, () => Promise.resolve(dispatchMeshCommand(coordinatorDaemonId, "mesh_forward_event", payload)).then((result) => {
14170
14221
  if (result && result.success === false) {
14171
14222
  LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued for retry`);
14172
14223
  traceMeshEventDrop("immediate_forward_rejected", fwdTraceCtx, readNonEmptyString2(result.error) || "no reason");
@@ -14175,7 +14226,7 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
14175
14226
  if (persisted) ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload);
14176
14227
  }).catch((e) => {
14177
14228
  LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} failed: ${e?.message || e} \u2014 left queued for retry`);
14178
- });
14229
+ }));
14179
14230
  LOG.info("MeshEvents", `Durably forwarded ${eventName} for unresolved-mesh worker at ${routing.workspace || "(no workspace)"} to coordinator daemon ${coordinatorDaemonId}`);
14180
14231
  return true;
14181
14232
  }
@@ -14258,7 +14309,7 @@ function setupMeshEventForwarding(components) {
14258
14309
  });
14259
14310
  });
14260
14311
  }
14261
- var import_fs13, REMOTE_IDLE_SESSION_TTL_MS, meshByWorkspaceCache, MESH_WORKSPACE_CACHE_TTL_MS, IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, RECENT_COMPLETION_FINGERPRINT_TTL_MS, DISPATCH_CONFIRM_TIMEOUT_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, AUTO_LAUNCH_AWAIT_CLAIM_MS, lastAutoLaunchLedgerKey, AUTO_LAUNCH_LEDGER_DEDUP_MAX, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, MESH_FORCE_INJECT_EVENTS;
14312
+ var import_fs13, REMOTE_IDLE_SESSION_TTL_MS, meshByWorkspaceCache, MESH_WORKSPACE_CACHE_TTL_MS, IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, RECENT_COMPLETION_FINGERPRINT_TTL_MS, DISPATCH_CONFIRM_TIMEOUT_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, AUTO_LAUNCH_AWAIT_CLAIM_MS, lastAutoLaunchLedgerKey, AUTO_LAUNCH_LEDGER_DEDUP_MAX, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, MESH_FORCE_INJECT_EVENTS, coordinatorForwardLanes;
14262
14313
  var init_mesh_events_coordinator = __esm({
14263
14314
  "src/mesh/mesh-events-coordinator.ts"() {
14264
14315
  "use strict";
@@ -14323,6 +14374,7 @@ var init_mesh_events_coordinator = __esm({
14323
14374
  "worktree_bootstrap_complete",
14324
14375
  "worktree_bootstrap_failed"
14325
14376
  ]);
14377
+ coordinatorForwardLanes = /* @__PURE__ */ new Map();
14326
14378
  }
14327
14379
  });
14328
14380
 
@@ -14665,6 +14717,8 @@ async function retryUnresolvedDelegateForwards(components) {
14665
14717
  expireStaleUnresolvedDelegateForwards();
14666
14718
  const entries = peekUnresolvedDelegateForwards();
14667
14719
  if (entries.length === 0) return;
14720
+ const selfIds = resolveCoordinatorDaemonIds(components);
14721
+ const isSelfCoordinatorId = (id) => selfIds.some((self) => daemonIdsEquivalent(self, id));
14668
14722
  for (const entry of entries) {
14669
14723
  const entryTraceCtx = {
14670
14724
  taskId: entry.payload.taskId,
@@ -14672,6 +14726,23 @@ async function retryUnresolvedDelegateForwards(components) {
14672
14726
  nodeId: readNonEmptyString2(entry.payload.nodeId),
14673
14727
  event: readNonEmptyString2(entry.payload.event)
14674
14728
  };
14729
+ if (isSelfCoordinatorId(entry.coordinatorDaemonId)) {
14730
+ let localResult;
14731
+ try {
14732
+ traceMeshEventStage("forward_send", entryTraceCtx, `self \u2192 local router (${entry.coordinatorDaemonId})`);
14733
+ localResult = handleMeshForwardEvent(components, entry.payload);
14734
+ } catch (e) {
14735
+ LOG.warn("MeshReconcile", `Local route of self-addressed forward to ${entry.coordinatorDaemonId} threw: ${e?.message || e} \u2014 draining anyway to break the retry loop`);
14736
+ }
14737
+ ackUnresolvedDelegateForward(entry.id);
14738
+ if (localResult && localResult.success === false) {
14739
+ LOG.warn("MeshReconcile", `Self-addressed unresolved-delegate ${readNonEmptyString2(entry.payload.event)} rejected by local router (${readNonEmptyString2(localResult.error) || "no reason"}) \u2014 drained to break the self-forward retry loop`);
14740
+ traceMeshEventDrop("self_forward_local_rejected", entryTraceCtx, readNonEmptyString2(localResult.error) || "no reason");
14741
+ } else {
14742
+ LOG.info("MeshReconcile", `Self-addressed unresolved-delegate ${readNonEmptyString2(entry.payload.event)} routed via local router (coordinator ${entry.coordinatorDaemonId} is self) \u2014 drained`);
14743
+ }
14744
+ continue;
14745
+ }
14675
14746
  let result;
14676
14747
  try {
14677
14748
  traceMeshEventStage("forward_send", entryTraceCtx, `retry \u2192 ${entry.coordinatorDaemonId}`);
@@ -28227,6 +28298,7 @@ var CHAT_SOURCE_REGISTRY = new ChatSourceRegistry();
28227
28298
 
28228
28299
  // src/commands/chat-commands.ts
28229
28300
  init_chat_message_normalization();
28301
+ init_dist();
28230
28302
  var RECENT_SEND_WINDOW_MS = 1200;
28231
28303
  var READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25e3;
28232
28304
  var HOT_TAIL_MIN_LIMIT = 60;
@@ -28788,6 +28860,24 @@ function normalizeComparableWorkspace(value) {
28788
28860
  if (!text) return "";
28789
28861
  return path16.resolve(text);
28790
28862
  }
28863
+ function evaluateReadChatNodeWorkspaceScope(args) {
28864
+ const targetSessionId = typeof args.targetSessionId === "string" ? args.targetSessionId.trim() : "";
28865
+ if (!targetSessionId) return { scoped: false };
28866
+ const intended = normalizeMeshWorkspaceForCompare(args.intendedWorkspace);
28867
+ const actual = normalizeMeshWorkspaceForCompare(args.sessionWorkspace);
28868
+ if (!intended || !actual) return { scoped: false };
28869
+ if (intended === actual) return { scoped: false };
28870
+ return { scoped: true, intended, actual };
28871
+ }
28872
+ function resolveTargetSessionActualWorkspace(h, targetSessionId) {
28873
+ const registryWorkspace = h.ctx?.sessionRegistry?.get?.(targetSessionId)?.workspace;
28874
+ if (typeof registryWorkspace === "string" && registryWorkspace.trim()) return registryWorkspace;
28875
+ const adapter = h.getCliAdapter?.(targetSessionId);
28876
+ if (adapter && typeof adapter.workingDir === "string" && adapter.workingDir.trim()) return adapter.workingDir;
28877
+ const instanceWorkspace = getTargetInstance(h, { targetSessionId })?.getState?.()?.workspace;
28878
+ if (typeof instanceWorkspace === "string" && instanceWorkspace.trim()) return instanceWorkspace;
28879
+ return "";
28880
+ }
28791
28881
  function isCurrentRuntimePtySafelyAttributed(args) {
28792
28882
  if (args.adapter.cliType !== "codex-cli") return false;
28793
28883
  if (!Array.isArray(args.ptyMessages) || args.ptyMessages.length === 0) return false;
@@ -29602,6 +29692,24 @@ async function handleChatHistory(h, args) {
29602
29692
  }
29603
29693
  }
29604
29694
  async function handleReadChat(h, args) {
29695
+ {
29696
+ const guardSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
29697
+ if (guardSessionId && typeof args?.workspace === "string" && args.workspace.trim()) {
29698
+ const verdict = evaluateReadChatNodeWorkspaceScope({
29699
+ targetSessionId: guardSessionId,
29700
+ intendedWorkspace: args.workspace,
29701
+ sessionWorkspace: resolveTargetSessionActualWorkspace(h, guardSessionId)
29702
+ });
29703
+ if (verdict.scoped) {
29704
+ LOG.info("Command", `[read_chat] node scope mismatch: session ${guardSessionId} workspace "${verdict.actual}" \u2260 requested node workspace "${verdict.intended}" \u2014 refusing cross-worktree transcript`);
29705
+ return {
29706
+ success: false,
29707
+ code: "read_chat_session_node_scope_mismatch",
29708
+ error: `Session ${guardSessionId} belongs to a different worktree (workspace "${verdict.actual}") than the requested node (workspace "${verdict.intended}"). Refusing to return a cross-worktree transcript \u2014 target the node that owns this session.`
29709
+ };
29710
+ }
29711
+ }
29712
+ }
29605
29713
  let providerHint = args?.agentType || args?.providerType;
29606
29714
  if (!providerHint) {
29607
29715
  const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
@@ -29990,10 +30098,19 @@ async function handleReadChat(h, args) {
29990
30098
  ptyStatusApprovalOnly: false
29991
30099
  });
29992
30100
  if (supportsNative && !decision.nativeSelected) {
30101
+ LOG.debug("Command", `[read_chat] soft pending: no live adapter and native history not safely mappable target=${String(args?.targetSessionId || "")} provider=${agentStr} reason=native_history_not_safely_available`);
29993
30102
  return {
29994
- success: false,
30103
+ success: true,
30104
+ pending: true,
30105
+ // Both signals are true here: we reached the history-only path
30106
+ // because no live adapter was found (`live_adapter_not_found`),
30107
+ // and native history is not safely mappable
30108
+ // (`native_history_not_safely_available`).
30109
+ reason: "native_history_not_safely_available",
30110
+ reasons: ["live_adapter_not_found", "native_history_not_safely_available"],
29995
30111
  code: "native_history_not_safely_available",
29996
- error: "Provider-native history was not safely available for the requested CLI session.",
30112
+ messages: [],
30113
+ status: "idle",
29997
30114
  providerSessionId: historyProviderSessionId,
29998
30115
  messageSource: decision.messageSource,
29999
30116
  transcriptProvenance: decision.messageSource
@@ -41453,6 +41570,18 @@ var DaemonCliManager = class {
41453
41570
  throw new Error(`Failed to start ${provider.displayName || provider.name || cliType}: ${spawnErr?.message}`);
41454
41571
  }
41455
41572
  this.adapters.set(key, cliInstance.getAdapter());
41573
+ const launchMeshNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
41574
+ const launchMeshNodeFor = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
41575
+ if (launchMeshNodeId || launchMeshNodeFor) {
41576
+ try {
41577
+ cliInstance.getAdapter().updateRuntimeMeta?.({
41578
+ ...launchMeshNodeId ? { meshNodeId: launchMeshNodeId } : {},
41579
+ ...launchMeshNodeFor ? { meshNodeFor: launchMeshNodeFor } : {},
41580
+ ...settings?.launchedByCoordinator === true ? { launchedByCoordinator: true } : {}
41581
+ });
41582
+ } catch {
41583
+ }
41584
+ }
41456
41585
  this.startCliExitMonitor(key, cliType);
41457
41586
  }
41458
41587
  // ─── Session start/management ──────────────────────────────
@@ -49493,6 +49622,7 @@ function readMeshTimeoutEnvMs(name, defaultMs) {
49493
49622
  }
49494
49623
  var MESH_DIRECT_PROBE_TIMEOUT_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_TIMEOUT_MS", 25e3);
49495
49624
  var MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS", 25e3);
49625
+ var MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS", 45e3);
49496
49626
  var MESH_DIRECT_PROBE_REUSE_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_REUSE_MS", 12e3);
49497
49627
  var MeshGitProbeCache = class {
49498
49628
  constructor(reuseMs, now = Date.now) {
@@ -49530,12 +49660,73 @@ var MeshGitProbeCache = class {
49530
49660
  }
49531
49661
  }
49532
49662
  };
49663
+ function awaitWithWarmupDeadline(work, opts) {
49664
+ const pollMs = Math.max(1, Math.min(opts.pollIntervalMs ?? 200, opts.connectTimeoutMs));
49665
+ return new Promise((resolve24, reject) => {
49666
+ let done = false;
49667
+ let poll;
49668
+ let responseTimer;
49669
+ const startedAt = Date.now();
49670
+ const cleanup = () => {
49671
+ if (poll) {
49672
+ clearInterval(poll);
49673
+ poll = void 0;
49674
+ }
49675
+ if (responseTimer) {
49676
+ clearTimeout(responseTimer);
49677
+ responseTimer = void 0;
49678
+ }
49679
+ };
49680
+ const settle = (fn) => {
49681
+ if (done) return;
49682
+ done = true;
49683
+ cleanup();
49684
+ fn();
49685
+ };
49686
+ const armResponse = () => {
49687
+ if (responseTimer || done) return;
49688
+ responseTimer = setTimeout(
49689
+ () => settle(() => reject(new Error("timeout"))),
49690
+ opts.responseTimeoutMs
49691
+ );
49692
+ if (typeof responseTimer.unref === "function") responseTimer.unref();
49693
+ };
49694
+ const onPoll = () => {
49695
+ if (done) return;
49696
+ if (opts.isConnected()) {
49697
+ if (poll) {
49698
+ clearInterval(poll);
49699
+ poll = void 0;
49700
+ }
49701
+ armResponse();
49702
+ return;
49703
+ }
49704
+ if (Date.now() - startedAt >= opts.connectTimeoutMs) {
49705
+ settle(() => reject(new Error("timeout")));
49706
+ }
49707
+ };
49708
+ if (opts.isConnected()) {
49709
+ armResponse();
49710
+ } else {
49711
+ poll = setInterval(onPoll, pollMs);
49712
+ if (typeof poll.unref === "function") poll.unref();
49713
+ }
49714
+ work.then(
49715
+ (val) => settle(() => resolve24(val)),
49716
+ (err) => settle(() => reject(err))
49717
+ );
49718
+ });
49719
+ }
49533
49720
  async function probeRemoteMeshGitStatus(args) {
49534
49721
  if (!args.dispatchMeshCommand) return null;
49535
- const remoteResult = await Promise.race([
49536
- args.dispatchMeshCommand(args.daemonId, "git_status", { workspace: args.workspace, refreshUpstream: true }),
49537
- new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), args.timeoutMs))
49538
- ]);
49722
+ const dispatch = args.dispatchMeshCommand(args.daemonId, "git_status", { workspace: args.workspace, refreshUpstream: true });
49723
+ const getConnection = args.getConnection;
49724
+ const isConnected = getConnection ? () => readMeshConnectionState(getConnection(args.daemonId)) === "connected" : () => true;
49725
+ const remoteResult = await awaitWithWarmupDeadline(dispatch, {
49726
+ isConnected,
49727
+ connectTimeoutMs: args.connectTimeoutMs,
49728
+ responseTimeoutMs: args.responseTimeoutMs
49729
+ });
49539
49730
  const remoteGit = remoteResult?.status ?? remoteResult?.git ?? remoteResult;
49540
49731
  if (!remoteGit || typeof remoteGit !== "object" || typeof remoteGit.isGitRepo !== "boolean") return null;
49541
49732
  const reporterPlatform = readStringValue(remoteResult?.reporterPlatform);
@@ -49570,7 +49761,9 @@ async function probeRemoteMeshGitStatusWithRetry(args) {
49570
49761
  dispatchMeshCommand: args.dispatchMeshCommand,
49571
49762
  daemonId: args.daemonId,
49572
49763
  workspace: args.workspace,
49573
- timeoutMs: attempt === 0 ? args.timeoutMs : args.retryTimeoutMs ?? args.timeoutMs
49764
+ responseTimeoutMs: attempt === 0 ? args.timeoutMs : args.retryTimeoutMs ?? args.timeoutMs,
49765
+ connectTimeoutMs: args.connectTimeoutMs ?? MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS,
49766
+ getConnection: args.getConnection
49574
49767
  });
49575
49768
  if (remoteGit) return remoteGit;
49576
49769
  } catch {
@@ -49653,6 +49846,7 @@ async function hydrateInlineMeshDirectTruth(args) {
49653
49846
  workspace,
49654
49847
  timeoutMs: MESH_DIRECT_PROBE_TIMEOUT_MS,
49655
49848
  retryTimeoutMs: MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS,
49849
+ connectTimeoutMs: MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS,
49656
49850
  getConnection: args.getMeshPeerConnectionStatus
49657
49851
  });
49658
49852
  const remoteGit = args.probeCache ? await args.probeCache.probe(daemonId, workspace, runProbe) : await runProbe();
@@ -49704,13 +49898,13 @@ function liveSessionRecordMatchesMeshNode(record, meshId, nodeId, nodeWorkspace
49704
49898
  if (!recordNodeId || recordNodeId !== nodeId) return false;
49705
49899
  if (nodeIsMissingLocalWorktree) return false;
49706
49900
  const recordWorkspace = readStringValue(record?.workspace);
49707
- if (nodeWorkspace && recordWorkspace && recordWorkspace !== nodeWorkspace) return false;
49901
+ if (nodeWorkspace && recordWorkspace && !meshWorkspacesEquivalent(recordWorkspace, nodeWorkspace)) return false;
49708
49902
  const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
49709
49903
  return !recordMeshId || recordMeshId === meshId;
49710
49904
  }
49711
49905
  function liveSessionRecordMatchesMeshWorkspace(record, meshId, workspace) {
49712
49906
  const recordWorkspace = readStringValue(record?.workspace);
49713
- if (!recordWorkspace || !workspace || recordWorkspace !== workspace) return false;
49907
+ if (!recordWorkspace || !workspace || !meshWorkspacesEquivalent(recordWorkspace, workspace)) return false;
49714
49908
  const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
49715
49909
  if (recordMeshId) return recordMeshId === meshId;
49716
49910
  return record?.meta?.launchedByCoordinator === true || !!readStringValue(record?.meta?.meshNodeId);