@archon-claw/cli 0.1.0 → 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 +120 -28
- package/dist/dev.d.ts +8 -8
- package/dist/dev.js +115 -66
- 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 -120
- 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/package.json +4 -2
- package/dist/public/assets/embed-Cn1IPR8U.js +0 -1
- package/dist/public/assets/main-CAioKyQy.js +0 -16
package/dist/cli.js
CHANGED
|
@@ -1,17 +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
11
|
import { startDev } from "./dev.js";
|
|
12
12
|
import { runToolTests, formatResults } from "./test-runner.js";
|
|
13
13
|
import { runEvals } from "./eval/runner.js";
|
|
14
|
-
import { scaffoldAgent, scaffoldWorkspace } from "./scaffold.js";
|
|
14
|
+
import { scaffoldAgent, scaffoldWorkspace, updateSkills } from "./scaffold.js";
|
|
15
15
|
const pkg = JSON.parse(readFileSync(path.resolve(__dirname, "../package.json"), "utf-8"));
|
|
16
16
|
const program = new Command();
|
|
17
17
|
program
|
|
@@ -31,15 +31,42 @@ program
|
|
|
31
31
|
.action(() => {
|
|
32
32
|
console.log("Starting agent...");
|
|
33
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
|
+
}
|
|
34
61
|
program
|
|
35
62
|
.command("dev")
|
|
36
63
|
.description("Start dev server with hot-reload for agent development")
|
|
37
|
-
.
|
|
64
|
+
.requiredOption("--agents-dir <path>", "Path to directory containing agent subdirectories")
|
|
38
65
|
.option("-p, --port <port>", "Server port", "5100")
|
|
39
66
|
.option("--no-open", "Don't auto-open browser")
|
|
40
|
-
.action(async (
|
|
67
|
+
.action(async (opts) => {
|
|
41
68
|
try {
|
|
42
|
-
await startDev(
|
|
69
|
+
await startDev(opts.agentsDir, {
|
|
43
70
|
port: parseInt(opts.port, 10),
|
|
44
71
|
open: opts.open,
|
|
45
72
|
});
|
|
@@ -52,28 +79,28 @@ program
|
|
|
52
79
|
program
|
|
53
80
|
.command("start")
|
|
54
81
|
.description("Start an agent HTTP server")
|
|
55
|
-
.
|
|
82
|
+
.requiredOption("--agents-dir <path>", "Path to directory containing agent subdirectories")
|
|
56
83
|
.option("-p, --port <port>", "Server port", "5100")
|
|
57
|
-
.action(async (
|
|
84
|
+
.action(async (opts) => {
|
|
58
85
|
try {
|
|
59
|
-
const
|
|
60
|
-
await initSessionStore(agentDir);
|
|
86
|
+
const agents = await loadAgentsFromDir(opts.agentsDir);
|
|
61
87
|
const port = parseInt(opts.port, 10);
|
|
62
|
-
console.log(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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}`);
|
|
68
96
|
}
|
|
69
|
-
|
|
70
|
-
console.log(` System prompt: ${config.systemPrompt.length} chars`);
|
|
71
|
-
const server = createServer(config, port);
|
|
97
|
+
const server = createServer(agents, port);
|
|
72
98
|
const shutdown = async () => {
|
|
73
99
|
console.log("\nShutting down...");
|
|
74
|
-
|
|
100
|
+
for (const [, entry] of agents) {
|
|
101
|
+
await entry.config.mcpManager?.shutdown();
|
|
102
|
+
}
|
|
75
103
|
server.close(() => process.exit(0));
|
|
76
|
-
// Force exit if server hasn't closed within 3s
|
|
77
104
|
setTimeout(() => process.exit(1), 3000).unref();
|
|
78
105
|
};
|
|
79
106
|
process.on("SIGINT", shutdown);
|
|
@@ -132,11 +159,40 @@ program
|
|
|
132
159
|
});
|
|
133
160
|
program
|
|
134
161
|
.command("init")
|
|
135
|
-
.description("Initialise an agent workspace
|
|
136
|
-
.argument("[dir]", "Directory to initialise"
|
|
137
|
-
.
|
|
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) => {
|
|
138
166
|
try {
|
|
139
|
-
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!");
|
|
140
196
|
}
|
|
141
197
|
catch (err) {
|
|
142
198
|
console.error(err instanceof Error ? err.message : err);
|
|
@@ -144,13 +200,49 @@ program
|
|
|
144
200
|
}
|
|
145
201
|
});
|
|
146
202
|
program
|
|
147
|
-
.command("create")
|
|
203
|
+
.command("create-agent")
|
|
148
204
|
.description("Create a new agent project")
|
|
149
|
-
.argument("
|
|
205
|
+
.argument("[agent-name]", "Name of the agent (used as directory name)")
|
|
150
206
|
.option("-d, --dir <path>", "Parent directory for the agent", "./agents")
|
|
151
207
|
.action(async (agentName, opts) => {
|
|
152
208
|
try {
|
|
153
|
-
|
|
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);
|
|
154
246
|
}
|
|
155
247
|
catch (err) {
|
|
156
248
|
console.error(err instanceof Error ? err.message : err);
|
package/dist/dev.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import { type FSWatcher } from "chokidar";
|
|
3
|
-
import type
|
|
3
|
+
import { type AgentEntry } from "./server.js";
|
|
4
4
|
export interface DevOptions {
|
|
5
5
|
port: number;
|
|
6
6
|
open: boolean;
|
|
@@ -8,13 +8,13 @@ export interface DevOptions {
|
|
|
8
8
|
/** Returned by startDev for cleanup and testing */
|
|
9
9
|
export interface DevHandle {
|
|
10
10
|
server: http.Server;
|
|
11
|
-
|
|
12
|
-
/** Current
|
|
13
|
-
|
|
14
|
-
/** Manually trigger config reload */
|
|
15
|
-
|
|
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
16
|
/** Graceful shutdown */
|
|
17
17
|
close(): Promise<void>;
|
|
18
18
|
}
|
|
19
|
-
export declare function startDev(
|
|
20
|
-
export declare function printBanner(
|
|
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
CHANGED
|
@@ -1,70 +1,115 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
3
|
import { watch } from "chokidar";
|
|
3
4
|
import open from "open";
|
|
4
5
|
import { loadAgentConfig } from "./config.js";
|
|
5
6
|
import { createServer } from "./server.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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);
|
|
15
46
|
// Open browser (skip in SSH)
|
|
16
47
|
if (opts.open && !process.env.SSH_CLIENT && !process.env.SSH_TTY) {
|
|
17
48
|
open(`http://localhost:${opts.port}`).catch(() => { });
|
|
18
49
|
}
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
async function
|
|
22
|
-
if (reloadInFlight)
|
|
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);
|
|
23
59
|
return;
|
|
24
|
-
|
|
60
|
+
}
|
|
25
61
|
const ts = new Date().toLocaleTimeString();
|
|
26
62
|
if (changedFiles?.length) {
|
|
27
|
-
const relFiles = changedFiles.map((f) => path.relative(
|
|
28
|
-
console.log(`\n[dev] ${ts} Changed: ${relFiles.join(", ")}`);
|
|
63
|
+
const relFiles = changedFiles.map((f) => path.relative(agentEntry.path, f));
|
|
64
|
+
console.log(`\n[dev] ${ts} [${agentId}] Changed: ${relFiles.join(", ")}`);
|
|
29
65
|
}
|
|
30
66
|
try {
|
|
31
|
-
|
|
32
|
-
await
|
|
33
|
-
const newConfig = await loadAgentConfig(
|
|
34
|
-
|
|
35
|
-
|
|
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` +
|
|
36
75
|
` (tools: ${newConfig.tools.length}, skills: ${Object.keys(newConfig.skills).length})`);
|
|
37
76
|
}
|
|
38
77
|
catch (err) {
|
|
39
|
-
console.error(`[dev] ${ts} Reload failed: ${err instanceof Error ? err.message : err}`);
|
|
78
|
+
console.error(`[dev] ${ts} [${agentId}] Reload failed: ${err instanceof Error ? err.message : err}`);
|
|
40
79
|
console.error(`[dev] Continuing with previous valid configuration`);
|
|
41
80
|
}
|
|
42
81
|
finally {
|
|
43
|
-
reloadInFlight
|
|
82
|
+
reloadInFlight.delete(agentId);
|
|
44
83
|
}
|
|
45
84
|
}
|
|
46
|
-
// Watch agent directory
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
}
|
|
68
113
|
// Graceful shutdown
|
|
69
114
|
const shutdown = async () => {
|
|
70
115
|
console.log("\nShutting down...");
|
|
@@ -76,40 +121,44 @@ export async function startDev(agentDir, opts) {
|
|
|
76
121
|
async function close() {
|
|
77
122
|
process.removeListener("SIGINT", shutdown);
|
|
78
123
|
process.removeListener("SIGTERM", shutdown);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
124
|
+
for (const watcher of watchers) {
|
|
125
|
+
await watcher.close();
|
|
126
|
+
}
|
|
127
|
+
for (const [, entry] of currentAgents) {
|
|
128
|
+
await entry.config.mcpManager?.shutdown();
|
|
129
|
+
}
|
|
83
130
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
84
131
|
}
|
|
85
132
|
return {
|
|
86
133
|
server,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
134
|
+
watchers,
|
|
135
|
+
getAgents: () => currentAgents,
|
|
136
|
+
reloadAgent,
|
|
90
137
|
close,
|
|
91
138
|
};
|
|
92
139
|
}
|
|
93
|
-
export function printBanner(
|
|
94
|
-
const mcpCount = config.mcpManager
|
|
95
|
-
? config.tools.length -
|
|
96
|
-
config.tools.filter((t) => config.toolImpls.has(t.name) &&
|
|
97
|
-
!t.name.includes("__")).length
|
|
98
|
-
: 0;
|
|
99
|
-
const localCount = config.tools.length - mcpCount;
|
|
140
|
+
export function printBanner(agents, port, absDir) {
|
|
100
141
|
const lines = [
|
|
101
142
|
"",
|
|
102
143
|
" archon-claw dev",
|
|
103
144
|
"",
|
|
104
|
-
`
|
|
105
|
-
` Model: ${config.model.provider}/${config.model.model}`,
|
|
106
|
-
` Tools: ${localCount}${mcpCount > 0 ? ` (+ ${mcpCount} from MCP)` : ""}`,
|
|
107
|
-
` Skills: ${Object.keys(config.skills).length}`,
|
|
108
|
-
` URL: http://localhost:${port}`,
|
|
109
|
-
"",
|
|
110
|
-
" Watching for changes...",
|
|
145
|
+
` Agents dir: ${path.relative(process.cwd(), absDir)}`,
|
|
111
146
|
"",
|
|
112
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("");
|
|
113
162
|
const maxLen = Math.max(...lines.map((l) => l.length));
|
|
114
163
|
const top = "\u250C" + "\u2500".repeat(maxLen + 1) + "\u2510";
|
|
115
164
|
const bot = "\u2514" + "\u2500".repeat(maxLen + 1) + "\u2518";
|
|
@@ -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
|
+
}
|