@hanhnd/agent-kit 1.0.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/README.md +133 -0
- package/dist/kit-server.d.ts +8 -0
- package/dist/kit-server.js +21573 -0
- package/dist/tools/agent.d.ts +9 -0
- package/dist/tools/agent.js +188 -0
- package/dist/tools/config.d.ts +25 -0
- package/dist/tools/config.js +60 -0
- package/dist/tools/core.d.ts +10 -0
- package/dist/tools/core.js +76 -0
- package/dist/tools/gemini.d.ts +9 -0
- package/dist/tools/gemini.js +111 -0
- package/dist/tools/integration.d.ts +6 -0
- package/dist/tools/integration.js +310 -0
- package/dist/tools/orchestration.d.ts +9 -0
- package/dist/tools/orchestration.js +168 -0
- package/dist/tools/security.d.ts +41 -0
- package/dist/tools/security.js +131 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +26 -0
- package/dist/workflows.d.ts +2 -0
- package/dist/workflows.js +268 -0
- package/package.json +32 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Delegation Tools - Invoke external agent CLIs (Gemini, Claude)
|
|
3
|
+
* Supports handoff file injection and automatic fallback
|
|
4
|
+
*/
|
|
5
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
/**
|
|
7
|
+
* Register agent delegation tools with MCP server
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerAgentTools(server: McpServer): void;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Delegation Tools - Invoke external agent CLIs (Gemini, Claude)
|
|
3
|
+
* Supports handoff file injection and automatic fallback
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { getWorkspaceRoot } from '../utils.js';
|
|
11
|
+
import { commandExists, validatePath } from './security.js';
|
|
12
|
+
// Configurable timeout via environment variable (default: 5 minutes)
|
|
13
|
+
const AGENT_TIMEOUT = parseInt(process.env.KIT_AGENT_TIMEOUT || '300000', 10);
|
|
14
|
+
const jobRegistry = new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Generate a URL-safe slug from a task string (first ~6 words, max 48 chars)
|
|
17
|
+
*/
|
|
18
|
+
function taskSlug(task) {
|
|
19
|
+
return task
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
22
|
+
.trim()
|
|
23
|
+
.split(/\s+/)
|
|
24
|
+
.slice(0, 6)
|
|
25
|
+
.join('-')
|
|
26
|
+
.slice(0, 48);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initialize a live-streaming log file for an agent job.
|
|
30
|
+
* Writes the header synchronously so the file exists immediately,
|
|
31
|
+
* then opens an append-mode WriteStream for real-time chunk writing.
|
|
32
|
+
*/
|
|
33
|
+
function initAgentLog(workspaceRoot, slug, agent) {
|
|
34
|
+
const logsDir = path.join(workspaceRoot, '.agent-kit', 'logs', 'agents');
|
|
35
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
36
|
+
const logFile = path.join(logsDir, `delegate-${slug}.log`);
|
|
37
|
+
const header = `Agent: ${agent}\nDate: ${new Date().toISOString()}\n${'─'.repeat(60)}\n\n`;
|
|
38
|
+
fs.writeFileSync(logFile, header, 'utf-8');
|
|
39
|
+
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
40
|
+
logStream.on('error', () => { });
|
|
41
|
+
return { logPath: path.relative(workspaceRoot, logFile), logStream };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Register agent delegation tools with MCP server
|
|
45
|
+
*/
|
|
46
|
+
export function registerAgentTools(server) {
|
|
47
|
+
// ═══════════════════════════════════════════════════════════════
|
|
48
|
+
// TOOL: TRIGGER AGENT
|
|
49
|
+
// Delegates a task to an external agent CLI (gemini or claude).
|
|
50
|
+
// Auto-detects if task is a file path and reads it as context.
|
|
51
|
+
// Falls back to the alternate agent if requested one is missing.
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════
|
|
53
|
+
server.tool('kit_trigger_agent', 'Delegate a task to an external agent CLI (gemini or claude). The task can be a direct message or a path to a handoff file (.agent-kit/handoffs/plans/plan-xyz.md). Falls back to the other agent CLI if the requested one is not installed.', {
|
|
54
|
+
agent: z.enum(['gemini', 'claude']).describe('The agent CLI to invoke: "gemini" or "claude"'),
|
|
55
|
+
task: z
|
|
56
|
+
.string()
|
|
57
|
+
.describe('Task message or path to a handoff file (e.g., ".agent-kit/handoffs/plans/plan-xyz.md")'),
|
|
58
|
+
}, async ({ agent, task }) => {
|
|
59
|
+
try {
|
|
60
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
61
|
+
// Detect if task is a file path — resolve and read if it exists
|
|
62
|
+
let prompt = task;
|
|
63
|
+
const resolvedTask = path.resolve(workspaceRoot, task);
|
|
64
|
+
if (fs.existsSync(resolvedTask) && fs.statSync(resolvedTask).isFile()) {
|
|
65
|
+
try {
|
|
66
|
+
validatePath(task, workspaceRoot);
|
|
67
|
+
prompt = fs.readFileSync(resolvedTask, 'utf-8');
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: 'text', text: `Error reading task file: ${err}` }],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Determine which agent to use — fallback if requested not available
|
|
76
|
+
let usedAgent = agent;
|
|
77
|
+
let fallbackReason;
|
|
78
|
+
if (!commandExists(agent)) {
|
|
79
|
+
const fallback = agent === 'gemini' ? 'claude' : 'gemini';
|
|
80
|
+
if (commandExists(fallback)) {
|
|
81
|
+
usedAgent = fallback;
|
|
82
|
+
fallbackReason = `${agent} CLI not installed, fell back to ${fallback}`;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const installHint = agent === 'gemini'
|
|
86
|
+
? 'Install Gemini CLI: https://github.com/google-gemini/gemini-cli'
|
|
87
|
+
: 'Install Claude CLI: npm install -g @anthropic-ai/claude-code';
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: 'text',
|
|
92
|
+
text: JSON.stringify({
|
|
93
|
+
agent,
|
|
94
|
+
status: 'error',
|
|
95
|
+
output: '',
|
|
96
|
+
error: `Neither ${agent} nor the fallback agent CLI is installed. ${installHint}`,
|
|
97
|
+
}, null, 2),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Execute the agent CLI with the prompt
|
|
104
|
+
// Gemini: -y (--yolo) for auto-accept, -p for headless prompt
|
|
105
|
+
// Claude: --dangerously-skip-permissions for auto-accept, -p/--print for headless
|
|
106
|
+
const agentArgs = usedAgent === 'gemini'
|
|
107
|
+
? ['-y', '-p', prompt]
|
|
108
|
+
: ['--dangerously-skip-permissions', '-p', prompt];
|
|
109
|
+
const slug = taskSlug(task);
|
|
110
|
+
const { logPath, logStream } = initAgentLog(workspaceRoot, slug, usedAgent);
|
|
111
|
+
const jobId = randomUUID();
|
|
112
|
+
const child = spawn(usedAgent, agentArgs, {
|
|
113
|
+
cwd: workspaceRoot,
|
|
114
|
+
env: { ...process.env, GEMINI_WORKSPACE: workspaceRoot },
|
|
115
|
+
});
|
|
116
|
+
const job = {
|
|
117
|
+
id: jobId,
|
|
118
|
+
agent: usedAgent,
|
|
119
|
+
process: child,
|
|
120
|
+
startedAt: new Date(),
|
|
121
|
+
chunks: [],
|
|
122
|
+
logStream,
|
|
123
|
+
timeoutHandle: setTimeout(() => {
|
|
124
|
+
child.kill('SIGTERM');
|
|
125
|
+
}, AGENT_TIMEOUT),
|
|
126
|
+
};
|
|
127
|
+
jobRegistry.set(jobId, job);
|
|
128
|
+
try {
|
|
129
|
+
await new Promise((resolve, reject) => {
|
|
130
|
+
child.stdout?.on('data', (data) => {
|
|
131
|
+
job.chunks.push(data.toString());
|
|
132
|
+
job.logStream.write(data);
|
|
133
|
+
});
|
|
134
|
+
child.stderr?.on('data', (data) => {
|
|
135
|
+
job.chunks.push(data.toString());
|
|
136
|
+
job.logStream.write(data);
|
|
137
|
+
});
|
|
138
|
+
child.on('close', (code) => {
|
|
139
|
+
if (code === 0)
|
|
140
|
+
resolve();
|
|
141
|
+
else
|
|
142
|
+
reject(new Error(`Process exited with code ${code}`));
|
|
143
|
+
});
|
|
144
|
+
child.on('error', reject);
|
|
145
|
+
});
|
|
146
|
+
const output = job.chunks.join('');
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: 'text',
|
|
151
|
+
text: JSON.stringify({
|
|
152
|
+
agent: usedAgent,
|
|
153
|
+
status: fallbackReason ? 'fallback' : 'success',
|
|
154
|
+
log: logPath,
|
|
155
|
+
output,
|
|
156
|
+
...(fallbackReason && { fallback_reason: fallbackReason }),
|
|
157
|
+
}, null, 2),
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
job.logStream.end();
|
|
167
|
+
clearTimeout(job.timeoutHandle);
|
|
168
|
+
jobRegistry.delete(jobId);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: 'text',
|
|
177
|
+
text: JSON.stringify({
|
|
178
|
+
agent,
|
|
179
|
+
status: 'error',
|
|
180
|
+
output: '',
|
|
181
|
+
error: msg,
|
|
182
|
+
}, null, 2),
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration utilities
|
|
3
|
+
* Shared configuration constants and loaders
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Default file extensions - expanded to support more languages
|
|
7
|
+
* Used by kit_get_project_context and other tools
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_EXTENSIONS: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Project settings interface
|
|
12
|
+
*/
|
|
13
|
+
export interface ProjectSettings {
|
|
14
|
+
fileExtensions?: string[];
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get file extensions - from settings.json or defaults
|
|
19
|
+
* Allows project-specific customization
|
|
20
|
+
*/
|
|
21
|
+
export declare function getFileExtensions(projectDir: string): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Load project settings from .gemini/settings.json
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadProjectSettings(projectDir: string): ProjectSettings;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration utilities
|
|
3
|
+
* Shared configuration constants and loaders
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
/**
|
|
8
|
+
* Default file extensions - expanded to support more languages
|
|
9
|
+
* Used by kit_get_project_context and other tools
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_EXTENSIONS = [
|
|
12
|
+
'.ts',
|
|
13
|
+
'.js',
|
|
14
|
+
'.tsx',
|
|
15
|
+
'.jsx', // JavaScript/TypeScript
|
|
16
|
+
'.py', // Python
|
|
17
|
+
'.go', // Go
|
|
18
|
+
'.rs', // Rust
|
|
19
|
+
'.java',
|
|
20
|
+
'.kt', // Java/Kotlin
|
|
21
|
+
'.cpp',
|
|
22
|
+
'.c',
|
|
23
|
+
'.h',
|
|
24
|
+
'.hpp', // C/C++
|
|
25
|
+
'.php', // PHP
|
|
26
|
+
'.rb', // Ruby
|
|
27
|
+
'.swift', // Swift
|
|
28
|
+
'.vue',
|
|
29
|
+
'.svelte', // Frontend frameworks
|
|
30
|
+
'.json',
|
|
31
|
+
'.yaml',
|
|
32
|
+
'.yml', // Config files
|
|
33
|
+
'.md', // Documentation
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Get file extensions - from settings.json or defaults
|
|
37
|
+
* Allows project-specific customization
|
|
38
|
+
*/
|
|
39
|
+
export function getFileExtensions(projectDir) {
|
|
40
|
+
const settings = loadProjectSettings(projectDir);
|
|
41
|
+
if (settings.fileExtensions && Array.isArray(settings.fileExtensions)) {
|
|
42
|
+
return settings.fileExtensions;
|
|
43
|
+
}
|
|
44
|
+
return DEFAULT_EXTENSIONS;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load project settings from .gemini/settings.json
|
|
48
|
+
*/
|
|
49
|
+
export function loadProjectSettings(projectDir) {
|
|
50
|
+
const settingsPath = path.join(projectDir, '.agent-kit', 'settings.json');
|
|
51
|
+
if (fs.existsSync(settingsPath)) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error(`[agent-kit] Warning: Failed to parse ${settingsPath}:`, error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Tools - Extension info and handoff persistence
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import { DEFAULT_EXTENSIONS } from './config.js';
|
|
6
|
+
/**
|
|
7
|
+
* Register core tools with MCP server
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerCoreTools(server: McpServer): void;
|
|
10
|
+
export { DEFAULT_EXTENSIONS };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Tools - Extension info and handoff persistence
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { getExtensionRoot, getWorkspaceRoot } from '../utils.js';
|
|
8
|
+
import { DEFAULT_EXTENSIONS } from './config.js';
|
|
9
|
+
/**
|
|
10
|
+
* Register core tools with MCP server
|
|
11
|
+
*/
|
|
12
|
+
export function registerCoreTools(server) {
|
|
13
|
+
// ═══════════════════════════════════════════════════════════════
|
|
14
|
+
// TOOL: GET EXTENSION INFO
|
|
15
|
+
// Returns absolute paths so agents can use native Read tool
|
|
16
|
+
// for loading agent personas and skill files
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════
|
|
18
|
+
server.tool('kit_get_extension_info', 'Get absolute paths to the agent-kit extension directories (agents, skills, commands, scripts). Use the returned paths with the native Read tool to load agent personas and skill modules.', {}, async () => {
|
|
19
|
+
try {
|
|
20
|
+
const root = getExtensionRoot();
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: JSON.stringify({
|
|
26
|
+
extensionRoot: root,
|
|
27
|
+
agentsDir: path.join(root, 'agents'),
|
|
28
|
+
skillsDir: path.join(root, 'skills'),
|
|
29
|
+
commandsDir: path.join(root, 'commands'),
|
|
30
|
+
scriptsDir: path.join(root, 'scripts'),
|
|
31
|
+
}, null, 2),
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: 'text', text: `Error getting extension info: ${error}` }],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════
|
|
43
|
+
// TOOL: SAVE HANDOFF
|
|
44
|
+
// Writes brainstorm/plan/ticket artifacts to the workspace
|
|
45
|
+
// Returns the saved file path for use in next-step instructions
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════
|
|
47
|
+
server.tool('kit_save_handoff', 'Save a brainstorm, plan, or ticket handoff artifact to .agent-kit/handoffs/. Returns the saved file path to use in next-step instructions.', {
|
|
48
|
+
type: z.enum(['brainstorm', 'plan', 'ticket']).describe('Handoff type'),
|
|
49
|
+
content: z.string().describe('Full markdown content to save'),
|
|
50
|
+
slug: z
|
|
51
|
+
.string()
|
|
52
|
+
.describe('Short identifier for the filename, e.g. "user-auth" or "PROJ-123"'),
|
|
53
|
+
}, async ({ type, content, slug }) => {
|
|
54
|
+
try {
|
|
55
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
56
|
+
const safeSlug = slug.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
|
|
57
|
+
const filename = `${type}-${timestamp}-${safeSlug}.md`;
|
|
58
|
+
const handoffDir = path.join(getWorkspaceRoot(), '.agent-kit', 'handoffs', `${type}s`);
|
|
59
|
+
if (!fs.existsSync(handoffDir)) {
|
|
60
|
+
fs.mkdirSync(handoffDir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
const filePath = path.join(handoffDir, filename);
|
|
63
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: 'text', text: `✅ Saved to: ${filePath}` }],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: 'text', text: `Error saving handoff: ${error}` }],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// Export DEFAULT_EXTENSIONS for backward compatibility
|
|
76
|
+
export { DEFAULT_EXTENSIONS };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Delegation Tools - Invoke external agent CLIs (Gemini, Claude)
|
|
3
|
+
* Supports handoff file injection and automatic fallback
|
|
4
|
+
*/
|
|
5
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
/**
|
|
7
|
+
* Register agent delegation tools with MCP server
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerGeminiTools(server: McpServer): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Delegation Tools - Invoke external agent CLIs (Gemini, Claude)
|
|
3
|
+
* Supports handoff file injection and automatic fallback
|
|
4
|
+
*/
|
|
5
|
+
import { execFileSync } from 'child_process';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { getWorkspaceRoot } from '../utils.js';
|
|
10
|
+
import { commandExists, validatePath } from './security.js';
|
|
11
|
+
// Configurable timeout via environment variable (default: 5 minutes)
|
|
12
|
+
const AGENT_TIMEOUT = parseInt(process.env.KIT_AGENT_TIMEOUT || '300000', 10);
|
|
13
|
+
/**
|
|
14
|
+
* Register agent delegation tools with MCP server
|
|
15
|
+
*/
|
|
16
|
+
export function registerGeminiTools(server) {
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════
|
|
18
|
+
// TOOL: TRIGGER AGENT
|
|
19
|
+
// Delegates a task to an external agent CLI (gemini or claude).
|
|
20
|
+
// Auto-detects if task is a file path and reads it as context.
|
|
21
|
+
// Falls back to the alternate agent if requested one is missing.
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════
|
|
23
|
+
server.tool('kit_trigger_agent', 'Delegate a task to an external agent CLI (gemini or claude). The task can be a direct message or a path to a handoff file (.agent-kit/handoffs/plans/plan-xyz.md). Falls back to the other agent CLI if the requested one is not installed.', {
|
|
24
|
+
agent: z.enum(['gemini', 'claude']).describe('The agent CLI to invoke: "gemini" or "claude"'),
|
|
25
|
+
task: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe('Task message or path to a handoff file (e.g., ".agent-kit/handoffs/plans/plan-xyz.md")'),
|
|
28
|
+
}, async ({ agent, task }) => {
|
|
29
|
+
try {
|
|
30
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
31
|
+
// Detect if task is a file path — resolve and read if it exists
|
|
32
|
+
let prompt = task;
|
|
33
|
+
const resolvedTask = path.resolve(workspaceRoot, task);
|
|
34
|
+
if (fs.existsSync(resolvedTask) && fs.statSync(resolvedTask).isFile()) {
|
|
35
|
+
try {
|
|
36
|
+
validatePath(task, workspaceRoot);
|
|
37
|
+
prompt = fs.readFileSync(resolvedTask, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: 'text', text: `Error reading task file: ${err}` }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Determine which agent to use — fallback if requested not available
|
|
46
|
+
let usedAgent = agent;
|
|
47
|
+
let fallbackReason;
|
|
48
|
+
if (!commandExists(agent)) {
|
|
49
|
+
const fallback = agent === 'gemini' ? 'claude' : 'gemini';
|
|
50
|
+
if (commandExists(fallback)) {
|
|
51
|
+
usedAgent = fallback;
|
|
52
|
+
fallbackReason = `${agent} CLI not installed, fell back to ${fallback}`;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const installHint = agent === 'gemini'
|
|
56
|
+
? 'Install Gemini CLI: https://github.com/google-gemini/gemini-cli'
|
|
57
|
+
: 'Install Claude CLI: npm install -g @anthropic-ai/claude-code';
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: 'text',
|
|
62
|
+
text: JSON.stringify({
|
|
63
|
+
agent,
|
|
64
|
+
status: 'error',
|
|
65
|
+
output: '',
|
|
66
|
+
error: `Neither ${agent} nor the fallback agent CLI is installed. ${installHint}`,
|
|
67
|
+
}, null, 2),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Execute the agent CLI with the prompt
|
|
74
|
+
const output = execFileSync(usedAgent, ['-p', prompt], {
|
|
75
|
+
encoding: 'utf8',
|
|
76
|
+
timeout: AGENT_TIMEOUT,
|
|
77
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
78
|
+
cwd: workspaceRoot,
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: JSON.stringify({
|
|
85
|
+
agent: usedAgent,
|
|
86
|
+
status: fallbackReason ? 'fallback' : 'success',
|
|
87
|
+
output,
|
|
88
|
+
...(fallbackReason && { fallback_reason: fallbackReason }),
|
|
89
|
+
}, null, 2),
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: JSON.stringify({
|
|
101
|
+
agent,
|
|
102
|
+
status: 'error',
|
|
103
|
+
output: '',
|
|
104
|
+
error: msg,
|
|
105
|
+
}, null, 2),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|