@adhdev/daemon-core 0.9.82-rc.485 → 0.9.82-rc.487

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/dist/index.js CHANGED
@@ -145,7 +145,8 @@ var init_repo_mesh_types = __esm({
145
145
  "first_eligible",
146
146
  "least_loaded",
147
147
  "round_robin",
148
- "priority_only"
148
+ "priority_only",
149
+ "fitness"
149
150
  ];
150
151
  DEFAULT_MESH_SCHEDULING_STRATEGY = "first_eligible";
151
152
  MESH_CONVERGE_REFINE_TAG = "converge=refine";
@@ -157,7 +158,11 @@ var init_repo_mesh_types = __esm({
157
158
  allowAutoPublishSubmoduleMainCommits: false,
158
159
  requireApprovalForDestructiveGit: true,
159
160
  dirtyWorkspaceBehavior: "warn",
160
- maxParallelTasks: 2,
161
+ // Mesh-wide task cap is effectively unlimited by default: the real concurrency
162
+ // limits live per node / per capability slot (ORCHESTRATION_NODE_SLOTS.md), so a
163
+ // global ceiling is rarely meaningful. The UI hides this control; set it via the
164
+ // API only to impose a deliberate mesh-wide cap.
165
+ maxParallelTasks: 200,
161
166
  // Coordinator-spawned worker sessions default to hidden so the dashboard is not
162
167
  // flooded with mesh noise tabs/notifications. Users can still surface or unmute
163
168
  // any specific session manually; that override is preserved per-device.
@@ -409,10 +414,10 @@ function readInjected(value) {
409
414
  }
410
415
  function getDaemonBuildInfo() {
411
416
  if (cached) return cached;
412
- const commit = readInjected(true ? "ddf2eb6eafcf949165edfda2c3cf7c77597fa6f8" : void 0) ?? "unknown";
413
- const commitShort = readInjected(true ? "ddf2eb6e" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
414
- const version = readInjected(true ? "0.9.82-rc.485" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
415
- const builtAt = readInjected(true ? "2026-07-08T15:14:11.978Z" : void 0);
417
+ const commit = readInjected(true ? "ef3ded0f5df148982ed222411ea08c6ab0fdb39b" : void 0) ?? "unknown";
418
+ const commitShort = readInjected(true ? "ef3ded0f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
419
+ const version = readInjected(true ? "0.9.82-rc.487" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
420
+ const builtAt = readInjected(true ? "2026-07-10T01:53:17.254Z" : void 0);
416
421
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
417
422
  return cached;
418
423
  }
@@ -784,30 +789,41 @@ function isNonRuntimeRootFile(file, policy) {
784
789
  }
785
790
  return false;
786
791
  }
792
+ function classifyChangedFileList(files, policy) {
793
+ if (files.length === 0) {
794
+ return { isDaemonAffecting: true, affectedPackages: [] };
795
+ }
796
+ const pkgs = /* @__PURE__ */ new Set();
797
+ let sawRuntimeAmbiguousNonPackage = false;
798
+ for (const file of files) {
799
+ const match = file.match(/(?:^|\/)packages\/([^/]+)\//);
800
+ if (!match) {
801
+ if (!isNonRuntimeRootFile(file, policy)) sawRuntimeAmbiguousNonPackage = true;
802
+ continue;
803
+ }
804
+ pkgs.add(match[1]);
805
+ }
806
+ const affectedPackages = [...pkgs].sort();
807
+ const allBenign = !sawRuntimeAmbiguousNonPackage && affectedPackages.every((p) => policy.webOnlyPackages.has(p) && !policy.daemonRuntimePackages.has(p));
808
+ return { isDaemonAffecting: !allBenign, affectedPackages };
809
+ }
787
810
  async function classifyDaemonBuildChange(repoPath, buildCommit, options, policy) {
788
811
  try {
789
812
  const diff = await runGit(repoPath, ["diff", "--name-only", `${buildCommit}..HEAD`], options);
790
813
  const files = diff.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
791
- if (files.length === 0) {
792
- return { isDaemonAffecting: true, affectedPackages: [] };
793
- }
794
- const pkgs = /* @__PURE__ */ new Set();
795
- let sawRuntimeAmbiguousNonPackage = false;
796
- for (const file of files) {
797
- const match = file.match(/(?:^|\/)packages\/([^/]+)\//);
798
- if (!match) {
799
- if (!isNonRuntimeRootFile(file, policy)) sawRuntimeAmbiguousNonPackage = true;
800
- continue;
801
- }
802
- pkgs.add(match[1]);
803
- }
804
- const affectedPackages = [...pkgs].sort();
805
- const allBenign = !sawRuntimeAmbiguousNonPackage && affectedPackages.every((p) => policy.webOnlyPackages.has(p) && !policy.daemonRuntimePackages.has(p));
806
- return { isDaemonAffecting: !allBenign, affectedPackages };
814
+ return classifyChangedFileList(files, policy);
807
815
  } catch {
808
816
  return { isDaemonAffecting: true, affectedPackages: [] };
809
817
  }
810
818
  }
819
+ async function classifyChangedPackages(repoPath, fromRef, toRef, options = {}) {
820
+ const repo = await resolveGitRepository(repoPath, options);
821
+ const { config } = resolveChangeImpactConfigForRepo(repo.repoRoot, options);
822
+ const policy = resolveChangeImpactPolicy(config);
823
+ const diff = await runGit(repoPath, ["diff", "--name-only", `${fromRef}..${toRef}`], options);
824
+ const files = diff.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
825
+ return classifyChangedFileList(files, policy);
826
+ }
811
827
  function resolveChangeImpactConfigForRepo(repoRoot, options) {
812
828
  if (options.changeImpactConfig === null) {
813
829
  return { config: null, sourceKey: "forced-default" };
@@ -2798,6 +2814,74 @@ function normalizeDifficultyBrainMap(raw) {
2798
2814
  }
2799
2815
  return out;
2800
2816
  }
2817
+ function normalizeNodeCapabilitySlot(raw) {
2818
+ const r = raw && typeof raw === "object" ? raw : {};
2819
+ const provider = typeof r.provider === "string" ? r.provider.trim() : "";
2820
+ if (!provider) return null;
2821
+ const model = typeof r.model === "string" ? r.model.trim() : "";
2822
+ const thinkingLevel = typeof r.thinkingLevel === "string" ? r.thinkingLevel.trim() : "";
2823
+ const difficulty = Array.isArray(r.difficulty) ? r.difficulty.filter(isMeshTaskDifficulty) : [];
2824
+ const capability = Array.isArray(r.capability) ? r.capability.filter((t) => typeof t === "string" && !!t.trim()).map((t) => t.trim()) : [];
2825
+ const maxParallelNum = Number(r.maxParallel);
2826
+ const maxParallel = Number.isFinite(maxParallelNum) && maxParallelNum > 0 ? Math.floor(maxParallelNum) : void 0;
2827
+ return {
2828
+ provider,
2829
+ ...model ? { model } : {},
2830
+ ...thinkingLevel ? { thinkingLevel } : {},
2831
+ ...difficulty.length ? { difficulty } : {},
2832
+ ...capability.length ? { capability } : {},
2833
+ ...maxParallel !== void 0 ? { maxParallel } : {}
2834
+ };
2835
+ }
2836
+ function normalizeNodeCapabilitySlots(raw) {
2837
+ if (!Array.isArray(raw)) return [];
2838
+ const out = [];
2839
+ for (const entry of raw) {
2840
+ const slot = normalizeNodeCapabilitySlot(entry);
2841
+ if (slot) out.push(slot);
2842
+ }
2843
+ return out;
2844
+ }
2845
+ function deriveSlotsFromLegacy(input) {
2846
+ const priority = Array.isArray(input.providerPriority) ? input.providerPriority.filter((p) => typeof p === "string" && !!p.trim()).map((p) => p.trim()) : [];
2847
+ if (priority.length === 0) return [];
2848
+ const roleCap = /* @__PURE__ */ new Map();
2849
+ for (const role of input.providerRoles || []) {
2850
+ if (role && typeof role.providerType === "string" && Number.isFinite(role.maxParallel)) {
2851
+ roleCap.set(role.providerType.trim(), Math.floor(Number(role.maxParallel)));
2852
+ }
2853
+ }
2854
+ const brains = input.difficultyBrains || {};
2855
+ const byProvider = /* @__PURE__ */ new Map();
2856
+ const shared = [];
2857
+ for (const diff of MESH_TASK_DIFFICULTIES) {
2858
+ const b = brains[diff];
2859
+ if (!b) continue;
2860
+ const entry = { difficulty: diff, model: b.model, thinkingLevel: b.thinkingLevel };
2861
+ if (b.provider) {
2862
+ const list = byProvider.get(b.provider) ?? [];
2863
+ list.push(entry);
2864
+ byProvider.set(b.provider, list);
2865
+ } else {
2866
+ shared.push(entry);
2867
+ }
2868
+ }
2869
+ return priority.map((provider) => {
2870
+ const specific = byProvider.get(provider) || [];
2871
+ const applied = specific.length ? specific : shared;
2872
+ const difficulty = applied.map((a) => a.difficulty);
2873
+ const model = applied.find((a) => a.model)?.model;
2874
+ const thinkingLevel = applied.find((a) => a.thinkingLevel)?.thinkingLevel;
2875
+ const maxParallel = roleCap.get(provider);
2876
+ return {
2877
+ provider,
2878
+ ...model ? { model } : {},
2879
+ ...thinkingLevel ? { thinkingLevel } : {},
2880
+ ...difficulty.length ? { difficulty } : {},
2881
+ ...maxParallel !== void 0 ? { maxParallel } : {}
2882
+ };
2883
+ });
2884
+ }
2801
2885
  var DAEMON_ID_PREFIXES, MAGI_RAW_ANSWER_CAP, MESH_TASK_DIFFICULTIES, DEFAULT_DIFFICULTY_BRAINS, CANONICAL_MESH_TOOL_NAMES, CANONICAL_MESH_TOOL_COUNT;
2802
2886
  var init_dist = __esm({
2803
2887
  "../mesh-shared/dist/index.mjs"() {
@@ -2853,7 +2937,9 @@ var init_dist = __esm({
2853
2937
  "mesh_magi_review",
2854
2938
  "mesh_magi_collect",
2855
2939
  "mesh_magi_kind_panel_set",
2856
- "mesh_magi_kind_panel_list"
2940
+ "mesh_magi_kind_panel_list",
2941
+ "mesh_node_slots_set",
2942
+ "mesh_node_slots_list"
2857
2943
  ];
2858
2944
  CANONICAL_MESH_TOOL_COUNT = CANONICAL_MESH_TOOL_NAMES.length;
2859
2945
  }
@@ -3802,6 +3888,7 @@ function buildRulesSection(coordinatorCliType) {
3802
3888
  - **Reuse idle sessions.** For follow-up, retry, commit/push, or cleanup on the same issue, send only the delta to the existing idle session. Start fresh only for independent work, provider mismatch, transcript contamination, or required worktree isolation.
3803
3889
  - **Worktree affinity.** A worktree is a durable per-branch workspace; keep all of a branch's code_change/fix/review work on its worktree node by targeting \`required_tags: ["worktree=<branch>"]\` or \`target_node_id\`. Get the id/branch from the \`mesh_clone_node\` result or a live \`mesh_status\` \u2014 the Configured Nodes snapshot won't list a worktree cloned after launch. Untargeted same-branch follow-ups drift to the base node. Only \`convergence\` (merge/push) runs on the base, never pinned to the worktree.
3804
3890
  - **Classify task difficulty to save tokens.** For each task you enqueue, judge its execution difficulty and pass \`difficulty\`: \`easy\` (extraction, renames, doc tweaks, trivial fixes), \`medium\` (ordinary feature/bugfix work), \`difficult\` (architecture, tricky debugging, multi-file refactors, subtle reasoning), or \`freeform\`. The mesh's per-difficulty brain preset then runs easy tasks on a cheaper model at low reasoning effort and hard tasks on a stronger model at high effort \u2014 real token savings on simple work. The current presets are shown in the "Brain presets" section below. You may still pass an explicit \`model\`/\`thinkingLevel\` to override the preset for one task.
3891
+ - **Retune node profiles when routing is a poor fit \u2014 but only with approval.** A node's capability slots (its provider/model/thinking + difficulty range + capability tags, seen via \`mesh_node_slots_list\`) are what task\u2192node fitness routing matches against. If you notice a persistent mismatch \u2014 e.g. every \`difficult\` task lands on a node whose only slot is a cheap model, or a capability a node clearly has isn't declared \u2014 you MAY propose a slot change with \`mesh_node_slots_set\` (write=false). That returns current-vs-proposed; present that diff to the user with a one-line reason and apply (write=true) ONLY after they approve. It is a WHOLESALE replacement of the node's slots, so include the slots you want to keep. Never rewrite a node's profile silently or without a clear routing reason.
3805
3892
  - **Respect explicit provider requests.** Map: Hermes \u2192 \`hermes-cli\`, Claude/Claude Code \u2192 \`claude-cli\`, Codex \u2192 \`codex-cli\`, Gemini \u2192 \`gemini-cli\`, Antigravity \u2192 \`antigravity-cli\`. Never substitute the coordinator's own runtime.
3806
3893
  - **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
3807
3894
  - **Limit parallelism.** Start with 1\u20132 tasks; scale only on success. Never duplicate a session because \`mesh_read_chat\` shows no final message while tool/terminal activity is ongoing. This caps *concurrent* load \u2014 it does not mean serialize independent work: when a new, independent request arrives and there is headroom under \`maxParallelTasks\`, dispatch it right away rather than waiting for an in-flight task or a user nudge (read-only diagnosis especially, since it has no merge cost).
@@ -3882,7 +3969,9 @@ var init_coordinator_prompt = __esm({
3882
3969
  | \`mesh_magi_review\` | Cross-verify a read-only investigation across a standing panel of independent mesh agents (different machines/providers) instead of a single worker |
3883
3970
  | \`mesh_magi_collect\` | Collect + synthesize a previously dispatched MAGI fan-out by its consensus group id (async companion to mesh_magi_review wait:false) |
3884
3971
  | \`mesh_magi_kind_panel_set\` | Bind a task_kind \u2192 MAGI kind-panel slots (the SOLE MAGI panel-resolution surface; machine-local, wholesale replacement \u2014 approve current-vs-new first) |
3885
- | \`mesh_magi_kind_panel_list\` | List configured task_kind \u2192 MAGI kind-panel slot bindings (machine-local, read-only) |`;
3972
+ | \`mesh_magi_kind_panel_list\` | List configured task_kind \u2192 MAGI kind-panel slot bindings (machine-local, read-only) |
3973
+ | \`mesh_node_slots_list\` | List a node's capability slots (its AI-tool profile: provider/model/thinking + difficulty range + capability tags), read-only |
3974
+ | \`mesh_node_slots_set\` | PROPOSE (dry-run) or APPLY a node's capability slots \u2014 how you autonomously retune a node's tool profile; WHOLESALE replacement, present current-vs-proposed and get user approval before write=true |`;
3886
3975
  TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
3887
3976
 
3888
3977
  Before doing any coordinator work, confirm that the actual callable tool list includes \`mesh_status\` and the other \`mesh_*\` tools from the table above. If this Repo Mesh coordinator prompt is present but the callable \`mesh_*\` tools are missing, the MCP server/tool manifest is stale or not injected yet. Do not substitute terminal/file/git tools, do not inspect or edit the repository directly, and do not continue as a non-mesh local coding agent. Stop immediately and tell the user to run \`/reload-mcp\` or start a fresh coordinator session so ADHDev can reconnect \`adhdev-mesh\`.`;
@@ -5830,6 +5919,7 @@ function enqueueTask(meshId, message, opts) {
5830
5919
  const maxRetries = typeof opts?.maxRetries === "number" && Number.isFinite(opts.maxRetries) && opts.maxRetries >= 0 ? Math.floor(opts.maxRetries) : void 0;
5831
5920
  let effectiveModel = typeof opts?.model === "string" && opts.model.trim() ? opts.model.trim() : void 0;
5832
5921
  let effectiveThinkingLevel = typeof opts?.thinkingLevel === "string" && opts.thinkingLevel.trim() ? opts.thinkingLevel.trim() : void 0;
5922
+ const taskDifficulty = isMeshTaskDifficulty(opts?.difficulty) ? opts.difficulty : void 0;
5833
5923
  if (isMeshTaskDifficulty(opts?.difficulty)) {
5834
5924
  try {
5835
5925
  const preset = getDifficultyBrains()[opts.difficulty];
@@ -5873,6 +5963,7 @@ function enqueueTask(meshId, message, opts) {
5873
5963
  ...typeof opts?.consensusGroupId === "string" && opts.consensusGroupId.trim() ? { consensusGroupId: opts.consensusGroupId.trim() } : {},
5874
5964
  ...effectiveModel ? { model: effectiveModel } : {},
5875
5965
  ...effectiveThinkingLevel ? { thinkingLevel: effectiveThinkingLevel } : {},
5966
+ ...taskDifficulty ? { difficulty: taskDifficulty } : {},
5876
5967
  ...typeof opts?.sourceCoordinatorSessionId === "string" && opts.sourceCoordinatorSessionId.trim() ? { sourceCoordinatorSessionId: opts.sourceCoordinatorSessionId.trim() } : {},
5877
5968
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5878
5969
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -15862,8 +15953,16 @@ function nodeHasActiveAssignment(meshId, nodeId) {
15862
15953
  function nodeActiveLoad(meshId, nodeId) {
15863
15954
  return MeshRuntimeStore.getInstance().nodeActiveAssignmentCount(meshId, nodeId);
15864
15955
  }
15956
+ function meshHasExplicitSlots(mesh) {
15957
+ const nodes = Array.isArray(mesh?.nodes) ? mesh.nodes : [];
15958
+ return nodes.some((n) => normalizeNodeCapabilitySlots(n?.policy?.slots).length > 0);
15959
+ }
15865
15960
  function resolveSchedulingStrategy(mesh) {
15866
- return normalizeMeshSchedulingStrategy(mesh?.policy?.schedulingStrategy);
15961
+ const raw = mesh?.policy?.schedulingStrategy;
15962
+ if (typeof raw === "string" && raw.trim()) {
15963
+ return normalizeMeshSchedulingStrategy(raw);
15964
+ }
15965
+ return meshHasExplicitSlots(mesh) ? "fitness" : normalizeMeshSchedulingStrategy(void 0);
15867
15966
  }
15868
15967
  function buildSchedulingPool(localCandidates, remoteCandidates) {
15869
15968
  const pool = [...localCandidates, ...remoteCandidates].map((c) => ({
@@ -15877,10 +15976,68 @@ function buildSchedulingPool(localCandidates, remoteCandidates) {
15877
15976
  }));
15878
15977
  return { pool, uniqueNodes };
15879
15978
  }
15979
+ function resolveNodeCapabilitySlots(node) {
15980
+ const explicit = normalizeNodeCapabilitySlots(node?.policy?.slots);
15981
+ if (explicit.length) return explicit;
15982
+ let difficultyBrains;
15983
+ try {
15984
+ difficultyBrains = getDifficultyBrains();
15985
+ } catch {
15986
+ difficultyBrains = void 0;
15987
+ }
15988
+ return deriveSlotsFromLegacy({
15989
+ providerPriority: normalizeProviderPriority(node?.policy),
15990
+ providerRoles: Array.isArray(node?.policy?.providerRoles) ? node.policy.providerRoles : void 0,
15991
+ difficultyBrains
15992
+ });
15993
+ }
15994
+ function scoreSlotForTask(slot, task) {
15995
+ let score = 1;
15996
+ const diff = isMeshTaskDifficulty(task.difficulty) ? task.difficulty : void 0;
15997
+ if (diff) {
15998
+ if (slot.difficulty?.length) {
15999
+ score += slot.difficulty.includes(diff) ? 100 : 0;
16000
+ } else {
16001
+ score += 20;
16002
+ }
16003
+ }
16004
+ const req = task.requiredTags?.filter((t) => !!t) ?? [];
16005
+ if (req.length) {
16006
+ const cap = new Set(slot.capability ?? []);
16007
+ const covered = req.every((t) => cap.has(t));
16008
+ score += covered ? 30 : 0;
16009
+ }
16010
+ return score;
16011
+ }
16012
+ function bestSlotForTask(node, task) {
16013
+ const slots = resolveNodeCapabilitySlots(node);
16014
+ if (!slots.length) return null;
16015
+ let best = null;
16016
+ for (const slot of slots) {
16017
+ const score = scoreSlotForTask(slot, task);
16018
+ if (!best || score > best.score) best = { slot, score };
16019
+ }
16020
+ return best;
16021
+ }
16022
+ function nodeFitnessForTask(node, task) {
16023
+ return bestSlotForTask(node, task)?.score ?? 0;
16024
+ }
15880
16025
  function orderEligibleNodes(meshId, strategy, nodes, opts) {
15881
16026
  if (strategy === "first_eligible" || nodes.length <= 1) {
15882
16027
  return nodes;
15883
16028
  }
16029
+ if (strategy === "fitness" && opts?.task) {
16030
+ const task = opts.task;
16031
+ return [...nodes].sort((a, b) => {
16032
+ const fitDelta = nodeFitnessForTask(b.node, task) - nodeFitnessForTask(a.node, task);
16033
+ if (fitDelta !== 0) return fitDelta;
16034
+ const prioDelta = resolveNodeSchedulingPriority(b.node?.policy) - resolveNodeSchedulingPriority(a.node?.policy);
16035
+ if (prioDelta !== 0) return prioDelta;
16036
+ const loadDelta = nodeActiveLoad(meshId, a.nodeId) - nodeActiveLoad(meshId, b.nodeId);
16037
+ if (loadDelta !== 0) return loadDelta;
16038
+ return a.index - b.index;
16039
+ });
16040
+ }
15884
16041
  const priorityOf = (n) => resolveNodeSchedulingPriority(n.node?.policy);
15885
16042
  let rotation = 0;
15886
16043
  if (strategy === "least_loaded" || strategy === "round_robin") {
@@ -16024,13 +16181,15 @@ function markAutoLaunch(meshId, taskId, args) {
16024
16181
  retractActionableSkipIfPreviouslyNotified(meshId, taskId);
16025
16182
  }
16026
16183
  }
16027
- async function resolveUsableProvider(components, nodeId, node, requiredTags) {
16028
- const providerPriority = normalizeProviderPriority(node?.policy);
16029
- if (!providerPriority.length) return { reason: "missing_provider_priority" };
16184
+ async function resolveUsableProvider(components, nodeId, node, requiredTags, task) {
16030
16185
  const providerLoader = components.providerLoader;
16031
16186
  if (!providerLoader) return { reason: "provider_loader_unavailable" };
16187
+ const slots = resolveNodeCapabilitySlots(node);
16188
+ if (!slots.length) return { reason: "missing_provider_priority" };
16189
+ const orderedSlots = task ? [...slots].sort((a, b) => scoreSlotForTask(b, task) - scoreSlotForTask(a, task)) : slots;
16032
16190
  const failed = [];
16033
- for (const requestedType of providerPriority) {
16191
+ for (const slot of orderedSlots) {
16192
+ const requestedType = slot.provider;
16034
16193
  const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
16035
16194
  if (requiredTags?.length && !nodeSatisfiesRequiredTags(requiredTags, buildMeshNodeCapabilityTags(node, normalizedType))) {
16036
16195
  failed.push(`${requestedType}: required_tags_mismatch`);
@@ -16055,7 +16214,13 @@ async function resolveUsableProvider(components, nodeId, node, requiredTags) {
16055
16214
  }], false);
16056
16215
  }
16057
16216
  components.onStatusChange?.();
16058
- if (detected) return { providerType: normalizedType };
16217
+ if (detected) {
16218
+ return {
16219
+ providerType: normalizedType,
16220
+ ...slot.model ? { model: slot.model } : {},
16221
+ ...slot.thinkingLevel ? { thinkingLevel: slot.thinkingLevel } : {}
16222
+ };
16223
+ }
16059
16224
  failed.push(`${requestedType}: not detected`);
16060
16225
  }
16061
16226
  return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
@@ -16183,7 +16348,9 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
16183
16348
  meshId,
16184
16349
  strategy,
16185
16350
  candidateNodes.map((node, index) => ({ nodeId: readMeshNodeId(node), node, index })).filter((c) => c.nodeId),
16186
- { bumpCursor: true }
16351
+ // Auto-launch drains one task at a time, so the task IS in scope here —
16352
+ // pass it through for the 'fitness' strategy's task→slot ranking.
16353
+ { bumpCursor: true, task: { difficulty: task.difficulty, requiredTags: task.requiredTags } }
16187
16354
  ).map((c) => c.node);
16188
16355
  for (const node of orderedCandidateNodes) {
16189
16356
  const nodeId = readMeshNodeId(node);
@@ -16230,11 +16397,13 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
16230
16397
  }
16231
16398
  autoLaunchInProgress.add(launchKey);
16232
16399
  try {
16233
- const resolved = await resolveUsableProvider(components, nodeId, node, task.requiredTags);
16400
+ const resolved = await resolveUsableProvider(components, nodeId, node, task.requiredTags, { difficulty: task.difficulty, requiredTags: task.requiredTags });
16234
16401
  if (!resolved.providerType) {
16235
16402
  markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
16236
16403
  continue;
16237
16404
  }
16405
+ const effectiveModel = typeof task.model === "string" && task.model.trim() ? task.model.trim() : resolved.model;
16406
+ const effectiveThinkingLevel = typeof task.thinkingLevel === "string" && task.thinkingLevel.trim() ? task.thinkingLevel.trim() : resolved.thinkingLevel;
16238
16407
  const providerCap = resolveProviderMaxParallel(node?.policy, resolved.providerType);
16239
16408
  if (providerCap !== void 0 && activeProviderAssignedCount(meshId, nodeId, resolved.providerType) >= providerCap) {
16240
16409
  markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_provider_parallel_reached", nodeId, providerType: resolved.providerType });
@@ -16268,9 +16437,10 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
16268
16437
  settings: remoteSettings,
16269
16438
  // MAGI-KIND-PANEL model axis: forward the task's model override so the
16270
16439
  // remote worker session launches with it (initialModel). Best-effort.
16271
- ...typeof task.model === "string" && task.model.trim() ? { initialModel: task.model.trim() } : {},
16272
- // BRAIN-ROUTING thinking axis: forward the task's thinking level (initialThinkingLevel).
16273
- ...typeof task.thinkingLevel === "string" && task.thinkingLevel.trim() ? { initialThinkingLevel: task.thinkingLevel.trim() } : {}
16440
+ // Slot-aware: task override wins, else the matched slot's model.
16441
+ ...effectiveModel ? { initialModel: effectiveModel } : {},
16442
+ // BRAIN-ROUTING thinking axis: forward the effective thinking level (initialThinkingLevel).
16443
+ ...effectiveThinkingLevel ? { initialThinkingLevel: effectiveThinkingLevel } : {}
16274
16444
  });
16275
16445
  } catch (e) {
16276
16446
  markAutoLaunch(meshId, task.id, { status: "failed", reason: `remote_launch_dispatch_failed: ${e?.message || String(e)}`, nodeId, providerType: resolved.providerType });
@@ -16297,11 +16467,11 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
16297
16467
  cliType: resolved.providerType,
16298
16468
  dir: node.workspace,
16299
16469
  settings: launchSettings,
16300
- // MAGI-KIND-PANEL model axis: local launch forwards the task's model
16301
- // override as initialModel (CLI → modelLaunchArgs; ACP → setConfigOption).
16302
- ...typeof task.model === "string" && task.model.trim() ? { initialModel: task.model.trim() } : {},
16303
- // BRAIN-ROUTING thinking axis: forward the task's thinking level (initialThinkingLevel).
16304
- ...typeof task.thinkingLevel === "string" && task.thinkingLevel.trim() ? { initialThinkingLevel: task.thinkingLevel.trim() } : {}
16470
+ // MAGI-KIND-PANEL model axis: local launch forwards the effective model
16471
+ // (task override, else matched slot) as initialModel (CLI → modelLaunchArgs; ACP → setConfigOption).
16472
+ ...effectiveModel ? { initialModel: effectiveModel } : {},
16473
+ // BRAIN-ROUTING thinking axis: forward the effective thinking level (initialThinkingLevel).
16474
+ ...effectiveThinkingLevel ? { initialThinkingLevel: effectiveThinkingLevel } : {}
16305
16475
  });
16306
16476
  if (!launchResult?.success) {
16307
16477
  const reason = launchResult?.error || "launch_cli_failed";
@@ -16436,7 +16606,7 @@ async function triggerMeshQueue(components, meshId) {
16436
16606
  const aPrio = resolveNodeSchedulingPriority(a.node?.policy);
16437
16607
  const bPrio = resolveNodeSchedulingPriority(b.node?.policy);
16438
16608
  if (aPrio !== bPrio) return bPrio - aPrio;
16439
- if (strategy === "least_loaded" || strategy === "round_robin") {
16609
+ if (strategy === "least_loaded" || strategy === "round_robin" || strategy === "fitness") {
16440
16610
  const loadDelta = nodeActiveLoad(meshId, a.nodeId) - nodeActiveLoad(meshId, b.nodeId);
16441
16611
  if (loadDelta !== 0) return loadDelta;
16442
16612
  }
@@ -18431,6 +18601,22 @@ var init_provider_input_support = __esm({
18431
18601
  });
18432
18602
 
18433
18603
  // src/status/builders.ts
18604
+ function isCoordinatorSpawnedHiddenWorker(settings) {
18605
+ if (!settings) return false;
18606
+ return settings.launchedByCoordinator === true && typeof settings.meshNodeFor === "string" && settings.meshNodeFor.trim().length > 0 && settings.spawnedSessionVisibility === "hidden";
18607
+ }
18608
+ function resolveSurfaceHidden(settings) {
18609
+ if (!settings) return false;
18610
+ if (settings.userHidden === true) return true;
18611
+ if (settings.userHidden === false) return false;
18612
+ return settings.spawnedSessionVisibility === "hidden" || isCoordinatorSpawnedHiddenWorker(settings);
18613
+ }
18614
+ function resolveMuted(settings) {
18615
+ if (!settings) return false;
18616
+ if (settings.userMuted === true) return true;
18617
+ if (settings.userMuted === false) return false;
18618
+ return isCoordinatorSpawnedHiddenWorker(settings);
18619
+ }
18434
18620
  function getActiveChatOptions(profile) {
18435
18621
  if (profile === "full") return {};
18436
18622
  return LIVE_STATUS_ACTIVE_CHAT_OPTIONS;
@@ -18636,7 +18822,8 @@ function buildCliSession(state, options) {
18636
18822
  settings: state.settings,
18637
18823
  ...coordinator && { coordinator },
18638
18824
  ...meshQueueStats && { meshQueueStats },
18639
- ...state.settings?.spawnedSessionVisibility === "hidden" && { surfaceHidden: true }
18825
+ ...resolveSurfaceHidden(state.settings) && { surfaceHidden: true },
18826
+ ...resolveMuted(state.settings) && { muted: true }
18640
18827
  };
18641
18828
  }
18642
18829
  function buildAcpSession(state, options) {
@@ -18677,7 +18864,8 @@ function buildAcpSession(state, options) {
18677
18864
  settings: state.settings,
18678
18865
  ...coordinator && { coordinator },
18679
18866
  ...meshQueueStats && { meshQueueStats },
18680
- ...state.settings?.spawnedSessionVisibility === "hidden" && { surfaceHidden: true }
18867
+ ...resolveSurfaceHidden(state.settings) && { surfaceHidden: true },
18868
+ ...resolveMuted(state.settings) && { muted: true }
18681
18869
  };
18682
18870
  }
18683
18871
  function buildSessionEntries(allStates, cdpManagers, options = {}) {
@@ -27639,6 +27827,7 @@ __export(index_exports, {
27639
27827
  appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
27640
27828
  assertNoDependencyCycle: () => assertNoDependencyCycle,
27641
27829
  buildAssistantChatMessage: () => buildAssistantChatMessage,
27830
+ buildAvailableProviders: () => buildAvailableProviders,
27642
27831
  buildChatMessage: () => buildChatMessage,
27643
27832
  buildChatMessageSignature: () => buildChatMessageSignature,
27644
27833
  buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
@@ -39497,11 +39686,15 @@ ${formatManifestValidationIssues2(validation.issues)}`,
39497
39686
  if (!manifestPath) continue;
39498
39687
  try {
39499
39688
  const m = JSON.parse(fs41.readFileSync(manifestPath, "utf-8"));
39689
+ const modelOptions = Array.isArray(m.modelOptions) ? m.modelOptions.filter((x) => typeof x === "string" && !!x.trim()) : [];
39690
+ const thinkingLevelOptions = Array.isArray(m.thinkingLevelOptions) ? m.thinkingLevelOptions.filter((x) => typeof x === "string" && !!x.trim()) : [];
39500
39691
  items.push({
39501
39692
  type,
39502
39693
  category,
39503
39694
  version: typeof m.providerVersion === "string" ? m.providerVersion : "0.0.0",
39504
- path: manifestPath
39695
+ path: manifestPath,
39696
+ ...modelOptions.length ? { modelOptions } : {},
39697
+ ...thinkingLevelOptions.length ? { thinkingLevelOptions } : {}
39505
39698
  });
39506
39699
  } catch {
39507
39700
  }
@@ -50285,6 +50478,31 @@ var cliAgentHandlers = {
50285
50478
  record_provider_pty: async (ctx, args) => {
50286
50479
  return ctx.deps.cliManager.handleCliCommand("record_provider_pty", args);
50287
50480
  },
50481
+ // Daemon-owned per-session user Mute/Hide. Replaces the old browser-local
50482
+ // localStorage layer: the user's manual hide/mute for a conversation is stored
50483
+ // in-memory on the live session's settings (userHidden / userMuted) and rides
50484
+ // the SAME status snapshot pipeline as the coordinator-policy surfaceHidden
50485
+ // flag, so every client of this daemon sees the same state. In-memory only —
50486
+ // resets on daemon restart (coordinator-spawned sessions re-derive their hidden
50487
+ // default from mesh policy on relaunch). Passing null/undefined for a field
50488
+ // leaves it unchanged; pass an explicit boolean to set, or false to clear an
50489
+ // earlier hide/mute (e.g. unmute a coordinator-spawned worker overrides the
50490
+ // policy default until restart).
50491
+ set_conversation_prefs: async (ctx, args) => {
50492
+ const sessionId = readStringValue(args?.sessionId, args?.targetSessionId, args?.instanceId);
50493
+ if (!sessionId) return { success: false, error: "sessionId required" };
50494
+ const inst = ctx.deps.instanceManager.getInstance(sessionId);
50495
+ if (!inst || typeof inst.updateSettings !== "function") {
50496
+ return { success: false, error: "Session not found or does not support preferences" };
50497
+ }
50498
+ const patch = {};
50499
+ if (typeof args?.hidden === "boolean") patch.userHidden = args.hidden;
50500
+ if (typeof args?.muted === "boolean") patch.userMuted = args.muted;
50501
+ if (!Object.keys(patch).length) return { success: false, error: "Nothing to update (hidden and/or muted required)" };
50502
+ inst.updateSettings(patch);
50503
+ ctx.deps.onStatusChange?.();
50504
+ return { success: true, sessionId, ...patch };
50505
+ },
50288
50506
  agent_command: async (ctx, args) => {
50289
50507
  {
50290
50508
  const dispatchSessionId = readStringValue(args?.targetSessionId, args?.sessionId, args?.instanceId);
@@ -56546,6 +56764,8 @@ var meshCoordinatorLaunchHandlers = {
56546
56764
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
56547
56765
  let cliType = typeof args?.cliType === "string" ? args.cliType.trim() : "";
56548
56766
  const extraSystemPrompt = typeof args?.extraSystemPrompt === "string" ? args.extraSystemPrompt.trim() : "";
56767
+ const initialModel = typeof args?.initialModel === "string" && args.initialModel.trim() ? args.initialModel.trim() : null;
56768
+ const initialThinkingLevel = typeof args?.initialThinkingLevel === "string" && args.initialThinkingLevel.trim() ? args.initialThinkingLevel.trim() : null;
56549
56769
  if (!meshId) return { success: false, error: "meshId required" };
56550
56770
  try {
56551
56771
  const { buildCoordinatorSystemPrompt: buildCoordinatorSystemPrompt2 } = await Promise.resolve().then(() => (init_coordinator_prompt(), coordinator_prompt_exports));
@@ -56816,7 +57036,9 @@ ${ptyResult.output.slice(-2e3)}`);
56816
57036
  dir: workspace,
56817
57037
  cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : void 0,
56818
57038
  env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : void 0,
56819
- settings: { meshCoordinatorFor: meshId }
57039
+ settings: { meshCoordinatorFor: meshId },
57040
+ ...initialModel ? { initialModel } : {},
57041
+ ...initialThinkingLevel ? { initialThinkingLevel } : {}
56820
57042
  });
56821
57043
  if (cliCmdLaunch?.success && cliCmdContextFilePath) {
56822
57044
  const stripPath = cliCmdContextFilePath;
@@ -57001,7 +57223,9 @@ ${ptyResult.output.slice(-2e3)}`);
57001
57223
  env: Object.keys(launchEnv).length > 0 ? launchEnv : void 0,
57002
57224
  settings: {
57003
57225
  meshCoordinatorFor: meshId
57004
- }
57226
+ },
57227
+ ...initialModel ? { initialModel } : {},
57228
+ ...initialThinkingLevel ? { initialThinkingLevel } : {}
57005
57229
  });
57006
57230
  if (launchResult?.success && autoImportContextFilePath) {
57007
57231
  const stripPath = autoImportContextFilePath;
@@ -57360,6 +57584,11 @@ var meshStatusHandlers = {
57360
57584
  ...node.reportedProviderVersions && typeof node.reportedProviderVersions === "object" ? { providerVersions: node.reportedProviderVersions } : {},
57361
57585
  ...typeof node.reportedDaemonBuildVersion === "string" && node.reportedDaemonBuildVersion ? { daemonBuildVersion: node.reportedDaemonBuildVersion } : {},
57362
57586
  providerPriority,
57587
+ // ORCHESTRATION_NODE_SLOTS.md: surface the node's capability
57588
+ // slots so the dashboard slot editor can read them. Only
57589
+ // emitted when explicitly configured (derived-from-legacy
57590
+ // slots stay implicit — the editor shows the legacy fields).
57591
+ ...Array.isArray(node.policy?.slots) && node.policy.slots.length ? { slots: normalizeNodeCapabilitySlots(node.policy.slots) } : {},
57363
57592
  activeSessions: [],
57364
57593
  activeSessionDetails: [],
57365
57594
  launchReady: false
@@ -58001,6 +58230,7 @@ function orderMeshRefineBatchNodes(changeAreas) {
58001
58230
 
58002
58231
  // src/commands/router-refine.ts
58003
58232
  init_repo_mesh_types();
58233
+ init_git_status();
58004
58234
  init_mesh_node_identity();
58005
58235
 
58006
58236
  // src/mesh/mesh-refine-gates.ts
@@ -58941,6 +59171,41 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
58941
59171
  if (fs31.existsSync((0, import_path14.join)(cwd, "node_modules"))) return false;
58942
59172
  return ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"].some((lock) => fs31.existsSync((0, import_path14.join)(cwd, lock)));
58943
59173
  };
59174
+ const needsNodeModules = (candidate, cwd) => isPackageManagerValidation(candidate) && dependenciesLikelyMissing(cwd);
59175
+ const isDaemonScopedCommand = (candidate) => {
59176
+ const haystack = [candidate.command, ...candidate.args || [], candidate.displayCommand || ""].join(" ").toLowerCase();
59177
+ if (candidate.category === "typecheck") return false;
59178
+ if (/\btypecheck\b/.test(haystack)) return false;
59179
+ if (/\bweb-core\b|\bweb-cloud\b|\bweb-standalone\b|\btest:web\b/.test(haystack)) return false;
59180
+ return /\bdaemon-core\b|\bdaemon-cloud\b|\btest:daemon\b|check-vendor-drift/.test(haystack);
59181
+ };
59182
+ const scopeUnaffectedDaemon = opts?.changeImpact?.isDaemonAffecting === false;
59183
+ const skippedDaemonCommands = [];
59184
+ const commandsToRun = [];
59185
+ for (const candidate of selection.commands) {
59186
+ if (scopeUnaffectedDaemon && isDaemonScopedCommand(candidate)) {
59187
+ skippedDaemonCommands.push(candidate.displayCommand);
59188
+ summary.commandsRun.push({
59189
+ command: candidate.command,
59190
+ args: candidate.args,
59191
+ displayCommand: candidate.displayCommand,
59192
+ category: candidate.category,
59193
+ source: candidate.source,
59194
+ passed: true,
59195
+ skipped: true,
59196
+ skipReason: "unaffected_daemon_scope"
59197
+ });
59198
+ continue;
59199
+ }
59200
+ commandsToRun.push(candidate);
59201
+ }
59202
+ if (opts?.changeImpact) {
59203
+ summary.changeImpact = {
59204
+ isDaemonAffecting: opts.changeImpact.isDaemonAffecting,
59205
+ affectedPackages: opts.changeImpact.affectedPackages,
59206
+ ...skippedDaemonCommands.length ? { skippedDaemonCommands } : {}
59207
+ };
59208
+ }
58944
59209
  if (runLegacyBootstrapCommands) {
58945
59210
  summary.bootstrap = { stage: "legacy" };
58946
59211
  for (const candidate of selection.bootstrapCommands) {
@@ -58975,23 +59240,22 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
58975
59240
  }
58976
59241
  }
58977
59242
  }
58978
- for (const candidate of selection.commands) {
59243
+ let missingDepsBlocked = false;
59244
+ for (const candidate of commandsToRun) {
58979
59245
  const startedAt = Date.now();
58980
59246
  const cwd = candidate.cwd ? (0, import_path14.resolve)(workspace, candidate.cwd) : workspace;
58981
59247
  const timeout = candidate.timeoutMs || REFINE_VALIDATION_TIMEOUT_MS;
58982
59248
  const bootstrapProvidedDependencies = summary.bootstrap?.stage === "cached" || summary.bootstrap?.stage === "ran" || summary.bootstrap?.stage === "legacy";
58983
- if (!bootstrapProvidedDependencies && isPackageManagerValidation(candidate) && dependenciesLikelyMissing(cwd)) {
59249
+ if (!bootstrapProvidedDependencies && needsNodeModules(candidate, cwd)) {
58984
59250
  summary.commandsRun.push(commandRecord(candidate, cwd, startedAt, {
58985
- stderr: "Dependencies appear to be missing: package.json and a lockfile are present, but node_modules is absent. Configure validation.bootstrapCommands in repo mesh/refine config if Refinery should install/bootstrap before validation."
59251
+ stderr: "Dependencies appear to be missing: package.json and a lockfile are present, but node_modules is absent. Configure validation.bootstrapCommands (or .adhdev/worktree_bootstrap.json) in repo mesh/refine config if Refinery should install/bootstrap before validation."
58986
59252
  }, false, {
58987
59253
  exitCode: null,
58988
59254
  skipped: true,
58989
59255
  failureKind: "missing_dependencies"
58990
59256
  }));
58991
- summary.status = "failed";
58992
- summary.failureKind = "missing_dependencies";
58993
- summary.failureCode = "missing_dependencies";
58994
- return summary;
59257
+ missingDepsBlocked = true;
59258
+ continue;
58995
59259
  }
58996
59260
  const resolvedCommand = resolveWin32Executable(candidate.command);
58997
59261
  const spawn5 = buildWin32ExecFileSpawn(resolvedCommand, candidate.args);
@@ -59027,6 +59291,12 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
59027
59291
  return summary;
59028
59292
  }
59029
59293
  }
59294
+ if (missingDepsBlocked) {
59295
+ summary.status = "failed";
59296
+ summary.failureKind = "missing_dependencies";
59297
+ summary.failureCode = "missing_dependencies";
59298
+ return summary;
59299
+ }
59030
59300
  summary.status = "passed";
59031
59301
  return summary;
59032
59302
  }
@@ -59234,7 +59504,20 @@ async function refineResolveRefsStage(self, meshId, nodeId, args, refineStages)
59234
59504
  const { stdout: branchHeadStdout } = await execFileAsync4("git", ["rev-parse", branch], { cwd: node.workspace, encoding: "utf8" });
59235
59505
  const baseHead = baseHeadRaw;
59236
59506
  const branchHead = branchHeadStdout.trim();
59237
- recordMeshRefineStage(refineStages, "resolve_refs", "passed", resolveStarted, { branch, baseBranch, baseHead, branchHead, ...fetchWarning ? { fetchWarning } : {} });
59507
+ let changeImpact;
59508
+ try {
59509
+ changeImpact = await classifyChangedPackages(node.workspace, baseHead, branchHead);
59510
+ } catch {
59511
+ changeImpact = void 0;
59512
+ }
59513
+ recordMeshRefineStage(refineStages, "resolve_refs", "passed", resolveStarted, {
59514
+ branch,
59515
+ baseBranch,
59516
+ baseHead,
59517
+ branchHead,
59518
+ ...changeImpact ? { changeImpact } : {},
59519
+ ...fetchWarning ? { fetchWarning } : {}
59520
+ });
59238
59521
  return {
59239
59522
  kind: "continue",
59240
59523
  ctx: {
@@ -59251,6 +59534,7 @@ async function refineResolveRefsStage(self, meshId, nodeId, args, refineStages)
59251
59534
  baseBranch,
59252
59535
  baseHead,
59253
59536
  branchHead,
59537
+ changeImpact,
59254
59538
  validationSummary: void 0,
59255
59539
  patchEquivalence: void 0,
59256
59540
  submoduleReachability: void 0
@@ -59261,6 +59545,9 @@ async function refineValidationStage(self, ctx) {
59261
59545
  const { mesh, node, branch, baseBranch, refineStages } = ctx;
59262
59546
  const validationStarted = Date.now();
59263
59547
  const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace, {
59548
+ // (a) Scope the validation command set by coarse change-impact (resolved
59549
+ // in resolve_refs). Undefined → gate runs the full command set (fail-open).
59550
+ changeImpact: ctx.changeImpact,
59264
59551
  // M2-2: consume the node's persisted bootstrap state; persist re-runs.
59265
59552
  persistedBootstrapState: node.worktreeBootstrap,
59266
59553
  onBootstrapStateChange: (state) => {
@@ -59280,7 +59567,7 @@ async function refineValidationStage(self, ctx) {
59280
59567
  if (validationSummary.status === "failed") {
59281
59568
  const firstFailedCmd = Array.isArray(validationSummary.commandsRun) ? validationSummary.commandsRun.find((c) => c.success === false) : void 0;
59282
59569
  const buildValidationFailedError = () => {
59283
- const base = validationSummary.failureCode === "missing_dependencies" ? "Refinery validation dependencies are missing; merge/refine was not attempted. Configure validation.bootstrapCommands if Refinery should bootstrap dependencies before validation." : validationSummary.failureCode === "dependency_bootstrap_failed" ? "Refinery dependency/bootstrap command failed; merge/refine was not attempted." : validationSummary.failureCode === "spawn_resolution_failed" ? validationSummary.spawnResolutionError || "Refinery validation command could not be spawned (executable not found); merge/refine was not attempted." : "Refinery validation gate failed; merge/refine was not attempted.";
59570
+ const base = validationSummary.failureCode === "missing_dependencies" ? "Refinery validation dependencies are missing for a change-affected package; merge/refine was not attempted. To make this self-service, either (1) configure .adhdev/worktree_bootstrap.json (or validation.bootstrapCommands in .adhdev/refine.json) so Refinery installs deps before validation, or (2) converge the branch via the documented manual fast-forward-only bypass (rebase onto the fetched base, verify strict ancestry, then push ff-only) instead of the refine gate." : validationSummary.failureCode === "dependency_bootstrap_failed" ? "Refinery dependency/bootstrap command failed; merge/refine was not attempted." : validationSummary.failureCode === "spawn_resolution_failed" ? validationSummary.spawnResolutionError || "Refinery validation command could not be spawned (executable not found); merge/refine was not attempted." : "Refinery validation gate failed; merge/refine was not attempted.";
59284
59571
  if (!firstFailedCmd) return base;
59285
59572
  const cmdName = typeof firstFailedCmd.displayCommand === "string" ? firstFailedCmd.displayCommand : typeof firstFailedCmd.command === "string" ? [firstFailedCmd.command, ...Array.isArray(firstFailedCmd.args) ? firstFailedCmd.args : []].join(" ").trim() : typeof firstFailedCmd.cmd === "string" ? firstFailedCmd.cmd : "";
59286
59573
  const rawOutput = [firstFailedCmd.stdout, firstFailedCmd.stderr, firstFailedCmd.output].filter((s2) => typeof s2 === "string" && s2.length > 0).join("\n");
@@ -70224,6 +70511,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
70224
70511
  appendRemoteLedgerEntries,
70225
70512
  assertNoDependencyCycle,
70226
70513
  buildAssistantChatMessage,
70514
+ buildAvailableProviders,
70227
70515
  buildChatMessage,
70228
70516
  buildChatMessageSignature,
70229
70517
  buildChatTailDeliverySignature,