@ateam-ai/mcp 0.3.57 → 0.4.0
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/package.json +1 -1
- package/src/tools.js +218 -28
package/package.json
CHANGED
package/src/tools.js
CHANGED
|
@@ -83,6 +83,89 @@ function _resolveDottedField(obj, dottedPath) {
|
|
|
83
83
|
return { parent, leaf: parts[parts.length - 1] };
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// ─── Protected array fields (v0.4.0 sibling-loss guard) ─────────────
|
|
87
|
+
//
|
|
88
|
+
// Historically, `ateam_patch(target:"solution", updates:{ linked_skills:["only-one"] })`
|
|
89
|
+
// silently REPLACED the whole linked_skills array — wiping every other
|
|
90
|
+
// skill wired into the solution. Same footgun on ui_plugins, handoffs,
|
|
91
|
+
// grants, connectors, etc. The Solution Builder skill hit this in the
|
|
92
|
+
// wild and wiped a tenant's whole solution.
|
|
93
|
+
//
|
|
94
|
+
// From v0.4.0, ateam_patch REFUSES a bare array-replace on any of these
|
|
95
|
+
// fields unless the caller explicitly opts in via one of:
|
|
96
|
+
// updates: { _replace: true, linked_skills: [...] } // object-level
|
|
97
|
+
// updates: { linked_skills: [...], linked_skills_replace: true } // field-level
|
|
98
|
+
// To ADD or REMOVE without opt-in, use the _push / _delete / _update
|
|
99
|
+
// suffix pattern that has always been the correct form.
|
|
100
|
+
const SOLUTION_ARRAY_FIELDS = new Set([
|
|
101
|
+
'linked_skills',
|
|
102
|
+
'ui_plugins',
|
|
103
|
+
'platform_connectors',
|
|
104
|
+
'handoffs',
|
|
105
|
+
'grants',
|
|
106
|
+
'triggers',
|
|
107
|
+
'notification_routes',
|
|
108
|
+
'channels',
|
|
109
|
+
'actor_types',
|
|
110
|
+
'admin_roles',
|
|
111
|
+
'plugins',
|
|
112
|
+
'security_contracts',
|
|
113
|
+
'connectors',
|
|
114
|
+
'skills',
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
const SKILL_ARRAY_FIELDS = new Set([
|
|
118
|
+
'tools',
|
|
119
|
+
'connectors',
|
|
120
|
+
'handoffs',
|
|
121
|
+
'scenarios',
|
|
122
|
+
'triggers',
|
|
123
|
+
'notification_routes',
|
|
124
|
+
'plugins',
|
|
125
|
+
'bootstrap_tools',
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
// Returns { ok:false, ... } if the write would REPLACE a protected array
|
|
129
|
+
// without an explicit opt-in; returns null if the write is safe to proceed.
|
|
130
|
+
function _guardArrayReplace({ target, key, value, current, updates }) {
|
|
131
|
+
const knownFields = target === 'skill' ? SKILL_ARRAY_FIELDS : SOLUTION_ARRAY_FIELDS;
|
|
132
|
+
if (!knownFields.has(key)) return null;
|
|
133
|
+
if (!Array.isArray(value)) return null;
|
|
134
|
+
const currentArr = Array.isArray(current) ? current : [];
|
|
135
|
+
if (currentArr.length === 0) return null; // nothing to lose
|
|
136
|
+
if (updates && updates._replace === true) return null;
|
|
137
|
+
if (updates && updates[key + '_replace'] === true) return null;
|
|
138
|
+
|
|
139
|
+
// Compute what would be dropped so the error message is actionable.
|
|
140
|
+
const keyOf = (item) => (item && typeof item === 'object') ? (item.id ?? item.name ?? JSON.stringify(item)) : item;
|
|
141
|
+
const newKeys = new Set(value.map(keyOf));
|
|
142
|
+
const dropped = currentArr.map(keyOf).filter(k => !newKeys.has(k));
|
|
143
|
+
const kept = currentArr.map(keyOf).filter(k => newKeys.has(k));
|
|
144
|
+
const wouldAdd = value.map(keyOf).filter(k => !currentArr.map(keyOf).includes(k));
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
phase: 'patch',
|
|
149
|
+
error:
|
|
150
|
+
`⚠️ REFUSED: bare-array replace on ${target}.${key} would drop ${dropped.length} sibling item(s): ` +
|
|
151
|
+
`[${dropped.slice(0, 8).join(', ')}${dropped.length > 8 ? ', ...' : ''}]. ` +
|
|
152
|
+
`This footgun wiped a whole solution in the wild — v0.4.0 refuses it by default. ` +
|
|
153
|
+
`\n\nWhat to do instead:` +
|
|
154
|
+
`\n • To ADD items: updates: { "${key}_push": ${JSON.stringify(wouldAdd)} }` +
|
|
155
|
+
(dropped.length ? `\n • To REMOVE items: updates: { "${key}_delete": ${JSON.stringify(dropped)} }` : '') +
|
|
156
|
+
`\n • To FULLY REPLACE (rare): updates: { "${key}": [...], "${key}_replace": true }` +
|
|
157
|
+
`\n • To replace many arrays in one call: updates: { _replace: true, "${key}": [...] }`,
|
|
158
|
+
dropped_ids: dropped,
|
|
159
|
+
kept_ids: kept,
|
|
160
|
+
would_add: wouldAdd,
|
|
161
|
+
safe_alternatives: {
|
|
162
|
+
push: { [`${key}_push`]: wouldAdd },
|
|
163
|
+
...(dropped.length && { delete: { [`${key}_delete`]: dropped } }),
|
|
164
|
+
force_replace: { [key]: value, [`${key}_replace`]: true },
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
86
169
|
// ─── Tool definitions ───────────────────────────────────────────────
|
|
87
170
|
|
|
88
171
|
export const tools = [
|
|
@@ -428,24 +511,27 @@ export const tools = [
|
|
|
428
511
|
core: true,
|
|
429
512
|
description:
|
|
430
513
|
"Surgically update ANY field in a skill or solution definition, redeploy, and optionally re-test — all in one step.\n\n" +
|
|
431
|
-
"
|
|
514
|
+
"⚠️ MERGE-BY-DEFAULT (v0.4.0) — Arrays are protected from silent replace. Bare array writes on solution.linked_skills / ui_plugins / platform_connectors / handoffs / grants / triggers (etc.) and skill.tools / connectors / handoffs / scenarios are REFUSED to prevent sibling loss. Add or remove items with the _push / _delete / _update suffixes; opt into a full-array replace only when you really mean it.\n\n" +
|
|
515
|
+
"OPERATIONS (safe by construction):\n" +
|
|
432
516
|
"1. Scalar (dot notation): { \"problem.statement\": \"new value\", \"role.persona\": \"You are...\" }\n" +
|
|
433
517
|
"2. Deep nested: { \"intents.thresholds.accept\": 0.9, \"policy.escalation.enabled\": true }\n" +
|
|
434
|
-
"3. Array
|
|
435
|
-
"4. Array
|
|
436
|
-
"5. Array
|
|
437
|
-
"6.
|
|
438
|
-
"EXAMPLES:\n" +
|
|
439
|
-
"-
|
|
440
|
-
"-
|
|
518
|
+
"3. Array APPEND: { \"tools_push\": [{ name: \"new_tool\", description: \"...\" }] }\n" +
|
|
519
|
+
"4. Array REMOVE: { \"tools_delete\": [\"tool_name\"] }\n" +
|
|
520
|
+
"5. Array MODIFY-ONE: { \"tools_update\": [{ name: \"existing_tool\", description: \"updated\" }] }\n" +
|
|
521
|
+
"6. Full-array REPLACE (opt-in): { \"linked_skills\": [...], \"linked_skills_replace\": true } — or { _replace: true, ... } to opt every array in this call.\n\n" +
|
|
522
|
+
"SOLUTION-LEVEL EXAMPLES (target='solution'):\n" +
|
|
523
|
+
"- ADD a skill to the solution: updates: { \"linked_skills_push\": [\"my-new-skill\"] } ← NOT { linked_skills: [\"my-new-skill\"] } (that would REFUSE — it drops your other skills)\n" +
|
|
524
|
+
"- REMOVE a skill: updates: { \"linked_skills_delete\": [\"old-skill\"] }\n" +
|
|
525
|
+
"- ADD a UI plugin: updates: { \"ui_plugins_push\": [{ id: \"mcp:conn:panel\", ... }] }\n" +
|
|
526
|
+
"- ADD a handoff: updates: { \"handoffs_push\": [{ id: \"h1\", ... }] }\n\n" +
|
|
527
|
+
"SKILL-LEVEL EXAMPLES (target='skill' + skill_id):\n" +
|
|
528
|
+
"- Change persona: updates: { \"role.persona\": \"You are a friendly assistant\" }\n" +
|
|
529
|
+
"- Append to persona: updates: { \"persona_append\": \"\\n\\nALWAYS respond in 2 sentences.\" }\n" +
|
|
441
530
|
"- Add a guardrail: updates: { \"policy.guardrails.never_push\": [\"Never share passwords\"] }\n" +
|
|
442
|
-
"- Update problem: updates: { \"problem.statement\": \"...\", \"problem.goals\": [\"goal1\"] }\n" +
|
|
443
531
|
"- Add a tool: updates: { \"tools_push\": [{ name: \"conn.tool\", description: \"...\", inputs: [...], output: {...} }] }\n" +
|
|
444
532
|
"- Change intent: updates: { \"intents.supported_update\": [{ id: \"i1\", description: \"new desc\" }] }\n" +
|
|
445
|
-
"-
|
|
446
|
-
"
|
|
447
|
-
" If the skill doesn't exist yet, a default scaffold is created and the updates are applied on top. The skill is automatically added to the solution topology.\n\n" +
|
|
448
|
-
"Use target='skill' + skill_id for skill fields. Use target='solution' for solution-level fields (linked_skills, platform_connectors, ui_plugins).",
|
|
533
|
+
"- CREATE a new skill: target='skill', skill_id='my-new-skill', updates: { \"problem.statement\": \"...\", \"role.persona\": \"...\" } — auto-scaffolded and added to solution topology.\n\n" +
|
|
534
|
+
"PREVIEW BEFORE WRITING: pass dry_run:true to see the diff (arrays_merged, arrays_replaced, dropped_ids, added_ids) without applying. Use this before any destructive-looking edit.",
|
|
449
535
|
inputSchema: {
|
|
450
536
|
type: "object",
|
|
451
537
|
properties: {
|
|
@@ -473,6 +559,10 @@ export const tools = [
|
|
|
473
559
|
type: "string",
|
|
474
560
|
description: "Optional: re-test the skill after patching. Requires skill_id.",
|
|
475
561
|
},
|
|
562
|
+
dry_run: {
|
|
563
|
+
type: "boolean",
|
|
564
|
+
description: "If true, apply the patch in memory and return the diff (arrays_merged, arrays_replaced, dropped_ids, added_ids, would_write_bytes) WITHOUT writing to GitHub or redeploying. Preview a change before committing to it.",
|
|
565
|
+
},
|
|
476
566
|
},
|
|
477
567
|
required: ["solution_id", "target", "updates"],
|
|
478
568
|
},
|
|
@@ -516,7 +606,9 @@ export const tools = [
|
|
|
516
606
|
name: "ateam_delete_solution",
|
|
517
607
|
core: true,
|
|
518
608
|
description:
|
|
519
|
-
"
|
|
609
|
+
"⚠️ IRREVERSIBLE — kills Mongo state, running MCP processes, and Builder FS for the whole solution and every skill. " +
|
|
610
|
+
"REQUIRES `confirm:true` AND `confirm_solution_id` echoing the solution id you're destroying (defeats typos and hallucinated ids). " +
|
|
611
|
+
"RECOVERY: the GitHub repo is untouched; `ateam_github_pull` rebuilds the solution from `main`. Prefer that over re-deploying from memory.",
|
|
520
612
|
inputSchema: {
|
|
521
613
|
type: "object",
|
|
522
614
|
properties: {
|
|
@@ -524,15 +616,24 @@ export const tools = [
|
|
|
524
616
|
type: "string",
|
|
525
617
|
description: "The solution ID to delete",
|
|
526
618
|
},
|
|
619
|
+
confirm: {
|
|
620
|
+
type: "boolean",
|
|
621
|
+
description: "REQUIRED. Must be exactly true. A missing/false value refuses the call with a recovery hint.",
|
|
622
|
+
},
|
|
623
|
+
confirm_solution_id: {
|
|
624
|
+
type: "string",
|
|
625
|
+
description: "REQUIRED. Must exactly equal `solution_id`. This defeats typos and hallucinated ids — you can't wipe a solution you couldn't spell.",
|
|
626
|
+
},
|
|
527
627
|
},
|
|
528
|
-
required: ["solution_id"],
|
|
628
|
+
required: ["solution_id", "confirm", "confirm_solution_id"],
|
|
529
629
|
},
|
|
530
630
|
},
|
|
531
631
|
{
|
|
532
632
|
name: "ateam_delete_skill",
|
|
533
633
|
core: true,
|
|
534
634
|
description:
|
|
535
|
-
"
|
|
635
|
+
"⚠️ IRREVERSIBLE in Core + Builder FS — kills the running MCP process, unregisters from skill registry, deletes the Mongo record, drops from solution.skills[] and solution.linked_skills, and removes the skill's files from Builder FS. " +
|
|
636
|
+
"REQUIRES `confirm:true`. RECOVERY: the skill still lives in GitHub — `ateam_github_pull` rebuilds the whole solution (no per-skill restore path).",
|
|
536
637
|
inputSchema: {
|
|
537
638
|
type: "object",
|
|
538
639
|
properties: {
|
|
@@ -544,15 +645,22 @@ export const tools = [
|
|
|
544
645
|
type: "string",
|
|
545
646
|
description: "The skill ID to remove (e.g. 'linkedin-agent')",
|
|
546
647
|
},
|
|
648
|
+
confirm: {
|
|
649
|
+
type: "boolean",
|
|
650
|
+
description: "REQUIRED. Must be exactly true. A missing/false value refuses the call with a recovery hint.",
|
|
651
|
+
},
|
|
547
652
|
},
|
|
548
|
-
required: ["solution_id", "skill_id"],
|
|
653
|
+
required: ["solution_id", "skill_id", "confirm"],
|
|
549
654
|
},
|
|
550
655
|
},
|
|
551
656
|
{
|
|
552
657
|
name: "ateam_delete_connector",
|
|
553
658
|
core: true,
|
|
554
659
|
description:
|
|
555
|
-
"
|
|
660
|
+
"⚠️ CASCADING — any skill whose engine.bootstrap_tools or tools[] name a tool from this connector will FAIL its next execution. " +
|
|
661
|
+
"Stops and deletes the connector from A-Team Core; drops references from the solution definition (grants, platform_connectors, ui_plugins ids starting `mcp:<connector-id>:*`) and skill definitions (connectors array); cleans up mcp-store files. " +
|
|
662
|
+
"GitHub source is preserved — a follow-up `ateam_build_and_run(github:true)` can resurrect. " +
|
|
663
|
+
"REQUIRES `confirm:true`.",
|
|
556
664
|
inputSchema: {
|
|
557
665
|
type: "object",
|
|
558
666
|
properties: {
|
|
@@ -564,8 +672,12 @@ export const tools = [
|
|
|
564
672
|
type: "string",
|
|
565
673
|
description: "The connector ID to remove (e.g. 'device-mock-mcp')",
|
|
566
674
|
},
|
|
675
|
+
confirm: {
|
|
676
|
+
type: "boolean",
|
|
677
|
+
description: "REQUIRED. Must be exactly true. A missing/false value refuses the call with a recovery hint.",
|
|
678
|
+
},
|
|
567
679
|
},
|
|
568
|
-
required: ["solution_id", "connector_id"],
|
|
680
|
+
required: ["solution_id", "connector_id", "confirm"],
|
|
569
681
|
},
|
|
570
682
|
},
|
|
571
683
|
|
|
@@ -2535,9 +2647,10 @@ const handlers = {
|
|
|
2535
2647
|
// Updates → Redeploys → Optionally tests
|
|
2536
2648
|
// One call replaces: ateam_update + ateam_redeploy
|
|
2537
2649
|
|
|
2538
|
-
ateam_patch: async ({ solution_id, target, skill_id, updates, test_message }, sid) => {
|
|
2650
|
+
ateam_patch: async ({ solution_id, target, skill_id, updates, test_message, dry_run }, sid) => {
|
|
2539
2651
|
const phases = [];
|
|
2540
2652
|
let isNewSkill = false;
|
|
2653
|
+
const _diff = { arrays_merged: [], arrays_replaced: [], scalars_changed: [], sections_replaced: [] };
|
|
2541
2654
|
|
|
2542
2655
|
// GitHub-first patch: read from GitHub → apply patch → write back → redeploy
|
|
2543
2656
|
// This ensures GitHub stays the single source of truth.
|
|
@@ -2569,7 +2682,7 @@ const handlers = {
|
|
|
2569
2682
|
role: { name: "", persona: "", goals: [], limitations: [], communication_style: { tone: "professional", verbosity: "concise" } },
|
|
2570
2683
|
intents: { supported: [], thresholds: { accept: 0.8, clarify: 0.5, reject: 0.5 }, out_of_domain: { action: "redirect", message: "" } },
|
|
2571
2684
|
tools: [],
|
|
2572
|
-
policy: { guardrails: { never: [], always: [] }, approvals: [], workflows: [], escalation: { enabled: false, conditions: [], target: "" } },
|
|
2685
|
+
policy: { access: { requires_roles: [] }, guardrails: { never: [], always: [] }, approvals: [], workflows: [], escalation: { enabled: false, conditions: [], target: "" } },
|
|
2573
2686
|
engine: { rv2: { max_iterations: 10, iteration_timeout_ms: 120000, allow_parallel_tools: false, on_max_iterations: "ask_user" }, hlr: { enabled: true, critic: { enabled: true, check_interval: 3, strictness: "medium" }, reflection: { enabled: true, depth: "shallow" }, replanning: { enabled: true, max_replans: 3 } }, autonomy: { level: "supervised" }, finalization_gate: { enabled: true, max_retries: 2 } },
|
|
2574
2687
|
access_policy: { rules: [{ tools: ["*"], effect: "allow" }] },
|
|
2575
2688
|
grant_mappings: [],
|
|
@@ -2656,9 +2769,30 @@ const handlers = {
|
|
|
2656
2769
|
else arr.push(upd);
|
|
2657
2770
|
}
|
|
2658
2771
|
parent[leaf] = arr;
|
|
2772
|
+
} else if (key === "_replace" || key.endsWith("_replace")) {
|
|
2773
|
+
// Escape-hatch flags handled by the guard — skip them here so they
|
|
2774
|
+
// don't get written into the patched object as literal fields.
|
|
2775
|
+
continue;
|
|
2659
2776
|
} else if (key.includes(".")) {
|
|
2660
2777
|
// Dot notation: "role.persona", "intents.thresholds.accept"
|
|
2661
2778
|
const parts = key.split(".");
|
|
2779
|
+
// Sibling-loss guard: if the leaf resolves to an existing non-empty
|
|
2780
|
+
// array and the incoming value is also an array, refuse the replace
|
|
2781
|
+
// unless the caller opted in. (Dot-notation is how many agents
|
|
2782
|
+
// accidentally hit this — e.g. updates:{ "linked_skills": ["one"] }
|
|
2783
|
+
// on target='solution'.)
|
|
2784
|
+
const leafKey = parts[parts.length - 1];
|
|
2785
|
+
let cursor = patched;
|
|
2786
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2787
|
+
if (!cursor || typeof cursor[parts[i]] !== 'object') { cursor = null; break; }
|
|
2788
|
+
cursor = cursor[parts[i]];
|
|
2789
|
+
}
|
|
2790
|
+
const currentLeaf = cursor && Object.prototype.hasOwnProperty.call(cursor, leafKey) ? cursor[leafKey] : undefined;
|
|
2791
|
+
const guardErr = _guardArrayReplace({ target, key: leafKey, value, current: currentLeaf, updates });
|
|
2792
|
+
if (guardErr) return guardErr;
|
|
2793
|
+
if (Array.isArray(value) && Array.isArray(currentLeaf)) _diff.arrays_replaced.push(key);
|
|
2794
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) _diff.sections_replaced.push(key);
|
|
2795
|
+
else _diff.scalars_changed.push(key);
|
|
2662
2796
|
let obj = patched;
|
|
2663
2797
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
2664
2798
|
if (!obj[parts[i]] || typeof obj[parts[i]] !== "object") obj[parts[i]] = {};
|
|
@@ -2666,7 +2800,14 @@ const handlers = {
|
|
|
2666
2800
|
}
|
|
2667
2801
|
obj[parts[parts.length - 1]] = value;
|
|
2668
2802
|
} else {
|
|
2669
|
-
// Direct field replacement
|
|
2803
|
+
// Direct top-level field replacement. Sibling-loss guard: if this
|
|
2804
|
+
// names a known array field and would drop items, refuse unless the
|
|
2805
|
+
// caller passed _replace:true (object-level) or <field>_replace:true.
|
|
2806
|
+
const guardErr = _guardArrayReplace({ target, key, value, current: patched[key], updates });
|
|
2807
|
+
if (guardErr) return guardErr;
|
|
2808
|
+
if (Array.isArray(value) && Array.isArray(patched[key])) _diff.arrays_replaced.push(key);
|
|
2809
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) _diff.sections_replaced.push(key);
|
|
2810
|
+
else _diff.scalars_changed.push(key);
|
|
2670
2811
|
patched[key] = value;
|
|
2671
2812
|
}
|
|
2672
2813
|
}
|
|
@@ -2675,6 +2816,23 @@ const handlers = {
|
|
|
2675
2816
|
return { ok: false, phase: "patch", error: `Failed to apply patch: ${err.message}` };
|
|
2676
2817
|
}
|
|
2677
2818
|
|
|
2819
|
+
// Dry-run: return diff + would-be after-state without writing to GitHub
|
|
2820
|
+
// or redeploying. Lets an agent preview any destructive-looking edit.
|
|
2821
|
+
if (dry_run) {
|
|
2822
|
+
return {
|
|
2823
|
+
ok: true,
|
|
2824
|
+
dry_run: true,
|
|
2825
|
+
target,
|
|
2826
|
+
solution_id,
|
|
2827
|
+
skill_id,
|
|
2828
|
+
phases,
|
|
2829
|
+
_diff,
|
|
2830
|
+
after_state: patched,
|
|
2831
|
+
would_write_bytes: JSON.stringify(patched, null, 2).length,
|
|
2832
|
+
hint: "No changes applied. Remove dry_run:true to commit + redeploy.",
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2678
2836
|
// Phase 3: Write patched version back to GitHub
|
|
2679
2837
|
try {
|
|
2680
2838
|
const patchKeys = Object.keys(updates || {});
|
|
@@ -3364,14 +3522,46 @@ const handlers = {
|
|
|
3364
3522
|
ateam_github_list_versions: async ({ solution_id }, sid) =>
|
|
3365
3523
|
get(`/deploy/solutions/${solution_id}/versions/dev`, sid),
|
|
3366
3524
|
|
|
3367
|
-
ateam_delete_solution: async ({ solution_id }, sid) =>
|
|
3368
|
-
|
|
3525
|
+
ateam_delete_solution: async ({ solution_id, confirm, confirm_solution_id }, sid) => {
|
|
3526
|
+
if (confirm !== true) {
|
|
3527
|
+
return {
|
|
3528
|
+
ok: false,
|
|
3529
|
+
error: "⚠️ REFUSED: ateam_delete_solution requires confirm:true. This is irreversible in Core + Builder FS. GitHub source is preserved — ateam_github_pull rebuilds from `main` if you already deleted by mistake.",
|
|
3530
|
+
recovery: "ateam_github_pull(solution_id, ref:'main')",
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
if (confirm_solution_id !== solution_id) {
|
|
3534
|
+
return {
|
|
3535
|
+
ok: false,
|
|
3536
|
+
error: `⚠️ REFUSED: confirm_solution_id must exactly equal solution_id. Got confirm_solution_id="${confirm_solution_id}" but solution_id="${solution_id}". This check defeats typos and hallucinated ids — you should not be able to wipe a solution whose id you can't spell correctly.`,
|
|
3537
|
+
expected: solution_id,
|
|
3538
|
+
received: confirm_solution_id,
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
return del(`/deploy/solutions/${solution_id}`, sid);
|
|
3542
|
+
},
|
|
3369
3543
|
|
|
3370
|
-
ateam_delete_skill: async ({ solution_id, skill_id }, sid) =>
|
|
3371
|
-
|
|
3544
|
+
ateam_delete_skill: async ({ solution_id, skill_id, confirm }, sid) => {
|
|
3545
|
+
if (confirm !== true) {
|
|
3546
|
+
return {
|
|
3547
|
+
ok: false,
|
|
3548
|
+
error: `⚠️ REFUSED: ateam_delete_skill requires confirm:true. Kills the running MCP process and deletes the skill from Core + Builder FS. GitHub source is preserved — ateam_github_pull rebuilds the whole solution.`,
|
|
3549
|
+
recovery: "ateam_github_pull(solution_id, ref:'main') — no per-skill restore path",
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
return del(`/deploy/solutions/${solution_id}/skills/${skill_id}`, sid);
|
|
3553
|
+
},
|
|
3372
3554
|
|
|
3373
|
-
ateam_delete_connector: async ({ solution_id, connector_id }, sid) =>
|
|
3374
|
-
|
|
3555
|
+
ateam_delete_connector: async ({ solution_id, connector_id, confirm }, sid) => {
|
|
3556
|
+
if (confirm !== true) {
|
|
3557
|
+
return {
|
|
3558
|
+
ok: false,
|
|
3559
|
+
error: `⚠️ REFUSED: ateam_delete_connector requires confirm:true. Cascading — any skill wired to this connector's tools will fail its next execution. GitHub source is preserved.`,
|
|
3560
|
+
recovery: "ateam_build_and_run(solution_id, github:true) can resurrect from GitHub",
|
|
3561
|
+
};
|
|
3562
|
+
}
|
|
3563
|
+
return del(`/deploy/solutions/${solution_id}/connectors/${connector_id}`, sid);
|
|
3564
|
+
},
|
|
3375
3565
|
|
|
3376
3566
|
ateam_upload_connector: async ({ solution_id, connector_id, github, files, ref, replace }, sid) =>
|
|
3377
3567
|
post(
|