@adhdev/daemon-standalone 0.9.82-rc.370 → 0.9.82-rc.371

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": "0.9.82-rc.370",
3
+ "version": "0.9.82-rc.371",
4
4
  "description": "ADHDev standalone daemon — embedded HTTP/WS server for local dashboard",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1249,7 +1249,7 @@ function compactMeshStatusNode(entry) {
1249
1249
  }
1250
1250
  delete next.capabilityTagsByProvider;
1251
1251
  for (const k of Object.keys(next)) {
1252
- if (k === "git" || k === "machine" || k === "branchConvergence" || k === "staleDaemonBuild" || k === "sessions") continue;
1252
+ if (k === "git" || k === "machine" || k === "branchConvergence" || k === "staleDaemonBuild" || k === "sessions" || k === "dataFreshness") continue;
1253
1253
  next[k] = elideLargeNestedValue(k, next[k]);
1254
1254
  }
1255
1255
  return next;
@@ -1303,6 +1303,10 @@ function minimalCompactNode(entry) {
1303
1303
  ...entry.launchBlockedReason !== void 0 ? { launchBlockedReason: entry.launchBlockedReason } : {},
1304
1304
  ...bc ? { branchConvergence: bc } : {},
1305
1305
  ...entry.sessionSummary ? { sessionSummary: entry.sessionSummary } : {},
1306
+ // The freshness/reachability marker is exactly the signal a coordinator needs
1307
+ // on a QUIET node — is this idle peer live, cached, or unreachable? — and it is
1308
+ // tiny (6 scalar fields), so keep it even on the minimal stub.
1309
+ ...entry.dataFreshness !== void 0 ? { dataFreshness: entry.dataFreshness } : {},
1306
1310
  folded: true
1307
1311
  };
1308
1312
  }
@@ -2492,6 +2496,18 @@ var MESH_FAST_FORWARD_NODE_TOOL = {
2492
2496
  required: ["node_id"]
2493
2497
  }
2494
2498
  };
