@adhdev/daemon-core 0.9.82-rc.507 → 0.9.82-rc.509
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/config/mesh-config.d.ts +12 -0
- package/dist/index.js +285 -83
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +285 -83
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-node-slots.d.ts +22 -0
- package/dist/mesh/mesh-runtime-store.d.ts +2 -2
- package/dist/mesh/mesh-scheduling-runtime.d.ts +5 -1
- package/dist/mesh/mesh-work-queue.d.ts +3 -3
- package/dist/mesh/model-provider-compat.d.ts +44 -0
- package/dist/providers/cli-provider-instance-types.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +54 -0
- package/dist/repo-mesh-types.d.ts +30 -29
- package/package.json +3 -3
- package/src/commands/med-family/mesh-crud.ts +24 -14
- package/src/config/mesh-config.ts +71 -24
- package/src/mesh/coordinator-prompt.ts +26 -16
- package/src/mesh/mesh-node-slots.ts +52 -0
- package/src/mesh/mesh-queue-assignment.ts +43 -24
- package/src/mesh/mesh-runtime-store.ts +3 -3
- package/src/mesh/mesh-scheduling-runtime.ts +24 -9
- package/src/mesh/mesh-work-queue.ts +3 -3
- package/src/mesh/model-provider-compat.ts +73 -0
- package/src/providers/cli-provider-instance-types.ts +15 -0
- package/src/providers/cli-provider-instance.ts +174 -2
- package/src/repo-mesh-types.ts +39 -38
package/dist/index.js
CHANGED
|
@@ -122,20 +122,20 @@ function resolveDelegatedWorkerAutoApprove(meshPolicy, nodePolicy) {
|
|
|
122
122
|
}
|
|
123
123
|
return true;
|
|
124
124
|
}
|
|
125
|
-
function resolveProviderMaxParallel(
|
|
125
|
+
function resolveProviderMaxParallel(slots, providerType) {
|
|
126
126
|
const wanted = typeof providerType === "string" ? providerType.trim().toLowerCase() : "";
|
|
127
127
|
if (!wanted) return void 0;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (const
|
|
131
|
-
if (!
|
|
132
|
-
const type = typeof
|
|
128
|
+
if (!Array.isArray(slots)) return void 0;
|
|
129
|
+
let total;
|
|
130
|
+
for (const slot of slots) {
|
|
131
|
+
if (!slot || typeof slot !== "object") continue;
|
|
132
|
+
const type = typeof slot.provider === "string" ? slot.provider.trim().toLowerCase() : "";
|
|
133
133
|
if (!type || type !== wanted) continue;
|
|
134
|
-
const raw = Number(
|
|
135
|
-
if (!Number.isFinite(raw) || raw < 0)
|
|
136
|
-
|
|
134
|
+
const raw = Number(slot.maxParallel);
|
|
135
|
+
if (!Number.isFinite(raw) || raw < 0) continue;
|
|
136
|
+
total = (total ?? 0) + Math.floor(raw);
|
|
137
137
|
}
|
|
138
|
-
return
|
|
138
|
+
return total;
|
|
139
139
|
}
|
|
140
140
|
var MESH_SCHEDULING_STRATEGIES, DEFAULT_MESH_SCHEDULING_STRATEGY, MESH_CONVERGE_REFINE_TAG, MESH_CONVERGE_FAST_FORWARD_TAG, DEFAULT_MESH_POLICY, SESSION_CLEANUP_MODES, SPAWNED_SESSION_VISIBILITY_MODES, DIRTY_WORKSPACE_BEHAVIORS, MESH_MAX_PARALLEL_TASKS_MIN, MESH_MAX_PARALLEL_TASKS_MAX, DEFAULT_MESH_READONLY_MULTIPLIER;
|
|
141
141
|
var init_repo_mesh_types = __esm({
|
|
@@ -417,10 +417,10 @@ function readInjected(value) {
|
|
|
417
417
|
}
|
|
418
418
|
function getDaemonBuildInfo() {
|
|
419
419
|
if (cached) return cached;
|
|
420
|
-
const commit = readInjected(true ? "
|
|
421
|
-
const commitShort = readInjected(true ? "
|
|
422
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
423
|
-
const builtAt = readInjected(true ? "2026-07-
|
|
420
|
+
const commit = readInjected(true ? "8ebe4d4bcdc658e11253a51a28237d392aa4d978" : void 0) ?? "unknown";
|
|
421
|
+
const commitShort = readInjected(true ? "8ebe4d4b" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
422
|
+
const version = readInjected(true ? "0.9.82-rc.509" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
423
|
+
const builtAt = readInjected(true ? "2026-07-13T02:51:57.282Z" : void 0);
|
|
424
424
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
425
425
|
return cached;
|
|
426
426
|
}
|
|
@@ -2848,12 +2848,6 @@ function normalizeNodeCapabilitySlots(raw) {
|
|
|
2848
2848
|
function deriveSlotsFromLegacy(input) {
|
|
2849
2849
|
const priority = Array.isArray(input.providerPriority) ? input.providerPriority.filter((p) => typeof p === "string" && !!p.trim()).map((p) => p.trim()) : [];
|
|
2850
2850
|
if (priority.length === 0) return [];
|
|
2851
|
-
const roleCap = /* @__PURE__ */ new Map();
|
|
2852
|
-
for (const role of input.providerRoles || []) {
|
|
2853
|
-
if (role && typeof role.providerType === "string" && Number.isFinite(role.maxParallel)) {
|
|
2854
|
-
roleCap.set(role.providerType.trim(), Math.floor(Number(role.maxParallel)));
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
2851
|
const brains = input.difficultyBrains || {};
|
|
2858
2852
|
const byProvider = /* @__PURE__ */ new Map();
|
|
2859
2853
|
const shared = [];
|
|
@@ -2875,13 +2869,11 @@ function deriveSlotsFromLegacy(input) {
|
|
|
2875
2869
|
const difficulty = applied.map((a) => a.difficulty);
|
|
2876
2870
|
const model = applied.find((a) => a.model)?.model;
|
|
2877
2871
|
const thinkingLevel = applied.find((a) => a.thinkingLevel)?.thinkingLevel;
|
|
2878
|
-
const maxParallel = roleCap.get(provider);
|
|
2879
2872
|
return {
|
|
2880
2873
|
provider,
|
|
2881
2874
|
...model ? { model } : {},
|
|
2882
2875
|
...thinkingLevel ? { thinkingLevel } : {},
|
|
2883
|
-
...difficulty.length ? { difficulty } : {}
|
|
2884
|
-
...maxParallel !== void 0 ? { maxParallel } : {}
|
|
2876
|
+
...difficulty.length ? { difficulty } : {}
|
|
2885
2877
|
};
|
|
2886
2878
|
});
|
|
2887
2879
|
}
|
|
@@ -3049,6 +3041,7 @@ __export(mesh_config_exports, {
|
|
|
3049
3041
|
listMagiKindPanels: () => listMagiKindPanels,
|
|
3050
3042
|
listMeshes: () => listMeshes,
|
|
3051
3043
|
markMeshHostPairingJoined: () => markMeshHostPairingJoined,
|
|
3044
|
+
migrateProviderRolesToSlots: () => migrateProviderRolesToSlots,
|
|
3052
3045
|
normalizeCapabilityTags: () => normalizeCapabilityTags,
|
|
3053
3046
|
normalizeMagiSlots: () => normalizeMagiSlots,
|
|
3054
3047
|
normalizeRepoIdentity: () => normalizeRepoIdentity,
|
|
@@ -3087,23 +3080,51 @@ function migrateLoadedMeshConfig(config) {
|
|
|
3087
3080
|
for (const mesh of config.meshes) {
|
|
3088
3081
|
if (!mesh || !Array.isArray(mesh.nodes)) continue;
|
|
3089
3082
|
for (const node of mesh.nodes) {
|
|
3090
|
-
if (
|
|
3083
|
+
if (migrateProviderRolesToSlots(node?.policy)) changed = true;
|
|
3091
3084
|
}
|
|
3092
3085
|
}
|
|
3093
3086
|
return changed;
|
|
3094
3087
|
}
|
|
3095
|
-
function
|
|
3096
|
-
if (!policy || typeof policy !== "object") return false;
|
|
3097
|
-
const
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3088
|
+
function migrateProviderRolesToSlots(policy) {
|
|
3089
|
+
if (!policy || typeof policy !== "object" || Array.isArray(policy)) return false;
|
|
3090
|
+
const p = policy;
|
|
3091
|
+
const rawRoles = p.providerRoles;
|
|
3092
|
+
if (!Array.isArray(rawRoles)) return false;
|
|
3093
|
+
const roleCap = /* @__PURE__ */ new Map();
|
|
3094
|
+
for (const entry of rawRoles) {
|
|
3095
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
3096
|
+
const rec = entry;
|
|
3097
|
+
const provider = typeof rec.providerType === "string" ? rec.providerType.trim() : "";
|
|
3098
|
+
if (!provider) continue;
|
|
3099
|
+
const cap = Number(rec.maxParallel);
|
|
3100
|
+
if (!Number.isFinite(cap) || cap < 0) continue;
|
|
3101
|
+
roleCap.set(provider.toLowerCase(), { provider, cap: Math.floor(cap) });
|
|
3102
|
+
}
|
|
3103
|
+
const explicitSlots = Array.isArray(p.slots) ? normalizeNodeCapabilitySlots(p.slots) : [];
|
|
3104
|
+
if (explicitSlots.length) {
|
|
3105
|
+
for (const { provider, cap } of roleCap.values()) {
|
|
3106
|
+
const target = explicitSlots.find((s2) => s2.provider.trim().toLowerCase() === provider.toLowerCase() && s2.maxParallel === void 0);
|
|
3107
|
+
if (target) target.maxParallel = cap;
|
|
3108
|
+
}
|
|
3109
|
+
p.slots = explicitSlots;
|
|
3110
|
+
} else if (roleCap.size) {
|
|
3111
|
+
let difficultyBrains;
|
|
3112
|
+
try {
|
|
3113
|
+
difficultyBrains = getDifficultyBrains();
|
|
3114
|
+
} catch {
|
|
3115
|
+
difficultyBrains = void 0;
|
|
3116
|
+
}
|
|
3117
|
+
const priority = Array.isArray(p.providerPriority) ? p.providerPriority.map((t) => typeof t === "string" ? t.trim() : "").filter(Boolean) : [];
|
|
3118
|
+
const derived = deriveSlotsFromLegacy({ providerPriority: priority, difficultyBrains });
|
|
3119
|
+
const slots = derived.length ? derived : [...roleCap.values()].map((r) => ({ provider: r.provider }));
|
|
3120
|
+
for (const slot of slots) {
|
|
3121
|
+
const match = roleCap.get(slot.provider.trim().toLowerCase());
|
|
3122
|
+
if (match && slot.maxParallel === void 0) slot.maxParallel = match.cap;
|
|
3104
3123
|
}
|
|
3124
|
+
p.slots = slots;
|
|
3105
3125
|
}
|
|
3106
|
-
|
|
3126
|
+
delete p.providerRoles;
|
|
3127
|
+
return true;
|
|
3107
3128
|
}
|
|
3108
3129
|
function normalizeCapabilityTags(value) {
|
|
3109
3130
|
if (!Array.isArray(value)) return void 0;
|
|
@@ -3561,6 +3582,39 @@ var init_mesh_config = __esm({
|
|
|
3561
3582
|
}
|
|
3562
3583
|
});
|
|
3563
3584
|
|
|
3585
|
+
// src/mesh/mesh-node-slots.ts
|
|
3586
|
+
function normalizeProviderPriority(policy) {
|
|
3587
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
3588
|
+
if (!Array.isArray(raw)) return [];
|
|
3589
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3590
|
+
return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
|
|
3591
|
+
if (seen.has(type)) return false;
|
|
3592
|
+
seen.add(type);
|
|
3593
|
+
return true;
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
function resolveNodeCapabilitySlots(node) {
|
|
3597
|
+
const explicit = normalizeNodeCapabilitySlots(node?.policy?.slots);
|
|
3598
|
+
if (explicit.length) return explicit;
|
|
3599
|
+
let difficultyBrains;
|
|
3600
|
+
try {
|
|
3601
|
+
difficultyBrains = getDifficultyBrains();
|
|
3602
|
+
} catch {
|
|
3603
|
+
difficultyBrains = void 0;
|
|
3604
|
+
}
|
|
3605
|
+
return deriveSlotsFromLegacy({
|
|
3606
|
+
providerPriority: normalizeProviderPriority(node?.policy),
|
|
3607
|
+
difficultyBrains
|
|
3608
|
+
});
|
|
3609
|
+
}
|
|
3610
|
+
var init_mesh_node_slots = __esm({
|
|
3611
|
+
"src/mesh/mesh-node-slots.ts"() {
|
|
3612
|
+
"use strict";
|
|
3613
|
+
init_dist();
|
|
3614
|
+
init_mesh_config();
|
|
3615
|
+
}
|
|
3616
|
+
});
|
|
3617
|
+
|
|
3564
3618
|
// src/mesh/coordinator-prompt.ts
|
|
3565
3619
|
var coordinator_prompt_exports = {};
|
|
3566
3620
|
__export(coordinator_prompt_exports, {
|
|
@@ -3744,13 +3798,20 @@ function buildNodeConfigSection(mesh) {
|
|
|
3744
3798
|
const explicitMachineLabel = typeof n.machineLabel === "string" ? n.machineLabel : "";
|
|
3745
3799
|
const explicitLabel = explicitMachineLabel ? ` label: **${explicitMachineLabel}** |` : "";
|
|
3746
3800
|
const providerPriority = n.policy?.providerPriority?.length ? ` | providers: ${n.policy.providerPriority.join(", ")}` : "";
|
|
3747
|
-
const
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3801
|
+
const nodeSlots = resolveNodeCapabilitySlots(n);
|
|
3802
|
+
const seenCapProvider = /* @__PURE__ */ new Set();
|
|
3803
|
+
const providerCaps = [];
|
|
3804
|
+
for (const slot of nodeSlots) {
|
|
3805
|
+
const type = typeof slot?.provider === "string" ? slot.provider.trim() : "";
|
|
3806
|
+
if (!type) continue;
|
|
3807
|
+
const key2 = type.toLowerCase();
|
|
3808
|
+
if (seenCapProvider.has(key2)) continue;
|
|
3809
|
+
seenCapProvider.add(key2);
|
|
3810
|
+
const cap = resolveProviderMaxParallel(nodeSlots, type);
|
|
3811
|
+
if (cap === void 0) continue;
|
|
3812
|
+
providerCaps.push(`${type} (max ${cap})`);
|
|
3813
|
+
}
|
|
3814
|
+
const providerRolesSuffix = providerCaps.length ? ` | caps: ${providerCaps.join(", ")}` : "";
|
|
3754
3815
|
lines.push(`- ${explicitLabel} nodeId: \`${n.id}\` | workspace: \`${n.workspace}\`${n.daemonId ? ` | daemon: \`${n.daemonId}\`` : ""}${providerPriority}${providerRolesSuffix}${suffix}`);
|
|
3755
3816
|
const routingTags = [];
|
|
3756
3817
|
const custom = Array.isArray(n.capabilities) ? n.capabilities : [];
|
|
@@ -3925,7 +3986,7 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
3925
3986
|
|
|
3926
3987
|
- **Route, don't implement.** Delegate all code reading, analysis, and execution to node agents. Never read source files or run commands in the coordinator \u2014 keep context lean.
|
|
3927
3988
|
- **Front-load task messages.** Include everything the agent needs (files, problem, expected fix) in \`mesh_enqueue_task\` / \`mesh_send_task\`. Append a structured result request at the end: ask the worker to conclude with a JSON block containing \`status\`, \`changedFiles\`, \`gitStatus\`, \`validationResults\`, \`errors\`, \`nextAction\`. The daemon parses this automatically; you can read it from \`mesh_task_history\`.
|
|
3928
|
-
- **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
|
|
3989
|
+
- **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 a fresh session only when: (a) branch/worktree isolation is required, (b) the existing session had a dispatch failure or provider mismatch, (c) the transcript/runtime is contaminated or interrupted, or (d) the user explicitly asks for a different provider/session. Continuation of the same issue in an already-idle session is allowed and preferred \u2014 this rule blocks concurrent unrelated work interleaved into a live (still-generating) session, not sequential same-issue follow-ups.
|
|
3929
3990
|
- **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.
|
|
3930
3991
|
- **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.
|
|
3931
3992
|
- **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.
|
|
@@ -3933,12 +3994,15 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
3933
3994
|
- **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
|
|
3934
3995
|
- **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).
|
|
3935
3996
|
- **Check history first.** Call \`mesh_task_history\` at session start to avoid duplicate work and inform recovery. On failure, read task history before retrying.
|
|
3997
|
+
- **Don't reopen already-done work after a resume.** Before reopening a reported issue after context compaction or session resume, check current git state and recent session context. If another session has already completed the work, continue from the existing diff/commit instead of starting a duplicate investigation.
|
|
3936
3998
|
- **Sequence shared-base-moving merges.** Parallel dispatch is encouraged, but merging one worktree can advance another in-flight worktree's base \u2014 especially a shared submodule pointer \u2014 turning a clean fast-forward into a diverged rebase (patch-equivalence correctly blocks this). Before merging an in-flight worktree while siblings are also in flight, land in an intentional order, re-clone long-running worktrees from the advanced base, or expect to manually rebase + ff-only the laggards; merging an independent fix mid-flight can strand siblings into a rebase.
|
|
3937
3999
|
- **Converge branches.** After worktree tasks: refine/fast-forward, or classify as \`pushed_feature_branch_needs_merge\` / \`blocked_review\` / \`cleanup_candidate\` / \`not_mergeable\`. Clean up with \`mesh_remove_node\`.
|
|
3938
4000
|
- **Refinery is config-driven.** \`mesh_refine_node\` must run validation from \`.adhdev/refine.{json,yaml,yml}\` or \`repo-mesh.refine.*\`. Heuristics are scaffolding only.
|
|
3939
4001
|
- **Submodule reachability = publish-needed.** \`submodule_reachability_failed\` \u2192 classify as \`blocked_review\`, request user approval to push to submodule main, then rerun \`mesh_refine_node\`.
|
|
3940
4002
|
- **Honor per-node instructions.** When a node carries a \u{1F4CC} Node instruction in the nodes section, include the relevant parts of that instruction in the task message you send to that node. Don't paraphrase the instruction into your own words \u2014 quote it verbatim so the worker agent sees exactly what the user wrote.
|
|
3941
4003
|
- **Mission status does not update itself.** When a mission's tasks are all done or the work is abandoned, explicitly call \`mesh_mission_upsert\` to set status \`completed\` or \`abandoned\`. Never leave a finished mission in \`active\`. All-cancelled tasks with no further work \u2192 \`abandoned\`.
|
|
4004
|
+
- **Don't spawn a nested coordinator for simple inspection.** Do not spawn a nested coordinator-like agent for simple inspection tasks. If delegation is required, use explicit provider selection and a fully self-contained, bounded task instruction.
|
|
4005
|
+
- **Keep internal traffic out of the transcript.** Internal tool calls, status events, control messages, and debug output must not appear as ordinary user-visible chat transcript content unless explicitly marked user-facing by the producing agent.
|
|
3942
4006
|
- **Never fabricate tool results.** Always call the actual tool.
|
|
3943
4007
|
- **Keep the user informed.** One or two sentences after each delegation round.${coordinatorNote}
|
|
3944
4008
|
|
|
@@ -3959,6 +4023,7 @@ var init_coordinator_prompt = __esm({
|
|
|
3959
4023
|
path8 = __toESM(require("path"));
|
|
3960
4024
|
init_repo_mesh_types();
|
|
3961
4025
|
init_mesh_config();
|
|
4026
|
+
init_mesh_node_slots();
|
|
3962
4027
|
init_dist();
|
|
3963
4028
|
PROMPT_SOFT_CAP_BYTES = 60 * 1024;
|
|
3964
4029
|
OPERATING_NOTES_PROMPT_CAP = 20;
|
|
@@ -4043,7 +4108,9 @@ Follow these recovery rules:
|
|
|
4043
4108
|
1. **If "Retry recommended"**: Check \`mesh_view_queue\` first \u2014 the daemon may have auto-requeued. If not, re-launch the session on the same node (\`mesh_launch_session\`), then resend the original task (\`mesh_send_task\`). The system message includes the original task text.
|
|
4044
4109
|
2. **If "Max retries exceeded"**: Do NOT retry on the same node. Either reassign the task to a different node, or inform the user that the task requires manual intervention.
|
|
4045
4110
|
3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
|
|
4046
|
-
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user
|
|
4111
|
+
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.
|
|
4112
|
+
5. **Stuck-but-done vs actually-stuck**: If a delegated session appears stuck but has already produced a verified final summary or diff, stop polling noisy tool/terminal transcript bubbles. Verify with \`mesh_git_status\` or a checkpoint and proceed to landing.
|
|
4113
|
+
6. **Refinery falsely blocks a verified-clean branch \u2014 manual fast-forward convergence**: When \`mesh_refine_node\` falsely blocks a verified-clean branch (stale preflight, or the submodule-gitlink trivial-fast-forward misjudgment), bypass the refine tool and converge by strict fast-forward \u2014 (1) rebase the submodule commit onto the submodule \`origin/main\`, (2) push the submodule ff-only (verify \`git merge-base --is-ancestor\` first), (3) rebase the root branch and re-bump the submodule pointer so the root diff stays non-empty, (4) push the root ff-only. NEVER force-push or reset; abort and report on any non-fast-forward.`;
|
|
4047
4114
|
ONBOARDING_SECTION = `## Onboarding / Reinit
|
|
4048
4115
|
|
|
4049
4116
|
When the user asks to **set up / configure / onboard** this repo for Repo Mesh (or to **re-init / reconfigure** an already-onboarded repo), run ONE guided, approval-gated conversation. You draft, the user approves, the daemon writes. Never auto-write a heuristic suggestion without an explicit user approval turn.
|
|
@@ -7158,8 +7225,8 @@ var init_mesh_runtime_store = __esm({
|
|
|
7158
7225
|
/**
|
|
7159
7226
|
* Count active (status='assigned') tasks on a (node, provider) combination,
|
|
7160
7227
|
* matched by the assignedProviderType stamped on the payload at claim time.
|
|
7161
|
-
* Drives the per-(node, provider) maxParallel cap (
|
|
7162
|
-
*
|
|
7228
|
+
* Drives the per-(node, provider) maxParallel cap (summed across a provider's
|
|
7229
|
+
* slots[].maxParallel). The active-assignment set for a single node is tiny, so
|
|
7163
7230
|
* parsing payloads here is cheap and avoids a schema migration. Pre-cap legacy
|
|
7164
7231
|
* rows (no provider stamp) and other providers on the same node do not consume
|
|
7165
7232
|
* this provider's budget, so the cap is fully backward compatible.
|
|
@@ -12909,15 +12976,22 @@ function buildMeshSchedulingRuntime(mesh, queue) {
|
|
|
12909
12976
|
if (globalWriteCapReached) capReasons.push("global_max_parallel_tasks_reached");
|
|
12910
12977
|
if ((writeAssignedByNode.get(nodeId) ?? 0) > 0) capReasons.push("node_has_active_assignment");
|
|
12911
12978
|
let providerRoles;
|
|
12912
|
-
const
|
|
12913
|
-
|
|
12979
|
+
const slots = resolveNodeCapabilitySlots(rawNode);
|
|
12980
|
+
const cappedProviders = [];
|
|
12981
|
+
const seenProvider = /* @__PURE__ */ new Set();
|
|
12982
|
+
for (const slot of slots) {
|
|
12983
|
+
const providerType = typeof slot?.provider === "string" ? slot.provider.trim() : "";
|
|
12984
|
+
if (!providerType) continue;
|
|
12985
|
+
const key2 = providerType.toLowerCase();
|
|
12986
|
+
if (seenProvider.has(key2)) continue;
|
|
12987
|
+
seenProvider.add(key2);
|
|
12988
|
+
if (resolveProviderMaxParallel(slots, providerType) !== void 0) cappedProviders.push(providerType);
|
|
12989
|
+
}
|
|
12990
|
+
if (cappedProviders.length) {
|
|
12914
12991
|
const byProvider = providerCountByNode.get(nodeId);
|
|
12915
12992
|
providerRoles = [];
|
|
12916
|
-
for (const
|
|
12917
|
-
|
|
12918
|
-
const providerType = typeof role.providerType === "string" ? role.providerType.trim() : "";
|
|
12919
|
-
if (!providerType) continue;
|
|
12920
|
-
const maxParallel = resolveProviderMaxParallel(policy, providerType);
|
|
12993
|
+
for (const providerType of cappedProviders) {
|
|
12994
|
+
const maxParallel = resolveProviderMaxParallel(slots, providerType);
|
|
12921
12995
|
const activeAssigned = byProvider?.get(providerType) ?? 0;
|
|
12922
12996
|
const capReached = maxParallel !== void 0 && activeAssigned >= maxParallel;
|
|
12923
12997
|
providerRoles.push({
|
|
@@ -12958,6 +13032,7 @@ var init_mesh_scheduling_runtime = __esm({
|
|
|
12958
13032
|
"use strict";
|
|
12959
13033
|
init_repo_mesh_types();
|
|
12960
13034
|
init_dist();
|
|
13035
|
+
init_mesh_node_slots();
|
|
12961
13036
|
init_mesh_work_queue();
|
|
12962
13037
|
}
|
|
12963
13038
|
});
|
|
@@ -15568,6 +15643,32 @@ var init_mesh_clone_grace = __esm({
|
|
|
15568
15643
|
}
|
|
15569
15644
|
});
|
|
15570
15645
|
|
|
15646
|
+
// src/mesh/model-provider-compat.ts
|
|
15647
|
+
function isAnthropicProvider(providerType) {
|
|
15648
|
+
const p = typeof providerType === "string" ? providerType.trim().toLowerCase() : "";
|
|
15649
|
+
return p.length > 0 && ANTHROPIC_PROVIDER_TYPES.has(p);
|
|
15650
|
+
}
|
|
15651
|
+
function isAnthropicModel(model) {
|
|
15652
|
+
const m = typeof model === "string" ? model.trim().toLowerCase() : "";
|
|
15653
|
+
if (!m) return false;
|
|
15654
|
+
if (m.startsWith("claude") || m.startsWith("anthropic")) return true;
|
|
15655
|
+
return /^(opus|sonnet|haiku)(\b|[-_])/.test(m);
|
|
15656
|
+
}
|
|
15657
|
+
function isModelCompatibleWithProvider(model, providerType) {
|
|
15658
|
+
if (!isAnthropicModel(model)) return true;
|
|
15659
|
+
if (isAnthropicProvider(providerType)) return true;
|
|
15660
|
+
return false;
|
|
15661
|
+
}
|
|
15662
|
+
var ANTHROPIC_PROVIDER_TYPES;
|
|
15663
|
+
var init_model_provider_compat = __esm({
|
|
15664
|
+
"src/mesh/model-provider-compat.ts"() {
|
|
15665
|
+
"use strict";
|
|
15666
|
+
ANTHROPIC_PROVIDER_TYPES = /* @__PURE__ */ new Set([
|
|
15667
|
+
"claude-cli"
|
|
15668
|
+
]);
|
|
15669
|
+
}
|
|
15670
|
+
});
|
|
15671
|
+
|
|
15571
15672
|
// src/mesh/mesh-queue-assignment.ts
|
|
15572
15673
|
function localCoordinatorDaemonId() {
|
|
15573
15674
|
return canonicalDaemonId(readNonEmptyString2(loadConfig().machineId));
|
|
@@ -15746,7 +15847,7 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
15746
15847
|
return false;
|
|
15747
15848
|
}
|
|
15748
15849
|
const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
|
|
15749
|
-
const providerMaxParallel = resolveProviderMaxParallel(node
|
|
15850
|
+
const providerMaxParallel = resolveProviderMaxParallel(resolveNodeCapabilitySlots(node), providerType);
|
|
15750
15851
|
const nodeIsWorktree = node?.isLocalWorktree === true;
|
|
15751
15852
|
const task = claimNextTask(meshId, nodeId, sessionId, capabilityTags, {
|
|
15752
15853
|
providerType,
|
|
@@ -16004,7 +16105,7 @@ function sweepExpiredCooldowns() {
|
|
|
16004
16105
|
if (now >= until) autoLaunchCooldownUntil.delete(key2);
|
|
16005
16106
|
}
|
|
16006
16107
|
}
|
|
16007
|
-
function
|
|
16108
|
+
function normalizeProviderPriority2(policy) {
|
|
16008
16109
|
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
16009
16110
|
if (!Array.isArray(raw)) return [];
|
|
16010
16111
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -16118,21 +16219,6 @@ function buildSchedulingPool(localCandidates, remoteCandidates) {
|
|
|
16118
16219
|
}));
|
|
16119
16220
|
return { pool, uniqueNodes };
|
|
16120
16221
|
}
|
|
16121
|
-
function resolveNodeCapabilitySlots(node) {
|
|
16122
|
-
const explicit = normalizeNodeCapabilitySlots(node?.policy?.slots);
|
|
16123
|
-
if (explicit.length) return explicit;
|
|
16124
|
-
let difficultyBrains;
|
|
16125
|
-
try {
|
|
16126
|
-
difficultyBrains = getDifficultyBrains();
|
|
16127
|
-
} catch {
|
|
16128
|
-
difficultyBrains = void 0;
|
|
16129
|
-
}
|
|
16130
|
-
return deriveSlotsFromLegacy({
|
|
16131
|
-
providerPriority: normalizeProviderPriority(node?.policy),
|
|
16132
|
-
providerRoles: Array.isArray(node?.policy?.providerRoles) ? node.policy.providerRoles : void 0,
|
|
16133
|
-
difficultyBrains
|
|
16134
|
-
});
|
|
16135
|
-
}
|
|
16136
16222
|
function scoreSlotForTask(slot, task) {
|
|
16137
16223
|
let score = 1;
|
|
16138
16224
|
const diff = isMeshTaskDifficulty(task.difficulty) ? task.difficulty : void 0;
|
|
@@ -16259,6 +16345,7 @@ function nodeHasLiveSessionPendingClaim(components, meshId, nodeId) {
|
|
|
16259
16345
|
const state = inst.getState();
|
|
16260
16346
|
const settings = state.settings || {};
|
|
16261
16347
|
if (readNonEmptyString2(settings.meshNodeFor) !== meshId) return false;
|
|
16348
|
+
if (readNonEmptyString2(settings.meshCoordinatorFor) === meshId) return false;
|
|
16262
16349
|
const instNodeId = readNonEmptyString2(settings.meshNodeId) || readNonEmptyString2(settings.nodeId);
|
|
16263
16350
|
if (!daemonIdsEquivalent(instNodeId, nodeId)) return false;
|
|
16264
16351
|
const status = readNonEmptyString2(state.status).toLowerCase();
|
|
@@ -16463,7 +16550,7 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
|
16463
16550
|
if (task.targetNodeId && !meshNodeIdMatches(node, task.targetNodeId)) return false;
|
|
16464
16551
|
if (task.taskMode === "convergence" && node?.isLocalWorktree === true) return false;
|
|
16465
16552
|
if (task.requiredTags?.length) {
|
|
16466
|
-
const priorities =
|
|
16553
|
+
const priorities = normalizeProviderPriority2(node?.policy);
|
|
16467
16554
|
const providerCandidates = priorities.length ? priorities : [void 0];
|
|
16468
16555
|
return providerCandidates.some(
|
|
16469
16556
|
(p) => nodeSatisfiesRequiredTags(task.requiredTags, buildMeshNodeCapabilityTags(node, p))
|
|
@@ -16544,9 +16631,13 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
|
16544
16631
|
markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
|
|
16545
16632
|
continue;
|
|
16546
16633
|
}
|
|
16547
|
-
const
|
|
16634
|
+
const rawEffectiveModel = typeof task.model === "string" && task.model.trim() ? task.model.trim() : resolved.model;
|
|
16548
16635
|
const effectiveThinkingLevel = typeof task.thinkingLevel === "string" && task.thinkingLevel.trim() ? task.thinkingLevel.trim() : resolved.thinkingLevel;
|
|
16549
|
-
const
|
|
16636
|
+
const effectiveModel = isModelCompatibleWithProvider(rawEffectiveModel, resolved.providerType) ? rawEffectiveModel : void 0;
|
|
16637
|
+
if (rawEffectiveModel && effectiveModel === void 0) {
|
|
16638
|
+
LOG.info("MeshQueue", `CODEX-400 GUARD: dropped incompatible launch model '${rawEffectiveModel}' for non-Anthropic provider '${resolved.providerType}' on node ${nodeId} (task ${task.id}); provider will use its own default model`);
|
|
16639
|
+
}
|
|
16640
|
+
const providerCap = resolveProviderMaxParallel(resolveNodeCapabilitySlots(node), resolved.providerType);
|
|
16550
16641
|
if (providerCap !== void 0 && activeProviderAssignedCount(meshId, nodeId, resolved.providerType) >= providerCap) {
|
|
16551
16642
|
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_provider_parallel_reached", nodeId, providerType: resolved.providerType });
|
|
16552
16643
|
continue;
|
|
@@ -16870,6 +16961,7 @@ var init_mesh_queue_assignment = __esm({
|
|
|
16870
16961
|
init_mesh_warmup_deadline();
|
|
16871
16962
|
init_repo_mesh_types();
|
|
16872
16963
|
init_dist();
|
|
16964
|
+
init_mesh_node_slots();
|
|
16873
16965
|
init_mesh_events_stale();
|
|
16874
16966
|
init_mesh_events_utils();
|
|
16875
16967
|
init_mesh_node_identity();
|
|
@@ -16877,6 +16969,7 @@ var init_mesh_queue_assignment = __esm({
|
|
|
16877
16969
|
init_worktree_bootstrap_config();
|
|
16878
16970
|
init_mesh_clone_grace();
|
|
16879
16971
|
init_mesh_task_inflight();
|
|
16972
|
+
init_model_provider_compat();
|
|
16880
16973
|
IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
|
|
16881
16974
|
idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
|
|
16882
16975
|
BOOTSTRAP_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["complete", "failed"]);
|
|
@@ -45725,6 +45818,7 @@ var STATUS_HYDRATION_TAIL_LIMIT = 200;
|
|
|
45725
45818
|
var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
45726
45819
|
var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
45727
45820
|
var NATIVE_HISTORY_MESH_IDLE_SETTLE_MS = 4e3;
|
|
45821
|
+
var PTY_PARSED_FINAL_ASSISTANT_QUIET_DWELL_MS = 1200;
|
|
45728
45822
|
var BACKGROUND_TASK_HOLD_MAX_MS = 5 * 6e4;
|
|
45729
45823
|
var USER_INPUT_ACK_DEDUP_WINDOW_MS = 6e4;
|
|
45730
45824
|
var STARTUP_GRACE_IDLE_COLLAPSE_WINDOW_MS = 12e3;
|
|
@@ -45967,6 +46061,39 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
45967
46061
|
* the nudge was NOT deferred) and leaked to the coordinator.
|
|
45968
46062
|
*/
|
|
45969
46063
|
static AUTO_APPROVE_MASK_STALL_MS = 10500;
|
|
46064
|
+
/**
|
|
46065
|
+
* AUTOAPPROVE-FLAP-INBOX-MISSING: sticky-approval overlay window. Same time-tick
|
|
46066
|
+
* hold idea as the FALSE-IDLE completion gate — an approval signal that was
|
|
46067
|
+
* DOMINANT within this recent window is re-presented across a momentary busy blip
|
|
46068
|
+
* instead of collapsing.
|
|
46069
|
+
*
|
|
46070
|
+
* RCA (live 2026-07-13): a claude-cli worker sitting at a Bash approval modal
|
|
46071
|
+
* ("Do you want to proceed? ❯1.Yes") flaps waiting_approval↔busy on a ~2-3s period.
|
|
46072
|
+
* The spec `approval→busy` transition fires whenever the footer/modal approval
|
|
46073
|
+
* markers momentarily drop out of their parsed sections while the PRIOR command's
|
|
46074
|
+
* residual spinner text ("✳ Checking vendor drift…") still matches the busy regex.
|
|
46075
|
+
* On the busy frame the adapter reports status='generating', activeModal=null. That
|
|
46076
|
+
* corrupts THREE consumers at once: (1) mesh_active_work samples 'generating' →
|
|
46077
|
+
* collectPendingApprovals never sees 'awaiting_approval' → mesh_list_pending_approvals
|
|
46078
|
+
* count:0 (inbox miss); (2) the auto-approve settle gate is torn down each busy phase
|
|
46079
|
+
* so the 600ms settle never accrues → auto-approve never fires; (3) a mesh_approve
|
|
46080
|
+
* landing on a busy frame hits "Not in approval state". The existing FLAP machinery
|
|
46081
|
+
* (AUTO_APPROVE_FLAP_CONTINUITY_MS) only keeps the settle gate warm while status is
|
|
46082
|
+
* STILL waiting_approval (buttons scrolled out) — it does nothing once the FSM fully
|
|
46083
|
+
* commits to 'busy', and it never stabilises the status the inbox samples.
|
|
46084
|
+
*
|
|
46085
|
+
* Fix: when the raw adapter status flaps to generating/busy/idle but a
|
|
46086
|
+
* waiting_approval WITH a concrete modal was observed within this window, overlay
|
|
46087
|
+
* the cached modal and report status='waiting_approval'. This stabilized status
|
|
46088
|
+
* feeds getState (→ inbox), detectStatusTransition (→ event emission), and
|
|
46089
|
+
* maybeAutoApproveStatus (→ settle gate) uniformly, so the approval both registers
|
|
46090
|
+
* in the inbox and settles for auto-approve across the flap. Bounded (a genuine
|
|
46091
|
+
* resume that never returns to approval unmasks after this window) and scoped at the
|
|
46092
|
+
* call site to autonomous mesh sessions. 4000ms bridges the observed ~2-3s flap with
|
|
46093
|
+
* margin while staying well under AUTO_APPROVE_MASK_STALL_MS (a truly stalled/absent
|
|
46094
|
+
* approval still surfaces).
|
|
46095
|
+
*/
|
|
46096
|
+
static APPROVAL_STICKY_FLAP_MS = 4e3;
|
|
45970
46097
|
/**
|
|
45971
46098
|
* FALSE-IDLE (inter-approval quiet valley): grace window after an auto-approve
|
|
45972
46099
|
* (or mesh_approve) RESOLVES a modal during which a subsequent generating→idle
|
|
@@ -46070,6 +46197,14 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
46070
46197
|
// signature) while a genuinely closed modal — buttons empty continuously past
|
|
46071
46198
|
// the continuity window — is still recognised and resets the gate.
|
|
46072
46199
|
autoApproveLastModalSeenAt = 0;
|
|
46200
|
+
// AUTOAPPROVE-FLAP-INBOX-MISSING sticky-approval overlay (see APPROVAL_STICKY_FLAP_MS).
|
|
46201
|
+
// The wall-clock of the last frame where the RAW adapter reported waiting_approval with
|
|
46202
|
+
// a CONCRETE modal (buttons present), the cached modal to re-present across a busy blip,
|
|
46203
|
+
// and the approvalEntrySeq at that frame (so a stabilized frame carries the right seq to
|
|
46204
|
+
// the emission-dedup fingerprint). All zero/null when no recent concrete approval.
|
|
46205
|
+
approvalStickyLastConcreteAt = 0;
|
|
46206
|
+
approvalStickyModal = null;
|
|
46207
|
+
approvalStickyEntrySeq = 0;
|
|
46073
46208
|
// STATUS-MISMATCH: wall-clock when the CURRENT auto-approve episode (waiting_approval
|
|
46074
46209
|
// + shouldAutoApprove) first began wanting to mask. Unlike pendingAutoApprovalSince it
|
|
46075
46210
|
// is NOT reset when the modal signature changes (a still-streaming/flapping prompt) and
|
|
@@ -46211,7 +46346,7 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
46211
46346
|
}
|
|
46212
46347
|
}
|
|
46213
46348
|
getState() {
|
|
46214
|
-
const adapterStatus = this.adapter.getStatus();
|
|
46349
|
+
const adapterStatus = this.stabilizeFlappingApprovalStatus(this.adapter.getStatus());
|
|
46215
46350
|
if (Object.prototype.hasOwnProperty.call(adapterStatus, "activeInteractivePrompt")) {
|
|
46216
46351
|
this.activeInteractivePrompt = adapterStatus.activeInteractivePrompt ?? null;
|
|
46217
46352
|
}
|
|
@@ -47075,6 +47210,9 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
47075
47210
|
return null;
|
|
47076
47211
|
}
|
|
47077
47212
|
if (this.type === "antigravity-cli") {
|
|
47213
|
+
if (allowMissingAssistantTimeout) {
|
|
47214
|
+
return { reason: "missing_final_assistant", terminal: false, holdForTranscript: true };
|
|
47215
|
+
}
|
|
47078
47216
|
return null;
|
|
47079
47217
|
}
|
|
47080
47218
|
if (allowMissingAssistantTimeout && pending.previousStatus === "waiting_approval") {
|
|
@@ -47106,6 +47244,19 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
47106
47244
|
}
|
|
47107
47245
|
} catch {
|
|
47108
47246
|
}
|
|
47247
|
+
if (allowMissingAssistantTimeout && !adapterOwnsMessagesElsewhere && finalAssistantEvidence.source === "parsed") {
|
|
47248
|
+
try {
|
|
47249
|
+
const outStatus = this.adapter.getStatus({ allowParse: false });
|
|
47250
|
+
const lastOutputAt = typeof outStatus?.lastOutputAt === "number" && Number.isFinite(outStatus.lastOutputAt) ? outStatus.lastOutputAt : void 0;
|
|
47251
|
+
if (typeof lastOutputAt === "number") {
|
|
47252
|
+
const quietMs = Date.now() - lastOutputAt;
|
|
47253
|
+
if (quietMs < PTY_PARSED_FINAL_ASSISTANT_QUIET_DWELL_MS) {
|
|
47254
|
+
return { reason: "parsed_final_assistant_quiet_dwell", terminal: false };
|
|
47255
|
+
}
|
|
47256
|
+
}
|
|
47257
|
+
} catch {
|
|
47258
|
+
}
|
|
47259
|
+
}
|
|
47109
47260
|
return null;
|
|
47110
47261
|
}
|
|
47111
47262
|
// (FALSEIDLE-a) Positive, structural proof that the latest approval entry was resolved
|
|
@@ -47527,6 +47678,53 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
47527
47678
|
...opts.completionDiagnostic !== void 0 ? { completionDiagnostic: opts.completionDiagnostic } : {}
|
|
47528
47679
|
});
|
|
47529
47680
|
}
|
|
47681
|
+
/**
|
|
47682
|
+
* AUTOAPPROVE-FLAP-INBOX-MISSING sticky-approval overlay. Returns the adapterStatus a
|
|
47683
|
+
* flap-prone claude-cli approval SHOULD present this frame — either the raw status
|
|
47684
|
+
* unchanged, or, when the raw status has momentarily flapped OFF a recently-dominant
|
|
47685
|
+
* concrete approval, a synthetic `waiting_approval` re-presenting the cached modal.
|
|
47686
|
+
*
|
|
47687
|
+
* Records the concrete approval whenever the raw status is waiting_approval WITH
|
|
47688
|
+
* buttons. On a subsequent non-approval frame (the spec `approval→busy` flap), if that
|
|
47689
|
+
* concrete approval was seen within APPROVAL_STICKY_FLAP_MS AND the engine has NOT
|
|
47690
|
+
* resolved a modal since (lastApprovalResolvedAt not advanced past the sticky start),
|
|
47691
|
+
* overlay the cached modal + waiting_approval so the inbox / auto-approve / mesh_approve
|
|
47692
|
+
* all see the stable approval. A genuine resolution (auto-approve or mesh_approve fires
|
|
47693
|
+
* resolveModal → lastApprovalResolvedAt advances) clears the sticky immediately, so a
|
|
47694
|
+
* legitimate post-approval resume is NEVER masked as a lingering approval. Bounded by the
|
|
47695
|
+
* window, and scoped to autonomous mesh sessions (a foreground/attended or non-mesh
|
|
47696
|
+
* session, where a human answers the prompt, is returned untouched).
|
|
47697
|
+
*/
|
|
47698
|
+
stabilizeFlappingApprovalStatus(adapterStatus, now = Date.now()) {
|
|
47699
|
+
if (!this.isAutonomousMeshSession() || !this.shouldAutoApprove()) return adapterStatus;
|
|
47700
|
+
const rawStatus = adapterStatus?.status;
|
|
47701
|
+
const resolvedAt = typeof this.adapter?.lastApprovalResolvedAt === "number" ? this.adapter.lastApprovalResolvedAt : 0;
|
|
47702
|
+
if (rawStatus === "waiting_approval") {
|
|
47703
|
+
if (hasNonEmptyCliModalButtons(adapterStatus?.activeModal)) {
|
|
47704
|
+
this.approvalStickyLastConcreteAt = now;
|
|
47705
|
+
this.approvalStickyModal = adapterStatus.activeModal;
|
|
47706
|
+
this.approvalStickyEntrySeq = typeof adapterStatus?.approvalEntrySeq === "number" ? adapterStatus.approvalEntrySeq : this.approvalStickyEntrySeq;
|
|
47707
|
+
}
|
|
47708
|
+
return adapterStatus;
|
|
47709
|
+
}
|
|
47710
|
+
if (this.approvalStickyLastConcreteAt > 0 && this.approvalStickyModal) {
|
|
47711
|
+
const withinWindow = now - this.approvalStickyLastConcreteAt < _CliProviderInstance.APPROVAL_STICKY_FLAP_MS;
|
|
47712
|
+
const resolvedSinceAnchor = resolvedAt >= this.approvalStickyLastConcreteAt;
|
|
47713
|
+
if (withinWindow && !resolvedSinceAnchor) {
|
|
47714
|
+
return {
|
|
47715
|
+
...adapterStatus,
|
|
47716
|
+
status: "waiting_approval",
|
|
47717
|
+
activeModal: this.approvalStickyModal,
|
|
47718
|
+
...this.approvalStickyEntrySeq ? { approvalEntrySeq: this.approvalStickyEntrySeq } : {},
|
|
47719
|
+
approvalStickyOverlay: true
|
|
47720
|
+
};
|
|
47721
|
+
}
|
|
47722
|
+
this.approvalStickyLastConcreteAt = 0;
|
|
47723
|
+
this.approvalStickyModal = null;
|
|
47724
|
+
this.approvalStickyEntrySeq = 0;
|
|
47725
|
+
}
|
|
47726
|
+
return adapterStatus;
|
|
47727
|
+
}
|
|
47530
47728
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
47531
47729
|
if (adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove() && this.manualAttendance.isAttended(now)) {
|
|
47532
47730
|
this.lastAutoApprovalSignature = "";
|
|
@@ -47756,7 +47954,7 @@ ${buttons.join("\n")}`;
|
|
|
47756
47954
|
}
|
|
47757
47955
|
detectStatusTransition() {
|
|
47758
47956
|
const now = Date.now();
|
|
47759
|
-
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
47957
|
+
const adapterStatus = this.stabilizeFlappingApprovalStatus(this.adapter.getStatus({ allowParse: false }), now);
|
|
47760
47958
|
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
47761
47959
|
this.provider,
|
|
47762
47960
|
typeof adapterStatus?.providerSessionId === "string" ? adapterStatus.providerSessionId : ""
|
|
@@ -56045,7 +56243,7 @@ var meshCrudHandlers = {
|
|
|
56045
56243
|
const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
|
|
56046
56244
|
if (ownerFailure) return ownerFailure;
|
|
56047
56245
|
try {
|
|
56048
|
-
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
56246
|
+
const { addNode: addNode2, migrateProviderRolesToSlots: migrateProviderRolesToSlots2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
56049
56247
|
const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
|
|
56050
56248
|
const readOnly = args?.readOnly === true;
|
|
56051
56249
|
const providerRoles = normalizeProviderRoles(args?.providerRoles);
|
|
@@ -56054,6 +56252,7 @@ var meshCrudHandlers = {
|
|
|
56054
56252
|
...providerPriority.length ? { providerPriority } : {},
|
|
56055
56253
|
...providerRoles.length ? { providerRoles } : {}
|
|
56056
56254
|
};
|
|
56255
|
+
if (providerRoles.length) migrateProviderRolesToSlots2(policy);
|
|
56057
56256
|
const role = normalizeMeshDaemonRole(args?.role);
|
|
56058
56257
|
const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
|
|
56059
56258
|
const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
|
|
@@ -56082,7 +56281,7 @@ var meshCrudHandlers = {
|
|
|
56082
56281
|
const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node update");
|
|
56083
56282
|
if (ownerFailure) return ownerFailure;
|
|
56084
56283
|
try {
|
|
56085
|
-
const { updateNode: updateNode2, normalizeCapabilityTags: normalizeCapabilityTags2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
56284
|
+
const { updateNode: updateNode2, normalizeCapabilityTags: normalizeCapabilityTags2, migrateProviderRolesToSlots: migrateProviderRolesToSlots2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
56086
56285
|
const policy = args?.policy && typeof args.policy === "object" && !Array.isArray(args.policy) ? { ...args.policy } : {};
|
|
56087
56286
|
if (Array.isArray(args?.providerPriority)) {
|
|
56088
56287
|
const providerPriority = args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean);
|
|
@@ -56095,12 +56294,10 @@ var meshCrudHandlers = {
|
|
|
56095
56294
|
}
|
|
56096
56295
|
if (Array.isArray(args?.providerRoles)) {
|
|
56097
56296
|
const providerRoles = normalizeProviderRoles(args.providerRoles);
|
|
56098
|
-
if (providerRoles.length)
|
|
56099
|
-
|
|
56100
|
-
} else {
|
|
56101
|
-
delete policy.providerRoles;
|
|
56102
|
-
}
|
|
56297
|
+
if (providerRoles.length) policy.providerRoles = providerRoles;
|
|
56298
|
+
else delete policy.providerRoles;
|
|
56103
56299
|
}
|
|
56300
|
+
migrateProviderRolesToSlots2(policy);
|
|
56104
56301
|
const patch = { policy };
|
|
56105
56302
|
if (typeof args?.systemPrompt === "string") {
|
|
56106
56303
|
const trimmed = args.systemPrompt.trim();
|
|
@@ -56372,8 +56569,11 @@ var meshCrudHandlers = {
|
|
|
56372
56569
|
console.log(`[mesh] clone_mesh_node base sync: ${result.baseSync.action} (startRef=${result.baseSync.startRef})`);
|
|
56373
56570
|
}
|
|
56374
56571
|
let node;
|
|
56572
|
+
const { migrateProviderRolesToSlots: migrateProviderRolesToSlots2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
56375
56573
|
if (meshRecord.inline) {
|
|
56376
56574
|
const { randomUUID: randomUUID15 } = await import("crypto");
|
|
56575
|
+
const clonedPolicy = { ...sourceNode.policy || {} };
|
|
56576
|
+
migrateProviderRolesToSlots2(clonedPolicy);
|
|
56377
56577
|
node = {
|
|
56378
56578
|
id: `node_${randomUUID15().replace(/-/g, "")}`,
|
|
56379
56579
|
workspace: result.worktreePath,
|
|
@@ -56381,7 +56581,7 @@ var meshCrudHandlers = {
|
|
|
56381
56581
|
daemonId: sourceNode.daemonId,
|
|
56382
56582
|
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
56383
56583
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
56384
|
-
policy:
|
|
56584
|
+
policy: clonedPolicy,
|
|
56385
56585
|
isLocalWorktree: true,
|
|
56386
56586
|
worktreeBranch: result.branch,
|
|
56387
56587
|
clonedFromNodeId: sourceNodeId
|
|
@@ -56389,6 +56589,8 @@ var meshCrudHandlers = {
|
|
|
56389
56589
|
ctx.updateInlineMeshNode(meshId, mesh, node);
|
|
56390
56590
|
} else {
|
|
56391
56591
|
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
56592
|
+
const clonedPolicy = { ...sourceNode.policy || {} };
|
|
56593
|
+
migrateProviderRolesToSlots2(clonedPolicy);
|
|
56392
56594
|
node = addNode2(meshId, {
|
|
56393
56595
|
workspace: result.worktreePath,
|
|
56394
56596
|
repoRoot: result.worktreePath,
|
|
@@ -56398,7 +56600,7 @@ var meshCrudHandlers = {
|
|
|
56398
56600
|
isLocalWorktree: true,
|
|
56399
56601
|
worktreeBranch: result.branch,
|
|
56400
56602
|
clonedFromNodeId: sourceNodeId,
|
|
56401
|
-
policy:
|
|
56603
|
+
policy: clonedPolicy
|
|
56402
56604
|
});
|
|
56403
56605
|
if (!node) return { success: false, error: "Failed to register worktree node" };
|
|
56404
56606
|
const inlineForReconcile = ctx.getCachedInlineMesh(meshId);
|