clawmatrix 0.1.14 → 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/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