clawmatrix 0.1.13 → 0.1.15
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/BOOTSTRAP.md +40 -8
- package/package.json +7 -5
- package/src/cluster-service.ts +5 -4
- package/src/config.ts +57 -6
- package/src/connection.ts +20 -3
- package/src/device-info.ts +48 -0
- package/src/handoff.ts +20 -5
- package/src/index.ts +14 -4
- package/src/model-proxy.ts +530 -229
- package/src/peer-manager.ts +11 -2
- package/src/router.ts +31 -23
- package/src/tool-proxy.ts +6 -0
- package/src/types.ts +24 -0
- package/src/web-ui.ts +227 -20
- package/src/web.ts +55 -1
package/src/web.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import type { PeerManager } from "./peer-manager.ts";
|
|
3
|
+
import type { HandoffManager } from "./handoff.ts";
|
|
3
4
|
import type { ClawMatrixConfig } from "./config.ts";
|
|
4
5
|
import { timingSafeEqual } from "./auth.ts";
|
|
5
6
|
import { renderDashboard } from "./web-ui.ts";
|
|
@@ -10,12 +11,14 @@ const SESSION_MAX_AGE = 86400 * 7; // 7 days
|
|
|
10
11
|
export class WebHandler {
|
|
11
12
|
private config: ClawMatrixConfig;
|
|
12
13
|
private peerManager: PeerManager;
|
|
14
|
+
private handoffManager: HandoffManager;
|
|
13
15
|
private token: string;
|
|
14
16
|
private startTime = Date.now();
|
|
15
17
|
|
|
16
|
-
constructor(config: ClawMatrixConfig, peerManager: PeerManager) {
|
|
18
|
+
constructor(config: ClawMatrixConfig, peerManager: PeerManager, handoffManager: HandoffManager) {
|
|
17
19
|
this.config = config;
|
|
18
20
|
this.peerManager = peerManager;
|
|
21
|
+
this.handoffManager = handoffManager;
|
|
19
22
|
this.token = config.web!.token;
|
|
20
23
|
}
|
|
21
24
|
|
|
@@ -55,6 +58,11 @@ export class WebHandler {
|
|
|
55
58
|
return true;
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
if (path === "/api/handoff" && req.method === "POST") {
|
|
62
|
+
this.handleHandoff(req, res);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
if (path === "/api/logout" && req.method === "POST") {
|
|
59
67
|
res.writeHead(200, {
|
|
60
68
|
"Content-Type": "application/json",
|
|
@@ -122,6 +130,7 @@ export class WebHandler {
|
|
|
122
130
|
tags: this.config.tags,
|
|
123
131
|
connection: "self" as const,
|
|
124
132
|
online: true,
|
|
133
|
+
deviceInfo: this.peerManager.localDeviceInfo,
|
|
125
134
|
};
|
|
126
135
|
|
|
127
136
|
const peerNodes = peers.map((p) => ({
|
|
@@ -134,6 +143,8 @@ export class WebHandler {
|
|
|
134
143
|
online: this.peerManager.canReach(p.nodeId),
|
|
135
144
|
lastSeen: p.lastSeen,
|
|
136
145
|
latencyMs: p.latencyMs,
|
|
146
|
+
directPeers: p.directPeers.length > 0 ? p.directPeers : undefined,
|
|
147
|
+
deviceInfo: p.deviceInfo,
|
|
137
148
|
}));
|
|
138
149
|
|
|
139
150
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -207,6 +218,49 @@ export class WebHandler {
|
|
|
207
218
|
}
|
|
208
219
|
}
|
|
209
220
|
}
|
|
221
|
+
private async handleHandoff(req: IncomingMessage, res: ServerResponse) {
|
|
222
|
+
try {
|
|
223
|
+
const body = await readBody(req);
|
|
224
|
+
const { nodeId, agent, task } = JSON.parse(body);
|
|
225
|
+
|
|
226
|
+
if (!nodeId || !agent || !task) {
|
|
227
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
228
|
+
res.end(JSON.stringify({ error: "nodeId, agent and task required" }));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// SSE headers
|
|
233
|
+
res.writeHead(200, {
|
|
234
|
+
"Content-Type": "text/event-stream",
|
|
235
|
+
"Cache-Control": "no-cache",
|
|
236
|
+
"Connection": "keep-alive",
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
let closed = false;
|
|
240
|
+
req.on("close", () => { closed = true; });
|
|
241
|
+
|
|
242
|
+
const result = await this.handoffManager.handoff(agent, task, undefined, {
|
|
243
|
+
nodeId,
|
|
244
|
+
onStream: (delta) => {
|
|
245
|
+
if (closed) return;
|
|
246
|
+
res.write(`data: ${JSON.stringify({ type: "delta", content: delta })}\n\n`);
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (!closed) {
|
|
251
|
+
res.write(`data: ${JSON.stringify({ type: "done", ...result })}\n\n`);
|
|
252
|
+
}
|
|
253
|
+
res.end();
|
|
254
|
+
} catch (err) {
|
|
255
|
+
if (!res.headersSent) {
|
|
256
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
257
|
+
res.end(JSON.stringify({ error: err instanceof Error ? err.message : "Internal error" }));
|
|
258
|
+
} else {
|
|
259
|
+
res.write(`data: ${JSON.stringify({ type: "error", error: err instanceof Error ? err.message : "Internal error" })}\n\n`);
|
|
260
|
+
res.end();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
210
264
|
}
|
|
211
265
|
|
|
212
266
|
const MAX_BODY_SIZE = 1024 * 1024; // 1 MB
|