@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.
@@ -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,{})}));
@@ -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-Cn1IPR8U.js"></script>
8
- <link rel="modulepreload" crossorigin href="/assets/chat-input-Cx9bd-IU.js">
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>
@@ -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-CAioKyQy.js"></script>
8
- <link rel="modulepreload" crossorigin href="/assets/chat-input-Cx9bd-IU.js">
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>
@@ -1,2 +1,7 @@
1
- export declare function scaffoldAgent(name: string, targetDir: string): Promise<void>;
2
- export declare function scaffoldWorkspace(targetDir: string): Promise<void>;
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
- export async function scaffoldAgent(name, targetDir) {
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
- // List created files
27
- const files = await listFiles(dest, dest);
28
- console.log(`\nCreated agent "${name}" at ${dest}\n`);
29
- console.log("Files:");
30
- for (const file of files) {
31
- console.log(` ${file}`);
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
- // Check if .claude/ already exists at the target
40
- const claudeDir = path.join(dest, ".claude");
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(claudeDir);
43
- throw new Error(`.claude/ already exists in ${dest}`);
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
- // Resolve template directory
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
- try {
52
- await fs.access(templateDir);
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
- catch {
55
- throw new Error(`Template directory not found: ${templateDir}`);
56
- }
57
- // Create target dir and copy workspace template into it
58
- await fs.mkdir(dest, { recursive: true });
59
- await fs.cp(templateDir, dest, { recursive: true });
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
- console.log(` archon-claw create <agent-name>`);
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
- export declare function createServer(config: AgentConfig, port: number): http.Server;
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(config, port) {
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 sessions
116
- if (method === "GET" && url.pathname === "/api/sessions") {
117
- const all = await listSessions();
118
- const sessions = all
119
- .map((s) => {
120
- const firstUser = s.messages.find((m) => m.role === "user");
121
- const title = firstUser
122
- ? firstUser.content.slice(0, 50) +
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
- // Get session detail
138
- const sessionMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)$/);
139
- if (method === "GET" && sessionMatch) {
140
- const session = await getSession(sessionMatch[1]);
141
- if (!session) {
142
- json(res, 404, { error: "Session not found" });
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
- json(res, 200, { session });
146
- return;
147
- }
148
- // Delete session
149
- const deleteMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)$/);
150
- if (method === "DELETE" && deleteMatch) {
151
- const deleted = await deleteSession(deleteMatch[1]);
152
- json(res, deleted ? 200 : 404, { deleted });
153
- return;
154
- }
155
- // Serve tool UI scripts
156
- const toolUIMatch = url.pathname.match(/^\/api\/tool-uis\/([^/]+)\.ui\.js$/);
157
- if (method === "GET" && toolUIMatch) {
158
- const name = toolUIMatch[1];
159
- if (!config.toolUIs.has(name)) {
160
- json(res, 404, { error: "Tool UI not found" });
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
- const filePath = path.join(config.agentDir, "tool-uis", `${name}.ui.js`);
164
- try {
165
- const content = fs.readFileSync(filePath);
166
- setCors(res);
167
- res.writeHead(200, { "Content-Type": "application/javascript" });
168
- res.end(content);
169
- }
170
- catch {
171
- json(res, 404, { error: "Tool UI file not found" });
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
- if (!body.toolCallId || typeof body.toolCallId !== "string") {
187
- json(res, 400, { error: "Missing required field: toolCallId" });
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
- if (body.error) {
191
- const found = rejectToolResult(body.toolCallId, body.error);
192
- json(res, found ? 200 : 404, { ok: found });
193
- }
194
- else {
195
- const found = resolveToolResult(body.toolCallId, body.result);
196
- json(res, found ? 200 : 404, { ok: found });
197
- }
198
- return;
199
- }
200
- // Chat (SSE)
201
- if (method === "POST" && url.pathname === "/api/chat") {
202
- let body;
203
- try {
204
- const raw = await readBody(req);
205
- body = JSON.parse(raw);
206
- }
207
- catch {
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
- if (!body.message || typeof body.message !== "string") {
212
- json(res, 400, { error: "Missing required field: message" });
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
- setCors(res);
216
- res.writeHead(200, {
217
- "Content-Type": "text/event-stream",
218
- "Cache-Control": "no-cache",
219
- Connection: "keep-alive",
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
- if (event.type === "tool_result") {
229
- const idx = pendingToolCallIds.indexOf(event.toolCallId);
230
- if (idx !== -1)
231
- pendingToolCallIds.splice(idx, 1);
248
+ catch {
249
+ json(res, 400, { error: "Invalid JSON body" });
250
+ return;
232
251
  }
233
- res.write(`data: ${JSON.stringify(event)}\n\n`);
234
- };
235
- req.on("close", () => {
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
- try {
241
- await runAgentLoop(config, session, body.message, emit, body.hostContext);
242
- }
243
- catch (err) {
244
- const message = err instanceof Error ? err.message : String(err);
245
- emit({ type: "error", message });
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
- await saveSession(session);
248
- res.end();
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 function initSessionStore(agentDir: string): Promise<void>;
3
- export declare function saveSession(session: Session): Promise<void>;
4
- export declare function createSession(): Promise<Session>;
5
- export declare function getSession(id: string): Promise<Session | undefined>;
6
- export declare function getOrCreateSession(id?: string): Promise<Session>;
7
- export declare function deleteSession(id: string): Promise<boolean>;
8
- export declare function listSessions(): Promise<Session[]>;
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
+ }