@archon-claw/cli 0.0.4 → 0.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.
- package/dist/cli.js +136 -25
- package/dist/config.js +3 -2
- package/dist/dev.d.ts +20 -0
- package/dist/dev.js +170 -0
- package/dist/mcp-server.d.ts +5 -0
- package/dist/mcp-server.js +106 -0
- package/dist/public/assets/{chat-input-Cx9bd-IU.js → chat-input-Cpyxmt8J.js} +54 -54
- package/dist/public/assets/embed-CIS9O-0V.js +1 -0
- package/dist/public/assets/main-CfVKtiE-.js +16 -0
- package/dist/public/embed.html +2 -2
- package/dist/public/index.html +2 -2
- package/dist/scaffold.d.ts +7 -2
- package/dist/scaffold.js +61 -25
- package/dist/server.d.ts +8 -1
- package/dist/server.js +162 -118
- package/dist/session.d.ts +13 -7
- package/dist/session.js +74 -70
- package/dist/templates/workspace/README.md +13 -0
- package/dist/templates/workspace/package.json +17 -0
- package/dist/validator/index.d.ts +3 -1
- package/dist/validator/index.js +10 -2
- package/dist/validator/plugin.d.ts +13 -1
- package/dist/validator/plugins/agent-dir.js +125 -141
- package/dist/validator/plugins/agent-skill.js +8 -4
- package/dist/validator/plugins/dataset.js +2 -2
- package/dist/validator/plugins/mcp.js +2 -2
- package/dist/validator/plugins/model.js +3 -2
- package/dist/validator/plugins/system-prompt.js +3 -2
- package/dist/validator/plugins/tool.js +2 -2
- package/dist/validator/zod-utils.d.ts +9 -2
- package/dist/validator/zod-utils.js +92 -3
- package/package.json +13 -6
- package/dist/public/assets/embed-Cn1IPR8U.js +0 -1
- package/dist/public/assets/main-CAioKyQy.js +0 -16
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { readFileSync } from "fs";
|
|
4
|
+
import { readFileSync, readdirSync, statSync } from "fs";
|
|
5
5
|
import dotenv from "dotenv";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
import { loadAgentConfig } from "./config.js";
|
|
9
9
|
import { createServer } from "./server.js";
|
|
10
|
-
import {
|
|
10
|
+
import { SessionStore } from "./session.js";
|
|
11
|
+
import { startDev } from "./dev.js";
|
|
11
12
|
import { runToolTests, formatResults } from "./test-runner.js";
|
|
12
13
|
import { runEvals } from "./eval/runner.js";
|
|
13
|
-
import { scaffoldAgent, scaffoldWorkspace } from "./scaffold.js";
|
|
14
|
+
import { scaffoldAgent, scaffoldWorkspace, updateSkills } from "./scaffold.js";
|
|
14
15
|
const pkg = JSON.parse(readFileSync(path.resolve(__dirname, "../package.json"), "utf-8"));
|
|
15
16
|
const program = new Command();
|
|
16
17
|
program
|
|
@@ -30,31 +31,76 @@ program
|
|
|
30
31
|
.action(() => {
|
|
31
32
|
console.log("Starting agent...");
|
|
32
33
|
});
|
|
34
|
+
/** Scan a directory for agent subdirectories and load them all */
|
|
35
|
+
async function loadAgentsFromDir(agentsDir) {
|
|
36
|
+
const absDir = path.resolve(agentsDir);
|
|
37
|
+
const entries = readdirSync(absDir);
|
|
38
|
+
const agents = new Map();
|
|
39
|
+
for (const name of entries) {
|
|
40
|
+
const fullPath = path.join(absDir, name);
|
|
41
|
+
if (!statSync(fullPath).isDirectory())
|
|
42
|
+
continue;
|
|
43
|
+
// Skip hidden dirs and common non-agent dirs
|
|
44
|
+
if (name.startsWith(".") || name === "node_modules")
|
|
45
|
+
continue;
|
|
46
|
+
try {
|
|
47
|
+
const config = await loadAgentConfig(fullPath);
|
|
48
|
+
const sessions = new SessionStore(fullPath);
|
|
49
|
+
await sessions.init();
|
|
50
|
+
agents.set(name, { config, sessions });
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
console.warn(`[warn] Skipping "${name}": ${err instanceof Error ? err.message : err}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (agents.size === 0) {
|
|
57
|
+
throw new Error(`No valid agents found in ${absDir}`);
|
|
58
|
+
}
|
|
59
|
+
return agents;
|
|
60
|
+
}
|
|
61
|
+
program
|
|
62
|
+
.command("dev")
|
|
63
|
+
.description("Start dev server with hot-reload for agent development")
|
|
64
|
+
.requiredOption("--agents-dir <path>", "Path to directory containing agent subdirectories")
|
|
65
|
+
.option("-p, --port <port>", "Server port", "5100")
|
|
66
|
+
.option("--no-open", "Don't auto-open browser")
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
try {
|
|
69
|
+
await startDev(opts.agentsDir, {
|
|
70
|
+
port: parseInt(opts.port, 10),
|
|
71
|
+
open: opts.open,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error(err instanceof Error ? err.message : err);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
33
79
|
program
|
|
34
80
|
.command("start")
|
|
35
81
|
.description("Start an agent HTTP server")
|
|
36
|
-
.
|
|
82
|
+
.requiredOption("--agents-dir <path>", "Path to directory containing agent subdirectories")
|
|
37
83
|
.option("-p, --port <port>", "Server port", "5100")
|
|
38
|
-
.action(async (
|
|
84
|
+
.action(async (opts) => {
|
|
39
85
|
try {
|
|
40
|
-
const
|
|
41
|
-
await initSessionStore(agentDir);
|
|
86
|
+
const agents = await loadAgentsFromDir(opts.agentsDir);
|
|
42
87
|
const port = parseInt(opts.port, 10);
|
|
43
|
-
console.log(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
88
|
+
console.log("Agents loaded:");
|
|
89
|
+
for (const [id, entry] of agents) {
|
|
90
|
+
const { config } = entry;
|
|
91
|
+
const mcpCount = config.mcpManager ? config.mcpManager.getServerNames().length : 0;
|
|
92
|
+
console.log(` [${id}] Model: ${config.model.provider}/${config.model.model}` +
|
|
93
|
+
` Tools: ${config.tools.length}` +
|
|
94
|
+
(mcpCount > 0 ? ` MCP: ${mcpCount} servers` : "") +
|
|
95
|
+
` Skills: ${Object.keys(config.skills).length}`);
|
|
49
96
|
}
|
|
50
|
-
|
|
51
|
-
console.log(` System prompt: ${config.systemPrompt.length} chars`);
|
|
52
|
-
const server = createServer(config, port);
|
|
97
|
+
const server = createServer(agents, port);
|
|
53
98
|
const shutdown = async () => {
|
|
54
99
|
console.log("\nShutting down...");
|
|
55
|
-
|
|
100
|
+
for (const [, entry] of agents) {
|
|
101
|
+
await entry.config.mcpManager?.shutdown();
|
|
102
|
+
}
|
|
56
103
|
server.close(() => process.exit(0));
|
|
57
|
-
// Force exit if server hasn't closed within 3s
|
|
58
104
|
setTimeout(() => process.exit(1), 3000).unref();
|
|
59
105
|
};
|
|
60
106
|
process.on("SIGINT", shutdown);
|
|
@@ -113,11 +159,40 @@ program
|
|
|
113
159
|
});
|
|
114
160
|
program
|
|
115
161
|
.command("init")
|
|
116
|
-
.description("Initialise an agent workspace
|
|
117
|
-
.argument("[dir]", "Directory to initialise"
|
|
118
|
-
.
|
|
162
|
+
.description("Initialise an agent workspace project")
|
|
163
|
+
.argument("[dir]", "Directory to initialise")
|
|
164
|
+
.option("--install", "Automatically install npm dependencies")
|
|
165
|
+
.action(async (dir, opts) => {
|
|
119
166
|
try {
|
|
120
|
-
await
|
|
167
|
+
const { intro, text, confirm, isCancel, outro } = await import("@clack/prompts");
|
|
168
|
+
intro("archon-claw init");
|
|
169
|
+
if (!dir) {
|
|
170
|
+
const name = await text({
|
|
171
|
+
message: "Project name",
|
|
172
|
+
placeholder: "my-ai-agent",
|
|
173
|
+
validate: (v) => {
|
|
174
|
+
if (!v?.trim())
|
|
175
|
+
return "Project name is required";
|
|
176
|
+
if (!/^[a-z0-9._-]+$/i.test(v))
|
|
177
|
+
return "Invalid directory name";
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
if (isCancel(name)) {
|
|
181
|
+
outro("Cancelled");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
dir = name;
|
|
185
|
+
}
|
|
186
|
+
if (opts.install === undefined) {
|
|
187
|
+
const install = await confirm({ message: "Install dependencies?" });
|
|
188
|
+
if (isCancel(install)) {
|
|
189
|
+
outro("Cancelled");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
opts.install = install;
|
|
193
|
+
}
|
|
194
|
+
await scaffoldWorkspace(dir, { install: opts.install });
|
|
195
|
+
outro("Done!");
|
|
121
196
|
}
|
|
122
197
|
catch (err) {
|
|
123
198
|
console.error(err instanceof Error ? err.message : err);
|
|
@@ -125,13 +200,49 @@ program
|
|
|
125
200
|
}
|
|
126
201
|
});
|
|
127
202
|
program
|
|
128
|
-
.command("create")
|
|
203
|
+
.command("create-agent")
|
|
129
204
|
.description("Create a new agent project")
|
|
130
|
-
.argument("
|
|
205
|
+
.argument("[agent-name]", "Name of the agent (used as directory name)")
|
|
131
206
|
.option("-d, --dir <path>", "Parent directory for the agent", "./agents")
|
|
132
207
|
.action(async (agentName, opts) => {
|
|
133
208
|
try {
|
|
134
|
-
|
|
209
|
+
if (!agentName) {
|
|
210
|
+
const { intro, text, isCancel, outro } = await import("@clack/prompts");
|
|
211
|
+
intro("archon-claw create");
|
|
212
|
+
const name = await text({
|
|
213
|
+
message: "Agent name",
|
|
214
|
+
placeholder: "my-agent",
|
|
215
|
+
validate: (v) => {
|
|
216
|
+
if (!v?.trim())
|
|
217
|
+
return "Agent name is required";
|
|
218
|
+
if (!/^[a-z0-9._-]+$/i.test(v))
|
|
219
|
+
return "Invalid directory name";
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
if (isCancel(name)) {
|
|
223
|
+
outro("Cancelled");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
agentName = name;
|
|
227
|
+
await scaffoldAgent(agentName, opts.dir);
|
|
228
|
+
outro("Done!");
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
await scaffoldAgent(agentName, opts.dir);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
console.error(err instanceof Error ? err.message : err);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
program
|
|
240
|
+
.command("update-skills")
|
|
241
|
+
.description("Update skills to the latest version bundled with @archon-claw/cli")
|
|
242
|
+
.argument("[dir]", "Workspace directory", ".")
|
|
243
|
+
.action(async (dir) => {
|
|
244
|
+
try {
|
|
245
|
+
await updateSkills(dir);
|
|
135
246
|
}
|
|
136
247
|
catch (err) {
|
|
137
248
|
console.error(err instanceof Error ? err.message : err);
|
package/dist/config.js
CHANGED
|
@@ -11,7 +11,7 @@ export async function loadAgentConfig(agentDir) {
|
|
|
11
11
|
// Validate directory structure
|
|
12
12
|
const validation = await validateDir("agent-dir", absDir);
|
|
13
13
|
if (!validation.valid) {
|
|
14
|
-
throw new Error(`Invalid agent directory:\n${validation.errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
14
|
+
throw new Error(`Invalid agent directory:\n${validation.errors.map((e) => ` - ${e.file ? `[${e.file}] ` : ""}${e.message}`).join("\n")}`);
|
|
15
15
|
}
|
|
16
16
|
// Load model.json
|
|
17
17
|
const modelRaw = await fs.readFile(path.join(absDir, "model.json"), "utf-8");
|
|
@@ -74,7 +74,8 @@ export async function loadAgentConfig(agentDir) {
|
|
|
74
74
|
const implEntries = await Promise.all(implFiles.map(async (file) => {
|
|
75
75
|
const name = file.replace(/\.impl\.js$/, "");
|
|
76
76
|
const implPath = pathToFileURL(path.join(implsDir, file)).href;
|
|
77
|
-
|
|
77
|
+
// Cache-bust ESM module cache so dev reload picks up changes
|
|
78
|
+
const mod = await import(`${implPath}?t=${Date.now()}`);
|
|
78
79
|
return [name, mod.default];
|
|
79
80
|
}));
|
|
80
81
|
for (const [name, impl] of implEntries) {
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { type FSWatcher } from "chokidar";
|
|
3
|
+
import { type AgentEntry } from "./server.js";
|
|
4
|
+
export interface DevOptions {
|
|
5
|
+
port: number;
|
|
6
|
+
open: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Returned by startDev for cleanup and testing */
|
|
9
|
+
export interface DevHandle {
|
|
10
|
+
server: http.Server;
|
|
11
|
+
watchers: FSWatcher[];
|
|
12
|
+
/** Current agents map (mutable via reload) */
|
|
13
|
+
getAgents(): Map<string, AgentEntry>;
|
|
14
|
+
/** Manually trigger config reload for a specific agent */
|
|
15
|
+
reloadAgent(agentId: string, changedFiles?: string[]): Promise<void>;
|
|
16
|
+
/** Graceful shutdown */
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function startDev(agentsDir: string, opts: DevOptions): Promise<DevHandle>;
|
|
20
|
+
export declare function printBanner(agents: Map<string, AgentEntry>, port: number, absDir: string): void;
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { watch } from "chokidar";
|
|
4
|
+
import open from "open";
|
|
5
|
+
import { loadAgentConfig } from "./config.js";
|
|
6
|
+
import { createServer } from "./server.js";
|
|
7
|
+
import { SessionStore } from "./session.js";
|
|
8
|
+
/** Scan agents-dir for valid agent subdirectories */
|
|
9
|
+
function scanAgentDirs(agentsDir) {
|
|
10
|
+
const results = [];
|
|
11
|
+
for (const name of readdirSync(agentsDir)) {
|
|
12
|
+
const fullPath = path.join(agentsDir, name);
|
|
13
|
+
if (!statSync(fullPath).isDirectory())
|
|
14
|
+
continue;
|
|
15
|
+
if (name.startsWith(".") || name === "node_modules")
|
|
16
|
+
continue;
|
|
17
|
+
results.push({ id: name, path: fullPath });
|
|
18
|
+
}
|
|
19
|
+
return results;
|
|
20
|
+
}
|
|
21
|
+
export async function startDev(agentsDir, opts) {
|
|
22
|
+
const absDir = path.resolve(agentsDir);
|
|
23
|
+
const agentDirs = scanAgentDirs(absDir);
|
|
24
|
+
if (agentDirs.length === 0) {
|
|
25
|
+
throw new Error(`No agent directories found in ${absDir}`);
|
|
26
|
+
}
|
|
27
|
+
// Initial load of all agents
|
|
28
|
+
const currentAgents = new Map();
|
|
29
|
+
for (const { id, path: agentPath } of agentDirs) {
|
|
30
|
+
try {
|
|
31
|
+
const config = await loadAgentConfig(agentPath);
|
|
32
|
+
const sessions = new SessionStore(agentPath);
|
|
33
|
+
await sessions.init();
|
|
34
|
+
currentAgents.set(id, { config, sessions });
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.warn(`[dev] Skipping "${id}": ${err instanceof Error ? err.message : err}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (currentAgents.size === 0) {
|
|
41
|
+
throw new Error(`No valid agents found in ${absDir}`);
|
|
42
|
+
}
|
|
43
|
+
// Start server with getter — every request reads latest agents
|
|
44
|
+
const server = createServer(() => currentAgents, opts.port);
|
|
45
|
+
printBanner(currentAgents, opts.port, absDir);
|
|
46
|
+
// Open browser (skip in SSH)
|
|
47
|
+
if (opts.open && !process.env.SSH_CLIENT && !process.env.SSH_TTY) {
|
|
48
|
+
open(`http://localhost:${opts.port}`).catch(() => { });
|
|
49
|
+
}
|
|
50
|
+
// Per-agent reload logic
|
|
51
|
+
const reloadInFlight = new Set();
|
|
52
|
+
async function reloadAgent(agentId, changedFiles) {
|
|
53
|
+
if (reloadInFlight.has(agentId))
|
|
54
|
+
return;
|
|
55
|
+
reloadInFlight.add(agentId);
|
|
56
|
+
const agentEntry = agentDirs.find((d) => d.id === agentId);
|
|
57
|
+
if (!agentEntry) {
|
|
58
|
+
reloadInFlight.delete(agentId);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const ts = new Date().toLocaleTimeString();
|
|
62
|
+
if (changedFiles?.length) {
|
|
63
|
+
const relFiles = changedFiles.map((f) => path.relative(agentEntry.path, f));
|
|
64
|
+
console.log(`\n[dev] ${ts} [${agentId}] Changed: ${relFiles.join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const existing = currentAgents.get(agentId);
|
|
68
|
+
await existing?.config.mcpManager?.shutdown();
|
|
69
|
+
const newConfig = await loadAgentConfig(agentEntry.path);
|
|
70
|
+
const sessions = existing?.sessions ?? new SessionStore(agentEntry.path);
|
|
71
|
+
if (!existing)
|
|
72
|
+
await sessions.init();
|
|
73
|
+
currentAgents.set(agentId, { config: newConfig, sessions });
|
|
74
|
+
console.log(`[dev] ${ts} [${agentId}] Reloaded \u2713` +
|
|
75
|
+
` (tools: ${newConfig.tools.length}, skills: ${Object.keys(newConfig.skills).length})`);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error(`[dev] ${ts} [${agentId}] Reload failed: ${err instanceof Error ? err.message : err}`);
|
|
79
|
+
console.error(`[dev] Continuing with previous valid configuration`);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
reloadInFlight.delete(agentId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Watch each agent directory
|
|
86
|
+
const watchers = [];
|
|
87
|
+
for (const { id, path: agentPath } of agentDirs) {
|
|
88
|
+
if (!currentAgents.has(id))
|
|
89
|
+
continue; // Skip agents that failed to load
|
|
90
|
+
const watcher = watch(agentPath, {
|
|
91
|
+
ignored: [
|
|
92
|
+
"**/sessions/**",
|
|
93
|
+
"**/node_modules/**",
|
|
94
|
+
"**/eval-results/**",
|
|
95
|
+
"**/.DS_Store",
|
|
96
|
+
],
|
|
97
|
+
ignoreInitial: true,
|
|
98
|
+
});
|
|
99
|
+
let debounceTimer = null;
|
|
100
|
+
let pendingFiles = [];
|
|
101
|
+
watcher.on("all", (_event, filePath) => {
|
|
102
|
+
pendingFiles.push(filePath);
|
|
103
|
+
if (debounceTimer)
|
|
104
|
+
clearTimeout(debounceTimer);
|
|
105
|
+
debounceTimer = setTimeout(async () => {
|
|
106
|
+
const files = [...pendingFiles];
|
|
107
|
+
pendingFiles = [];
|
|
108
|
+
await reloadAgent(id, files);
|
|
109
|
+
}, 100);
|
|
110
|
+
});
|
|
111
|
+
watchers.push(watcher);
|
|
112
|
+
}
|
|
113
|
+
// Graceful shutdown
|
|
114
|
+
const shutdown = async () => {
|
|
115
|
+
console.log("\nShutting down...");
|
|
116
|
+
await close();
|
|
117
|
+
process.exit(0);
|
|
118
|
+
};
|
|
119
|
+
process.on("SIGINT", shutdown);
|
|
120
|
+
process.on("SIGTERM", shutdown);
|
|
121
|
+
async function close() {
|
|
122
|
+
process.removeListener("SIGINT", shutdown);
|
|
123
|
+
process.removeListener("SIGTERM", shutdown);
|
|
124
|
+
for (const watcher of watchers) {
|
|
125
|
+
await watcher.close();
|
|
126
|
+
}
|
|
127
|
+
for (const [, entry] of currentAgents) {
|
|
128
|
+
await entry.config.mcpManager?.shutdown();
|
|
129
|
+
}
|
|
130
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
server,
|
|
134
|
+
watchers,
|
|
135
|
+
getAgents: () => currentAgents,
|
|
136
|
+
reloadAgent,
|
|
137
|
+
close,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function printBanner(agents, port, absDir) {
|
|
141
|
+
const lines = [
|
|
142
|
+
"",
|
|
143
|
+
" archon-claw dev",
|
|
144
|
+
"",
|
|
145
|
+
` Agents dir: ${path.relative(process.cwd(), absDir)}`,
|
|
146
|
+
"",
|
|
147
|
+
];
|
|
148
|
+
for (const [id, entry] of agents) {
|
|
149
|
+
const { config } = entry;
|
|
150
|
+
const mcpCount = config.mcpManager
|
|
151
|
+
? config.mcpManager.getServerNames().length
|
|
152
|
+
: 0;
|
|
153
|
+
lines.push(` [${id}] ${config.model.provider}/${config.model.model}` +
|
|
154
|
+
` tools: ${config.tools.length}${mcpCount > 0 ? ` (+${mcpCount} mcp)` : ""}` +
|
|
155
|
+
` skills: ${Object.keys(config.skills).length}`);
|
|
156
|
+
}
|
|
157
|
+
lines.push("");
|
|
158
|
+
lines.push(` URL: http://localhost:${port}`);
|
|
159
|
+
lines.push("");
|
|
160
|
+
lines.push(" Watching for changes...");
|
|
161
|
+
lines.push("");
|
|
162
|
+
const maxLen = Math.max(...lines.map((l) => l.length));
|
|
163
|
+
const top = "\u250C" + "\u2500".repeat(maxLen + 1) + "\u2510";
|
|
164
|
+
const bot = "\u2514" + "\u2500".repeat(maxLen + 1) + "\u2518";
|
|
165
|
+
console.log(top);
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
console.log("\u2502" + line.padEnd(maxLen + 1) + "\u2502");
|
|
168
|
+
}
|
|
169
|
+
console.log(bot);
|
|
170
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { runAgentLoop } from "./agent.js";
|
|
5
|
+
function setupTools(server, agents) {
|
|
6
|
+
server.registerTool("list_agents", {
|
|
7
|
+
description: "List available agents",
|
|
8
|
+
}, async () => {
|
|
9
|
+
const list = [...agents.entries()].map(([id, entry]) => ({
|
|
10
|
+
id,
|
|
11
|
+
toolCount: entry.config.tools.length,
|
|
12
|
+
skillCount: Object.keys(entry.config.skills).length,
|
|
13
|
+
}));
|
|
14
|
+
return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
|
|
15
|
+
});
|
|
16
|
+
server.registerTool("chat", {
|
|
17
|
+
description: "Send a message to an agent and get a response. Only server-side tools are available; client/host tools are excluded.",
|
|
18
|
+
inputSchema: z.object({
|
|
19
|
+
agent: z.string().describe("Agent ID"),
|
|
20
|
+
message: z.string().describe("User message"),
|
|
21
|
+
sessionId: z.string().optional().describe("Session ID for conversation continuity"),
|
|
22
|
+
context: z.record(z.unknown()).optional().describe("Additional context passed to the agent system prompt"),
|
|
23
|
+
}),
|
|
24
|
+
}, async ({ agent, message, sessionId, context }) => {
|
|
25
|
+
const entry = agents.get(agent);
|
|
26
|
+
if (!entry) {
|
|
27
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent}` }], isError: true };
|
|
28
|
+
}
|
|
29
|
+
const { config, sessions } = entry;
|
|
30
|
+
// Filter to server-side tools only (client/host tools can't execute without a browser)
|
|
31
|
+
const serverTools = config.tools.filter(t => (t.execution_target ?? "server") === "server");
|
|
32
|
+
const filteredConfig = { ...config, tools: serverTools };
|
|
33
|
+
const session = await sessions.getOrCreate(sessionId);
|
|
34
|
+
const texts = [];
|
|
35
|
+
try {
|
|
36
|
+
await runAgentLoop(filteredConfig, session, message, (event) => {
|
|
37
|
+
if (event.type === "text")
|
|
38
|
+
texts.push(event.content);
|
|
39
|
+
}, context);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
await sessions.save(session);
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
45
|
+
isError: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
await sessions.save(session);
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: "text", text: texts.join("") }],
|
|
51
|
+
structuredContent: { response: texts.join(""), sessionId: session.id },
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
server.registerTool("list_sessions", {
|
|
55
|
+
description: "List chat sessions for an agent",
|
|
56
|
+
inputSchema: z.object({
|
|
57
|
+
agent: z.string().describe("Agent ID"),
|
|
58
|
+
}),
|
|
59
|
+
}, async ({ agent }) => {
|
|
60
|
+
const entry = agents.get(agent);
|
|
61
|
+
if (!entry) {
|
|
62
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent}` }], isError: true };
|
|
63
|
+
}
|
|
64
|
+
const all = await entry.sessions.list();
|
|
65
|
+
const list = all.map((s) => {
|
|
66
|
+
const firstUser = s.messages.find((m) => m.role === "user");
|
|
67
|
+
const title = firstUser
|
|
68
|
+
? firstUser.content.slice(0, 50) + (firstUser.content.length > 50 ? "..." : "")
|
|
69
|
+
: undefined;
|
|
70
|
+
return { id: s.id, title, messageCount: s.messages.length, createdAt: s.createdAt, updatedAt: s.updatedAt };
|
|
71
|
+
}).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
72
|
+
return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
|
|
73
|
+
});
|
|
74
|
+
server.registerTool("get_session", {
|
|
75
|
+
description: "Get session details and message history",
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
agent: z.string().describe("Agent ID"),
|
|
78
|
+
sessionId: z.string().describe("Session ID"),
|
|
79
|
+
}),
|
|
80
|
+
}, async ({ agent, sessionId }) => {
|
|
81
|
+
const entry = agents.get(agent);
|
|
82
|
+
if (!entry) {
|
|
83
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent}` }], isError: true };
|
|
84
|
+
}
|
|
85
|
+
const session = await entry.sessions.get(sessionId);
|
|
86
|
+
if (!session) {
|
|
87
|
+
return { content: [{ type: "text", text: "Session not found" }], isError: true };
|
|
88
|
+
}
|
|
89
|
+
return { content: [{ type: "text", text: JSON.stringify(session, null, 2) }] };
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export async function handleMcpRequest(agents, req, res) {
|
|
93
|
+
const server = new McpServer({ name: "archon-claw", version: "1.0.0" });
|
|
94
|
+
setupTools(server, agents);
|
|
95
|
+
const transport = new StreamableHTTPServerTransport({
|
|
96
|
+
sessionIdGenerator: undefined,
|
|
97
|
+
enableJsonResponse: true,
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
await server.connect(transport);
|
|
101
|
+
await transport.handleRequest(req, res);
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
await server.close();
|
|
105
|
+
}
|
|
106
|
+
}
|