adhdev 0.9.82-rc.7 → 0.9.82-rc.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -264,8 +264,14 @@ async function getGitRepoStatus(workspace, options = {}) {
264
264
  const includeSubmodules = options.includeSubmodules !== false;
265
265
  try {
266
266
  const repo = await resolveGitRepository(workspace, options);
267
- const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
268
- const parsed = parsePorcelainV2Status(statusOutput.stdout);
267
+ let parsed = await readPorcelainStatus(repo, options);
268
+ let upstreamProbe = getInitialUpstreamProbe(parsed);
269
+ if (options.refreshUpstream) {
270
+ upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
271
+ if (upstreamProbe.upstreamStatus === "fresh") {
272
+ parsed = await readPorcelainStatus(repo, options);
273
+ }
274
+ }
269
275
  const head = await readHead(repo, options);
270
276
  const stashCount = await readStashCount(repo, options);
271
277
  let submodules;
@@ -280,6 +286,9 @@ async function getGitRepoStatus(workspace, options = {}) {
280
286
  headCommit: head.commit,
281
287
  headMessage: head.message,
282
288
  upstream: parsed.upstream,
289
+ upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
290
+ upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
291
+ upstreamFetchError: upstreamProbe.upstreamFetchError,
283
292
  ahead: parsed.ahead,
284
293
  behind: parsed.behind,
285
294
  staged: parsed.staged,
@@ -304,6 +313,60 @@ async function getGitRepoStatus(workspace, options = {}) {
304
313
  );
305
314
  }
306
315
  }
316
+ async function readPorcelainStatus(repo, options) {
317
+ const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
318
+ return parsePorcelainV2Status(statusOutput.stdout);
319
+ }
320
+ function getInitialUpstreamProbe(parsed) {
321
+ return {
322
+ upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
323
+ };
324
+ }
325
+ async function refreshTrackedUpstream(repo, parsed, options) {
326
+ if (!parsed.upstream || !parsed.branch) {
327
+ return { upstreamStatus: "no_upstream" };
328
+ }
329
+ const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
330
+ if (!remoteName) {
331
+ return {
332
+ upstreamStatus: "stale",
333
+ upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
334
+ };
335
+ }
336
+ try {
337
+ await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
338
+ return {
339
+ upstreamStatus: "fresh",
340
+ upstreamFetchedAt: Date.now()
341
+ };
342
+ } catch (error48) {
343
+ return {
344
+ upstreamStatus: "stale",
345
+ upstreamFetchError: formatGitError(error48)
346
+ };
347
+ }
348
+ }
349
+ async function readBranchRemote(repo, branch, options) {
350
+ try {
351
+ const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
352
+ return result.stdout.trim() || null;
353
+ } catch {
354
+ return null;
355
+ }
356
+ }
357
+ function inferRemoteName(upstream) {
358
+ const [remoteName] = upstream.split("/");
359
+ return remoteName?.trim() || null;
360
+ }
361
+ function formatGitError(error48) {
362
+ if (error48 instanceof GitCommandError) {
363
+ return error48.stderr || error48.message;
364
+ }
365
+ if (error48 instanceof Error) {
366
+ return error48.message;
367
+ }
368
+ return String(error48);
369
+ }
307
370
  function parsePorcelainV2Status(output) {
308
371
  const parsed = {
309
372
  branch: null,
@@ -398,6 +461,7 @@ function emptyStatus(workspace, lastCheckedAt, error48) {
398
461
  headCommit: null,
399
462
  headMessage: null,
400
463
  upstream: null,
464
+ upstreamStatus: "unavailable",
401
465
  ahead: 0,
402
466
  behind: 0,
403
467
  staged: 0,
@@ -690,6 +754,9 @@ function createGitCompactSummary(status, diffSummary) {
690
754
  isGitRepo: status.isGitRepo,
691
755
  repoRoot: status.repoRoot,
692
756
  branch: status.branch,
757
+ upstreamStatus: status.upstreamStatus,
758
+ upstreamFetchedAt: status.upstreamFetchedAt,
759
+ upstreamFetchError: status.upstreamFetchError,
693
760
  dirty: status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0 || conflictCount > 0 || changedFiles > 0,
694
761
  changedFiles,
695
762
  ahead: status.ahead,
@@ -1017,7 +1084,7 @@ function serviceNotImplemented(command) {
1017
1084
  }
1018
1085
  function createDefaultGitCommandServices() {
1019
1086
  return {
1020
- getStatus: ({ workspace }) => getGitRepoStatus(workspace),
1087
+ getStatus: ({ workspace, refreshUpstream }) => getGitRepoStatus(workspace, { refreshUpstream }),
1021
1088
  getDiffSummary: ({ workspace }) => getGitDiffSummary(workspace),
1022
1089
  getDiffFile: ({ workspace, path: filePath }) => getGitFileDiff(workspace, filePath),
1023
1090
  createSnapshot: ({ workspace, reason, sessionId, turnId }) => defaultSnapshotStore.create({
@@ -1102,7 +1169,7 @@ async function handleGitCommand(command, args, services = defaultGitCommandServi
1102
1169
  switch (command) {
1103
1170
  case "git_status": {
1104
1171
  if (!services.getStatus) return serviceNotImplemented(command);
1105
- const status = await runService(() => services.getStatus({ workspace }));
1172
+ const status = await runService(() => services.getStatus({ workspace, refreshUpstream: optionalBoolean(args?.refreshUpstream) }));
1106
1173
  return "success" in status ? status : { success: true, status };
1107
1174
  }
1108
1175
  case "git_diff_summary": {
@@ -45536,17 +45603,87 @@ function readCachedInlineMeshActiveSessions(node) {
45536
45603
  const sessionId = readStringValue(fallbackSession.id, fallbackSession.sessionId, fallbackSession.session_id, node?.activeSessionId, node?.active_session_id, node?.sessionId, node?.session_id);
45537
45604
  return sessionId ? [sessionId] : [];
45538
45605
  }
45606
+ function readCachedInlineMeshActiveSessionDetails(node) {
45607
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
45608
+ const activeSession = readObjectRecord(cachedStatus.activeSession);
45609
+ const fallbackSession = Object.keys(activeSession).length ? activeSession : readObjectRecord(node?.activeSession ?? node?.active_session);
45610
+ const sessionId = readStringValue(
45611
+ fallbackSession.id,
45612
+ fallbackSession.sessionId,
45613
+ fallbackSession.session_id,
45614
+ node?.activeSessionId,
45615
+ node?.active_session_id,
45616
+ node?.sessionId,
45617
+ node?.session_id
45618
+ );
45619
+ if (!sessionId) return [];
45620
+ return [{
45621
+ sessionId,
45622
+ providerType: readStringValue(
45623
+ fallbackSession.providerType,
45624
+ fallbackSession.provider_type,
45625
+ fallbackSession.cliType,
45626
+ fallbackSession.cli_type,
45627
+ fallbackSession.provider,
45628
+ node?.providerType,
45629
+ node?.provider_type
45630
+ ),
45631
+ state: readStringValue(fallbackSession.status, fallbackSession.state, fallbackSession.lifecycle),
45632
+ lifecycle: readStringValue(fallbackSession.lifecycle),
45633
+ title: readStringValue(fallbackSession.title, fallbackSession.displayName, fallbackSession.display_name) ?? null,
45634
+ workspace: readStringValue(fallbackSession.workspace, node?.workspace) ?? null,
45635
+ lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
45636
+ recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
45637
+ isCached: true
45638
+ }];
45639
+ }
45640
+ function readLiveMeshSessionState(record2) {
45641
+ return readStringValue(
45642
+ record2?.meta?.sessionStatus,
45643
+ record2?.meta?.status,
45644
+ record2?.meta?.providerStatus,
45645
+ record2?.status,
45646
+ record2?.state,
45647
+ record2?.lifecycle
45648
+ );
45649
+ }
45650
+ function toIsoTimestamp(value) {
45651
+ if (typeof value === "number" && Number.isFinite(value)) return new Date(value).toISOString();
45652
+ const stringValue = readStringValue(value);
45653
+ return stringValue || null;
45654
+ }
45655
+ function summarizeMeshSessionRecord(record2) {
45656
+ return {
45657
+ sessionId: readStringValue(record2?.sessionId) || "unknown",
45658
+ providerType: readStringValue(record2?.providerType),
45659
+ state: readLiveMeshSessionState(record2),
45660
+ lifecycle: readStringValue(record2?.lifecycle),
45661
+ surfaceKind: getSessionHostSurfaceKind(record2),
45662
+ recoveryState: readStringValue(record2?.meta?.runtimeRecoveryState) ?? null,
45663
+ workspace: readStringValue(record2?.workspace) ?? null,
45664
+ title: readStringValue(record2?.displayName, record2?.workspaceLabel) ?? null,
45665
+ lastActivityAt: toIsoTimestamp(record2?.updatedAt ?? record2?.lastActivityAt ?? record2?.last_activity_at),
45666
+ isCached: false
45667
+ };
45668
+ }
45539
45669
  function applyCachedInlineMeshNodeStatus(status, node) {
45540
45670
  const cachedStatus = readObjectRecord(node?.cachedStatus);
45541
45671
  const git = buildCachedInlineMeshGitStatus(node);
45542
45672
  const error48 = readStringValue(cachedStatus.error, node?.error);
45543
45673
  const health = readStringValue(cachedStatus.health, node?.health);
45544
45674
  const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
45675
+ const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
45676
+ const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
45545
45677
  const activeSessions = readCachedInlineMeshActiveSessions(node);
45546
- if (!git && !error48 && !health && !machineStatus && activeSessions.length === 0) return false;
45678
+ const activeSessionDetails = readCachedInlineMeshActiveSessionDetails(node);
45679
+ if (!git && !error48 && !health && !machineStatus && !lastSeenAt && !updatedAt && activeSessions.length === 0) return false;
45547
45680
  if (git) status.git = git;
45548
45681
  if (error48) status.error = error48;
45682
+ if (machineStatus) status.machineStatus = machineStatus;
45683
+ if (lastSeenAt) status.lastSeenAt = lastSeenAt;
45684
+ if (updatedAt) status.updatedAt = updatedAt;
45549
45685
  if (activeSessions.length > 0) status.activeSessions = activeSessions;
45686
+ if (activeSessionDetails.length > 0) status.activeSessionDetails = activeSessionDetails;
45550
45687
  if (health) {
45551
45688
  status.health = health;
45552
45689
  return true;
@@ -45555,7 +45692,7 @@ function applyCachedInlineMeshNodeStatus(status, node) {
45555
45692
  status.health = deriveMeshNodeHealthFromGit(git);
45556
45693
  return true;
45557
45694
  }
45558
- return activeSessions.length > 0 || !!machineStatus;
45695
+ return activeSessions.length > 0 || !!machineStatus || !!lastSeenAt || !!updatedAt;
45559
45696
  }
45560
45697
  async function resolveProviderTypeFromPriority(args) {
45561
45698
  if (!args.providerPriority.length) {
@@ -47834,33 +47971,84 @@ ${block}`);
47834
47971
  const ledgerSummary = getLedgerSummary2(meshId);
47835
47972
  const sessionHostRecords = this.deps.sessionHostControl?.listSessions ? await this.deps.sessionHostControl.listSessions().catch(() => []) : [];
47836
47973
  const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
47974
+ const localMachineId = loadConfig().machineId || "";
47975
+ const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes) ? readStringValue(mesh.nodes[0]?.id, mesh.nodes[0]?.nodeId) : void 0;
47976
+ const refreshedAt = (/* @__PURE__ */ new Date()).toISOString();
47837
47977
  const nodeStatuses = [];
47838
- for (const node of mesh.nodes || []) {
47978
+ for (const [nodeIndex, node] of (mesh.nodes || []).entries()) {
47979
+ const nodeId = String(node.id || node.nodeId || "");
47980
+ const daemonId = readStringValue(node.daemonId);
47981
+ const providerPriority = readProviderPriorityFromPolicy(node.policy);
47982
+ const isSelfNode = Boolean(
47983
+ nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId
47984
+ ) || Boolean(
47985
+ daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId)
47986
+ ) || Boolean(meshRecord?.inline && nodeIndex === 0);
47839
47987
  const status = {
47840
- nodeId: node.id || node.nodeId,
47988
+ nodeId,
47841
47989
  machineLabel: node.machineLabel || node.id || node.nodeId,
47842
47990
  workspace: node.workspace,
47843
47991
  repoRoot: node.repoRoot,
47844
47992
  isLocalWorktree: node.isLocalWorktree,
47845
47993
  worktreeBranch: node.worktreeBranch,
47846
- daemonId: node.daemonId,
47994
+ daemonId,
47847
47995
  machineId: node.machineId,
47996
+ machineStatus: node.machineStatus,
47848
47997
  health: "unknown",
47849
47998
  providers: node.providers || [],
47850
- activeSessions: []
47999
+ providerPriority,
48000
+ activeSessions: [],
48001
+ activeSessionDetails: [],
48002
+ launchReady: false
47851
48003
  };
47852
- const nodeId = String(node.id || node.nodeId || "");
47853
- const matchedLiveSessions = liveMeshSessions.filter((record2) => this.sessionMatchesMeshNode(record2, node, nodeId)).map((record2) => typeof record2?.sessionId === "string" ? record2.sessionId : "").filter(Boolean);
47854
- if (matchedLiveSessions.length > 0) {
47855
- status.activeSessions = matchedLiveSessions;
48004
+ if (isSelfNode) {
48005
+ status.connection = {
48006
+ perspective: "selected_coordinator",
48007
+ source: "mesh_peer_status",
48008
+ state: "self",
48009
+ transport: "local",
48010
+ reported: true,
48011
+ reason: "Selected coordinator daemon",
48012
+ lastStateChangeAt: refreshedAt
48013
+ };
48014
+ } else if (daemonId) {
48015
+ const connection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
48016
+ status.connection = connection ?? {
48017
+ perspective: "selected_coordinator",
48018
+ source: "not_reported",
48019
+ state: "unknown",
48020
+ transport: "unknown",
48021
+ reported: false,
48022
+ reason: "No live mesh peer telemetry reported by the selected coordinator yet."
48023
+ };
48024
+ } else {
48025
+ status.connection = {
48026
+ perspective: "selected_coordinator",
48027
+ source: "not_reported",
48028
+ state: "unknown",
48029
+ transport: "unknown",
48030
+ reported: false,
48031
+ reason: "Node has no daemon id, so mesh transport cannot be reported from the selected coordinator."
48032
+ };
48033
+ }
48034
+ const matchedLiveSessionRecords = liveMeshSessions.filter((record2) => this.sessionMatchesMeshNode(record2, node, nodeId));
48035
+ if (matchedLiveSessionRecords.length > 0) {
48036
+ const sessionIds = matchedLiveSessionRecords.map((record2) => typeof record2?.sessionId === "string" ? record2.sessionId : "").filter(Boolean);
48037
+ const providerTypes = matchedLiveSessionRecords.map((record2) => readStringValue(record2?.providerType)).filter(Boolean);
48038
+ status.activeSessions = sessionIds;
48039
+ status.activeSessionDetails = matchedLiveSessionRecords.map(summarizeMeshSessionRecord);
48040
+ if (providerTypes.length > 0) {
48041
+ status.providers = Array.from(/* @__PURE__ */ new Set([...Array.isArray(status.providers) ? status.providers : [], ...providerTypes]));
48042
+ }
47856
48043
  }
47857
48044
  if (node.workspace && typeof node.workspace === "string") {
47858
48045
  if (!fs10.existsSync(node.workspace) && applyCachedInlineMeshNodeStatus(status, node)) {
48046
+ status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === "online" || isSelfNode);
47859
48047
  nodeStatuses.push(status);
47860
48048
  continue;
47861
48049
  }
47862
48050
  try {
47863
- const gitStatus = await getGitRepoStatus(node.workspace, { timeoutMs: 1e4 });
48051
+ const gitStatus = await getGitRepoStatus(node.workspace, { timeoutMs: 1e4, refreshUpstream: true });
47864
48052
  status.git = gitStatus;
47865
48053
  if (gitStatus.isGitRepo) {
47866
48054
  status.health = deriveMeshNodeHealthFromGit(gitStatus);
@@ -47876,6 +48064,7 @@ ${block}`);
47876
48064
  } else {
47877
48065
  applyCachedInlineMeshNodeStatus(status, node);
47878
48066
  }
48067
+ status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === "online" || isSelfNode);
47879
48068
  nodeStatuses.push(status);
47880
48069
  }
47881
48070
  return {
@@ -47884,6 +48073,7 @@ ${block}`);
47884
48073
  meshName: mesh.name,
47885
48074
  repoIdentity: mesh.repoIdentity,
47886
48075
  defaultBranch: mesh.defaultBranch,
48076
+ refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
47887
48077
  nodes: nodeStatuses,
47888
48078
  queue: { tasks: queue, summary: queueSummary },
47889
48079
  ledger: { entries: ledgerEntries, summary: ledgerSummary }
@@ -55962,6 +56152,7 @@ async function initDaemonComponents(config2) {
55962
56152
  sessionHostControl: config2.sessionHostControl,
55963
56153
  statusInstanceId: config2.statusInstanceId,
55964
56154
  statusVersion: config2.statusVersion,
56155
+ getMeshPeerConnectionStatus: config2.getMeshPeerConnectionStatus,
55965
56156
  getCdpLogFn: config2.getCdpLogFn || ((ideType) => LOG.forComponent(`CDP:${ideType}`).asLogFn())
55966
56157
  });
55967
56158
  poller = new AgentStreamPoller({
@@ -66054,11 +66245,30 @@ var init_daemon_mesh_manager = __esm({
66054
66245
  nodeDatachannel = null;
66055
66246
  peers = /* @__PURE__ */ new Map();
66056
66247
  // Map<targetDaemonId, PeerEntry>
66248
+ peerSnapshots = /* @__PURE__ */ new Map();
66057
66249
  pendingRequests = /* @__PURE__ */ new Map();
66058
66250
  commandCallback;
66059
66251
  p2pFailure(message, command, targetDaemonId) {
66060
66252
  return new P2pRelayFailureError(message, { command, targetDaemonId });
66061
66253
  }
66254
+ updatePeerSnapshot(targetDaemonId, state, patch = {}) {
66255
+ const previous = this.peerSnapshots.get(targetDaemonId);
66256
+ const now = (/* @__PURE__ */ new Date()).toISOString();
66257
+ this.peerSnapshots.set(targetDaemonId, {
66258
+ perspective: "selected_coordinator",
66259
+ source: "mesh_peer_status",
66260
+ reported: true,
66261
+ state,
66262
+ transport: patch.transport ?? previous?.transport ?? "unknown",
66263
+ reason: patch.reason ?? previous?.reason,
66264
+ lastStateChangeAt: now,
66265
+ lastConnectedAt: patch.lastConnectedAt ?? previous?.lastConnectedAt,
66266
+ lastCommandAt: patch.lastCommandAt ?? previous?.lastCommandAt
66267
+ });
66268
+ }
66269
+ getPeerConnectionStatus(targetDaemonId) {
66270
+ return this.peerSnapshots.get(targetDaemonId) ?? null;
66271
+ }
66062
66272
  invalidatePeer(targetDaemonId, reason, options = {}) {
66063
66273
  const peer = this.peers.get(targetDaemonId);
66064
66274
  if (peer?.commandQueue) {
@@ -66073,6 +66283,11 @@ var init_daemon_mesh_manager = __esm({
66073
66283
  pending.reject(this.p2pFailure(reason, pending.command, targetDaemonId));
66074
66284
  }
66075
66285
  }
66286
+ const snapshotState = peer?.state === "closed" ? "closed" : peer?.state === "disconnected" ? "disconnected" : "failed";
66287
+ this.updatePeerSnapshot(targetDaemonId, snapshotState, {
66288
+ reason,
66289
+ transport: peer?.isRelay === true ? "relay" : peer?.isRelay === false ? "direct" : "unknown"
66290
+ });
66076
66291
  if (options.closeResources !== false && peer) {
66077
66292
  try {
66078
66293
  peer.dataChannel?.close?.();
@@ -66192,6 +66407,20 @@ var init_daemon_mesh_manager = __esm({
66192
66407
  if (!peer) {
66193
66408
  throw this.p2pFailure("Failed to initiate P2P connection entry", command, targetDaemonId);
66194
66409
  }
66410
+ const lastCommandAt = (/* @__PURE__ */ new Date()).toISOString();
66411
+ if (peer.state === "connected") {
66412
+ this.updatePeerSnapshot(targetDaemonId, "connected", {
66413
+ transport: peer.isRelay === true ? "relay" : peer.isRelay === false ? "direct" : "unknown",
66414
+ lastConnectedAt: this.peerSnapshots.get(targetDaemonId)?.lastConnectedAt,
66415
+ lastCommandAt
66416
+ });
66417
+ } else {
66418
+ this.updatePeerSnapshot(targetDaemonId, "connecting", {
66419
+ transport: peer.isRelay === true ? "relay" : peer.isRelay === false ? "direct" : "unknown",
66420
+ reason: "Waiting for mesh DataChannel to open.",
66421
+ lastCommandAt
66422
+ });
66423
+ }
66195
66424
  return new Promise((resolve20, reject) => {
66196
66425
  const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
66197
66426
  const timer = setTimeout(() => {
@@ -66335,6 +66564,9 @@ var init_daemon_mesh_manager = __esm({
66335
66564
  remoteDescriptionSet: false
66336
66565
  };
66337
66566
  this.peers.set(targetDaemonId, entry);
66567
+ this.updatePeerSnapshot(targetDaemonId, "connecting", {
66568
+ reason: isInitiator ? "P2P mesh connection initiated by the selected coordinator." : "Waiting for the remote daemon to finish the mesh DataChannel handshake."
66569
+ });
66338
66570
  pc.onLocalDescription((sdp, type2) => {
66339
66571
  this.serverConn.sendMeshCommand(targetDaemonId, type2 === "offer" ? "mesh_p2p_offer" : "mesh_p2p_answer", { sdp, type: type2 });
66340
66572
  });
@@ -66345,7 +66577,26 @@ var init_daemon_mesh_manager = __esm({
66345
66577
  LOG.info("Mesh", `[Mesh] P2P state with ${targetDaemonId.slice(0, 12)}: ${state}`);
66346
66578
  if (state === "connected") {
66347
66579
  entry.state = "connected";
66580
+ let transport = "unknown";
66581
+ try {
66582
+ const pair = pc.getSelectedCandidatePair?.();
66583
+ if (pair) {
66584
+ const localType = pair.local?.type || "unknown";
66585
+ const remoteType = pair.remote?.type || "unknown";
66586
+ entry.isRelay = localType === "relay" || remoteType === "relay";
66587
+ transport = entry.isRelay ? "relay" : "direct";
66588
+ LOG.info("Mesh", `[Mesh] Candidate pair with ${targetDaemonId.slice(0, 12)}: local=${localType} remote=${remoteType} \u2192 ${transport}`);
66589
+ }
66590
+ } catch {
66591
+ transport = entry.isRelay === true ? "relay" : entry.isRelay === false ? "direct" : "unknown";
66592
+ }
66593
+ this.updatePeerSnapshot(targetDaemonId, "connected", {
66594
+ transport,
66595
+ reason: transport === "relay" ? "Connected over TURN relay." : transport === "direct" ? "Connected directly peer-to-peer." : "Connected, but selected candidate pair details are unavailable.",
66596
+ lastConnectedAt: (/* @__PURE__ */ new Date()).toISOString()
66597
+ });
66348
66598
  } else if (state === "failed" || state === "closed" || state === "disconnected") {
66599
+ entry.state = state;
66349
66600
  this.invalidatePeer(targetDaemonId, `P2P state changed to ${state}`, { rejectPending: true, closeResources: false });
66350
66601
  }
66351
66602
  });
@@ -66363,6 +66614,11 @@ var init_daemon_mesh_manager = __esm({
66363
66614
  dc.onOpen(() => {
66364
66615
  LOG.info("Mesh", `[Mesh] DataChannel OPEN with ${targetDaemonId.slice(0, 12)}`);
66365
66616
  entry.state = "connected";
66617
+ this.updatePeerSnapshot(targetDaemonId, "connected", {
66618
+ transport: entry.isRelay === true ? "relay" : entry.isRelay === false ? "direct" : "unknown",
66619
+ reason: entry.isRelay === true ? "Connected over TURN relay." : entry.isRelay === false ? "Connected directly peer-to-peer." : "DataChannel open; transport details not reported yet.",
66620
+ lastConnectedAt: (/* @__PURE__ */ new Date()).toISOString()
66621
+ });
66366
66622
  if (entry.commandQueue) {
66367
66623
  const queue = entry.commandQueue;
66368
66624
  entry.commandQueue = [];
@@ -66658,7 +66914,7 @@ var init_adhdev_daemon = __esm({
66658
66914
  init_version();
66659
66915
  init_src();
66660
66916
  init_runtime_defaults();
66661
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.7" });
66917
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.9" });
66662
66918
  AdhdevDaemon = class _AdhdevDaemon {
66663
66919
  localHttpServer = null;
66664
66920
  localWss = null;
@@ -67182,6 +67438,7 @@ ${err?.stack || ""}`);
67182
67438
  if (!this.meshManager) throw new Error("Mesh manager not initialized");
67183
67439
  return this.meshManager.sendCommand(daemonId, command, args);
67184
67440
  },
67441
+ getMeshPeerConnectionStatus: (daemonId) => this.meshManager?.getPeerConnectionStatus(daemonId) ?? null,
67185
67442
  onStatusChange: () => {
67186
67443
  this.invalidateHotChatSnapshotCache();
67187
67444
  this.statusReporter?.onStatusChange();