@adhdev/daemon-standalone 1.0.39-rc.4 → 1.0.39-rc.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-standalone",
3
- "version": "1.0.39-rc.4",
3
+ "version": "1.0.39-rc.6",
4
4
  "description": "ADHDev standalone daemon — embedded HTTP/WS server for local dashboard",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "license": "AGPL-3.0-or-later",
38
38
  "dependencies": {
39
39
  "@adhdev/ghostty-vt-node": "*",
40
- "@adhdev/session-host-core": "1.0.39-rc.4",
40
+ "@adhdev/session-host-core": "1.0.39-rc.6",
41
41
  "@xterm/addon-serialize": "^0.14.0",
42
42
  "@xterm/xterm": "^6.0.0",
43
43
  "chalk": "^5.3.0",
@@ -2404,14 +2404,32 @@ function findNode(mesh, nodeId) {
2404
2404
  }
2405
2405
  var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
2406
2406
  async function refreshMeshFromDaemon(ctx) {
2407
+ const settledNodeIds = /* @__PURE__ */ new Set();
2407
2408
  try {
2408
2409
  const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
2409
- if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
2410
+ if (!result?.success || !Array.isArray(result.mesh?.nodes)) return { settledNodeIds };
2410
2411
  const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
2411
- ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
2412
+ const merged = [...refreshedNodes];
2413
+ for (const existing of ctx.mesh.nodes) {
2414
+ const existingId = existing?.id;
2415
+ if (!existingId) continue;
2416
+ if (merged.some((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, existingId))) continue;
2417
+ if (isLocalControlPlaneNode(ctx, existing)) {
2418
+ settledNodeIds.add(existingId);
2419
+ continue;
2420
+ }
2421
+ const clonedFromNodeId = readString(existing?.clonedFromNodeId) || readString(existing?.cloned_from_node_id);
2422
+ if (clonedFromNodeId && refreshedNodes.some((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, clonedFromNodeId))) {
2423
+ settledNodeIds.add(existingId);
2424
+ continue;
2425
+ }
2426
+ merged.push(existing);
2427
+ }
2428
+ ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...merged);
2412
2429
  ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
2413
2430
  } catch {
2414
2431
  }
2432
+ return { settledNodeIds };
2415
2433
  }
2416
2434
  async function syncCoordinatorDaemonMeshCache(ctx) {
2417
2435
  if (!(ctx.transport instanceof IpcTransport)) return;
@@ -2423,19 +2441,71 @@ async function syncCoordinatorDaemonMeshCache(ctx) {
2423
2441
  } catch {
2424
2442
  }
2425
2443
  }
2444
+ async function resolveNodeFromOwningDaemons(ctx, nodeId) {
2445
+ const transport = ctx.transport;
2446
+ if (typeof transport?.meshCommand !== "function") return { node: null, ownerUnreachable: false };
2447
+ const localDaemonId = ctx.localDaemonId;
2448
+ const candidates = [];
2449
+ const pushCandidate = (id) => {
2450
+ if (typeof id !== "string" || !id.trim()) return;
2451
+ if (localDaemonId && id === localDaemonId) return;
2452
+ if (!candidates.includes(id)) candidates.push(id);
2453
+ };
2454
+ for (const node of ctx.mesh.nodes) pushCandidate(node?.daemonId);
2455
+ pushCandidate(ctx.mesh?.meshHost?.hostDaemonId);
2456
+ if (candidates.length === 0) return { node: null, ownerUnreachable: false };
2457
+ let ownerUnreachable = false;
2458
+ for (const daemonId of candidates) {
2459
+ let result;
2460
+ try {
2461
+ result = await transport.meshCommand(daemonId, "get_mesh", { meshId: ctx.mesh.id });
2462
+ } catch {
2463
+ ownerUnreachable = true;
2464
+ continue;
2465
+ }
2466
+ const nodes = result?.mesh?.nodes ?? result?.result?.mesh?.nodes;
2467
+ if (!result?.success || !Array.isArray(nodes)) {
2468
+ if (result && result.success === false) ownerUnreachable = true;
2469
+ continue;
2470
+ }
2471
+ const found = nodes.find((n) => n?.id && (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2472
+ if (!found) continue;
2473
+ const existingIndex = ctx.mesh.nodes.findIndex((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2474
+ if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = found;
2475
+ else ctx.mesh.nodes.push(found);
2476
+ return { node: found, ownerUnreachable: false };
2477
+ }
2478
+ return { node: null, ownerUnreachable };
2479
+ }
2426
2480
  async function findNodeWithRefresh(ctx, nodeId) {
2427
2481
  const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2428
2482
  if (hit && !hit.isLocalWorktree) return hit;
2429
- await refreshMeshFromDaemon(ctx);
2483
+ const { settledNodeIds } = await refreshMeshFromDaemon(ctx);
2430
2484
  const refreshed = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2431
- if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
2432
- return refreshed;
2485
+ if (refreshed) return refreshed;
2486
+ if (settledNodeIds.has(nodeId)) {
2487
+ throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
2488
+ }
2489
+ const owned = await resolveNodeFromOwningDaemons(ctx, nodeId);
2490
+ if (owned.node) return owned.node;
2491
+ if (owned.ownerUnreachable) {
2492
+ const err = new Error(
2493
+ `Node '${nodeId}' could not be resolved: the daemon that owns it is unreachable. This is NOT proof of non-membership \u2014 retry once the owning daemon is online.`
2494
+ );
2495
+ err.code = "mesh_node_owner_unreachable";
2496
+ throw err;
2497
+ }
2498
+ throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
2433
2499
  }
2434
2500
  async function findOptionalNodeWithRefresh(ctx, nodeId) {
2435
2501
  const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2436
2502
  if (hit && !hit.isLocalWorktree) return hit;
2437
- await refreshMeshFromDaemon(ctx);
2438
- return ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId)) ?? null;
2503
+ const { settledNodeIds } = await refreshMeshFromDaemon(ctx);
2504
+ const refreshed = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2505
+ if (refreshed) return refreshed;
2506
+ if (settledNodeIds.has(nodeId)) return null;
2507
+ const owned = await resolveNodeFromOwningDaemons(ctx, nodeId);
2508
+ return owned.node;
2439
2509
  }
2440
2510
  function hasRecentDuplicateDispatch(ctx, args) {
2441
2511
  const now = Date.now();