@adhdev/daemon-standalone 1.0.45-rc.8 → 1.0.47

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-CH6cVfIs.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-D3K4NyAl.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-DgFmqOGd.js">
12
- <link rel="stylesheet" crossorigin href="/assets/index-TrEtD2r5.css">
12
+ <link rel="stylesheet" crossorigin href="/assets/index-CEPCfMWL.css">
13
13
  </head>
14
14
  <body>
15
15
  <!-- Apply theme immediately to prevent FOIT (Flash of Incorrect Theme) -->
@@ -996,7 +996,7 @@ var MESH_ENQUEUE_TASK_TOOL = {
996
996
  priority: { type: "string", enum: ["low", "normal", "high"], description: "G6 (task-level scheduling priority). Within the claim tier a high task is pulled ahead of an older normal/low task (created_at is the tie-break); low is pulled last. Defaults to normal. This is the TASK priority (which task a node pulls first) \u2014 distinct from a node's schedulingPriority (which node work goes to). Use high to jump an urgent fix ahead of a backlog without cancelling the queue." },
997
997
  model: { type: "string", description: "Optional model override for the agent that runs this task, e.g. opus, sonnet, haiku. Best-effort: applied at launch for providers that support a model flag (claude-cli --model, ACP setConfigOption); ignored by providers that cannot honor it. Use a cheaper model for simple tasks to save tokens, a stronger one for hard work. Blank = the provider default." },
998
998
  thinkingLevel: { type: "string", enum: ["low", "medium", "high"], description: "Optional reasoning-effort level for this task. Best-effort: applied at launch for providers that support it (claude-cli --effort, codex-cli reasoning effort, ACP thought_level); ignored otherwise. Use low for simple tasks (fewer tokens), high for hard reasoning." },
999
- difficulty: { type: "string", enum: ["easy", "medium", "difficult", "freeform"], description: "Optional task execution difficulty \u2014 a ROUTING HINT, not a model selector. It is matched against each node's capability slots so the task lands on a slot configured for that difficulty, and THAT SLOT's own model + thinkingLevel are what launch. It does not by itself mean a cheaper or stronger model: to change what a difficulty runs on, edit the node's slots (mesh_node_slots_set) rather than picking a different difficulty. Classify each task by how hard the work actually is. An explicit model/thinkingLevel above always wins." },
999
+ difficulty: { type: "string", enum: ["easy", "medium", "difficult", "freeform"], description: "REQUIRED task execution difficulty \u2014 a ROUTING HINT, not a model selector. It is matched against each node's capability slots so the task lands on a slot configured for that difficulty, and THAT SLOT's own model + thinkingLevel are what launch. It does not by itself mean a cheaper or stronger model: to change what a difficulty runs on, edit the node's slots (mesh_node_slots_set) rather than picking a different difficulty. Classify each task by how hard the work actually is. An explicit model/thinkingLevel above always wins." },
1000
1000
  notBefore: { type: "number", description: "CamelCase alias for not_before. Also accepts an ISO-8601 timestamp string." },
1001
1001
  max_retries: { type: "number", description: "P3 (retry cap). Max automatic requeue attempts before the task auto-fails instead of returning to pending. When requeueCount reaches this, mesh_queue_requeue auto-fails the task unless force=true. Omit to use the mesh policy default (maxTaskRetries, typically 1)." },
1002
1002
  maxRetries: { type: "number", description: "CamelCase alias for max_retries." },
@@ -1005,7 +1005,7 @@ var MESH_ENQUEUE_TASK_TOOL = {
1005
1005
  allow_duplicate: { type: "boolean", description: "G4. Set true to skip duplicate detection entirely (no warning, no block) for an intentional re-enqueue of the same instruction." },
1006
1006
  allowDuplicate: { type: "boolean", description: "CamelCase alias for allow_duplicate." }
1007
1007
  },
1008
- required: ["message"]
1008
+ required: ["message", "difficulty"]
1009
1009
  }
1010
1010
  };
1011
1011
  var MESH_VIEW_QUEUE_TOOL = {
@@ -1072,9 +1072,10 @@ var MESH_SEND_TASK_TOOL = {
1072
1072
  readonly: { type: "boolean", description: "Optional read-only axis (orthogonal to task_mode). When true the task runs without write isolation, is counted under the read-only cap, and rejects write/commit/push/deploy/destructive instructions like live_debug_readonly. Composable with any task_mode." },
1073
1073
  read_only: { type: "boolean", description: "Snake-case alias for readonly." },
1074
1074
  mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id). When set, the directly dispatched task is attributed to the mission task aggregates exactly like mesh_enqueue_task, including terminal completion. Omit for an unattributed direct dispatch." },
1075
- missionId: { type: "string", description: "CamelCase alias for mission_id." }
1075
+ missionId: { type: "string", description: "CamelCase alias for mission_id." },
1076
+ difficulty: { type: "string", enum: ["easy", "medium", "difficult", "freeform"], description: "REQUIRED task execution difficulty. Classify each task by how hard the work actually is. On a direct dispatch the target node/session is already chosen, so difficulty is not used to ROUTE \u2014 it is recorded on the task so scheduling analytics, mission aggregates and (critically) failure-recovery relaunch all see the same axis a queued task carries. A recovery relaunch inherits this value from the ledger, so an unclassified direct dispatch would silently downgrade its own retry." }
1076
1077
  },
