@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as n,r as a,f as d,i as l,j as e,b as h,C as u,X as x,d as f}from"./chat-input-Cpyxmt8J.js";function m(){return e.jsxs("div",{className:"flex h-10 shrink-0 items-center justify-between border-b px-3",children:[e.jsx("span",{className:"text-sm font-semibold",children:"Archon Claw"}),e.jsx("button",{type:"button",className:"text-muted-foreground hover:bg-accent rounded p-1 transition-colors",onClick:()=>window.parent.postMessage({type:"archon-claw:request-close"},"*"),children:e.jsx(x,{className:"h-4 w-4"})})]})}function g(){const t=n(s=>s.setAgentId),o=n(s=>s.createSession),r=n(s=>s.activeSession());return a.useEffect(()=>{const c=new URLSearchParams(window.location.search).get("agentId");c?t(c):d().then(i=>{i.length>0&&t(i[0].id)}).catch(()=>{}),l()},[t]),a.useEffect(()=>{r||o()},[r,o]),e.jsxs("div",{className:"flex h-dvh flex-col bg-background text-foreground",children:[e.jsx(m,{}),e.jsx(h,{}),e.jsx(u,{})]})}f.createRoot(document.getElementById("embed-root")).render(e.jsx(a.StrictMode,{children:e.jsx(g,{})}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import{c as h,r as c,j as e,a as x,u as a,B as i,X as f,M as p,b,C as j,f as y,d as g}from"./chat-input-Cpyxmt8J.js";/**
|
|
2
|
+
* @license lucide-react v0.469.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/const v=h("Menu",[["line",{x1:"4",x2:"20",y1:"12",y2:"12",key:"1e0a9i"}],["line",{x1:"4",x2:"20",y1:"6",y2:"6",key:"1owob3"}],["line",{x1:"4",x2:"20",y1:"18",y2:"18",key:"yk5zj1"}]]);/**
|
|
7
|
+
* @license lucide-react v0.469.0 - ISC
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the ISC license.
|
|
10
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/const N=h("Plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);/**
|
|
12
|
+
* @license lucide-react v0.469.0 - ISC
|
|
13
|
+
*
|
|
14
|
+
* This source code is licensed under the ISC license.
|
|
15
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
16
|
+
*/const w=h("Trash2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);function k({content:r,children:n,side:o="top"}){const[l,t]=c.useState(!1),d={top:"bottom-full left-1/2 -translate-x-1/2 mb-2",bottom:"top-full left-1/2 -translate-x-1/2 mt-2",left:"right-full top-1/2 -translate-y-1/2 mr-2",right:"left-full top-1/2 -translate-y-1/2 ml-2"};return e.jsxs("div",{className:"relative inline-flex",onMouseEnter:()=>t(!0),onMouseLeave:()=>t(!1),children:[n,l&&e.jsx("div",{className:x("absolute z-50 whitespace-nowrap rounded-md bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md border border-border animate-in fade-in-0",d[o]),children:r})]})}function S(){const r=a(s=>s.sessions),n=a(s=>s.activeLocalId),o=a(s=>s.sidebarOpen),l=a(s=>s.setSidebarOpen),t=a(s=>s.createSession),d=a(s=>s.switchSession),u=a(s=>s.deleteSession);return e.jsxs(e.Fragment,{children:[o&&e.jsx("div",{className:"fixed inset-0 z-40 bg-black/60 md:hidden",onClick:()=>l(!1)}),e.jsxs("aside",{className:x("fixed inset-y-0 left-0 z-50 flex w-60 flex-col border-r border-border bg-secondary transition-transform duration-200 md:relative md:translate-x-0",o?"translate-x-0":"-translate-x-full"),children:[e.jsxs("div",{className:"flex h-14 items-center justify-between border-b border-border px-4",children:[e.jsx("h1",{className:"text-sm font-semibold text-foreground",children:"Archon Claw"}),e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(k,{content:"New chat",side:"bottom",children:e.jsx(i,{variant:"ghost",size:"icon",className:"h-8 w-8",onClick:t,children:e.jsx(N,{className:"h-4 w-4"})})}),e.jsx(i,{variant:"ghost",size:"icon",className:"h-8 w-8 md:hidden",onClick:()=>l(!1),children:e.jsx(f,{className:"h-4 w-4"})})]})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-2",children:[r.length===0&&e.jsx("p",{className:"px-2 py-4 text-center text-xs text-muted-foreground",children:"No conversations yet"}),r.map(s=>e.jsxs("div",{className:x("group flex items-center gap-2 rounded-lg px-3 py-2 text-sm cursor-pointer transition-colors",s.localId===n?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:bg-accent"),onClick:()=>d(s.localId),children:[e.jsx(p,{className:"h-4 w-4 shrink-0"}),e.jsx("span",{className:"flex-1 truncate",children:s.title}),e.jsx(i,{variant:"ghost",size:"icon",className:"h-6 w-6 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity",onClick:m=>{m.stopPropagation(),u(s.localId)},children:e.jsx(w,{className:"h-3.5 w-3.5 text-muted-foreground hover:text-destructive"})})]},s.localId))]})]})]})}function M(){const r=a(o=>o.setSidebarOpen),n=a(o=>o.activeSession());return e.jsxs("div",{className:"flex h-full flex-col",children:[e.jsxs("div",{className:"flex h-14 items-center gap-3 border-b border-border px-4",children:[e.jsx(i,{variant:"ghost",size:"icon",className:"md:hidden",onClick:()=>r(!0),children:e.jsx(v,{className:"h-5 w-5"})}),e.jsx("h2",{className:"truncate text-sm font-medium text-foreground",children:(n==null?void 0:n.title)??""})]}),e.jsx(b,{}),e.jsx(j,{})]})}function C(){const r=a(t=>t.setAgentId),n=a(t=>t.loadSessions),[o,l]=c.useState(!1);return c.useEffect(()=>{y().then(t=>{t.length>0&&r(t[0].id)}).then(()=>n()).then(()=>l(!0)).catch(()=>l(!0))},[r,n]),o?e.jsxs("div",{className:"flex h-dvh overflow-hidden bg-background text-foreground",children:[e.jsx(S,{}),e.jsx("main",{className:"flex-1 overflow-hidden",children:e.jsx(M,{})})]}):null}g.createRoot(document.getElementById("root")).render(e.jsx(c.StrictMode,{children:e.jsx(C,{})}));
|
package/dist/public/embed.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Archon Claw Embed</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/embed-
|
|
8
|
-
<link rel="modulepreload" crossorigin href="/assets/chat-input-
|
|
7
|
+
<script type="module" crossorigin src="/assets/embed-CIS9O-0V.js"></script>
|
|
8
|
+
<link rel="modulepreload" crossorigin href="/assets/chat-input-Cpyxmt8J.js">
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/chat-input-qoFFWtdQ.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist/public/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Archon Claw</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/main-
|
|
8
|
-
<link rel="modulepreload" crossorigin href="/assets/chat-input-
|
|
7
|
+
<script type="module" crossorigin src="/assets/main-CfVKtiE-.js"></script>
|
|
8
|
+
<link rel="modulepreload" crossorigin href="/assets/chat-input-Cpyxmt8J.js">
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/chat-input-qoFFWtdQ.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export declare function scaffoldAgent(name: string, targetDir: string
|
|
2
|
-
|
|
1
|
+
export declare function scaffoldAgent(name: string, targetDir: string, opts?: {
|
|
2
|
+
quiet?: boolean;
|
|
3
|
+
}): Promise<void>;
|
|
4
|
+
export declare function scaffoldWorkspace(targetDir: string, opts?: {
|
|
5
|
+
install?: boolean;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function updateSkills(targetDir: string): Promise<void>;
|
package/dist/scaffold.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import { skillsDir } from "@archon-claw/skills";
|
|
4
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
-
|
|
8
|
+
const cliVersion = JSON.parse(readFileSync(path.resolve(__dirname, "../package.json"), "utf-8")).version;
|
|
9
|
+
export async function scaffoldAgent(name, targetDir, opts = {}) {
|
|
6
10
|
const dest = path.resolve(targetDir, name);
|
|
7
11
|
// Check if destination already exists
|
|
8
12
|
try {
|
|
@@ -23,48 +27,80 @@ export async function scaffoldAgent(name, targetDir) {
|
|
|
23
27
|
}
|
|
24
28
|
// Recursively copy template to destination
|
|
25
29
|
await fs.cp(templateDir, dest, { recursive: true });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (!opts.quiet) {
|
|
31
|
+
const files = await listFiles(dest, dest);
|
|
32
|
+
console.log(`\nCreated agent "${name}" at ${dest}\n`);
|
|
33
|
+
console.log("Files:");
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
console.log(` ${file}`);
|
|
36
|
+
}
|
|
37
|
+
console.log(`\nNext steps:`);
|
|
38
|
+
console.log(` cd ${dest}`);
|
|
39
|
+
console.log(` archon-claw start .`);
|
|
32
40
|
}
|
|
33
|
-
console.log(`\nNext steps:`);
|
|
34
|
-
console.log(` cd ${dest}`);
|
|
35
|
-
console.log(` archon-claw start .`);
|
|
36
41
|
}
|
|
37
|
-
export async function scaffoldWorkspace(targetDir) {
|
|
42
|
+
export async function scaffoldWorkspace(targetDir, opts = {}) {
|
|
38
43
|
const dest = path.resolve(targetDir);
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
const name = path.basename(dest);
|
|
45
|
+
// Check if package.json already exists at the target
|
|
46
|
+
const pkgPath = path.join(dest, "package.json");
|
|
41
47
|
try {
|
|
42
|
-
await fs.access(
|
|
43
|
-
throw new Error(
|
|
48
|
+
await fs.access(pkgPath);
|
|
49
|
+
throw new Error(`package.json already exists in ${dest}`);
|
|
44
50
|
}
|
|
45
51
|
catch (err) {
|
|
46
52
|
if (err.code !== "ENOENT")
|
|
47
53
|
throw err;
|
|
48
54
|
}
|
|
49
|
-
|
|
55
|
+
await fs.mkdir(dest, { recursive: true });
|
|
56
|
+
// Copy and render workspace template files (package.json, README.md, .gitignore)
|
|
50
57
|
const templateDir = path.resolve(__dirname, "../templates/workspace");
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
const vars = { name, cliVersion };
|
|
59
|
+
const templateFiles = await listFiles(templateDir, templateDir);
|
|
60
|
+
for (const relPath of templateFiles) {
|
|
61
|
+
const src = path.join(templateDir, relPath);
|
|
62
|
+
const destFile = path.join(dest, relPath);
|
|
63
|
+
await fs.mkdir(path.dirname(destFile), { recursive: true });
|
|
64
|
+
let content = await fs.readFile(src, "utf-8");
|
|
65
|
+
content = content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? _);
|
|
66
|
+
await fs.writeFile(destFile, content);
|
|
53
67
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
await
|
|
68
|
+
// Copy skills from @archon-claw/skills
|
|
69
|
+
const destSkillsDir = path.join(dest, ".claude", "skills");
|
|
70
|
+
await fs.mkdir(destSkillsDir, { recursive: true });
|
|
71
|
+
await fs.cp(skillsDir, destSkillsDir, { recursive: true });
|
|
72
|
+
// Create default agent
|
|
73
|
+
await scaffoldAgent("my-agent", path.join(dest, "agents"), { quiet: true });
|
|
60
74
|
const files = await listFiles(dest, dest);
|
|
61
75
|
console.log(`\nInitialised workspace at ${dest}\n`);
|
|
62
76
|
console.log("Files:");
|
|
63
77
|
for (const file of files) {
|
|
64
78
|
console.log(` ${file}`);
|
|
65
79
|
}
|
|
80
|
+
// Install dependencies
|
|
81
|
+
if (opts.install) {
|
|
82
|
+
console.log("\nInstalling dependencies...\n");
|
|
83
|
+
execSync("npm install", { cwd: dest, stdio: "inherit" });
|
|
84
|
+
}
|
|
66
85
|
console.log(`\nNext steps:`);
|
|
67
|
-
|
|
86
|
+
if (!opts.install) {
|
|
87
|
+
console.log(` cd ${name}`);
|
|
88
|
+
console.log(` npm install`);
|
|
89
|
+
}
|
|
90
|
+
console.log(` archon-claw dev agents/my-agent`);
|
|
91
|
+
}
|
|
92
|
+
export async function updateSkills(targetDir) {
|
|
93
|
+
const dest = path.resolve(targetDir);
|
|
94
|
+
const destSkillsDir = path.join(dest, ".claude", "skills");
|
|
95
|
+
// Remove old skills directory and copy fresh
|
|
96
|
+
await fs.rm(destSkillsDir, { recursive: true, force: true });
|
|
97
|
+
await fs.mkdir(destSkillsDir, { recursive: true });
|
|
98
|
+
await fs.cp(skillsDir, destSkillsDir, { recursive: true });
|
|
99
|
+
const entries = await fs.readdir(destSkillsDir);
|
|
100
|
+
console.log(`\nUpdated ${entries.length} skills in ${destSkillsDir}\n`);
|
|
101
|
+
for (const entry of entries.sort()) {
|
|
102
|
+
console.log(` ${entry}/SKILL.md`);
|
|
103
|
+
}
|
|
68
104
|
}
|
|
69
105
|
async function listFiles(dir, root) {
|
|
70
106
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
package/dist/server.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import type { AgentConfig } from "./types.js";
|
|
3
|
-
|
|
3
|
+
import type { SessionStore } from "./session.js";
|
|
4
|
+
export interface AgentEntry {
|
|
5
|
+
config: AgentConfig;
|
|
6
|
+
sessions: SessionStore;
|
|
7
|
+
}
|
|
8
|
+
type AgentResolver = Map<string, AgentEntry> | (() => Map<string, AgentEntry>);
|
|
9
|
+
export declare function createServer(agents: AgentResolver, port: number): http.Server;
|
|
10
|
+
export {};
|
package/dist/server.js
CHANGED
|
@@ -2,9 +2,9 @@ import http from "node:http";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { getOrCreateSession, getSession, deleteSession, listSessions, saveSession } from "./session.js";
|
|
6
5
|
import { runAgentLoop } from "./agent.js";
|
|
7
6
|
import { resolveToolResult, rejectToolResult, cancelAllPending } from "./pending-tool-results.js";
|
|
7
|
+
import { handleMcpRequest } from "./mcp-server.js";
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const STATIC_DIR = path.resolve(__dirname, "public");
|
|
10
10
|
// Dev-mode fallback: when running from src/ via tsx, serve web assets from packages/web
|
|
@@ -96,8 +96,10 @@ function readBody(req) {
|
|
|
96
96
|
});
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
|
-
export function createServer(
|
|
99
|
+
export function createServer(agents, port) {
|
|
100
|
+
const resolveAgents = typeof agents === "function" ? agents : () => agents;
|
|
100
101
|
const server = http.createServer(async (req, res) => {
|
|
102
|
+
const agentMap = resolveAgents();
|
|
101
103
|
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
102
104
|
const method = req.method ?? "GET";
|
|
103
105
|
// CORS preflight
|
|
@@ -107,145 +109,187 @@ export function createServer(config, port) {
|
|
|
107
109
|
res.end();
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
112
|
+
// MCP endpoint (Streamable HTTP, POST only for stateless mode)
|
|
113
|
+
if (url.pathname === "/mcp") {
|
|
114
|
+
if (method !== "POST") {
|
|
115
|
+
setCors(res);
|
|
116
|
+
res.writeHead(405, { Allow: "POST" });
|
|
117
|
+
res.end();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
setCors(res);
|
|
121
|
+
try {
|
|
122
|
+
await handleMcpRequest(agentMap, req, res);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (!res.headersSent) {
|
|
126
|
+
json(res, 500, { error: err instanceof Error ? err.message : "MCP handler error" });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
110
131
|
// Health check
|
|
111
132
|
if (method === "GET" && url.pathname === "/api/health") {
|
|
112
133
|
json(res, 200, { status: "ok" });
|
|
113
134
|
return;
|
|
114
135
|
}
|
|
115
|
-
// List
|
|
116
|
-
if (method === "GET" && url.pathname === "/api/
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
(firstUser.content.length > 50 ? "..." : "")
|
|
124
|
-
: undefined;
|
|
125
|
-
return {
|
|
126
|
-
id: s.id,
|
|
127
|
-
title,
|
|
128
|
-
messageCount: s.messages.length,
|
|
129
|
-
createdAt: s.createdAt,
|
|
130
|
-
updatedAt: s.updatedAt,
|
|
131
|
-
};
|
|
132
|
-
})
|
|
133
|
-
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
134
|
-
json(res, 200, { sessions });
|
|
136
|
+
// List agents
|
|
137
|
+
if (method === "GET" && url.pathname === "/api/agents") {
|
|
138
|
+
const agentList = [...agentMap.entries()].map(([id, entry]) => ({
|
|
139
|
+
id,
|
|
140
|
+
toolCount: entry.config.tools.length,
|
|
141
|
+
skillCount: Object.keys(entry.config.skills).length,
|
|
142
|
+
}));
|
|
143
|
+
json(res, 200, { agents: agentList });
|
|
135
144
|
return;
|
|
136
145
|
}
|
|
137
|
-
//
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
// Agent-scoped routes: /api/agents/:agentId/...
|
|
147
|
+
const agentMatch = url.pathname.match(/^\/api\/agents\/([^/]+)(\/.*)?$/);
|
|
148
|
+
if (agentMatch) {
|
|
149
|
+
const agentId = agentMatch[1];
|
|
150
|
+
const subPath = agentMatch[2] ?? "";
|
|
151
|
+
const entry = agentMap.get(agentId);
|
|
152
|
+
if (!entry) {
|
|
153
|
+
json(res, 404, { error: `Agent not found: ${agentId}` });
|
|
143
154
|
return;
|
|
144
155
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
const { config, sessions } = entry;
|
|
157
|
+
// List sessions
|
|
158
|
+
if (method === "GET" && subPath === "/sessions") {
|
|
159
|
+
const all = await sessions.list();
|
|
160
|
+
const list = all
|
|
161
|
+
.map((s) => {
|
|
162
|
+
const firstUser = s.messages.find((m) => m.role === "user");
|
|
163
|
+
const title = firstUser
|
|
164
|
+
? firstUser.content.slice(0, 50) +
|
|
165
|
+
(firstUser.content.length > 50 ? "..." : "")
|
|
166
|
+
: undefined;
|
|
167
|
+
return {
|
|
168
|
+
id: s.id,
|
|
169
|
+
title,
|
|
170
|
+
messageCount: s.messages.length,
|
|
171
|
+
createdAt: s.createdAt,
|
|
172
|
+
updatedAt: s.updatedAt,
|
|
173
|
+
};
|
|
174
|
+
})
|
|
175
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
176
|
+
json(res, 200, { sessions: list });
|
|
161
177
|
return;
|
|
162
178
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
json(res,
|
|
172
|
-
}
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
// Tool result from client/host
|
|
176
|
-
if (method === "POST" && url.pathname === "/api/tool-result") {
|
|
177
|
-
let body;
|
|
178
|
-
try {
|
|
179
|
-
const raw = await readBody(req);
|
|
180
|
-
body = JSON.parse(raw);
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
json(res, 400, { error: "Invalid JSON body" });
|
|
179
|
+
// Get session detail
|
|
180
|
+
const sessionMatch = subPath.match(/^\/sessions\/([^/]+)$/);
|
|
181
|
+
if (method === "GET" && sessionMatch) {
|
|
182
|
+
const session = await sessions.get(sessionMatch[1]);
|
|
183
|
+
if (!session) {
|
|
184
|
+
json(res, 404, { error: "Session not found" });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
json(res, 200, { session });
|
|
184
188
|
return;
|
|
185
189
|
}
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
// Delete session
|
|
191
|
+
if (method === "DELETE" && sessionMatch) {
|
|
192
|
+
const deleted = await sessions.delete(sessionMatch[1]);
|
|
193
|
+
json(res, deleted ? 200 : 404, { deleted });
|
|
188
194
|
return;
|
|
189
195
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
json(res, 400, { error: "Invalid JSON body" });
|
|
196
|
+
// Serve tool UI scripts
|
|
197
|
+
const toolUIMatch = subPath.match(/^\/tool-uis\/([^/]+)\.ui\.js$/);
|
|
198
|
+
if (method === "GET" && toolUIMatch) {
|
|
199
|
+
const name = toolUIMatch[1];
|
|
200
|
+
if (!config.toolUIs.has(name)) {
|
|
201
|
+
json(res, 404, { error: "Tool UI not found" });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const filePath = path.join(config.agentDir, "tool-uis", `${name}.ui.js`);
|
|
205
|
+
try {
|
|
206
|
+
const content = fs.readFileSync(filePath);
|
|
207
|
+
setCors(res);
|
|
208
|
+
res.writeHead(200, { "Content-Type": "application/javascript" });
|
|
209
|
+
res.end(content);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
json(res, 404, { error: "Tool UI file not found" });
|
|
213
|
+
}
|
|
209
214
|
return;
|
|
210
215
|
}
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
// Tool result from client/host
|
|
217
|
+
if (method === "POST" && subPath === "/tool-result") {
|
|
218
|
+
let body;
|
|
219
|
+
try {
|
|
220
|
+
const raw = await readBody(req);
|
|
221
|
+
body = JSON.parse(raw);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!body.toolCallId || typeof body.toolCallId !== "string") {
|
|
228
|
+
json(res, 400, { error: "Missing required field: toolCallId" });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (body.error) {
|
|
232
|
+
const found = rejectToolResult(body.toolCallId, body.error);
|
|
233
|
+
json(res, found ? 200 : 404, { ok: found });
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
const found = resolveToolResult(body.toolCallId, body.result);
|
|
237
|
+
json(res, found ? 200 : 404, { ok: found });
|
|
238
|
+
}
|
|
213
239
|
return;
|
|
214
240
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const session = await getOrCreateSession(body.sessionId);
|
|
222
|
-
const pendingToolCallIds = [];
|
|
223
|
-
const emit = (event) => {
|
|
224
|
-
// Track pending client/host tool calls for cleanup on disconnect
|
|
225
|
-
if (event.type === "tool_call" && event.executionTarget) {
|
|
226
|
-
pendingToolCallIds.push(event.toolCallId);
|
|
241
|
+
// Chat (SSE)
|
|
242
|
+
if (method === "POST" && subPath === "/chat") {
|
|
243
|
+
let body;
|
|
244
|
+
try {
|
|
245
|
+
const raw = await readBody(req);
|
|
246
|
+
body = JSON.parse(raw);
|
|
227
247
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
pendingToolCallIds.splice(idx, 1);
|
|
248
|
+
catch {
|
|
249
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
250
|
+
return;
|
|
232
251
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (pendingToolCallIds.length > 0) {
|
|
237
|
-
cancelAllPending(pendingToolCallIds);
|
|
252
|
+
if (!body.message || typeof body.message !== "string") {
|
|
253
|
+
json(res, 400, { error: "Missing required field: message" });
|
|
254
|
+
return;
|
|
238
255
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
256
|
+
setCors(res);
|
|
257
|
+
res.writeHead(200, {
|
|
258
|
+
"Content-Type": "text/event-stream",
|
|
259
|
+
"Cache-Control": "no-cache",
|
|
260
|
+
Connection: "keep-alive",
|
|
261
|
+
});
|
|
262
|
+
const session = await sessions.getOrCreate(body.sessionId);
|
|
263
|
+
const pendingToolCallIds = [];
|
|
264
|
+
const emit = (event) => {
|
|
265
|
+
if (event.type === "tool_call" && event.executionTarget) {
|
|
266
|
+
pendingToolCallIds.push(event.toolCallId);
|
|
267
|
+
}
|
|
268
|
+
if (event.type === "tool_result") {
|
|
269
|
+
const idx = pendingToolCallIds.indexOf(event.toolCallId);
|
|
270
|
+
if (idx !== -1)
|
|
271
|
+
pendingToolCallIds.splice(idx, 1);
|
|
272
|
+
}
|
|
273
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
274
|
+
};
|
|
275
|
+
req.on("close", () => {
|
|
276
|
+
if (pendingToolCallIds.length > 0) {
|
|
277
|
+
cancelAllPending(pendingToolCallIds);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
try {
|
|
281
|
+
await runAgentLoop(config, session, body.message, emit, body.hostContext);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
285
|
+
emit({ type: "error", message });
|
|
286
|
+
}
|
|
287
|
+
await sessions.save(session);
|
|
288
|
+
res.end();
|
|
289
|
+
return;
|
|
246
290
|
}
|
|
247
|
-
|
|
248
|
-
res
|
|
291
|
+
// Unknown agent sub-route
|
|
292
|
+
json(res, 404, { error: "Not found" });
|
|
249
293
|
return;
|
|
250
294
|
}
|
|
251
295
|
// Static files (SPA fallback)
|
package/dist/session.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { Session } from "./types.js";
|
|
2
|
-
export declare
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
export declare class SessionStore {
|
|
3
|
+
private dir;
|
|
4
|
+
private cache;
|
|
5
|
+
constructor(agentDir: string);
|
|
6
|
+
init(): Promise<void>;
|
|
7
|
+
private filePath;
|
|
8
|
+
save(session: Session): Promise<void>;
|
|
9
|
+
create(): Promise<Session>;
|
|
10
|
+
get(id: string): Promise<Session | undefined>;
|
|
11
|
+
getOrCreate(id?: string): Promise<Session>;
|
|
12
|
+
delete(id: string): Promise<boolean>;
|
|
13
|
+
list(): Promise<Session[]>;
|
|
14
|
+
}
|