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

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.373",
4
4
  "description": "ADHDev standalone daemon — embedded HTTP/WS server for local dashboard",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1210,6 +1210,7 @@ function summarizeCompactSubmodules(submodules) {
1210
1210
  ...outOfSync.length > 0 ? { outOfSyncPaths: outOfSync } : {}
1211
1211
  };
1212
1212
  }
1213
+ var MESH_COMPACT_PRESERVED_MARKER_FIELDS = ["dataFreshness"];
1213
1214
  function compactMeshStatusNode(entry) {
1214
1215
  if (!entry || typeof entry !== "object") return entry;
1215
1216
  const next = { ...entry };
@@ -1248,8 +1249,9 @@ function compactMeshStatusNode(entry) {
1248
1249
  };
1249
1250
  }
1250
1251
  delete next.capabilityTagsByProvider;
1252
+ const elideSkip = /* @__PURE__ */ new Set(["git", "machine", "branchConvergence", "staleDaemonBuild", "sessions", ...MESH_COMPACT_PRESERVED_MARKER_FIELDS]);
1251
1253
  for (const k of Object.keys(next)) {
1252
- if (k === "git" || k === "machine" || k === "branchConvergence" || k === "staleDaemonBuild" || k === "sessions") continue;
1254
+ if (elideSkip.has(k)) continue;
1253
1255
  next[k] = elideLargeNestedValue(k, next[k]);
1254
1256
  }
1255
1257
  return next;
@@ -1289,6 +1291,10 @@ function minimalCompactNode(entry) {
1289
1291
  reason: entry.branchConvergence.reason,
1290
1292
  branch: entry.branchConvergence.branch
1291
1293
  } : void 0;
1294
+ const preservedMarkers = {};
1295
+ for (const field of MESH_COMPACT_PRESERVED_MARKER_FIELDS) {
1296
+ if (entry[field] !== void 0) preservedMarkers[field] = entry[field];
1297
+ }
1292
1298
  return {
1293
1299
  nodeId: entry.nodeId,
1294
1300
  workspace: entry.workspace,
@@ -1303,6 +1309,7 @@ function minimalCompactNode(entry) {
1303
1309
  ...entry.launchBlockedReason !== void 0 ? { launchBlockedReason: entry.launchBlockedReason } : {},
1304
1310
  ...bc ? { branchConvergence: bc } : {},
1305
1311
  ...entry.sessionSummary ? { sessionSummary: entry.sessionSummary } : {},
1312
+ ...preservedMarkers,
1306
1313
  folded: true
1307
1314
  };
1308
1315
  }
@@ -2492,6 +2499,18 @@ var MESH_FAST_FORWARD_NODE_TOOL = {
2492
2499
  required: ["node_id"]
2493
2500
  }
2494
2501
  };
