Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/architecture/agentConfigurationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ Code extension and standalone CLI use this installer. The npm package includes
the complete skill tree, and `debugmcp configure` installs it while registering
the selected agents.

The extension's activation migration refreshes the skill unconditionally, before
looking for agent configuration files. Fresh Dev Container/Remote extension hosts
therefore install into their own home even without desktop MCP configuration or
onboarding. Missing bundles and copy failures are reported in the DebugMCP log.
Skill delivery does not depend on resolving desktop Copilot configuration paths.

This fixes issue #105: earlier builds copied the skill next to each agent's config (e.g. `Code/User/skills/` for VS Code Copilot), a directory no harness scans, so the skill never loaded. Installing to `~/.agents/skills/` — which VS Code agent mode does scan — makes it discoverable.

## Key Code Locations
Expand All @@ -118,14 +124,16 @@ This fixes issue #105: earlier builds copied the skill next to each agent's conf

## User Flow

1. Extension activates
1. Extension activates and refreshes the bundled skill in the extension host's home
2. Check if popup was previously shown
3. If not, display multi-select dialog with supported agents
4. For each selected agent, write/update config file
5. Show success message with option to open config file
6. Mark popup as shown after it is accepted or dismissed

The bundled `debug-live` skill is installed into the standard skills directories (`~/.agents/skills/`, plus `~/.copilot/skills/` when present) during step 4, so every skills-compatible harness discovers it from one shared location.
The bundled `debug-live` skill is refreshed in the standard skills directories
at step 1 and also during explicit agent setup. Installation does not depend on
accepting the popup or having an existing MCP registration.

## Commands

Expand Down
31 changes: 31 additions & 0 deletions src/test/agentConfigurationManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,37 @@ suite('AgentConfigurationManager JSON configuration', () => {
await fs.promises.rm(tempDir, { recursive: true, force: true });
}
});

});

suite('Activation skill installation without desktop configuration (#174)', () => {
for (const scenario of ['no agents', 'missing configuration', 'malformed configuration', 'no DebugMCP entry']) {
test(`refreshes the skill on every activation with ${scenario}`, async () => {
const directory = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'debugmcp-remote-skill-'));
try {
const manager = new AgentConfigurationManager({ extensionPath: directory } as vscode.ExtensionContext, 180, 3001);
const agents = await manager['getSupportedAgents']();
const copilot = agents.find(agent => agent.id === 'copilot');
assert.ok(copilot);
const configPath = path.join(directory, 'mcp.json');
if (scenario === 'malformed configuration') {
await fs.promises.writeFile(configPath, '{invalid');
} else if (scenario === 'no DebugMCP entry') {
await fs.promises.writeFile(configPath, '{"servers":{}}');
}
manager['getSupportedAgents'] = async () =>
scenario === 'no agents' ? [] : [{ ...copilot, configPath }];
let installs = 0;
manager['installDebugMCPSkill'] = async () => { installs++; return directory; };
await manager.migrateExistingConfigurations();
await manager.migrateExistingConfigurations();
assert.strictEqual(installs, 2);
assert.strictEqual(fs.existsSync(configPath), scenario === 'malformed configuration' || scenario === 'no DebugMCP entry');
} finally {
await fs.promises.rm(directory, { recursive: true, force: true });
}
});
}
});

suite('AgentConfigurationManager Claude Code configuration', () => {
Expand Down
14 changes: 14 additions & 0 deletions src/test/debugSkillInstaller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ suite('debug-live skill installer', () => {
]);
});

test('fresh remote homes still get the cross-agent target without Copilot configuration (#174)', () => {
const home = path.join(directory, 'fresh-remote-home');
assert.deepStrictEqual(getDebugSkillInstallTargets(home, path.join(home, '.copilot')), [
path.join(home, '.agents', 'skills', 'debug-live')
]);
});

