@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
|
@@ -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,10 +96,10 @@ function readBody(req) {
|
|
|
96
96
|
});
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
|
-
export function createServer(
|
|
100
|
-
const
|
|
99
|
+
export function createServer(agents, port) {
|
|
100
|
+
const resolveAgents = typeof agents === "function" ? agents : () => agents;
|
|
101
101
|
const server = http.createServer(async (req, res) => {
|
|
102
|
-
const
|
|
102
|
+
const agentMap = resolveAgents();
|
|
103
103
|
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
104
104
|
const method = req.method ?? "GET";
|
|
105
105
|
// CORS preflight
|
|
@@ -109,145 +109,187 @@ export function createServer(getConfig, port) {
|
|
|
109
109
|
res.end();
|
|
110
110
|
return;
|
|
111
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
|
+
}
|
|
112
131
|
// Health check
|
|
113
132
|
if (method === "GET" && url.pathname === "/api/health") {
|
|
114
133
|
json(res, 200, { status: "ok" });
|
|
115
134
|
return;
|
|
116
135
|
}
|
|
117
|
-
// List
|
|
118
|
-
if (method === "GET" && url.pathname === "/api/
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
(firstUser.content.length > 50 ? "..." : "")
|
|
126
|
-
: undefined;
|
|
127
|
-
return {
|
|
128
|
-
id: s.id,
|
|
129
|
-
title,
|
|
130
|
-
messageCount: s.messages.length,
|
|
131
|
-
createdAt: s.createdAt,
|
|
132
|
-
updatedAt: s.updatedAt,
|
|
133
|
-
};
|
|
134
|
-
})
|
|
135
|
-
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
136
|
-
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 });
|
|
137
144
|
return;
|
|
138
145
|
}
|
|
139
|
-
//
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
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}` });
|
|
145
154
|
return;
|
|
146
155
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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 });
|
|
163
177
|
return;
|
|
164
178
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
json(res,
|
|
174
|
-
}
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
// Tool result from client/host
|
|
178
|
-
if (method === "POST" && url.pathname === "/api/tool-result") {
|
|
179
|
-
let body;
|
|
180
|
-
try {
|
|
181
|
-
const raw = await readBody(req);
|
|
182
|
-
body = JSON.parse(raw);
|
|
183
|
-
}
|
|
184
|
-
catch {
|
|
185
|
-
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 });
|
|
186
188
|
return;
|
|
187
189
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
// Delete session
|
|
191
|
+
if (method === "DELETE" && sessionMatch) {
|
|
192
|
+
const deleted = await sessions.delete(sessionMatch[1]);
|
|
193
|
+
json(res, deleted ? 200 : 404, { deleted });
|
|
190
194
|
return;
|
|
191
195
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
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
|
+
}
|
|
211
214
|
return;
|
|
212
215
|
}
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|
|
215
239
|
return;
|
|
216
240
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const session = await getOrCreateSession(body.sessionId);
|
|
224
|
-
const pendingToolCallIds = [];
|
|
225
|
-
const emit = (event) => {
|
|
226
|
-
// Track pending client/host tool calls for cleanup on disconnect
|
|
227
|
-
if (event.type === "tool_call" && event.executionTarget) {
|
|
228
|
-
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);
|
|
229
247
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
pendingToolCallIds.splice(idx, 1);
|
|
248
|
+
catch {
|
|
249
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
250
|
+
return;
|
|
234
251
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (pendingToolCallIds.length > 0) {
|
|
239
|
-
cancelAllPending(pendingToolCallIds);
|
|
252
|
+
if (!body.message || typeof body.message !== "string") {
|
|
253
|
+
json(res, 400, { error: "Missing required field: message" });
|
|
254
|
+
return;
|
|
240
255
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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;
|
|
248
290
|
}
|
|
249
|
-
|
|
250
|
-
res
|
|
291
|
+
// Unknown agent sub-route
|
|
292
|
+
json(res, 404, { error: "Not found" });
|
|
251
293
|
return;
|
|
252
294
|
}
|
|
253
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
|
+
}
|