2502
+ var MESH_RESTART_DAEMON_TOOL = {
2503
+ name: "mesh_restart_daemon",
2504
+ 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.`,
2505
+ inputSchema: {
2506
+ type: "object",
2507
+ properties: {
2508
+ node_id: { type: "string", description: "Target node ID \u2014 the daemon that owns this node is updated and restarted." },
2509
+ 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." }
2510
+ },
2511
+ required: ["node_id"]
2512
+ }
2513
+ };
2495
2514
  var MESH_CHECKPOINT_TOOL = {
2496
2515
  name: "mesh_checkpoint",
2497
2516
  description: "Create a git checkpoint (commit) on a mesh node workspace.",
@@ -2767,6 +2786,7 @@ var ALL_MESH_TOOLS = [
2767
2786
  MESH_GIT_STATUS_TOOL,
2768
2787
  MESH_READ_NODE_LOGS_TOOL,
2769
2788
  MESH_FAST_FORWARD_NODE_TOOL,
2789
+ MESH_RESTART_DAEMON_TOOL,
2770
2790
  MESH_CHECKPOINT_TOOL,
2771
2791
  MESH_APPROVE_TOOL,
2772
2792
  MESH_CLONE_NODE_TOOL,
@@ -2805,6 +2825,7 @@ async function meshStatus(ctx, args = {}) {
2805
2825
  ...getNodeLaunchReadiness(node),
2806
2826
  ...buildNodeCapabilityExposure(node)
2807
2827
  };
2828
+ let liveTruthProbed = false;
2808
2829
  try {
2809
2830
  const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
2810
2831
  const statusResult = await commandForNode(ctx, node, "git_status", {
@@ -2813,6 +2834,7 @@ async function meshStatus(ctx, args = {}) {
2813
2834
  includeSubmodules: autoDiscover,
2814
2835
  submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
2815
2836
  });
2837
+ liveTruthProbed = true;
2816
2838
  const status = extractGitStatus(statusResult);
2817
2839
  const uncommittedChanges = countUncommittedChanges(status);
2818
2840
  const dirty = isGitStatusDirty(status);
@@ -2848,6 +2870,13 @@ async function meshStatus(ctx, args = {}) {
2848
2870
  noFallbackReason: failure.noFallbackReason
2849
2871
  });
2850
2872
  }
2873
+ entry.dataFreshness = (0, import_daemon_core.buildMeshNodeProbeFreshness)({
2874
+ git: entry.git,
2875
+ liveTruthProbed,
2876
+ isSelfNode: entry.machine?.sameMachine === true,
2877
+ daemonId: readNodeDaemonId(node),
2878
+ node
2879
+ });
2851
2880
  const recoveryContext = (0, import_daemon_core.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
2852
2881
  if (recoveryContext.consecutiveNodeFailures > 0) {
2853
2882
  entry.recoveryHints = {
@@ -3718,6 +3747,19 @@ async function meshSendTask(ctx, args) {
3718
3747
  if (node.policy?.readOnly) {
3719
3748
  return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
3720
3749
  }
3750
+ if (taskMode === "convergence" && node.isLocalWorktree === true) {
3751
+ return JSON.stringify({
3752
+ success: false,
3753
+ recoverable: true,
3754
+ code: "mesh_convergence_target_is_worktree",
3755
+ reason: "mesh_convergence_target_is_worktree",
3756
+ nodeId: args.node_id,
3757
+ sessionId: args.session_id,
3758
+ taskMode,
3759
+ error: `Node '${args.node_id}' is a worktree clone; a convergence task is base-only (it merges/pushes onto base). Dispatching it to a worktree session risks a multi-worktree push/deploy race.`,
3760
+ nextAction: `Dispatch the convergence task to the base node for this mesh, or run the deterministic fast-forward convergence path (mesh_fast_forward_node / mesh_refine_node) instead of mesh_send_task.`
3761
+ });
3762
+ }
3721
3763
  let explicitTargetSession;
3722
3764
  if (args.session_id && isWorkerTaskMode(taskMode)) {
3723
3765
  try {
@@ -4420,6 +4462,26 @@ async function meshFastForwardNode(ctx, args) {
4420
4462
  }, null, 2);
4421
4463
  }
4422
4464
  }
4465
+ async function meshRestartDaemon(ctx, args) {
4466
+ await refreshMeshFromDaemon(ctx);
4467
+ const node = await findNodeWithRefresh(ctx, args.node_id);
4468
+ try {
4469
+ const result = await commandForNode(ctx, node, "restart_daemon_node", {
4470
+ meshId: ctx.mesh.id,
4471
+ nodeId: node.id,
4472
+ inlineMesh: ctx.mesh,
4473
+ ...args.channel ? { channel: args.channel } : {}
4474
+ });
4475
+ return JSON.stringify(unwrapCommandPayload(result), null, 2);
4476
+ } catch (e) {
4477
+ const failure = buildCoordinatorP2pRelayFailure(e, {
4478
+ command: "restart_daemon_node",
4479
+ targetDaemonId: node.daemonId,
4480
+ nodeId: args.node_id
4481
+ });
4482
+ return JSON.stringify(failure, null, 2);
4483
+ }
4484
+ }
4423
4485
  async function meshCheckpoint(ctx, args) {
4424
4486
  const node = await findNodeWithRefresh(ctx, args.node_id);
4425
4487
  if (node.policy?.readOnly) {
@@ -5842,6 +5904,9 @@ async function startMcpServer(opts) {
5842
5904
  case "mesh_fast_forward_node":
5843
5905
  text = await meshFastForwardNode(meshCtx, a);
5844
5906
  break;
5907
+ case "mesh_restart_daemon":
5908
+ text = await meshRestartDaemon(meshCtx, a);
5909
+ break;
5845
5910
  case "mesh_checkpoint":
5846
5911
  text = await meshCheckpoint(meshCtx, a);
5847
5912
  break;