@adhdev/daemon-standalone 1.0.45-rc.11 → 1.0.45-rc.12

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,7 +7,7 @@
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-digx-PVW.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-s_TifdFn.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-DgFmqOGd.js">
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-TrEtD2r5.css">
13
13
  </head>
@@ -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 = {
@@ -3344,10 +3345,10 @@ async function collectRelatedRepoStatuses(ctx, node) {
3344
3345
  return results;
3345
3346
  }
3346
3347
  function readProviderPriority(policy) {
3348
+ const fromSlots = deriveProviderPriorityFromSlots(policy?.slots);
3349
+ if (fromSlots.length) return fromSlots;
3347
3350
  const raw = policy?.providerPriority;
3348
- const explicit = Array.isArray(raw) ? raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
3349
- if (explicit.length) return explicit;
3350
- return deriveProviderPriorityFromSlots(policy?.slots);
3351
+ return Array.isArray(raw) ? raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
3351
3352
  }
3352
3353
  function readNodeSupportedProviders(policy) {
3353
3354
  const seen = /* @__PURE__ */ new Set();
@@ -6242,6 +6243,22 @@ Target: ${args.target}` : ""}`,
6242
6243
  const task = (0, import_daemon_core5.enqueueTask)(ctx.mesh.id, prompt, {
6243
6244
  readonly: true,
6244
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",
6245
6262
  requiredTags: replica.requiredTags,
6246
6263
  missionId: mission.id,
6247
6264
  consensusGroupId,
@@ -7043,6 +7060,7 @@ async function meshNodeSlotsPropose(ctx, args = {}) {
7043
7060
  }
7044
7061
 
7045
7062
  // src/tools/mesh-tools-session.ts
7063
+ var import_daemon_core7 = require("@adhdev/daemon-core");
7046
7064
  function computeIdleDispatchAckRisk(sessionWasIdle, dispatchPreRecorded, sessionId) {
7047
7065
  if (!sessionWasIdle || dispatchPreRecorded) return {};
7048
7066
  return {
@@ -7111,6 +7129,16 @@ async function meshSendTask(ctx, args) {
7111
7129
  const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
7112
7130
  const readonly = args.readonly === true || args.read_only === true;
7113
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;
7114
7142
  const modeValidation = (0, import_daemon_core5.validateMeshTaskModeRequest)(requestedTaskMode, message, readonly);
7115
7143
  if (!modeValidation.valid) {
7116
7144
  return JSON.stringify({
@@ -7260,6 +7288,7 @@ async function meshSendTask(ctx, args) {
7260
7288
  assignedNodeId: args.node_id,
7261
7289
  assignedSessionId: dispatchedSessionId,
7262
7290
  taskMode,
7291
+ difficulty,
7263
7292
  ...readonly ? { readonly: true } : {},
7264
7293
  dispatchedAt
7265
7294
  });
@@ -7362,6 +7391,7 @@ async function meshSendTask(ctx, args) {
7362
7391
  targetNodeId: args.node_id,
7363
7392
  targetSessionId: args.session_id,
7364
7393
  taskMode,
7394
+ difficulty,
7365
7395
  ...readonly ? { readonly: true } : {},
7366
7396
  ...missionId ? { missionId } : {},
7367
7397
  ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {}
@@ -7465,6 +7495,7 @@ async function meshSendTask(ctx, args) {
7465
7495
  assignedNodeId: args.node_id,
7466
7496
  assignedSessionId: args.session_id,
7467
7497
  taskMode,
7498
+ difficulty,
7468
7499
  ...readonly ? { readonly: true } : {},
7469
7500
  dispatchedAt
7470
7501
  });
@@ -7508,6 +7539,7 @@ async function meshSendTask(ctx, args) {
7508
7539
  targetNodeId: args.node_id,
7509
7540
  targetSessionId: args.session_id,
7510
7541
  taskMode,
7542
+ difficulty,
7511
7543
  ...readonly ? { readonly: true } : {},
7512
7544
  ...missionId ? { missionId } : {},
7513
7545
  ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {}
@@ -7702,6 +7734,7 @@ async function meshLaunchSession(ctx, args) {
7702
7734
  {
7703
7735
  const requestedType = typeof args.type === "string" && args.type.trim() ? args.type.trim() : "";
7704
7736
  let resolvedProviderType = requestedType;
7737
+ let explicitTypeQuotaWarning = null;
7705
7738
  if (requestedType) {
7706
7739
  const slotProviders = normalizeNodeCapabilitySlots(node.policy?.slots).map((s) => s.provider);
7707
7740
  if (slotProviders.length && !slotProviders.includes(requestedType)) {
@@ -7714,6 +7747,19 @@ async function meshLaunchSession(ctx, args) {
7714
7747
  supportedProviders: slotProviders
7715
7748
  }, null, 2);
7716
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
+ }
7717
7763
  }
7718
7764
  if (!resolvedProviderType) {
7719
7765
  const providerPriority = readProviderPriority(node.policy);
@@ -7721,6 +7767,7 @@ async function meshLaunchSession(ctx, args) {
7721
7767
  return JSON.stringify({ success: false, error: missingProviderPriorityMessage(args.node_id) });
7722
7768
  }
7723
7769
  const failed = [];
7770
+ const detectedCandidates = [];
7724
7771
  let unreachableError = null;
7725
7772
  for (const providerType of providerPriority) {
7726
7773
  let detectedPayload;
@@ -7732,11 +7779,33 @@ async function meshLaunchSession(ctx, args) {
7732
7779
  break;
7733
7780
  }
7734
7781
  if (detectedPayload?.success && detectedPayload?.detected) {
7735
- resolvedProviderType = providerType;
7736
- break;
7782
+ if (!detectedCandidates.includes(providerType)) detectedCandidates.push(providerType);
7783
+ continue;
7737
7784
  }
7738
7785
  failed.push(`${providerType}: ${detectedPayload?.error || "not detected"}`);
7739
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
+ }
7740
7809
  if (!resolvedProviderType) {
7741
7810
  if (unreachableError) {
7742
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.` });
@@ -7865,6 +7934,7 @@ async function meshLaunchSession(ctx, args) {
7865
7934
  ...launchPayload,
7866
7935
  resolvedProviderType,
7867
7936
  ...providerSessionId ? { providerSessionId } : {},
7937
+ ...explicitTypeQuotaWarning ?? {},
7868
7938
  queueTrigger,
7869
7939
  ...buildQueueTriggerGuidance(queueTrigger)
7870
7940
  }, null, 2);