@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.mjs
CHANGED
|
@@ -117,20 +117,20 @@ function resolveDelegatedWorkerAutoApprove(meshPolicy, nodePolicy) {
|
|
|
117
117
|
}
|
|
118
118
|
return true;
|
|
119
119
|
}
|
|
120
|
-
function resolveProviderMaxParallel(
|
|
120
|
+
function resolveProviderMaxParallel(slots, providerType) {
|
|
121
121
|
const wanted = typeof providerType === "string" ? providerType.trim().toLowerCase() : "";
|
|
122
122
|
if (!wanted) return void 0;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for (const
|
|
126
|
-
if (!
|
|
127
|
-
const type = typeof
|
|
123
|
+
if (!Array.isArray(slots)) return void 0;
|
|
124
|
+
let total;
|
|
125
|
+
for (const slot of slots) {
|
|
126
|
+
if (!slot || typeof slot !== "object") continue;
|
|
127
|
+
const type = typeof slot.provider === "string" ? slot.provider.trim().toLowerCase() : "";
|
|
128
128
|
if (!type || type !== wanted) continue;
|
|
129
|
-
const raw = Number(
|
|
130
|
-
if (!Number.isFinite(raw) || raw < 0)
|
|
131
|
-
|
|
129
|
+
const raw = Number(slot.maxParallel);
|
|
130
|
+
if (!Number.isFinite(raw) || raw < 0) continue;
|
|
131
|
+
total = (total ?? 0) + Math.floor(raw);
|
|
132
132
|
}
|
|
133
|
-
return
|
|
133
|
+
return total;
|
|
134
134
|
}
|
|
135
135
|
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;
|
|
136
136
|
var init_repo_mesh_types = __esm({
|
|
@@ -412,10 +412,10 @@ function readInjected(value) {
|
|
|
412
412
|
}
|
|
413
413
|
function getDaemonBuildInfo() {
|
|
414
414
|
if (cached) return cached;
|
|
415
|
-
const commit = readInjected(true ? "
|
|
416
|
-
const commitShort = readInjected(true ? "
|
|
417
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
418
|
-
const builtAt = readInjected(true ? "2026-07-
|
|
415
|
+
const commit = readInjected(true ? "8ebe4d4bcdc658e11253a51a28237d392aa4d978" : void 0) ?? "unknown";
|
|
416
|
+
const commitShort = readInjected(true ? "8ebe4d4b" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
417
|
+
const version = readInjected(true ? "0.9.82-rc.509" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
418
|
+
const builtAt = readInjected(true ? "2026-07-13T02:51:57.282Z" : void 0);
|
|
419
419
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
420
420
|
return cached;
|
|
421
421
|
}
|
|
@@ -2842,12 +2842,6 @@ function normalizeNodeCapabilitySlots(raw) {
|
|
|
2842
2842
|
function deriveSlotsFromLegacy(input) {
|
|
2843
2843
|
const priority = Array.isArray(input.providerPriority) ? input.providerPriority.filter((p) => typeof p === "string" && !!p.trim()).map((p) => p.trim()) : [];
|
|
2844
2844
|
if (priority.length === 0) return [];
|
|
2845
|
-
const roleCap = /* @__PURE__ */ new Map();
|
|
2846
|
-
for (const role of input.providerRoles || []) {
|
|
2847
|
-
if (role && typeof role.providerType === "string" && Number.isFinite(role.maxParallel)) {
|
|
2848
|
-
roleCap.set(role.providerType.trim(), Math.floor(Number(role.maxParallel)));
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
2845
|
const brains = input.difficultyBrains || {};
|
|
2852
2846
|
const byProvider = /* @__PURE__ */ new Map();
|
|
2853
2847
|
const shared = [];
|
|
@@ -2869,13 +2863,11 @@ function deriveSlotsFromLegacy(input) {
|
|
|
2869
2863
|
const difficulty = applied.map((a) => a.difficulty);
|
|
2870
2864
|
const model = applied.find((a) => a.model)?.model;
|
|
2871
2865
|
const thinkingLevel = applied.find((a) => a.thinkingLevel)?.thinkingLevel;
|
|
2872
|
-
const maxParallel = roleCap.get(provider);
|
|
2873
2866
|
return {
|
|
2874
2867
|
provider,
|
|
2875
2868
|
...model ? { model } : {},
|
|
2876
2869
|
...thinkingLevel ? { thinkingLevel } : {},
|
|
2877
|
-
...difficulty.length ? { difficulty } : {}
|
|
2878
|
-
...maxParallel !== void 0 ? { maxParallel } : {}
|
|
2870
|
+
...difficulty.length ? { difficulty } : {}
|
|
2879
2871
|
};
|
|
2880
2872
|
});
|
|
2881
2873
|
}
|
|
@@ -3043,6 +3035,7 @@ __export(mesh_config_exports, {
|
|
|
3043
3035
|
listMagiKindPanels: () => listMagiKindPanels,
|
|
3044
3036
|
listMeshes: () => listMeshes,
|
|
3045
3037
|
markMeshHostPairingJoined: () => markMeshHostPairingJoined,
|
|
3038
|
+
migrateProviderRolesToSlots: () => migrateProviderRolesToSlots,
|
|
3046
3039
|
normalizeCapabilityTags: () => normalizeCapabilityTags,
|
|
3047
3040
|
normalizeMagiSlots: () => normalizeMagiSlots,
|
|
3048
3041
|
normalizeRepoIdentity: () => normalizeRepoIdentity,
|
|
@@ -3084,23 +3077,51 @@ function migrateLoadedMeshConfig(config) {
|
|
|
3084
3077
|
for (const mesh of config.meshes) {
|
|
3085
3078
|
if (!mesh || !Array.isArray(mesh.nodes)) continue;
|
|
3086
3079
|
for (const node of mesh.nodes) {
|
|
3087
|
-
if (
|
|
3080
|
+
if (migrateProviderRolesToSlots(node?.policy)) changed = true;
|
|
3088
3081
|
}
|
|
3089
3082
|
}
|
|
3090
3083
|
return changed;
|
|
3091
3084
|
}
|
|
3092
|
-
function
|
|
3093
|
-
if (!policy || typeof policy !== "object") return false;
|
|
3094
|
-
const
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3085
|
+
function migrateProviderRolesToSlots(policy) {
|
|
3086
|
+
if (!policy || typeof policy !== "object" || Array.isArray(policy)) return false;
|
|
3087
|
+
const p = policy;
|
|
3088
|
+
const rawRoles = p.providerRoles;
|
|
3089
|
+
if (!Array.isArray(rawRoles)) return false;
|
|
3090
|
+
const roleCap = /* @__PURE__ */ new Map();
|
|
3091
|
+
for (const entry of rawRoles) {
|
|
3092
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
3093
|
+
const rec = entry;
|
|
3094
|
+
const provider = typeof rec.providerType === "string" ? rec.providerType.trim() : "";
|
|
3095
|
+
if (!provider) continue;
|
|
3096
|
+
const cap = Number(rec.maxParallel);
|
|
3097
|
+
if (!Number.isFinite(cap) || cap < 0) continue;
|
|
3098
|
+
roleCap.set(provider.toLowerCase(), { provider, cap: Math.floor(cap) });
|
|
3099
|
+
}
|
|
3100
|
+
const explicitSlots = Array.isArray(p.slots) ? normalizeNodeCapabilitySlots(p.slots) : [];
|
|
3101
|
+
if (explicitSlots.length) {
|
|
3102
|
+
for (const { provider, cap } of roleCap.values()) {
|
|
3103
|
+
const target = explicitSlots.find((s2) => s2.provider.trim().toLowerCase() === provider.toLowerCase() && s2.maxParallel === void 0);
|
|
3104
|
+
if (target) target.maxParallel = cap;
|
|
3105
|
+
}
|
|
3106
|
+
p.slots = explicitSlots;
|
|
3107
|
+
} else if (roleCap.size) {
|
|
3108
|
+
let difficultyBrains;
|
|
3109
|
+
try {
|
|
3110
|
+
difficultyBrains = getDifficultyBrains();
|
|
3111
|
+
} catch {
|
|
3112
|
+
difficultyBrains = void 0;
|
|
3113
|
+
}
|
|
3114
|
+
const priority = Array.isArray(p.providerPriority) ? p.providerPriority.map((t) => typeof t === "string" ? t.trim() : "").filter(Boolean) : [];
|
|
3115
|
+
const derived = deriveSlotsFromLegacy({ providerPriority: priority, difficultyBrains });
|
|
3116
|
+
const slots = derived.length ? derived : [...roleCap.values()].map((r) => ({ provider: r.provider }));
|
|
3117
|
+
for (const slot of slots) {
|
|
3118
|
+
const match = roleCap.get(slot.provider.trim().toLowerCase());
|
|
3119
|
+
if (match && slot.maxParallel === void 0) slot.maxParallel = match.cap;
|
|
3101
3120
|
}
|
|
3121
|
+
p.slots = slots;
|
|
3102
3122
|
}
|
|
3103
|
-
|
|
3123
|
+
delete p.providerRoles;
|
|
3124
|
+
return true;
|
|
3104
3125
|
}
|
|
3105
3126
|
function normalizeCapabilityTags(value) {
|
|
3106
3127
|
if (!Array.isArray(value)) return void 0;
|
|
@@ -3555,6 +3576,39 @@ var init_mesh_config = __esm({
|
|
|
3555
3576
|
}
|
|
3556
3577
|
});
|
|
3557
3578
|
|
|
3579
|
+
// src/mesh/mesh-node-slots.ts
|
|
3580
|
+
function normalizeProviderPriority(policy) {
|
|
3581
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
3582
|
+
if (!Array.isArray(raw)) return [];
|
|
3583
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3584
|
+
return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
|
|
3585
|
+
if (seen.has(type)) return false;
|
|
3586
|
+
seen.add(type);
|
|
3587
|
+
return true;
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
function resolveNodeCapabilitySlots(node) {
|
|
3591
|
+
const explicit = normalizeNodeCapabilitySlots(node?.policy?.slots);
|
|
3592
|
+
if (explicit.length) return explicit;
|
|
3593
|
+
let difficultyBrains;
|
|
3594
|
+
try {
|
|
3595
|
+
difficultyBrains = getDifficultyBrains();
|
|
3596
|
+
} catch {
|
|
3597
|
+
difficultyBrains = void 0;
|
|
3598
|
+
}
|
|
3599
|
+
return deriveSlotsFromLegacy({
|
|
3600
|
+
providerPriority: normalizeProviderPriority(node?.policy),
|
|
3601
|
+
difficultyBrains
|
|
3602
|
+
});
|
|
3603
|
+
}
|
|
3604
|
+
var init_mesh_node_slots = __esm({
|
|
3605
|
+
"src/mesh/mesh-node-slots.ts"() {
|
|
3606
|
+
"use strict";
|
|
3607
|
+
init_dist();
|
|
3608
|
+
init_mesh_config();
|
|
3609
|
+
}
|
|
3610
|
+
});
|
|
3611
|
+
|
|
3558
3612
|
// src/mesh/coordinator-prompt.ts
|
|
3559
3613
|
var coordinator_prompt_exports = {};
|
|
3560
3614
|
__export(coordinator_prompt_exports, {
|
|
@@ -3741,13 +3795,20 @@ function buildNodeConfigSection(mesh) {
|
|
|
3741
3795
|
const explicitMachineLabel = typeof n.machineLabel === "string" ? n.machineLabel : "";
|
|
3742
3796
|
const explicitLabel = explicitMachineLabel ? ` label: **${explicitMachineLabel}** |` : "";
|
|
3743
3797
|
const providerPriority = n.policy?.providerPriority?.length ? ` | providers: ${n.policy.providerPriority.join(", ")}` : "";
|
|
3744
|
-
const
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3798
|
+
const nodeSlots = resolveNodeCapabilitySlots(n);
|
|
3799
|
+
const seenCapProvider = /* @__PURE__ */ new Set();
|
|
3800
|
+
const providerCaps = [];
|
|
3801
|
+
for (const slot of nodeSlots) {
|
|
3802
|
+
const type = typeof slot?.provider === "string" ? slot.provider.trim() : "";
|
|
3803
|
+
if (!type) continue;
|
|
3804
|
+
const key2 = type.toLowerCase();
|
|
3805
|
+
if (seenCapProvider.has(key2)) continue;
|
|
3806
|
+
seenCapProvider.add(key2);
|
|
3807
|
+
const cap = resolveProviderMaxParallel(nodeSlots, type);
|
|
3808
|
+
if (cap === void 0) continue;
|
|
3809
|
+
providerCaps.push(`${type} (max ${cap})`);
|
|
3810
|
+
}
|
|
3811
|
+
const providerRolesSuffix = providerCaps.length ? ` | caps: ${providerCaps.join(", ")}` : "";
|
|
3751
3812
|
lines.push(`- ${explicitLabel} nodeId: \`${n.id}\` | workspace: \`${n.workspace}\`${n.daemonId ? ` | daemon: \`${n.daemonId}\`` : ""}${providerPriority}${providerRolesSuffix}${suffix}`);
|
|
3752
3813
|
const routingTags = [];
|
|
3753
3814
|
const custom = Array.isArray(n.capabilities) ? n.capabilities : [];
|
|
@@ -3922,7 +3983,7 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
3922
3983
|
|
|
3923
3984
|
- **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.
|
|
3924
3985
|
- **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\`.
|
|
3925
|
-
- **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
|
|
3986
|
+
- **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.
|
|
3926
3987
|
- **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.
|
|
3927
3988
|
- **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.
|
|
3928
3989
|
- **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.
|
|
@@ -3930,12 +3991,15 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
3930
3991
|
- **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
|
|
3931
3992
|
- **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).
|
|
3932
3993
|
- **Check history first.** Call \`mesh_task_history\` at session start to avoid duplicate work and inform recovery. On failure, read task history before retrying.
|
|
3994
|
+
- **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.
|
|
3933
3995
|
- **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.
|
|
3934
3996
|
- **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\`.
|
|
3935
3997
|
- **Refinery is config-driven.** \`mesh_refine_node\` must run validation from \`.adhdev/refine.{json,yaml,yml}\` or \`repo-mesh.refine.*\`. Heuristics are scaffolding only.
|
|
3936
3998
|
- **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\`.
|
|
3937
3999
|
- **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.
|
|
3938
4000
|
- **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\`.
|
|
4001
|
+
- **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.
|
|
4002
|
+
- **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.
|
|
3939
4003
|
- **Never fabricate tool results.** Always call the actual tool.
|
|
3940
4004
|
- **Keep the user informed.** One or two sentences after each delegation round.${coordinatorNote}
|
|
3941
4005
|
|
|
@@ -3953,6 +4017,7 @@ var init_coordinator_prompt = __esm({
|
|
|
3953
4017
|
"use strict";
|
|
3954
4018
|
init_repo_mesh_types();
|
|
3955
4019
|
init_mesh_config();
|
|
4020
|
+
init_mesh_node_slots();
|
|
3956
4021
|
init_dist();
|
|
3957
4022
|
PROMPT_SOFT_CAP_BYTES = 60 * 1024;
|
|
3958
4023
|
OPERATING_NOTES_PROMPT_CAP = 20;
|
|
@@ -4037,7 +4102,9 @@ Follow these recovery rules:
|
|
|
4037
4102
|
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.
|
|
4038
4103
|
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.
|
|
4039
4104
|
3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
|
|
4040
|
-
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user
|
|
4105
|
+
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.
|
|
4106
|
+
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.
|
|
4107
|
+
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.`;
|
|
4041
4108
|
ONBOARDING_SECTION = `## Onboarding / Reinit
|
|
4042
4109
|
|
|
4043
4110
|
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.
|
|
@@ -7151,8 +7218,8 @@ var init_mesh_runtime_store = __esm({
|
|
|
7151
7218
|
/**
|
|
7152
7219
|
* Count active (status='assigned') tasks on a (node, provider) combination,
|
|
7153
7220
|
* matched by the assignedProviderType stamped on the payload at claim time.
|
|
7154
|
-
* Drives the per-(node, provider) maxParallel cap (
|
|
7155
|
-
*
|
|
7221
|
+
* Drives the per-(node, provider) maxParallel cap (summed across a provider's
|
|
7222
|
+
* slots[].maxParallel). The active-assignment set for a single node is tiny, so
|
|
7156
7223
|
* parsing payloads here is cheap and avoids a schema migration. Pre-cap legacy
|
|
7157
7224
|
* rows (no provider stamp) and other providers on the same node do not consume
|
|
7158
7225
|
* this provider's budget, so the cap is fully backward compatible.
|
|
@@ -12902,15 +12969,22 @@ function buildMeshSchedulingRuntime(mesh, queue) {
|
|
|
12902
12969
|
if (globalWriteCapReached) capReasons.push("global_max_parallel_tasks_reached");
|
|
12903
12970
|
if ((writeAssignedByNode.get(nodeId) ?? 0) > 0) capReasons.push("node_has_active_assignment");
|
|
12904
12971
|
let providerRoles;
|
|
12905
|
-
const
|
|
12906
|
-
|
|
12972
|
+
const slots = resolveNodeCapabilitySlots(rawNode);
|
|
12973
|
+
const cappedProviders = [];
|
|
12974
|
+
const seenProvider = /* @__PURE__ */ new Set();
|
|
12975
|
+
for (const slot of slots) {
|
|
12976
|
+
const providerType = typeof slot?.provider === "string" ? slot.provider.trim() : "";
|
|
12977
|
+
if (!providerType) continue;
|
|
12978
|
+
const key2 = providerType.toLowerCase();
|
|
12979
|
+
if (seenProvider.has(key2)) continue;
|
|
12980
|
+
seenProvider.add(key2);
|
|
12981
|
+
if (resolveProviderMaxParallel(slots, providerType) !== void 0) cappedProviders.push(providerType);
|
|
12982
|
+
}
|
|
12983
|
+
if (cappedProviders.length) {
|
|
12907
12984
|
const byProvider = providerCountByNode.get(nodeId);
|
|
12908
12985
|
providerRoles = [];
|
|
12909
|
-
for (const
|
|
12910
|
-
|
|
12911
|
-
const providerType = typeof role.providerType === "string" ? role.providerType.trim() : "";
|
|
12912
|
-
if (!providerType) continue;
|
|
12913
|
-
const maxParallel = resolveProviderMaxParallel(policy, providerType);
|
|
12986
|
+
for (const providerType of cappedProviders) {
|
|
12987
|
+
const maxParallel = resolveProviderMaxParallel(slots, providerType);
|
|
12914
12988
|
const activeAssigned = byProvider?.get(providerType) ?? 0;
|
|
12915
12989
|
const capReached = maxParallel !== void 0 && activeAssigned >= maxParallel;
|
|
12916
12990
|
providerRoles.push({
|
|
@@ -12951,6 +13025,7 @@ var init_mesh_scheduling_runtime = __esm({
|
|
|
12951
13025
|
"use strict";
|
|
12952
13026
|
init_repo_mesh_types();
|
|
12953
13027
|
init_dist();
|
|
13028
|
+
init_mesh_node_slots();
|
|
12954
13029
|
init_mesh_work_queue();
|
|
12955
13030
|
}
|
|
12956
13031
|
});
|
|
@@ -15570,6 +15645,32 @@ var init_mesh_clone_grace = __esm({
|
|
|
15570
15645
|
}
|
|
15571
15646
|
});
|
|
15572
15647
|
|
|
15648
|
+
// src/mesh/model-provider-compat.ts
|
|
15649
|
+
function isAnthropicProvider(providerType) {
|
|
15650
|
+
const p = typeof providerType === "string" ? providerType.trim().toLowerCase() : "";
|
|
15651
|
+
return p.length > 0 && ANTHROPIC_PROVIDER_TYPES.has(p);
|
|
15652
|
+
}
|
|
15653
|
+
function isAnthropicModel(model) {
|
|
15654
|
+
const m = typeof model === "string" ? model.trim().toLowerCase() : "";
|
|
15655
|
+
if (!m) return false;
|
|
15656
|
+
if (m.startsWith("claude") || m.startsWith("anthropic")) return true;
|
|
15657
|
+
return /^(opus|sonnet|haiku)(\b|[-_])/.test(m);
|
|
15658
|
+
}
|
|
15659
|
+
function isModelCompatibleWithProvider(model, providerType) {
|
|
15660
|
+
if (!isAnthropicModel(model)) return true;
|
|
15661
|
+
if (isAnthropicProvider(providerType)) return true;
|
|
15662
|
+
return false;
|
|
15663
|
+
}
|
|
15664
|
+
var ANTHROPIC_PROVIDER_TYPES;
|
|
15665
|
+
var init_model_provider_compat = __esm({
|
|
15666
|
+
"src/mesh/model-provider-compat.ts"() {
|
|
15667
|
+
"use strict";
|
|
15668
|
+
ANTHROPIC_PROVIDER_TYPES = /* @__PURE__ */ new Set([
|
|
15669
|
+
"claude-cli"
|
|
15670
|
+
]);
|
|
15671
|
+
}
|
|
15672
|
+
});
|
|
15673
|
+
|
|
15573
15674
|
// src/mesh/mesh-queue-assignment.ts
|
|
15574
15675
|
import { existsSync as existsSync18 } from "fs";
|
|
15575
15676
|
function localCoordinatorDaemonId() {
|
|
@@ -15749,7 +15850,7 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
15749
15850
|
return false;
|
|
15750
15851
|
}
|
|
15751
15852
|
const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
|
|
15752
|
-
const providerMaxParallel = resolveProviderMaxParallel(node
|
|
15853
|
+
const providerMaxParallel = resolveProviderMaxParallel(resolveNodeCapabilitySlots(node), providerType);
|
|
15753
15854
|
const nodeIsWorktree = node?.isLocalWorktree === true;
|
|
15754
15855
|
const task = claimNextTask(meshId, nodeId, sessionId, capabilityTags, {
|
|
15755
15856
|
providerType,
|
|
@@ -16007,7 +16108,7 @@ function sweepExpiredCooldowns() {
|
|
|
16007
16108
|
if (now >= until) autoLaunchCooldownUntil.delete(key2);
|
|
16008
16109
|
}
|
|
16009
16110
|
}
|
|
16010
|
-
function
|
|
16111
|
+
function normalizeProviderPriority2(policy) {
|
|
16011
16112
|
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
16012
16113
|
if (!Array.isArray(raw)) return [];
|
|
16013
16114
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -16121,21 +16222,6 @@ function buildSchedulingPool(localCandidates, remoteCandidates) {
|
|
|
16121
16222
|
}));
|
|
16122
16223
|
return { pool, uniqueNodes };
|
|
16123
16224
|
}
|
|
16124
|
-
function resolveNodeCapabilitySlots(node) {
|
|
16125
|
-
const explicit = normalizeNodeCapabilitySlots(node?.policy?.slots);
|
|
16126
|
-
if (explicit.length) return explicit;
|
|
16127
|
-
let difficultyBrains;
|
|
16128
|
-
try {
|
|
16129
|
-
difficultyBrains = getDifficultyBrains();
|
|
16130
|
-
} catch {
|
|
16131
|
-
difficultyBrains = void 0;
|
|
16132
|
-
}
|
|
16133
|
-
return deriveSlotsFromLegacy({
|
|
16134
|
-
providerPriority: normalizeProviderPriority(node?.policy),
|
|
16135
|
-
providerRoles: Array.isArray(node?.policy?.providerRoles) ? node.policy.providerRoles : void 0,
|
|
16136
|
-
difficultyBrains
|
|
16137
|
-
});
|
|
16138
|
-
}
|
|
16139
16225
|
function scoreSlotForTask(slot, task) {
|
|
16140
16226
|
let score = 1;
|
|
16141
16227
|
const diff = isMeshTaskDifficulty(task.difficulty) ? task.difficulty : void 0;
|
|
@@ -16262,6 +16348,7 @@ function nodeHasLiveSessionPendingClaim(components, meshId, nodeId) {
|
|
|
16262
16348
|
const state = inst.getState();
|
|
16263
16349
|
const settings = state.settings || {};
|
|
16264
16350
|
if (readNonEmptyString2(settings.meshNodeFor) !== meshId) return false;
|
|
16351
|
+
if (readNonEmptyString2(settings.meshCoordinatorFor) === meshId) return false;
|
|
16265
16352
|
const instNodeId = readNonEmptyString2(settings.meshNodeId) || readNonEmptyString2(settings.nodeId);
|
|
16266
16353
|
if (!daemonIdsEquivalent(instNodeId, nodeId)) return false;
|
|
16267
16354
|
const status = readNonEmptyString2(state.status).toLowerCase();
|
|
@@ -16466,7 +16553,7 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
|
16466
16553
|
if (task.targetNodeId && !meshNodeIdMatches(node, task.targetNodeId)) return false;
|
|
16467
16554
|
if (task.taskMode === "convergence" && node?.isLocalWorktree === true) return false;
|
|
16468
16555
|
if (task.requiredTags?.length) {
|
|
16469
|
-
const priorities =
|
|
16556
|
+
const priorities = normalizeProviderPriority2(node?.policy);
|
|
16470
16557
|
const providerCandidates = priorities.length ? priorities : [void 0];
|
|
16471
16558
|
return providerCandidates.some(
|
|
16472
16559
|
(p) => nodeSatisfiesRequiredTags(task.requiredTags, buildMeshNodeCapabilityTags(node, p))
|
|
@@ -16547,9 +16634,13 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
|
16547
16634
|
markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
|
|
16548
16635
|
continue;
|
|
16549
16636
|
}
|
|
16550
|
-
const
|
|
16637
|
+
const rawEffectiveModel = typeof task.model === "string" && task.model.trim() ? task.model.trim() : resolved.model;
|
|
16551
16638
|
const effectiveThinkingLevel = typeof task.thinkingLevel === "string" && task.thinkingLevel.trim() ? task.thinkingLevel.trim() : resolved.thinkingLevel;
|
|
16552
|
-
const
|
|
16639
|
+
const effectiveModel = isModelCompatibleWithProvider(rawEffectiveModel, resolved.providerType) ? rawEffectiveModel : void 0;
|
|
16640
|
+
if (rawEffectiveModel && effectiveModel === void 0) {
|
|
16641
|
+
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`);
|
|
16642
|
+
}
|
|
16643
|
+
const providerCap = resolveProviderMaxParallel(resolveNodeCapabilitySlots(node), resolved.providerType);
|
|
16553
16644
|
if (providerCap !== void 0 && activeProviderAssignedCount(meshId, nodeId, resolved.providerType) >= providerCap) {
|
|
16554
16645
|
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_provider_parallel_reached", nodeId, providerType: resolved.providerType });
|
|
16555
16646
|
continue;
|
|
@@ -16872,6 +16963,7 @@ var init_mesh_queue_assignment = __esm({
|
|
|
16872
16963
|
init_mesh_warmup_deadline();
|
|
16873
16964
|
init_repo_mesh_types();
|
|
16874
16965
|
init_dist();
|
|
16966
|
+
init_mesh_node_slots();
|
|
16875
16967
|
init_mesh_events_stale();
|
|
16876
16968
|
init_mesh_events_utils();
|
|
16877
16969
|
init_mesh_node_identity();
|
|
@@ -16879,6 +16971,7 @@ var init_mesh_queue_assignment = __esm({
|
|
|
16879
16971
|
init_worktree_bootstrap_config();
|
|
16880
16972
|
init_mesh_clone_grace();
|
|
16881
16973
|
init_mesh_task_inflight();
|
|
16974
|
+
init_model_provider_compat();
|
|
16882
16975
|
IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
|
|
16883
16976
|
idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
|
|
16884
16977
|
BOOTSTRAP_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["complete", "failed"]);
|
|
@@ -45300,6 +45393,7 @@ var STATUS_HYDRATION_TAIL_LIMIT = 200;
|
|
|
45300
45393
|
var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
45301
45394
|
var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
45302
45395
|
var NATIVE_HISTORY_MESH_IDLE_SETTLE_MS = 4e3;
|
|
45396
|
+
var PTY_PARSED_FINAL_ASSISTANT_QUIET_DWELL_MS = 1200;
|
|
45303
45397
|
var BACKGROUND_TASK_HOLD_MAX_MS = 5 * 6e4;
|
|
45304
45398
|
var USER_INPUT_ACK_DEDUP_WINDOW_MS = 6e4;
|
|
45305
45399
|
var STARTUP_GRACE_IDLE_COLLAPSE_WINDOW_MS = 12e3;
|
|
@@ -45542,6 +45636,39 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
45542
45636
|
* the nudge was NOT deferred) and leaked to the coordinator.
|
|
45543
45637
|
*/
|
|
45544
45638
|
static AUTO_APPROVE_MASK_STALL_MS = 10500;
|
|
45639
|
+
/**
|
|
45640
|
+
* AUTOAPPROVE-FLAP-INBOX-MISSING: sticky-approval overlay window. Same time-tick
|
|
45641
|
+
* hold idea as the FALSE-IDLE completion gate — an approval signal that was
|
|
45642
|
+
* DOMINANT within this recent window is re-presented across a momentary busy blip
|
|
45643
|
+
* instead of collapsing.
|
|
45644
|
+
*
|
|
45645
|
+
* RCA (live 2026-07-13): a claude-cli worker sitting at a Bash approval modal
|
|
45646
|
+
* ("Do you want to proceed? ❯1.Yes") flaps waiting_approval↔busy on a ~2-3s period.
|
|
45647
|
+
* The spec `approval→busy` transition fires whenever the footer/modal approval
|
|
45648
|
+
* markers momentarily drop out of their parsed sections while the PRIOR command's
|
|
45649
|
+
* residual spinner text ("✳ Checking vendor drift…") still matches the busy regex.
|
|
45650
|
+
* On the busy frame the adapter reports status='generating', activeModal=null. That
|
|
45651
|
+
* corrupts THREE consumers at once: (1) mesh_active_work samples 'generating' →
|
|
45652
|
+
* collectPendingApprovals never sees 'awaiting_approval' → mesh_list_pending_approvals
|
|
45653
|
+
* count:0 (inbox miss); (2) the auto-approve settle gate is torn down each busy phase
|
|
45654
|
+
* so the 600ms settle never accrues → auto-approve never fires; (3) a mesh_approve
|
|
45655
|
+
* landing on a busy frame hits "Not in approval state". The existing FLAP machinery
|
|
45656
|
+
* (AUTO_APPROVE_FLAP_CONTINUITY_MS) only keeps the settle gate warm while status is
|
|
45657
|
+
* STILL waiting_approval (buttons scrolled out) — it does nothing once the FSM fully
|
|
45658
|
+
* commits to 'busy', and it never stabilises the status the inbox samples.
|
|
45659
|
+
*
|
|
45660
|
+
* Fix: when the raw adapter status flaps to generating/busy/idle but a
|
|
45661
|
+
* waiting_approval WITH a concrete modal was observed within this window, overlay
|
|
45662
|
+
* the cached modal and report status='waiting_approval'. This stabilized status
|
|
45663
|
+
* feeds getState (→ inbox), detectStatusTransition (→ event emission), and
|
|
45664
|
+
* maybeAutoApproveStatus (→ settle gate) uniformly, so the approval both registers
|
|
45665
|
+
* in the inbox and settles for auto-approve across the flap. Bounded (a genuine
|
|
45666
|
+
* resume that never returns to approval unmasks after this window) and scoped at the
|
|
45667
|
+
* call site to autonomous mesh sessions. 4000ms bridges the observed ~2-3s flap with
|
|
45668
|
+
* margin while staying well under AUTO_APPROVE_MASK_STALL_MS (a truly stalled/absent
|
|
45669
|
+
* approval still surfaces).
|
|
45670
|
+
*/
|
|
45671
|
+
static APPROVAL_STICKY_FLAP_MS = 4e3;
|
|
45545
45672
|
/**
|
|
45546
45673
|
* FALSE-IDLE (inter-approval quiet valley): grace window after an auto-approve
|
|
45547
45674
|
* (or mesh_approve) RESOLVES a modal during which a subsequent generating→idle
|
|
@@ -45645,6 +45772,14 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
45645
45772
|
// signature) while a genuinely closed modal — buttons empty continuously past
|
|
45646
45773
|
// the continuity window — is still recognised and resets the gate.
|
|
45647
45774
|
autoApproveLastModalSeenAt = 0;
|
|
45775
|
+
// AUTOAPPROVE-FLAP-INBOX-MISSING sticky-approval overlay (see APPROVAL_STICKY_FLAP_MS).
|
|
45776
|
+
// The wall-clock of the last frame where the RAW adapter reported waiting_approval with
|
|
45777
|
+
// a CONCRETE modal (buttons present), the cached modal to re-present across a busy blip,
|
|
45778
|
+
// and the approvalEntrySeq at that frame (so a stabilized frame carries the right seq to
|
|
45779
|
+
// the emission-dedup fingerprint). All zero/null when no recent concrete approval.
|
|
45780
|
+
approvalStickyLastConcreteAt = 0;
|
|
45781
|
+
approvalStickyModal = null;
|
|
45782
|
+
approvalStickyEntrySeq = 0;
|
|
45648
45783
|
// STATUS-MISMATCH: wall-clock when the CURRENT auto-approve episode (waiting_approval
|
|
45649
45784
|
// + shouldAutoApprove) first began wanting to mask. Unlike pendingAutoApprovalSince it
|
|
45650
45785
|
// is NOT reset when the modal signature changes (a still-streaming/flapping prompt) and
|
|
@@ -45786,7 +45921,7 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
45786
45921
|
}
|
|
45787
45922
|
}
|
|
45788
45923
|
getState() {
|
|
45789
|
-
const adapterStatus = this.adapter.getStatus();
|
|
45924
|
+
const adapterStatus = this.stabilizeFlappingApprovalStatus(this.adapter.getStatus());
|
|
45790
45925
|
if (Object.prototype.hasOwnProperty.call(adapterStatus, "activeInteractivePrompt")) {
|
|
45791
45926
|
this.activeInteractivePrompt = adapterStatus.activeInteractivePrompt ?? null;
|
|
45792
45927
|
}
|
|
@@ -46650,6 +46785,9 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
46650
46785
|
return null;
|
|
46651
46786
|
}
|
|
46652
46787
|
if (this.type === "antigravity-cli") {
|
|
46788
|
+
if (allowMissingAssistantTimeout) {
|
|
46789
|
+
return { reason: "missing_final_assistant", terminal: false, holdForTranscript: true };
|
|
46790
|
+
}
|
|
46653
46791
|
return null;
|
|
46654
46792
|
}
|
|
46655
46793
|
if (allowMissingAssistantTimeout && pending.previousStatus === "waiting_approval") {
|
|
@@ -46681,6 +46819,19 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
46681
46819
|
}
|
|
46682
46820
|
} catch {
|
|
46683
46821
|
}
|
|
46822
|
+
if (allowMissingAssistantTimeout && !adapterOwnsMessagesElsewhere && finalAssistantEvidence.source === "parsed") {
|
|
46823
|
+
try {
|
|
46824
|
+
const outStatus = this.adapter.getStatus({ allowParse: false });
|
|
46825
|
+
const lastOutputAt = typeof outStatus?.lastOutputAt === "number" && Number.isFinite(outStatus.lastOutputAt) ? outStatus.lastOutputAt : void 0;
|
|
46826
|
+
if (typeof lastOutputAt === "number") {
|
|
46827
|
+
const quietMs = Date.now() - lastOutputAt;
|
|
46828
|
+
if (quietMs < PTY_PARSED_FINAL_ASSISTANT_QUIET_DWELL_MS) {
|
|
46829
|
+
return { reason: "parsed_final_assistant_quiet_dwell", terminal: false };
|
|
46830
|
+
}
|
|
46831
|
+
}
|
|
46832
|
+
} catch {
|
|
46833
|
+
}
|
|
46834
|
+
}
|
|
46684
46835
|
return null;
|
|
46685
46836
|
}
|
|
46686
46837
|
// (FALSEIDLE-a) Positive, structural proof that the latest approval entry was resolved
|
|
@@ -47102,6 +47253,53 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
47102
47253
|
...opts.completionDiagnostic !== void 0 ? { completionDiagnostic: opts.completionDiagnostic } : {}
|
|
47103
47254
|
});
|
|
47104
47255
|
}
|
|
47256
|
+
/**
|
|
47257
|
+
* AUTOAPPROVE-FLAP-INBOX-MISSING sticky-approval overlay. Returns the adapterStatus a
|
|
47258
|
+
* flap-prone claude-cli approval SHOULD present this frame — either the raw status
|
|
47259
|
+
* unchanged, or, when the raw status has momentarily flapped OFF a recently-dominant
|
|
47260
|
+
* concrete approval, a synthetic `waiting_approval` re-presenting the cached modal.
|
|
47261
|
+
*
|
|
47262
|
+
* Records the concrete approval whenever the raw status is waiting_approval WITH
|
|
47263
|
+
* buttons. On a subsequent non-approval frame (the spec `approval→busy` flap), if that
|
|
47264
|
+
* concrete approval was seen within APPROVAL_STICKY_FLAP_MS AND the engine has NOT
|
|
47265
|
+
* resolved a modal since (lastApprovalResolvedAt not advanced past the sticky start),
|
|
47266
|
+
* overlay the cached modal + waiting_approval so the inbox / auto-approve / mesh_approve
|
|
47267
|
+
* all see the stable approval. A genuine resolution (auto-approve or mesh_approve fires
|
|
47268
|
+
* resolveModal → lastApprovalResolvedAt advances) clears the sticky immediately, so a
|
|
47269
|
+
* legitimate post-approval resume is NEVER masked as a lingering approval. Bounded by the
|
|
47270
|
+
* window, and scoped to autonomous mesh sessions (a foreground/attended or non-mesh
|
|
47271
|
+
* session, where a human answers the prompt, is returned untouched).
|
|
47272
|
+
*/
|
|
47273
|
+
stabilizeFlappingApprovalStatus(adapterStatus, now = Date.now()) {
|
|
47274
|
+
if (!this.isAutonomousMeshSession() || !this.shouldAutoApprove()) return adapterStatus;
|
|
47275
|
+
const rawStatus = adapterStatus?.status;
|
|
47276
|
+
const resolvedAt = typeof this.adapter?.lastApprovalResolvedAt === "number" ? this.adapter.lastApprovalResolvedAt : 0;
|
|
47277
|
+
if (rawStatus === "waiting_approval") {
|
|
47278
|
+
if (hasNonEmptyCliModalButtons(adapterStatus?.activeModal)) {
|
|
47279
|
+
this.approvalStickyLastConcreteAt = now;
|
|
47280
|
+
this.approvalStickyModal = adapterStatus.activeModal;
|
|
47281
|
+
this.approvalStickyEntrySeq = typeof adapterStatus?.approvalEntrySeq === "number" ? adapterStatus.approvalEntrySeq : this.approvalStickyEntrySeq;
|
|
47282
|
+
}
|
|
47283
|
+
return adapterStatus;
|
|
47284
|
+
}
|
|
47285
|
+
if (this.approvalStickyLastConcreteAt > 0 && this.approvalStickyModal) {
|
|
47286
|
+
const withinWindow = now - this.approvalStickyLastConcreteAt < _CliProviderInstance.APPROVAL_STICKY_FLAP_MS;
|
|
47287
|
+
const resolvedSinceAnchor = resolvedAt >= this.approvalStickyLastConcreteAt;
|
|
47288
|
+
if (withinWindow && !resolvedSinceAnchor) {
|
|
47289
|
+
return {
|
|
47290
|
+
...adapterStatus,
|
|
47291
|
+
status: "waiting_approval",
|
|
47292
|
+
activeModal: this.approvalStickyModal,
|
|
47293
|
+
...this.approvalStickyEntrySeq ? { approvalEntrySeq: this.approvalStickyEntrySeq } : {},
|
|
47294
|
+
approvalStickyOverlay: true
|
|
47295
|
+
};
|
|
47296
|
+
}
|
|
47297
|
+
this.approvalStickyLastConcreteAt = 0;
|
|
47298
|
+
this.approvalStickyModal = null;
|
|
47299
|
+
this.approvalStickyEntrySeq = 0;
|
|
47300
|
+
}
|
|
47301
|
+
return adapterStatus;
|
|
47302
|
+
}
|
|
47105
47303
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
47106
47304
|
if (adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove() && this.manualAttendance.isAttended(now)) {
|
|
47107
47305
|
this.lastAutoApprovalSignature = "";
|
|
@@ -47331,7 +47529,7 @@ ${buttons.join("\n")}`;
|
|
|
47331
47529
|
}
|
|
47332
47530
|
detectStatusTransition() {
|
|
47333
47531
|
const now = Date.now();
|
|
47334
|
-
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
47532
|
+
const adapterStatus = this.stabilizeFlappingApprovalStatus(this.adapter.getStatus({ allowParse: false }), now);
|
|
47335
47533
|
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
47336
47534
|
this.provider,
|
|
47337
47535
|
typeof adapterStatus?.providerSessionId === "string" ? adapterStatus.providerSessionId : ""
|
|
@@ -55625,7 +55823,7 @@ var meshCrudHandlers = {
|
|
|
55625
55823
|
const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
|
|
55626
55824
|
if (ownerFailure) return ownerFailure;
|
|
55627
55825
|
try {
|
|
55628
|
-
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
55826
|
+
const { addNode: addNode2, migrateProviderRolesToSlots: migrateProviderRolesToSlots2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
55629
55827
|
const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
|
|
55630
55828
|
const readOnly = args?.readOnly === true;
|
|
55631
55829
|
const providerRoles = normalizeProviderRoles(args?.providerRoles);
|
|
@@ -55634,6 +55832,7 @@ var meshCrudHandlers = {
|
|
|
55634
55832
|
...providerPriority.length ? { providerPriority } : {},
|
|
55635
55833
|
...providerRoles.length ? { providerRoles } : {}
|
|
55636
55834
|
};
|
|
55835
|
+
if (providerRoles.length) migrateProviderRolesToSlots2(policy);
|
|
55637
55836
|
const role = normalizeMeshDaemonRole(args?.role);
|
|
55638
55837
|
const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
|
|
55639
55838
|
const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
|
|
@@ -55662,7 +55861,7 @@ var meshCrudHandlers = {
|
|
|
55662
55861
|
const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node update");
|
|
55663
55862
|
if (ownerFailure) return ownerFailure;
|
|
55664
55863
|
try {
|
|
55665
|
-
const { updateNode: updateNode2, normalizeCapabilityTags: normalizeCapabilityTags2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
55864
|
+
const { updateNode: updateNode2, normalizeCapabilityTags: normalizeCapabilityTags2, migrateProviderRolesToSlots: migrateProviderRolesToSlots2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
55666
55865
|
const policy = args?.policy && typeof args.policy === "object" && !Array.isArray(args.policy) ? { ...args.policy } : {};
|
|
55667
55866
|
if (Array.isArray(args?.providerPriority)) {
|
|
55668
55867
|
const providerPriority = args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean);
|
|
@@ -55675,12 +55874,10 @@ var meshCrudHandlers = {
|
|
|
55675
55874
|
}
|
|
55676
55875
|
if (Array.isArray(args?.providerRoles)) {
|
|
55677
55876
|
const providerRoles = normalizeProviderRoles(args.providerRoles);
|
|
55678
|
-
if (providerRoles.length)
|
|
55679
|
-
|
|
55680
|
-
} else {
|
|
55681
|
-
delete policy.providerRoles;
|
|
55682
|
-
}
|
|
55877
|
+
if (providerRoles.length) policy.providerRoles = providerRoles;
|
|
55878
|
+
else delete policy.providerRoles;
|
|
55683
55879
|
}
|
|
55880
|
+
migrateProviderRolesToSlots2(policy);
|
|
55684
55881
|
const patch = { policy };
|
|
55685
55882
|
if (typeof args?.systemPrompt === "string") {
|
|
55686
55883
|
const trimmed = args.systemPrompt.trim();
|
|
@@ -55952,8 +56149,11 @@ var meshCrudHandlers = {
|
|
|
55952
56149
|
console.log(`[mesh] clone_mesh_node base sync: ${result.baseSync.action} (startRef=${result.baseSync.startRef})`);
|
|
55953
56150
|
}
|
|
55954
56151
|
let node;
|
|
56152
|
+
const { migrateProviderRolesToSlots: migrateProviderRolesToSlots2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
55955
56153
|
if (meshRecord.inline) {
|
|
55956
56154
|
const { randomUUID: randomUUID15 } = await import("crypto");
|
|
56155
|
+
const clonedPolicy = { ...sourceNode.policy || {} };
|
|
56156
|
+
migrateProviderRolesToSlots2(clonedPolicy);
|
|
55957
56157
|
node = {
|
|
55958
56158
|
id: `node_${randomUUID15().replace(/-/g, "")}`,
|
|
55959
56159
|
workspace: result.worktreePath,
|
|
@@ -55961,7 +56161,7 @@ var meshCrudHandlers = {
|
|
|
55961
56161
|
daemonId: sourceNode.daemonId,
|
|
55962
56162
|
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
55963
56163
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
55964
|
-
policy:
|
|
56164
|
+
policy: clonedPolicy,
|
|
55965
56165
|
isLocalWorktree: true,
|
|
55966
56166
|
worktreeBranch: result.branch,
|
|
55967
56167
|
clonedFromNodeId: sourceNodeId
|
|
@@ -55969,6 +56169,8 @@ var meshCrudHandlers = {
|
|
|
55969
56169
|
ctx.updateInlineMeshNode(meshId, mesh, node);
|
|
55970
56170
|
} else {
|
|
55971
56171
|
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
56172
|
+
const clonedPolicy = { ...sourceNode.policy || {} };
|
|
56173
|
+
migrateProviderRolesToSlots2(clonedPolicy);
|
|
55972
56174
|
node = addNode2(meshId, {
|
|
55973
56175
|
workspace: result.worktreePath,
|
|
55974
56176
|
repoRoot: result.worktreePath,
|
|
@@ -55978,7 +56180,7 @@ var meshCrudHandlers = {
|
|
|
55978
56180
|
isLocalWorktree: true,
|
|
55979
56181
|
worktreeBranch: result.branch,
|
|
55980
56182
|
clonedFromNodeId: sourceNodeId,
|
|
55981
|
-
policy:
|
|
56183
|
+
policy: clonedPolicy
|
|
55982
56184
|
});
|
|
55983
56185
|
if (!node) return { success: false, error: "Failed to register worktree node" };
|
|
55984
56186
|
const inlineForReconcile = ctx.getCachedInlineMesh(meshId);
|