1077
- required: ["node_id", "session_id", "message"]
1078
+ required: ["node_id", "session_id", "message", "difficulty"]
1078
1079
  }
1079
1080
  };
1080
1081
  var MESH_READ_CHAT_TOOL = {
@@ -3159,6 +3160,8 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
3159
3160
  cliType: resolvedProviderType,
3160
3161
  action: "send_chat",
3161
3162
  message: args.message,
3163
+ // DISPATCH-SOURCE-TRACE: call-site tag echoed in the worker daemon log.
3164
+ dispatchSource: "mesh-tools-internal:ipcDispatchToRemoteAgent",
3162
3165
  // WTCLAIM (B): carry the node workspace so a sessionless dispatch can be
3163
3166
  // scoped to THIS node's session on the worker (findAdapter dir match /
3164
3167
  // findMeshNodeAdapter). Without it, a worker hosting both a base node and a
@@ -3342,10 +3345,10 @@ async function collectRelatedRepoStatuses(ctx, node) {
3342
3345
  return results;
3343
3346
  }
3344
3347
  function readProviderPriority(policy) {
3348
+ const fromSlots = deriveProviderPriorityFromSlots(policy?.slots);
3349
+ if (fromSlots.length) return fromSlots;
3345
3350
  const raw = policy?.providerPriority;
3346
- const explicit = Array.isArray(raw) ? raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
3347
- if (explicit.length) return explicit;
3348
- return deriveProviderPriorityFromSlots(policy?.slots);
3351
+ return Array.isArray(raw) ? raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
3349
3352
  }
3350
3353
  function readNodeSupportedProviders(policy) {
3351
3354
  const seen = /* @__PURE__ */ new Set();
@@ -3929,6 +3932,10 @@ async function meshStatus(ctx, args = {}) {
3929
3932
  const { nodeId: _omit, ...rest } = nodeScheduling;
3930
3933
  entry.scheduling = compact ? { load: rest.load, capReached: rest.capReached } : rest;
3931
3934
  }
3935
+ const lastQuotaRanking = (0, import_daemon_core5.getLastQuotaRanking)(node.id);
3936
+ if (lastQuotaRanking) {
3937
+ entry.scheduling = { ...entry.scheduling ?? {}, lastQuotaRanking };
3938
+ }
3932
3939
  let liveTruthProbed = false;
3933
3940
  try {
3934
3941
  const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
@@ -6236,6 +6243,22 @@ Target: ${args.target}` : ""}`,
6236
6243
  const task = (0, import_daemon_core5.enqueueTask)(ctx.mesh.id, prompt, {
6237
6244
  readonly: true,
6238
6245
  taskMode: "live_debug_readonly",
6246
+ // DIFFICULTY-REQUIRED (MAGI decision): a fixed 'freeform' sentinel, NOT an
6247
+ // exemption from the guard. MAGI routes on a different axis entirely — each
6248
+ // replica is already hard-pinned to a (node, provider) slot by the kind-panel
6249
+ // via requiredTags (`provider=<X>`) and often an explicit targetNodeId, and
6250
+ // its model comes from that slot. Difficulty exists to MATCH a task against
6251
+ // node capability slots at assignment time; here the slot is already chosen,
6252
+ // so any difficulty we stamped would be inert at best and would fight the
6253
+ // panel's own slot selection at worst.
6254
+ //
6255
+ // 'freeform' is the correct sentinel rather than a guard bypass: it is a real
6256
+ // member of the axis meaning "no difficulty-based constraint", so the fan-out
6257
+ // satisfies the required-difficulty invariant honestly instead of carving out
6258
+ // a hole that a future non-MAGI caller could slip through. Deliberately NOT
6259
+ // caller-configurable — exposing a difficulty knob on mesh_magi_review would
6260
+ // imply it influences replica placement, which it does not.
6261
+ difficulty: "freeform",
6239
6262
  requiredTags: replica.requiredTags,
6240
6263
  missionId: mission.id,
6241
6264
  consensusGroupId,
@@ -6686,6 +6709,8 @@ ${magiOutputContractFor(kind)}`;
6686
6709
  agentType: task.assignedProviderType,
6687
6710
  action: "send_chat",
6688
6711
  message,
6712
+ // DISPATCH-SOURCE-TRACE: call-site tag echoed in the worker daemon log.
6713
+ dispatchSource: "mesh-tools-magi:sendKindRetry",
6689
6714
  meshContext: {
6690
6715
  meshId: ctx.mesh.id,
6691
6716
  nodeId: task.assignedNodeId,
@@ -7035,6 +7060,7 @@ async function meshNodeSlotsPropose(ctx, args = {}) {
7035
7060
  }
7036
7061
 
7037
7062
  // src/tools/mesh-tools-session.ts
7063
+ var import_daemon_core7 = require("@adhdev/daemon-core");
7038
7064
  function computeIdleDispatchAckRisk(sessionWasIdle, dispatchPreRecorded, sessionId) {
7039
7065
  if (!sessionWasIdle || dispatchPreRecorded) return {};
7040
7066
  return {
@@ -7103,6 +7129,16 @@ async function meshSendTask(ctx, args) {
7103
7129
  const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
7104
7130
  const readonly = args.readonly === true || args.read_only === true;
7105
7131
  const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
7132
+ const difficultyRaw = readString(args.difficulty);
7133
+ if (!difficultyRaw || !isMeshTaskDifficulty(difficultyRaw)) {
7134
+ return JSON.stringify({
7135
+ success: false,
7136
+ code: difficultyRaw ? "invalid_difficulty" : "missing_difficulty",
7137
+ error: difficultyRaw ? `mesh_send_task received an unrecognized \`difficulty\` value '${difficultyRaw}'. Allowed: ${MESH_TASK_DIFFICULTIES.join(" | ")}.` : `mesh_send_task requires a \`difficulty\`. Allowed: ${MESH_TASK_DIFFICULTIES.join(" | ")}. Classify the task by how hard the work actually is.`,
7138
+ allowedDifficulties: MESH_TASK_DIFFICULTIES
7139
+ });
7140
+ }
7141
+ const difficulty = difficultyRaw;
7106
7142
  const modeValidation = (0, import_daemon_core5.validateMeshTaskModeRequest)(requestedTaskMode, message, readonly);
7107
7143
  if (!modeValidation.valid) {
7108
7144
  return JSON.stringify({
@@ -7252,6 +7288,7 @@ async function meshSendTask(ctx, args) {
7252
7288
  assignedNodeId: args.node_id,
7253
7289
  assignedSessionId: dispatchedSessionId,
7254
7290
  taskMode,
7291
+ difficulty,
7255
7292
  ...readonly ? { readonly: true } : {},
7256
7293
  dispatchedAt
7257
7294
  });
@@ -7354,6 +7391,7 @@ async function meshSendTask(ctx, args) {
7354
7391
  targetNodeId: args.node_id,
7355
7392
  targetSessionId: args.session_id,
7356
7393
  taskMode,
7394
+ difficulty,
7357
7395
  ...readonly ? { readonly: true } : {},
7358
7396
  ...missionId ? { missionId } : {},
7359
7397
  ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {}
@@ -7423,6 +7461,8 @@ async function meshSendTask(ctx, args) {
7423
7461
  providerType: resolvedProviderType,
7424
7462
  action: "send_chat",
7425
7463
  message,
7464
+ // DISPATCH-SOURCE-TRACE: call-site tag echoed in the worker daemon log.
7465
+ dispatchSource: "mesh-tools-session:mesh_send_task:direct",
7426
7466
  meshContext: {
7427
7467
  meshId: ctx.mesh.id,
7428
7468
  nodeId: args.node_id,
@@ -7455,6 +7495,7 @@ async function meshSendTask(ctx, args) {
7455
7495
  assignedNodeId: args.node_id,
7456
7496
  assignedSessionId: args.session_id,
7457
7497
  taskMode,
7498
+ difficulty,
7458
7499
  ...readonly ? { readonly: true } : {},
7459
7500
  dispatchedAt
7460
7501
  });
@@ -7498,6 +7539,7 @@ async function meshSendTask(ctx, args) {
7498
7539
  targetNodeId: args.node_id,
7499
7540
  targetSessionId: args.session_id,
7500
7541
  taskMode,
7542
+ difficulty,
7501
7543
  ...readonly ? { readonly: true } : {},
7502
7544
  ...missionId ? { missionId } : {},
7503
7545
  ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {}
@@ -7692,6 +7734,7 @@ async function meshLaunchSession(ctx, args) {
7692
7734
  {
7693
7735
  const requestedType = typeof args.type === "string" && args.type.trim() ? args.type.trim() : "";
7694
7736
  let resolvedProviderType = requestedType;
7737
+ let explicitTypeQuotaWarning = null;
7695
7738
  if (requestedType) {
7696
7739
  const slotProviders = normalizeNodeCapabilitySlots(node.policy?.slots).map((s) => s.provider);
7697
7740
  if (slotProviders.length && !slotProviders.includes(requestedType)) {
@@ -7704,6 +7747,19 @@ async function meshLaunchSession(ctx, args) {
7704
7747
  supportedProviders: slotProviders
7705
7748
  }, null, 2);
7706
7749
  }
7750
+ const explicitBlock = (0, import_daemon_core7.evaluateProviderQuotaGate)(node, requestedType, ctx.mesh.policy?.quotaRouting ?? null);
7751
+ if (explicitBlock) {
7752
+ explicitTypeQuotaWarning = {
7753
+ quotaWarning: `Provider '${requestedType}' on node '${args.node_id}' is quota-gated (${explicitBlock.reason}; ${explicitBlock.window} window at ${explicitBlock.remainingPercent}% remaining, threshold ${explicitBlock.thresholdPercent}%). Launching anyway because the type was requested explicitly \u2014 the session may fail immediately if the provider rejects on quota.`,
7754
+ quotaBlock: {
7755
+ providerType: requestedType,
7756
+ reason: explicitBlock.reason,
7757
+ window: explicitBlock.window,
7758
+ remainingPercent: explicitBlock.remainingPercent,
7759
+ thresholdPercent: explicitBlock.thresholdPercent
7760
+ }
7761
+ };
7762
+ }
7707
7763
  }
7708
7764
  if (!resolvedProviderType) {
7709
7765
  const providerPriority = readProviderPriority(node.policy);
@@ -7711,6 +7767,7 @@ async function meshLaunchSession(ctx, args) {
7711
7767
  return JSON.stringify({ success: false, error: missingProviderPriorityMessage(args.node_id) });
7712
7768
  }
7713
7769
  const failed = [];
7770
+ const detectedCandidates = [];
7714
7771
  let unreachableError = null;
7715
7772
  for (const providerType of providerPriority) {
7716
7773
  let detectedPayload;
@@ -7722,11 +7779,33 @@ async function meshLaunchSession(ctx, args) {
7722
7779
  break;
7723
7780
  }
7724
7781
  if (detectedPayload?.success && detectedPayload?.detected) {
7725
- resolvedProviderType = providerType;
7726
- break;
7782
+ if (!detectedCandidates.includes(providerType)) detectedCandidates.push(providerType);
7783
+ continue;
7727
7784
  }
7728
7785
  failed.push(`${providerType}: ${detectedPayload?.error || "not detected"}`);
7729
7786
  }
7787
+ if (detectedCandidates.length) {
7788
+ const ranked = (0, import_daemon_core7.rankProvidersByQuotaGate)(node, detectedCandidates, ctx.mesh.policy?.quotaRouting ?? null);
7789
+ if (ranked.clear.length) {
7790
+ resolvedProviderType = ranked.clear[0];
7791
+ } else {
7792
+ const detail = ranked.gated.map((g) => `${g.providerType}: ${g.block.reason}`).join("; ");
7793
+ return JSON.stringify({
7794
+ success: false,
7795
+ code: "mesh_all_providers_quota_gated",
7796
+ error: `Every detected provider on node '${args.node_id}' is quota-gated (${detail}). This is a WAIT, not a misconfiguration \u2014 the quota windows reset on their own.`,
7797
+ nodeId: args.node_id,
7798
+ gated: ranked.gated.map((g) => ({
7799
+ providerType: g.providerType,
7800
+ reason: g.block.reason,
7801
+ window: g.block.window,
7802
+ remainingPercent: g.block.remainingPercent,
7803
+ thresholdPercent: g.block.thresholdPercent
7804
+ })),
7805
+ nextAction: `Enqueue the work (mesh_enqueue_task) so the drain claims it when a window resets, or launch on a node whose providers still have quota. Retrying mesh_launch_session immediately will hit the same gate.`
7806
+ }, null, 2);
7807
+ }
7808
+ }
7730
7809
  if (!resolvedProviderType) {
7731
7810
  if (unreachableError) {
7732
7811
  return JSON.stringify({ success: false, error: `Node '${args.node_id}' is unreachable \u2014 cannot detect a provider (${unreachableError}). The node's daemon may be offline; retry once it reconnects.` });
@@ -7855,6 +7934,7 @@ async function meshLaunchSession(ctx, args) {
7855
7934
  ...launchPayload,
7856
7935
  resolvedProviderType,
7857
7936
  ...providerSessionId ? { providerSessionId } : {},
7937
+ ...explicitTypeQuotaWarning ?? {},
7858
7938
  queueTrigger,
7859
7939
  ...buildQueueTriggerGuidance(queueTrigger)
7860
7940
  }, null, 2);