test('copies the complete skill and removes legacy names', async () => {
const source = path.join(directory, 'bundled', 'debug-live');
const destination = path.join(directory, 'home', '.agents', 'skills', 'debug-live');
Expand All @@ -57,6 +64,13 @@ suite('debug-live skill installer', () => {
);
assert.strictEqual(fs.existsSync(path.join(skillsDirectory, 'debug')), false);
assert.strictEqual(fs.existsSync(path.join(skillsDirectory, 'really-debug')), false);

await fs.promises.writeFile(path.join(source, 'SKILL.md'), '# Updated debug live\n', 'utf8');
await installDebugSkill(source, destination);
assert.strictEqual(
await fs.promises.readFile(path.join(destination, 'SKILL.md'), 'utf8'),
'# Updated debug live\n'
);
});

test('rejects a package without the skill entry point', async () => {
Expand Down
25 changes: 7 additions & 18 deletions src/utils/agentConfigurationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
installDebugSkill
} from './debugSkillInstaller';
import { selectCopilotDebugMcpHost } from '../cli/copilotMcpConfig';
import { logger } from './logger';
export { AgentInfo, JsonAgentInfo, TomlAgentInfo } from './agentCatalog';

export interface MCPServerConfig {
Expand Down Expand Up @@ -188,20 +189,20 @@ export class AgentConfigurationManager {
const bundledSkillPath = this.getBundledSkillPath();

if (!fs.existsSync(bundledSkillPath)) {
console.warn(`Bundled debugmcp skill not found at ${bundledSkillPath}; skipping skill install`);
logger.warn(`Bundled debugmcp skill not found at ${bundledSkillPath}; skipping skill install`);
return null;
}

let primaryDestination: string | null = null;
for (const destination of getDebugSkillInstallTargets()) {
try {
await installDebugSkill(bundledSkillPath, destination);
console.log(`Installed debugmcp skill at ${destination}`);
logger.info(`Installed debugmcp skill at ${destination}`);
if (!primaryDestination) {
primaryDestination = destination;
}
} catch (error) {
console.warn(`Failed to install debugmcp skill at ${destination}:`, error);
logger.warn(`Failed to install debugmcp skill at ${destination}:`, error);
}
}

Expand Down Expand Up @@ -256,6 +257,8 @@ export class AgentConfigurationManager {
* This should be called on extension activation to ensure backward compatibility
*/
public async migrateExistingConfigurations(): Promise<void> {
// Remote hosts may have no desktop agent config, but still need the skill.
await this.ensureSkillRegistered();
const agents = await this.getSupportedAgents();
let migrationCount = 0;

Expand All @@ -279,12 +282,6 @@ export class AgentConfigurationManager {
console.log(`Successfully migrated ${agent.displayName} configuration`);
}

// Back-compat: existing Codex users had only the MCP server registered.
// If DebugMCP is configured, also ensure the bundled skill is installed.
if (this.hasCodexDebugMCPSection(configContent)) {
await this.ensureSkillRegistered();
}

continue;
}

Expand Down Expand Up @@ -332,9 +329,6 @@ export class AgentConfigurationManager {
console.log(`Successfully migrated ${agent.displayName} configuration`);
}

// Back-compat: existing users had only the MCP server registered.
// If DebugMCP is configured, also ensure the bundled skill is installed.
await this.ensureSkillRegistered();
} catch (error) {
console.error(`Error migrating config for ${agent.name}:`, error);
// Continue with other agents even if one fails
Expand All @@ -358,15 +352,10 @@ export class AgentConfigurationManager {
try {
await this.installDebugMCPSkill();
} catch (error) {
console.warn('Failed to ensure skill installation:', error);
logger.warn('Failed to ensure skill installation:', error);
}
}

private hasCodexDebugMCPSection(configContent: string): boolean {
const lines = configContent.replace(/\r\n/g, '\n').split('\n');
return lines.some(line => isCodexDebugMCPSectionHeader(line));
}

private shouldMigrateCodexConfig(configContent: string): boolean {
const normalizedConfigContent = configContent.replace(/\r\n/g, '\n');
const lines = normalizedConfigContent.split('\n');
Expand Down
Loading