gentle-agent-extensions 1.0.0 → 1.2.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.
@@ -0,0 +1,544 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync, cpSync, statSync } from "node:fs";
3
+ import { join, resolve, dirname } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const EXTENSION_NAME = "gentle-agent-extensions";
8
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
+
10
+ const BUNDLED = {
11
+ agents: join(PACKAGE_ROOT, "resources", "agents"),
12
+ overlays: join(PACKAGE_ROOT, "resources", "overlays"),
13
+ routing: join(PACKAGE_ROOT, "resources", "routing"),
14
+ mcp: join(PACKAGE_ROOT, "resources", "mcp"),
15
+ };
16
+
17
+ type OverlayMode = "append" | "prepend" | "replace";
18
+
19
+ interface OverlayDefinition {
20
+ target: string;
21
+ priority: number;
22
+ mode: OverlayMode;
23
+ instructions: string;
24
+ }
25
+
26
+ interface McpServerDefinition {
27
+ name: string;
28
+ description: string;
29
+ transport: "stdio" | "http";
30
+ command?: string;
31
+ args?: string[];
32
+ url?: string;
33
+ env?: Record<string, string>;
34
+ exposure?: "codemode" | "direct" | "deferred" | "hidden";
35
+ toolExposure?: Record<string, string>;
36
+ enabled?: boolean;
37
+ timeout?: number;
38
+ install?: {
39
+ package: string;
40
+ version: string;
41
+ manager: string;
42
+ };
43
+ auth?: {
44
+ type: "oauth" | "bearer" | "none";
45
+ envVar?: string;
46
+ description?: string;
47
+ };
48
+ capabilities?: string[];
49
+ agentDependencies?: string[];
50
+ }
51
+
52
+ interface ProjectState {
53
+ overlays: Map<string, OverlayDefinition[]>;
54
+ agents: Set<string>;
55
+ mcpServers: Map<string, McpServerDefinition>;
56
+ initialized: boolean;
57
+ }
58
+
59
+ const projectState: ProjectState = {
60
+ overlays: new Map(),
61
+ agents: new Set(),
62
+ mcpServers: new Map(),
63
+ initialized: false,
64
+ };
65
+
66
+ function getAgentHome(env: NodeJS.ProcessEnv = process.env): string {
67
+ if (env.GENTLE_PI_AGENT_HOME) return resolve(env.GENTLE_PI_AGENT_HOME);
68
+ if (env.PI_CODING_AGENT_DIR) return resolve(env.PI_CODING_AGENT_DIR);
69
+ return join(homedir(), ".pi", "agent");
70
+ }
71
+
72
+ function getProjectAgentDir(cwd: string, agentHome: string): string {
73
+ return join(cwd, ".pi", "agents");
74
+ }
75
+
76
+ function ensureDir(dir: string): void {
77
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
78
+ }
79
+
80
+ function discoverBundledAgents(): string[] {
81
+ if (!existsSync(BUNDLED.agents)) return [];
82
+ return readdirSync(BUNDLED.agents)
83
+ .filter((f) => f.endsWith(".md"))
84
+ .map((f) => f.slice(0, -3));
85
+ }
86
+
87
+ function loadBundledOverlays(): Map<string, OverlayDefinition[]> {
88
+ const overlays = new Map<string, OverlayDefinition[]>();
89
+
90
+ if (!existsSync(BUNDLED.overlays)) return overlays;
91
+
92
+ for (const file of readdirSync(BUNDLED.overlays)) {
93
+ if (!file.endsWith(".overlay.md")) continue;
94
+
95
+ const filePath = join(BUNDLED.overlays, file);
96
+ const content = readFileSync(filePath, "utf8");
97
+
98
+ const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/.exec(content);
99
+ if (!frontmatterMatch) continue;
100
+
101
+ const [, fm, body] = frontmatterMatch;
102
+ const targetMatch = /target:\s*([^\n]+)/.exec(fm);
103
+ const priorityMatch = /priority:\s*(\d+)/.exec(fm);
104
+ const modeMatch = /mode:\s*(\w+)/.exec(fm);
105
+
106
+ if (!targetMatch) continue;
107
+
108
+ const target = targetMatch[1].trim();
109
+ const priority = priorityMatch ? parseInt(priorityMatch[1], 10) : 100;
110
+ const mode = (modeMatch ? modeMatch[1].trim() : "append") as OverlayMode;
111
+
112
+ if (!["append", "prepend", "replace"].includes(mode)) {
113
+ console.warn(`[${EXTENSION_NAME}] Unknown overlay mode '${mode}' for ${file}, defaulting to 'append'`);
114
+ }
115
+
116
+ const overlay: OverlayDefinition = {
117
+ target,
118
+ priority,
119
+ mode,
120
+ instructions: body.trim(),
121
+ };
122
+
123
+ const existing = overlays.get(target) || [];
124
+ existing.push(overlay);
125
+ existing.sort((a, b) => a.priority - b.priority);
126
+ overlays.set(target, existing);
127
+ }
128
+
129
+ return overlays;
130
+ }
131
+
132
+ function loadBundledMcpServers(): Map<string, McpServerDefinition> {
133
+ const servers = new Map<string, McpServerDefinition>();
134
+
135
+ if (!existsSync(BUNDLED.mcp)) return servers;
136
+
137
+ for (const file of readdirSync(BUNDLED.mcp)) {
138
+ if (!file.endsWith(".mcp.json")) continue;
139
+
140
+ const filePath = join(BUNDLED.mcp, file);
141
+ const content = readFileSync(filePath, "utf8");
142
+
143
+ let server: McpServerDefinition;
144
+ try {
145
+ server = JSON.parse(content);
146
+ } catch (error) {
147
+ continue;
148
+ }
149
+
150
+ if (!server.name || !server.description || !server.transport) continue;
151
+ if (server.transport === "stdio" && !server.command) continue;
152
+ if (server.transport === "http" && !server.url) continue;
153
+ if (!/^[A-Za-z0-9_-]+$/.test(server.name)) continue;
154
+
155
+ servers.set(server.name, server);
156
+ }
157
+
158
+ return servers;
159
+ }
160
+
161
+ function appendSystemPromptOnce(options: { appendSystemPrompt?: string; systemPrompt?: string } | null | undefined, text: string): void {
162
+ const normalized = text.replace(/^\n+/, "");
163
+ if (!options || !normalized) return;
164
+ const current = options.appendSystemPrompt ?? "";
165
+ if (current.includes(normalized)) return;
166
+ options.appendSystemPrompt = current ? `${current}\n\n${normalized}` : normalized;
167
+ }
168
+
169
+ function prependSystemPromptOnce(options: { appendSystemPrompt?: string; systemPrompt?: string } | null | undefined, text: string): void {
170
+ const normalized = text.replace(/^\n+/, "");
171
+ if (!options || !normalized) return;
172
+ const current = options.appendSystemPrompt ?? "";
173
+ if (current.includes(normalized)) return;
174
+ options.appendSystemPrompt = normalized + (current ? `\n\n${current}` : "");
175
+ }
176
+
177
+ function replaceSystemPrompt(options: { appendSystemPrompt?: string; systemPrompt?: string } | null | undefined, text: string): void {
178
+ const normalized = text.replace(/^\n+/, "");
179
+ if (!options || !normalized) return;
180
+ options.systemPrompt = normalized;
181
+ options.appendSystemPrompt = "";
182
+ }
183
+
184
+ function buildMcpOrchestratorInstructions(servers: Map<string, McpServerDefinition>): string {
185
+ const enabled = Array.from(servers.values()).filter((s) => s.enabled !== false);
186
+ if (enabled.length === 0) return "";
187
+
188
+ let instructions = "\n\n<!-- gentle-agent-extensions: bundled MCP servers -->\n";
189
+ instructions += "## Available MCP Servers (bundled)\n\n";
190
+ instructions += "The following MCP servers are registered and available for use:\n\n";
191
+
192
+ for (const server of enabled) {
193
+ instructions += `### ${server.name}\n`;
194
+ instructions += `- **Description**: ${server.description}\n`;
195
+ instructions += `- **Transport**: ${server.transport}\n`;
196
+ if (server.transport === "stdio") {
197
+ instructions += `- **Command**: ${server.command} ${server.args?.join(" ") || ""}\n`;
198
+ } else {
199
+ instructions += `- **URL**: ${server.url}\n`;
200
+ }
201
+ if (server.exposure) instructions += `- **Exposure**: ${server.exposure}\n`;
202
+ if (server.capabilities && server.capabilities.length > 0) instructions += `- **Capabilities**: ${server.capabilities.join(", ")}\n`;
203
+ if (server.agentDependencies && server.agentDependencies.length > 0) instructions += `- **Recommended for agents**: ${server.agentDependencies.join(", ")}\n`;
204
+ instructions += "\n";
205
+ }
206
+
207
+ instructions += "When a task requires capabilities from these servers, consider delegating to agents that declare those MCP dependencies, or use the MCP tools directly via `codemode` or `tool_search`.\n";
208
+ return instructions;
209
+ }
210
+
211
+ function initializeBundledState(): void {
212
+ if (projectState.initialized) return;
213
+ projectState.agents = new Set(discoverBundledAgents());
214
+ projectState.overlays = loadBundledOverlays();
215
+ projectState.mcpServers = loadBundledMcpServers();
216
+ projectState.initialized = true;
217
+ }
218
+
219
+ async function copyBundledAgentsToProject(cwd: string): Promise<void> {
220
+ const agentHome = getAgentHome();
221
+ const targetDir = getProjectAgentDir(cwd, agentHome);
222
+ ensureDir(targetDir);
223
+
224
+ if (!existsSync(BUNDLED.agents)) return;
225
+
226
+ for (const agentName of projectState.agents) {
227
+ const sourcePath = join(BUNDLED.agents, `${agentName}.md`);
228
+ const targetPath = join(targetDir, `${agentName}.md`);
229
+
230
+ try {
231
+ const sourceContent = readFileSync(sourcePath, "utf8");
232
+ let shouldWrite = true;
233
+
234
+ if (existsSync(targetPath)) {
235
+ const targetContent = readFileSync(targetPath, "utf8");
236
+ if (targetContent === sourceContent) shouldWrite = false;
237
+ }
238
+
239
+ if (shouldWrite) {
240
+ writeFileSync(targetPath, sourceContent);
241
+ }
242
+ } catch (error) {
243
+ console.warn(`[${EXTENSION_NAME}] Failed to sync agent ${agentName}:`, error);
244
+ }
245
+ }
246
+ }
247
+
248
+ function registerBundledMcpServers(ctx: ExtensionContext): void {
249
+ for (const [name, server] of projectState.mcpServers) {
250
+ if (server.enabled === false) continue;
251
+
252
+ const config: Record<string, unknown> = {
253
+ description: server.description,
254
+ exposure: server.exposure || "codemode",
255
+ enabled: server.enabled !== false,
256
+ timeout: server.timeout || 60,
257
+ };
258
+
259
+ if (server.transport === "stdio") {
260
+ config.command = server.command;
261
+ config.args = server.args || [];
262
+ if (server.env) config.env = server.env;
263
+ if (server.cwd) config.cwd = server.cwd;
264
+ } else {
265
+ config.url = server.url;
266
+ if (server.headers) config.headers = server.headers;
267
+ if (server.oauth) config.oauth = server.oauth;
268
+ }
269
+
270
+ if (server.toolExposure) config.toolExposure = server.toolExposure;
271
+
272
+ try {
273
+ ctx.api.registerMcpServer(name, config);
274
+ } catch (error) {
275
+ ctx.ui.notify(`[${EXTENSION_NAME}] Failed to register MCP server ${name}: ${error}`, "warning");
276
+ }
277
+ }
278
+ }
279
+
280
+ function applyOverlay(overlay: OverlayDefinition, targetName: string, options: { appendSystemPrompt?: string; systemPrompt?: string } | null | undefined): void {
281
+ const text = `\n\n<!-- gentle-agent-extensions: bundled overlay for ${targetName} -->\n${overlay.instructions}`;
282
+ switch (overlay.mode) {
283
+ case "prepend":
284
+ prependSystemPromptOnce(options, text);
285
+ break;
286
+ case "replace":
287
+ replaceSystemPrompt(options, text);
288
+ break;
289
+ case "append":
290
+ default:
291
+ appendSystemPromptOnce(options, text);
292
+ break;
293
+ }
294
+ }
295
+
296
+ async function runDoctorChecks(cwd: string, env: NodeJS.ProcessEnv, includeMcpHealth = false): Promise<{ issues: string[]; warnings: string[] }> {
297
+ const issues: string[] = [];
298
+ const warnings: string[] = [];
299
+
300
+ // Check bundled agents
301
+ if (!existsSync(BUNDLED.agents)) {
302
+ warnings.push("Bundled agents directory missing");
303
+ } else {
304
+ for (const file of readdirSync(BUNDLED.agents)) {
305
+ if (!file.endsWith(".md")) continue;
306
+ const content = readFileSync(join(BUNDLED.agents, file), "utf8");
307
+ if (!content.startsWith("---")) issues.push(`Bundled agent ${file}: missing frontmatter`);
308
+ if (!/^name:/m.test(content)) issues.push(`Bundled agent ${file}: missing 'name'`);
309
+ }
310
+ }
311
+
312
+ // Check bundled overlays
313
+ if (!existsSync(BUNDLED.overlays)) {
314
+ warnings.push("Bundled overlays directory missing");
315
+ } else {
316
+ for (const file of readdirSync(BUNDLED.overlays)) {
317
+ if (!file.endsWith(".overlay.md")) continue;
318
+ const content = readFileSync(join(BUNDLED.overlays, file), "utf8");
319
+ if (!/^target:/m.test(content)) issues.push(`Bundled overlay ${file}: missing 'target'`);
320
+ const modeMatch = /^mode:\s*(\w+)/m.exec(content);
321
+ if (modeMatch && !["append", "prepend", "replace"].includes(modeMatch[1])) {
322
+ issues.push(`Bundled overlay ${file}: invalid mode '${modeMatch[1]}'`);
323
+ }
324
+ }
325
+ }
326
+
327
+ // Check bundled MCPs
328
+ if (existsSync(BUNDLED.mcp)) {
329
+ for (const file of readdirSync(BUNDLED.mcp)) {
330
+ if (!file.endsWith(".mcp.json")) continue;
331
+ try {
332
+ const config = JSON.parse(readFileSync(join(BUNDLED.mcp, file), "utf8"));
333
+ if (!config.name || !config.description || !config.transport) {
334
+ issues.push(`Bundled MCP ${file}: missing required fields`);
335
+ }
336
+ if (config.transport === "stdio" && !config.command) issues.push(`Bundled MCP ${file}: stdio missing command`);
337
+ if (config.transport === "http" && !config.url) issues.push(`Bundled MCP ${file}: http missing url`);
338
+ } catch {
339
+ issues.push(`Bundled MCP ${file}: invalid JSON`);
340
+ }
341
+ }
342
+ }
343
+
344
+ // Check extension installation
345
+ const agentHome = getAgentHome(env);
346
+ const extensionTarget = join(agentHome, "extensions", "gentle-agent-extensions.ts");
347
+ let extensionInstalled = false;
348
+ if (existsSync(extensionTarget)) {
349
+ extensionInstalled = true;
350
+ } else {
351
+ const settingsJson = join(agentHome, "settings.json");
352
+ if (existsSync(settingsJson) && readFileSync(settingsJson, "utf8").includes("gentle-agent-extensions")) {
353
+ extensionInstalled = true;
354
+ }
355
+ }
356
+ if (!extensionInstalled) {
357
+ issues.push("Extension not installed (run: pi install npm:gentle-agent-extensions or pi install git:...)");
358
+ }
359
+
360
+ // MCP Health Check
361
+ if (includeMcpHealth) {
362
+ for (const [name, server] of projectState.mcpServers) {
363
+ if (server.enabled === false) continue;
364
+ if (server.transport === "stdio") {
365
+ try {
366
+ const { spawn } = await import("node:child_process");
367
+ const child = spawn(server.command!, server.args || [], { stdio: "ignore" });
368
+ await new Promise<void>((resolve, reject) => {
369
+ const timer = setTimeout(() => {
370
+ child.kill();
371
+ resolve();
372
+ }, 2000);
373
+ child.on("error", () => {
374
+ clearTimeout(timer);
375
+ reject(new Error("failed to start"));
376
+ });
377
+ child.on("exit", (code) => {
378
+ clearTimeout(timer);
379
+ if (code !== 0 && code !== null) reject(new Error(`exited with ${code}`));
380
+ else resolve();
381
+ });
382
+ });
383
+ } catch {
384
+ warnings.push(`MCP ${name}: stdio server failed to start`);
385
+ }
386
+ } else if (server.transport === "http") {
387
+ try {
388
+ const response = await fetch(server.url!, { method: "HEAD", signal: AbortSignal.timeout(5000) });
389
+ if (!response.ok) warnings.push(`MCP ${name}: HTTP ${response.status} at ${server.url}`);
390
+ } catch {
391
+ warnings.push(`MCP ${name}: HTTP server not reachable at ${server.url}`);
392
+ }
393
+ }
394
+ }
395
+ }
396
+
397
+ return { issues, warnings };
398
+ }
399
+
400
+ export default function gentleAgentExtensions(pi: ExtensionAPI): void {
401
+ pi.on("session_start", async (_event, ctx: ExtensionContext) => {
402
+ initializeBundledState();
403
+
404
+ // Copy bundled agents to project's .pi/agents/
405
+ await copyBundledAgentsToProject(ctx.cwd);
406
+
407
+ // Register bundled MCP servers
408
+ registerBundledMcpServers(ctx);
409
+ });
410
+
411
+ pi.on("before_agent_start", (event, ctx: ExtensionContext) => {
412
+ if (!projectState.initialized) initializeBundledState();
413
+
414
+ const agentName = (event as { agentName?: string }).agentName;
415
+ const options = event.systemPromptOptions;
416
+ if (!options) return;
417
+
418
+ // Apply bundled overlays
419
+ if (!agentName || agentName === "orchestrator") {
420
+ const orchestratorOverlays = projectState.overlays.get("orchestrator") || [];
421
+ for (const overlay of orchestratorOverlays) {
422
+ applyOverlay(overlay, "orchestrator", options);
423
+ }
424
+ const mcpInstructions = buildMcpOrchestratorInstructions(projectState.mcpServers);
425
+ if (mcpInstructions) appendSystemPromptOnce(options, mcpInstructions);
426
+ }
427
+
428
+ if (agentName) {
429
+ const agentOverlays = projectState.overlays.get(agentName) || [];
430
+ for (const overlay of agentOverlays) {
431
+ applyOverlay(overlay, agentName, options);
432
+ }
433
+ }
434
+
435
+ return undefined;
436
+ });
437
+
438
+ // Commands
439
+ pi.registerCommand("gentle-extensions:sync", {
440
+ description: "Sync bundled agents to current project",
441
+ handler: async (_args, ctx) => {
442
+ await copyBundledAgentsToProject(ctx.cwd);
443
+ ctx.ui.notify("gentle-agent-extensions: bundled agents synced to project", "info");
444
+ },
445
+ });
446
+
447
+ pi.registerCommand("gentle-extensions:doctor", {
448
+ description: "Check bundled resources and installation",
449
+ handler: async (args, ctx) => {
450
+ const includeMcpHealth = args?.mcpHealth === true;
451
+ const { issues, warnings } = await runDoctorChecks(ctx.cwd, ctx.env, includeMcpHealth);
452
+
453
+ let output = "gentle-agent-extensions doctor:\n\n";
454
+ if (issues.length > 0) { output += "❌ Issues:\n"; for (const i of issues) output += ` - ${i}\n`; output += "\n"; }
455
+ if (warnings.length > 0) { output += "⚠️ Warnings:\n"; for (const w of warnings) output += ` - ${w}\n`; output += "\n"; }
456
+ if (issues.length === 0 && warnings.length === 0) output += "✅ All checks passed\n";
457
+
458
+ ctx.ui.notify(output, issues.length > 0 ? "warning" : "info");
459
+ },
460
+ });
461
+
462
+ pi.registerCommand("gentle-extensions:fix", {
463
+ description: "Auto-fix non-destructive issues (perms, dirs, manifest, overlay modes)",
464
+ handler: async (_args, ctx) => {
465
+ let fixes = 0;
466
+
467
+ // Ensure project agent dir exists
468
+ const agentHome = getAgentHome(ctx.env);
469
+ const projectAgentDir = getProjectAgentDir(ctx.cwd, agentHome);
470
+ if (!existsSync(projectAgentDir)) {
471
+ ensureDir(projectAgentDir);
472
+ fixes++;
473
+ }
474
+
475
+ // Fix overlay mode defaults (in bundled resources - read only, so we fix at runtime)
476
+ // Actually bundled resources are read-only, we apply defaults in applyOverlay
477
+
478
+ // Re-sync agents
479
+ await copyBundledAgentsToProject(ctx.cwd);
480
+ fixes++;
481
+
482
+ ctx.ui.notify(`gentle-agent-extensions: applied ${fixes} fix(es)`, "info");
483
+ },
484
+ });
485
+
486
+ pi.registerCommand("gentle-extensions:fix-destructive", {
487
+ description: "Auto-fix including destructive actions (requires confirmation via UI)",
488
+ handler: async (_args, ctx) => {
489
+ // This would need UI confirmation - for now delegate to doctor with flag
490
+ ctx.ui.notify("Use /gentle-extensions:doctor with --fix-destructive flag via CLI for destructive fixes", "warning");
491
+ },
492
+ });
493
+
494
+ pi.registerCommand("gentle-extensions:mcp-health", {
495
+ description: "Check MCP server connectivity (stdio startup, HTTP reachability)",
496
+ handler: async (_args, ctx) => {
497
+ const { issues, warnings } = await runDoctorChecks(ctx.cwd, ctx.env, true);
498
+ let output = "MCP Health Check:\n\n";
499
+ if (issues.length > 0) { output += "❌ Issues:\n"; for (const i of issues) output += ` - ${i}\n`; output += "\n"; }
500
+ if (warnings.length > 0) { output += "⚠️ Warnings:\n"; for (const w of warnings) output += ` - ${w}\n`; output += "\n"; }
501
+ if (issues.length === 0 && warnings.length === 0) output += "✅ All MCP servers healthy\n";
502
+ ctx.ui.notify(output, issues.length > 0 ? "warning" : "info");
503
+ },
504
+ });
505
+
506
+ pi.registerCommand("gentle-extensions:conflicts", {
507
+ description: "Interactive conflict resolution for shadowing agents and unknown overlay targets",
508
+ handler: async (_args, ctx) => {
509
+ ctx.ui.notify("Interactive conflict resolution not yet implemented in extension. Use CLI doctor --interactive.", "warning");
510
+ },
511
+ });
512
+
513
+ pi.registerCommand("gentle-extensions:status", {
514
+ description: "Show bundled resources status",
515
+ handler: async (_args, ctx) => {
516
+ if (!projectState.initialized) initializeBundledState();
517
+
518
+ const agents = Array.from(projectState.agents);
519
+ const overlayTargets = Array.from(projectState.overlays.keys());
520
+ const mcpServers = Array.from(projectState.mcpServers.keys());
521
+
522
+ let msg = `gentle-agent-extensions status:\n`;
523
+ msg += ` Package root: ${PACKAGE_ROOT}\n`;
524
+ msg += ` Bundled agents: ${agents.length > 0 ? agents.join(", ") : "none"}\n`;
525
+ msg += ` Bundled overlays: ${overlayTargets.length > 0 ? overlayTargets.join(", ") : "none"}\n`;
526
+ msg += ` Bundled MCP servers: ${mcpServers.length > 0 ? mcpServers.join(", ") : "none"}\n`;
527
+ msg += ` Bundled skills: ${existsSync(join(PACKAGE_ROOT, "resources", "skills")) ? "present" : "missing"}\n`;
528
+
529
+ ctx.ui.notify(msg, "info");
530
+ },
531
+ });
532
+
533
+ pi.registerCommand("gentle-extensions:mcp-reload", {
534
+ description: "Reload MCP servers from bundled resources",
535
+ handler: async (_args, ctx) => {
536
+ for (const [name] of projectState.mcpServers) {
537
+ try { ctx.api.unregisterMcpServer(name); } catch {}
538
+ }
539
+ projectState.mcpServers = loadBundledMcpServers();
540
+ registerBundledMcpServers(ctx);
541
+ ctx.ui.notify("MCP servers reloaded from bundled resources", "info");
542
+ },
543
+ });
544
+ }
package/.manifest.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "version": "1.0",
3
- "description": "Manifest of files managed by gentle-agent-extensions. Used by sync.sh to verify ownership and prevent modification of external files.",
4
- "managedPaths": {
5
- "extensions": [],
6
- "agents": [],
7
- "routing": [],
8
- "overlays": [],
9
- "skills": [],
10
- "mcp": [],
11
- "scripts": [],
12
- "assets": []
13
- },
14
- "installProfiles": {}
15
- }
package/agents/.gitkeep DELETED
File without changes
package/assets/.gitkeep DELETED
File without changes