@adhdev/daemon-standalone 0.9.82-rc.380 → 0.9.82-rc.381

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/public/index.html CHANGED
@@ -7,9 +7,9 @@
7
7
  <meta name="description" content="ADHDev self-hosted dashboard for controlling AI agents" />
8
8
  <link rel="icon" href="/otter-logo.png" />
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-Mk-rTdMe.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-3wtc5vDj.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-BHUMCOj6.js">
12
- <link rel="stylesheet" crossorigin href="/assets/index-CSQu5pix.css">
12
+ <link rel="stylesheet" crossorigin href="/assets/index-BirH1m9z.css">
13
13
  </head>
14
14
  <body>
15
15
  <!-- Apply theme immediately to prevent FOIT (Flash of Incorrect Theme) -->
@@ -685,8 +685,10 @@ var MESH_ENQUEUE_TASK_TOOL = {
685
685
  taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
686
686
  requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
687
687
  required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
688
- target_node_id: { type: "string", description: "Optional: only this node may claim the task. Use to route a queued task to a specific (e.g. freshly cloned) worktree node instead of letting the first idle base node claim it. Takes priority over prefer_worktree." },
688
+ target_node_id: { type: "string", description: "Optional HARD constraint: ONLY this node may claim the task. No other node (especially a different machine) will ever claim it \u2014 if the target node has no idle session the task stays pending until it does. Use to route a queued task to a specific (e.g. freshly cloned) worktree node instead of letting the first idle base node claim it. Takes priority over prefer_worktree. An unresolvable target id is rejected at enqueue (no silent unpin)." },
689
689
  targetNodeId: { type: "string", description: "CamelCase alias for target_node_id." },
690
+ target_node: { type: "string", description: "Alias for target_node_id." },
691
+ targetNode: { type: "string", description: "CamelCase alias for target_node_id." },
690
692
  prefer_worktree: { type: "boolean", description: "Optional: when true, route this task to the most recently cloned idle worktree node (avoids the main/base workspace preemptively claiming an isolated task). No-op if no worktree node exists; resolves to a target_node_id when one does." },
691
693
  preferWorktree: { type: "boolean", description: "CamelCase alias for prefer_worktree." },
692
694
  depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
@@ -2930,6 +2932,8 @@ async function meshStatus(ctx, args = {}) {
2930
2932
  await refreshMeshFromDaemon(ctx);
2931
2933
  const { mesh, transport } = ctx;
2932
2934
  let ledgerSummary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
2935
+ const schedulingRuntime = (0, import_daemon_core3.buildMeshSchedulingRuntime)(mesh, (0, import_daemon_core3.getQueue)(mesh.id));
2936
+ const schedulingByNode = new Map(schedulingRuntime.nodes.map((n) => [n.nodeId, n]));
2933
2937
  const results = await Promise.all(mesh.nodes.map(async (node) => {
2934
2938
  const entry = {
2935
2939
  nodeId: node.id,
@@ -2940,6 +2944,11 @@ async function meshStatus(ctx, args = {}) {
2940
2944
  ...getNodeLaunchReadiness(node),
2941
2945
  ...buildNodeCapabilityExposure(node)
2942
2946
  };
2947
+ const nodeScheduling = schedulingByNode.get(node.id);
2948
+ if (nodeScheduling) {
2949
+ const { nodeId: _omit, ...rest } = nodeScheduling;
2950
+ entry.scheduling = compact ? { load: rest.load, capReached: rest.capReached } : rest;
2951
+ }
2943
2952
  let liveTruthProbed = false;
2944
2953
  try {
2945
2954
  const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
@@ -3220,6 +3229,18 @@ async function meshStatus(ctx, args = {}) {
3220
3229
  meshName: mesh.name,
3221
3230
  repoIdentity: mesh.repoIdentity,
3222
3231
  policy: mesh.policy,
3232
+ // Mesh-level scheduling rollup (strategy + global cap consumption). Per-node
3233
+ // detail (load/priority/provider caps/claim-block reasons) lives on each
3234
+ // nodes[].scheduling; the node array is dropped here to avoid duplicating it.
3235
+ scheduling: {
3236
+ strategy: schedulingRuntime.strategy,
3237
+ maxParallelTasks: schedulingRuntime.maxParallelTasks,
3238
+ maxReadonlyParallelTasks: schedulingRuntime.maxReadonlyParallelTasks,
3239
+ activeWriteAssigned: schedulingRuntime.activeWriteAssigned,
3240
+ activeReadonlyAssigned: schedulingRuntime.activeReadonlyAssigned,
3241
+ globalWriteCapReached: schedulingRuntime.globalWriteCapReached,
3242
+ globalReadonlyCapReached: schedulingRuntime.globalReadonlyCapReached
3243
+ },
3223
3244
  payloadMode: compact ? "compact" : "full",
3224
3245
  refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
3225
3246
  sourceOfTruth: {
@@ -3364,9 +3385,24 @@ async function meshEnqueueTask(ctx, args) {
3364
3385
  const requiredTags = (0, import_daemon_core3.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
3365
3386
  const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
3366
3387
  const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
3367
- const explicitTarget = readString(args.targetNodeId) || readString(args.target_node_id) || void 0;
3388
+ const explicitTargetRaw = readString(args.targetNodeId) || readString(args.target_node_id) || readString(args.targetNode) || readString(args.target_node) || void 0;
3368
3389
  const preferWorktree = args.preferWorktree === true || args.prefer_worktree === true;
3369
- const targetNodeId = explicitTarget || (preferWorktree ? resolvePreferredWorktreeNodeId(ctx) : void 0);
3390
+ let targetNodeId;
3391
+ if (explicitTargetRaw) {
3392
+ const matched = ctx.mesh.nodes.find((n) => (0, import_daemon_core3.meshNodeIdMatches)(n, explicitTargetRaw));
3393
+ if (!matched) {
3394
+ return JSON.stringify({
3395
+ success: false,
3396
+ code: "target_node_not_found",
3397
+ error: `target node '${explicitTargetRaw}' is not a member of this mesh \u2014 refusing to enqueue an unpinned task (it could be claimed by any node, including a different machine). Use mesh_list_nodes to get a valid node id.`,
3398
+ targetNodeId: explicitTargetRaw,
3399
+ availableNodeIds: ctx.mesh.nodes.map((n) => n.id).filter(Boolean)
3400
+ });
3401
+ }
3402
+ targetNodeId = readString(matched.id) || explicitTargetRaw;
3403
+ } else if (preferWorktree) {
3404
+ targetNodeId = resolvePreferredWorktreeNodeId(ctx) || void 0;
3405
+ }
3370
3406
  try {
3371
3407
  const task = (0, import_daemon_core3.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId, targetNodeId, ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {} });
3372
3408
  if (!(ctx.transport instanceof IpcTransport)) {
@@ -3379,7 +3415,7 @@ async function meshEnqueueTask(ctx, args) {
3379
3415
  taskMode: task.taskMode,
3380
3416
  requiredTags: task.requiredTags,
3381
3417
  ...targetNodeId ? { targetNodeId } : {},
3382
- ...preferWorktree && !explicitTarget && !targetNodeId ? { preferWorktreeNoOp: true } : {},
3418
+ ...preferWorktree && !explicitTargetRaw && !targetNodeId ? { preferWorktreeNoOp: true } : {},
3383
3419
  queueTrigger,
3384
3420
  ...buildQueueTriggerGuidance(queueTrigger)
3385
3421
  });
@@ -3446,7 +3482,7 @@ async function meshEnqueueTask(ctx, args) {
3446
3482
  taskMode: task.taskMode,
3447
3483
  requiredTags: task.requiredTags,
3448
3484
  ...targetNodeId ? { targetNodeId } : {},
3449
- ...preferWorktree && !explicitTarget && !targetNodeId ? { preferWorktreeNoOp: true } : {},
3485
+ ...preferWorktree && !explicitTargetRaw && !targetNodeId ? { preferWorktreeNoOp: true } : {},
3450
3486
  queueTrigger,
3451
3487
  ...buildQueueTriggerGuidance(queueTrigger)
3452
3488
  });
@@ -3591,11 +3627,31 @@ async function meshQueueCancel(ctx, args) {
3591
3627
  try {
3592
3628
  const taskId = (args.task_id || args.taskId || "").trim();
3593
3629
  if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
3630
+ const preCancel = (0, import_daemon_core3.getQueue)(ctx.mesh.id).find((t) => t?.id === taskId);
3631
+ const wasAssigned = preCancel?.status === "assigned";
3632
+ const assignedSessionId = readString(preCancel?.assignedSessionId) || void 0;
3633
+ const assignedNodeId = readString(preCancel?.assignedNodeId) || void 0;
3634
+ const assignedProviderType = readString(preCancel?.assignedProviderType) || void 0;
3594
3635
  const task = (0, import_daemon_core3.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
3595
3636
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
3596
3637
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
3597
3638
  });
3598
- return JSON.stringify({ success: true, task }, null, 2);
3639
+ let workerStop = { attempted: false };
3640
+ if (wasAssigned && assignedSessionId && assignedSessionId !== ctx.coordinatorSessionId && assignedProviderType) {
3641
+ workerStop = { attempted: true, sessionId: assignedSessionId, nodeId: assignedNodeId };
3642
+ ctx.transport.command("agent_command", {
3643
+ targetSessionId: assignedSessionId,
3644
+ cliType: assignedProviderType,
3645
+ agentType: assignedProviderType,
3646
+ action: "stop",
3647
+ ...assignedNodeId ? { meshContext: { meshId: ctx.mesh.id, nodeId: assignedNodeId, taskId } } : {}
3648
+ }).catch((e) => {
3649
+ workerStop.reason = e?.message || String(e);
3650
+ });
3651
+ } else if (wasAssigned && assignedSessionId === ctx.coordinatorSessionId) {
3652
+ workerStop = { attempted: false, reason: "assigned_session_is_coordinator_self \u2014 stop suppressed" };
3653
+ }
3654
+ return JSON.stringify({ success: true, task, workerStop }, null, 2);
3599
3655
  } catch (e) {
3600
3656
  return JSON.stringify({ success: false, error: e.message });
3601
3657
  }