2499
+ var MESH_RESTART_DAEMON_TOOL = {
2500
+ name: "mesh_restart_daemon",
2501
+ description: `Update a mesh node's daemon to the latest published version on its release channel and restart it \u2014 the same path as the dashboard "preview update" button, exposed as a mesh command so a coordinator can roll a worker daemon onto a freshly deployed version without a manual restart round-trip. No agent session is launched. Idle-gated: a node whose daemon has an active session (generating / waiting_approval / starting) is refused with code "blocking_sessions" so an in-flight turn is never interrupted. If the node is already on the latest version it is a no-op (no restart), matching the dashboard button (returns alreadyLatest:true). Targets a single node \u2014 call other (idle) nodes first; restarting the coordinator's OWN daemon is naturally refused while its calling turn is active. Passing channel switches the daemon's release channel (and server URL) before restarting; omit it to keep the daemon on its configured channel.`,
2502
+ inputSchema: {
2503
+ type: "object",
2504
+ properties: {
2505
+ node_id: { type: "string", description: "Target node ID \u2014 the daemon that owns this node is updated and restarted." },
2506
+ channel: { type: "string", enum: ["stable", "preview"], description: "Optional release channel to update from. Defaults to the daemon's configured updateChannel. Setting it also repoints the daemon's server URL to that channel." }
2507
+ },
2508
+ required: ["node_id"]
2509
+ }
2510
+ };
2495
2511
  var MESH_CHECKPOINT_TOOL = {
2496
2512
  name: "mesh_checkpoint",
2497
2513
  description: "Create a git checkpoint (commit) on a mesh node workspace.",
@@ -2767,6 +2783,7 @@ var ALL_MESH_TOOLS = [
2767
2783
  MESH_GIT_STATUS_TOOL,
2768
2784
  MESH_READ_NODE_LOGS_TOOL,
2769
2785
  MESH_FAST_FORWARD_NODE_TOOL,
2786
+ MESH_RESTART_DAEMON_TOOL,
2770
2787
  MESH_CHECKPOINT_TOOL,
2771
2788
  MESH_APPROVE_TOOL,
2772
2789
  MESH_CLONE_NODE_TOOL,
@@ -2805,6 +2822,7 @@ async function meshStatus(ctx, args = {}) {
2805
2822
  ...getNodeLaunchReadiness(node),
2806
2823
  ...buildNodeCapabilityExposure(node)
2807
2824
  };
2825
+ let liveTruthProbed = false;
2808
2826
  try {
2809
2827
  const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
2810
2828
  const statusResult = await commandForNode(ctx, node, "git_status", {
@@ -2813,6 +2831,7 @@ async function meshStatus(ctx, args = {}) {
2813
2831
  includeSubmodules: autoDiscover,
2814
2832
  submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
2815
2833
  });
2834
+ liveTruthProbed = true;
2816
2835
  const status = extractGitStatus(statusResult);
2817
2836
  const uncommittedChanges = countUncommittedChanges(status);
2818
2837
  const dirty = isGitStatusDirty(status);
@@ -2848,6 +2867,23 @@ async function meshStatus(ctx, args = {}) {
2848
2867
  noFallbackReason: failure.noFallbackReason
2849
2868
  });
2850
2869
  }
2870
+ const freshnessDaemonId = readNodeDaemonId(node);
2871
+ const freshnessStatus = {
2872
+ git: entry.git,
2873
+ connection: { state: liveTruthProbed ? "connected" : "disconnected" }
2874
+ };
2875
+ if (liveTruthProbed) freshnessStatus[import_daemon_core.MESH_NODE_LIVE_TRUTH_MARKER] = true;
2876
+ entry.dataFreshness = (0, import_daemon_core.buildMeshNodeDataFreshness)({
2877
+ status: freshnessStatus,
2878
+ node,
2879
+ isSelfNode: entry.machine?.sameMachine === true,
2880
+ daemonId: freshnessDaemonId,
2881
+ liveTruthProbed,
2882
+ // A probe that threw against a configured (daemonId-bearing) peer is an
2883
+ // unreachable peer; an unconfigured node (no daemonId) falls through to
2884
+ // the classifier's `unconfigured` branch instead of being mislabeled.
2885
+ directTruthUnavailable: !liveTruthProbed && !!freshnessDaemonId
2886
+ });
2851
2887
  const recoveryContext = (0, import_daemon_core.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
2852
2888
  if (recoveryContext.consecutiveNodeFailures > 0) {
2853
2889
  entry.recoveryHints = {
@@ -4420,6 +4456,26 @@ async function meshFastForwardNode(ctx, args) {
4420
4456
  }, null, 2);
4421
4457
  }
4422
4458
  }
4459
+ async function meshRestartDaemon(ctx, args) {
4460
+ await refreshMeshFromDaemon(ctx);
4461
+ const node = await findNodeWithRefresh(ctx, args.node_id);
4462
+ try {
4463
+ const result = await commandForNode(ctx, node, "restart_daemon_node", {
4464
+ meshId: ctx.mesh.id,
4465
+ nodeId: node.id,
4466
+ inlineMesh: ctx.mesh,
4467
+ ...args.channel ? { channel: args.channel } : {}
4468
+ });
4469
+ return JSON.stringify(unwrapCommandPayload(result), null, 2);
4470
+ } catch (e) {
4471
+ const failure = buildCoordinatorP2pRelayFailure(e, {
4472
+ command: "restart_daemon_node",
4473
+ targetDaemonId: node.daemonId,
4474
+ nodeId: args.node_id
4475
+ });
4476
+ return JSON.stringify(failure, null, 2);
4477
+ }
4478
+ }
4423
4479
  async function meshCheckpoint(ctx, args) {
4424
4480
  const node = await findNodeWithRefresh(ctx, args.node_id);
4425
4481
  if (node.policy?.readOnly) {
@@ -5842,6 +5898,9 @@ async function startMcpServer(opts) {
5842
5898
  case "mesh_fast_forward_node":
5843
5899
  text = await meshFastForwardNode(meshCtx, a);
5844
5900
  break;
5901
+ case "mesh_restart_daemon":
5902
+ text = await meshRestartDaemon(meshCtx, a);
5903
+ break;
5845
5904
  case "mesh_checkpoint":
5846
5905
  text = await meshCheckpoint(meshCtx, a);
5847
5906
  break;