@yesod/openclaw 0.0.2 → 0.1.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/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,74 +17,886 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
23
- GatewayApiClient: () => GatewayApiClient,
24
33
  GatewayWebSocketClient: () => GatewayWebSocketClient,
25
- OpenClawAdapter: () => OpenClawAdapter
34
+ OpenClawAdapter: () => OpenClawAdapter,
35
+ buildAuthPayload: () => buildAuthPayload,
36
+ createYesodMcpServer: () => createYesodMcpServer,
37
+ deviceIdFromPrivateKey: () => deviceIdFromPrivateKey,
38
+ enrichInstruction: () => enrichInstruction,
39
+ generateDeviceIdentity: () => generateDeviceIdentity,
40
+ loadOrCreateIdentity: () => loadOrCreateIdentity,
41
+ parseOutput: () => parseOutput,
42
+ signPayload: () => signPayload,
43
+ startMcpServer: () => startMcpServer
26
44
  });
27
45
  module.exports = __toCommonJS(index_exports);
28
46
 
29
47
  // src/adapter.ts
30
- var OpenClawAdapter = class {
31
- constructor(gatewayUrl, authToken) {
32
- this.gatewayUrl = gatewayUrl;
33
- this.authToken = authToken;
48
+ var import_node_crypto2 = require("crypto");
49
+ var import_core = require("@yesod/core");
50
+
51
+ // src/websocket.ts
52
+ var import_ws = __toESM(require("ws"), 1);
53
+
54
+ // src/identity.ts
55
+ var import_node_crypto = require("crypto");
56
+ var import_node_fs = require("fs");
57
+ var import_node_path = require("path");
58
+ var import_node_os = require("os");
59
+ var PROTOCOL_VERSION = "v2";
60
+ var DEFAULT_IDENTITY_PATH = (0, import_node_path.join)((0, import_node_os.homedir)(), ".yesod", "device.json");
61
+ function generateDeviceIdentity() {
62
+ const { publicKey, privateKey } = (0, import_node_crypto.generateKeyPairSync)("ed25519");
63
+ const pubKeyDer = publicKey.export({ type: "spki", format: "der" });
64
+ const pubKeyRaw = pubKeyDer.subarray(pubKeyDer.length - 32);
65
+ const publicKeyB64Url = pubKeyRaw.toString("base64url");
66
+ const deviceId = (0, import_node_crypto.createHash)("sha256").update(pubKeyRaw).digest("hex");
67
+ const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
68
+ return { deviceId, publicKeyB64Url, privateKeyPem };
69
+ }
70
+ function loadOrCreateIdentity(path = DEFAULT_IDENTITY_PATH) {
71
+ if ((0, import_node_fs.existsSync)(path)) {
72
+ const data = JSON.parse((0, import_node_fs.readFileSync)(path, "utf-8"));
73
+ return data;
74
+ }
75
+ const identity = generateDeviceIdentity();
76
+ const dir = (0, import_node_path.dirname)(path);
77
+ if (!(0, import_node_fs.existsSync)(dir)) {
78
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
34
79
  }
80
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(identity, null, 2), "utf-8");
81
+ return identity;
82
+ }
83
+ function signPayload(privateKeyPem, payload) {
84
+ const sig = (0, import_node_crypto.sign)(null, Buffer.from(payload), privateKeyPem);
85
+ return sig.toString("base64url");
86
+ }
87
+ function buildAuthPayload(deviceId, clientId, nonce, scopes, token = "") {
88
+ const signedAtMs = Date.now();
89
+ const parts = [
90
+ PROTOCOL_VERSION,
91
+ deviceId,
92
+ clientId,
93
+ clientId,
94
+ // clientMode = clientId
95
+ "operator",
96
+ scopes.join(","),
97
+ String(signedAtMs),
98
+ token,
99
+ nonce
100
+ ];
101
+ return { payload: parts.join("|"), signedAtMs };
102
+ }
103
+ function deviceIdFromPrivateKey(privateKeyPem) {
104
+ const pubKey = (0, import_node_crypto.createPublicKey)(privateKeyPem);
105
+ const pubKeyDer = pubKey.export({ type: "spki", format: "der" });
106
+ const pubKeyRaw = pubKeyDer.subarray(pubKeyDer.length - 32);
107
+ return (0, import_node_crypto.createHash)("sha256").update(pubKeyRaw).digest("hex");
108
+ }
109
+
110
+ // src/websocket.ts
111
+ var PROTOCOL_VERSION2 = 3;
112
+ var DEFAULT_SCOPES = ["operator.read", "operator.write", "operator.admin"];
113
+ var GatewayWebSocketClient = class {
114
+ ws = null;
115
+ identity;
116
+ scopes;
117
+ clientId;
118
+ displayName;
119
+ reqCounter = 0;
120
+ pendingRequests = /* @__PURE__ */ new Map();
121
+ eventHandlers = [];
122
+ connected = false;
123
+ disposed = false;
124
+ reconnectEnabled;
125
+ maxReconnectDelay;
126
+ reconnectDelay = 1e3;
127
+ reconnectTimer = null;
35
128
  gatewayUrl;
36
- authToken;
129
+ constructor(config) {
130
+ this.gatewayUrl = config.gatewayUrl;
131
+ this.identity = config.identity ?? generateDeviceIdentity();
132
+ this.scopes = config.scopes ?? DEFAULT_SCOPES;
133
+ this.clientId = config.clientId ?? "cli";
134
+ this.displayName = config.displayName ?? "yesod";
135
+ this.reconnectEnabled = config.reconnect ?? false;
136
+ this.maxReconnectDelay = config.maxReconnectDelayMs ?? 3e4;
137
+ }
37
138
  async connect() {
139
+ if (this.connected) return;
140
+ return new Promise((resolve, reject) => {
141
+ const ws = new import_ws.default(this.gatewayUrl);
142
+ this.ws = ws;
143
+ const timeout = setTimeout(() => {
144
+ ws.close();
145
+ reject(new Error("Connection timeout"));
146
+ }, 15e3);
147
+ ws.on("error", (err) => {
148
+ clearTimeout(timeout);
149
+ reject(err);
150
+ });
151
+ ws.on("open", async () => {
152
+ try {
153
+ await this.handleAuth(ws);
154
+ clearTimeout(timeout);
155
+ this.connected = true;
156
+ this.reconnectDelay = 1e3;
157
+ this.setupMessageHandler(ws);
158
+ this.setupCloseHandler(ws);
159
+ resolve();
160
+ } catch (err) {
161
+ clearTimeout(timeout);
162
+ ws.close();
163
+ reject(err);
164
+ }
165
+ });
166
+ });
38
167
  }
39
- async disconnect() {
168
+ async handleAuth(ws) {
169
+ const challenge = await this.waitForFrame(
170
+ ws,
171
+ (f) => f.type === "event" && f.event === "connect.challenge",
172
+ 1e4
173
+ );
174
+ const nonce = challenge.payload.nonce;
175
+ const { payload: authPayload, signedAtMs } = buildAuthPayload(
176
+ this.identity.deviceId,
177
+ this.clientId,
178
+ nonce,
179
+ this.scopes
180
+ );
181
+ const signature = signPayload(this.identity.privateKeyPem, authPayload);
182
+ const connectId = `r${++this.reqCounter}`;
183
+ const connectReq = {
184
+ type: "req",
185
+ id: connectId,
186
+ method: "connect",
187
+ params: {
188
+ minProtocol: PROTOCOL_VERSION2,
189
+ maxProtocol: PROTOCOL_VERSION2,
190
+ client: {
191
+ id: this.clientId,
192
+ displayName: this.displayName,
193
+ version: "0.0.2",
194
+ platform: "linux",
195
+ mode: this.clientId
196
+ },
197
+ role: "operator",
198
+ scopes: this.scopes,
199
+ caps: ["tool-events"],
200
+ device: {
201
+ id: this.identity.deviceId,
202
+ publicKey: this.identity.publicKeyB64Url,
203
+ signature,
204
+ signedAt: signedAtMs,
205
+ nonce
206
+ }
207
+ }
208
+ };
209
+ ws.send(JSON.stringify(connectReq));
210
+ const res = await this.waitForFrame(
211
+ ws,
212
+ (f) => f.type === "res" && f.id === connectId,
213
+ 15e3
214
+ );
215
+ const resFrame = res;
216
+ if (!resFrame.ok) {
217
+ throw new Error(
218
+ `Connect failed: ${resFrame.error?.message ?? "unknown error"}`
219
+ );
220
+ }
221
+ }
222
+ waitForFrame(ws, filter, timeoutMs) {
223
+ return new Promise((resolve, reject) => {
224
+ const timer = setTimeout(
225
+ () => {
226
+ ws.off("message", handler);
227
+ reject(new Error("Timeout waiting for frame"));
228
+ },
229
+ timeoutMs
230
+ );
231
+ const handler = (data) => {
232
+ let frame;
233
+ try {
234
+ frame = JSON.parse(String(data));
235
+ } catch {
236
+ return;
237
+ }
238
+ if (!filter(frame)) return;
239
+ clearTimeout(timer);
240
+ ws.off("message", handler);
241
+ resolve(frame);
242
+ };
243
+ ws.on("message", handler);
244
+ });
245
+ }
246
+ setupMessageHandler(ws) {
247
+ ws.on("message", (data) => {
248
+ let frame;
249
+ try {
250
+ frame = JSON.parse(String(data));
251
+ } catch {
252
+ return;
253
+ }
254
+ if (frame.type === "res") {
255
+ const pending = this.pendingRequests.get(frame.id);
256
+ if (pending) {
257
+ clearTimeout(pending.timer);
258
+ this.pendingRequests.delete(frame.id);
259
+ pending.resolve(frame);
260
+ }
261
+ } else if (frame.type === "event") {
262
+ for (const handler of this.eventHandlers) {
263
+ handler(frame);
264
+ }
265
+ }
266
+ });
40
267
  }
41
- onEvent(_handler) {
268
+ setupCloseHandler(ws) {
269
+ ws.on("close", () => {
270
+ this.connected = false;
271
+ for (const [id, pending] of this.pendingRequests) {
272
+ clearTimeout(pending.timer);
273
+ pending.reject(new Error("Connection closed"));
274
+ this.pendingRequests.delete(id);
275
+ }
276
+ if (this.reconnectEnabled && !this.disposed) {
277
+ this.scheduleReconnect();
278
+ }
279
+ });
42
280
  }
43
- async sendNotification(_sessionId, _message) {
281
+ scheduleReconnect() {
282
+ if (this.reconnectTimer) return;
283
+ this.reconnectTimer = setTimeout(async () => {
284
+ this.reconnectTimer = null;
285
+ try {
286
+ await this.connect();
287
+ } catch {
288
+ this.reconnectDelay = Math.min(
289
+ this.reconnectDelay * 2,
290
+ this.maxReconnectDelay
291
+ );
292
+ this.scheduleReconnect();
293
+ }
294
+ }, this.reconnectDelay);
295
+ }
296
+ async request(method, params = {}, timeoutMs = 3e4) {
297
+ if (!this.ws || !this.connected) {
298
+ throw new Error("Not connected");
299
+ }
300
+ const id = `r${++this.reqCounter}`;
301
+ const frame = { type: "req", id, method, params };
302
+ return new Promise((resolve, reject) => {
303
+ const timer = setTimeout(() => {
304
+ this.pendingRequests.delete(id);
305
+ reject(new Error(`Request timeout: ${method}`));
306
+ }, timeoutMs);
307
+ this.pendingRequests.set(id, { resolve, reject, timer });
308
+ this.ws.send(JSON.stringify(frame));
309
+ });
310
+ }
311
+ onEvent(handler) {
312
+ this.eventHandlers.push(handler);
313
+ return () => {
314
+ const idx = this.eventHandlers.indexOf(handler);
315
+ if (idx >= 0) this.eventHandlers.splice(idx, 1);
316
+ };
317
+ }
318
+ async subscribeToSession(sessionKey) {
319
+ await this.request("sessions.subscribe", { sessionKey });
320
+ await this.request("sessions.messages.subscribe", { sessionKey });
321
+ }
322
+ isConnected() {
323
+ return this.connected;
324
+ }
325
+ async disconnect() {
326
+ this.disposed = true;
327
+ if (this.reconnectTimer) {
328
+ clearTimeout(this.reconnectTimer);
329
+ this.reconnectTimer = null;
330
+ }
331
+ if (this.ws) {
332
+ this.ws.close();
333
+ this.ws = null;
334
+ }
335
+ this.connected = false;
336
+ }
337
+ getIdentity() {
338
+ return this.identity;
44
339
  }
45
340
  };
46
341
 
47
- // src/websocket.ts
48
- var GatewayWebSocketClient = class {
49
- constructor(gatewayUrl, authToken) {
50
- this.gatewayUrl = gatewayUrl;
51
- this.authToken = authToken;
342
+ // src/prompt.ts
343
+ var FORMAT_INSTRUCTIONS = {
344
+ summary: "When you are done, output a final summary of what you accomplished. Keep it concise (1-3 paragraphs).",
345
+ structured: "When you are done, output your result as a JSON object with the requested fields. Wrap the JSON in a ```json code block.",
346
+ diff: 'When you are done, output a JSON object summarizing the changes: { "summary": "...", "filesChanged": [{ "path": "...", "linesAdded": N, "linesRemoved": N }] }. Wrap in a ```json code block.',
347
+ review: 'When you are done, output a JSON object: { "summary": "...", "findings": [{ "severity": "critical|high|medium|low|info", "location": "file:line", "issue": "..." }], "filesReviewed": ["..."] }. Wrap in a ```json code block.',
348
+ test: 'When you are done, output a JSON object: { "summary": "...", "passed": N, "failed": N, "skipped": N, "failures": [{ "test": "...", "error": "..." }] }. Wrap in a ```json code block.'
349
+ };
350
+ function formatPriorContext(results) {
351
+ const parts = results.map((r) => {
352
+ const status = r.status === "completed" ? "completed" : `${r.status}`;
353
+ let outputSummary;
354
+ if (r.output.format === "summary") {
355
+ outputSummary = r.output.summary;
356
+ } else if (r.output.format === "raw") {
357
+ outputSummary = r.output.raw.slice(0, 500);
358
+ } else if ("summary" in r.output) {
359
+ outputSummary = r.output.summary;
360
+ } else {
361
+ outputSummary = JSON.stringify(r.output).slice(0, 500);
362
+ }
363
+ return `[Step "${r.stepId}" \u2014 ${status}]
364
+ ${outputSummary}`;
365
+ });
366
+ return parts.join("\n\n");
367
+ }
368
+ function enrichInstruction(taskSpec) {
369
+ const parts = [];
370
+ if (taskSpec.priorContext && taskSpec.priorContext.length > 0) {
371
+ parts.push("## Context from prior steps\n");
372
+ parts.push(formatPriorContext(taskSpec.priorContext));
373
+ parts.push("");
374
+ }
375
+ parts.push(taskSpec.instruction);
376
+ const formatInstr = FORMAT_INSTRUCTIONS[taskSpec.expectedResult.format];
377
+ if (formatInstr) {
378
+ parts.push("");
379
+ parts.push("## Output format");
380
+ parts.push(formatInstr);
381
+ }
382
+ if (taskSpec.expectedResult.format === "structured" && taskSpec.expectedResult.fields?.length) {
383
+ parts.push(
384
+ `Include these fields: ${taskSpec.expectedResult.fields.join(", ")}`
385
+ );
386
+ }
387
+ if (taskSpec.expectedResult.maxLength) {
388
+ parts.push(
389
+ `Keep your output under ${taskSpec.expectedResult.maxLength} characters.`
390
+ );
391
+ }
392
+ return parts.join("\n");
393
+ }
394
+
395
+ // src/output-parser.ts
396
+ function extractJsonBlock(text) {
397
+ const match = text.match(/```json\s*([\s\S]*?)```/);
398
+ if (match) return match[1].trim();
399
+ const objMatch = text.match(/\{[\s\S]*\}/);
400
+ if (objMatch) return objMatch[0];
401
+ return null;
402
+ }
403
+ function tryParse(text) {
404
+ const json = extractJsonBlock(text);
405
+ if (!json) return null;
406
+ try {
407
+ return JSON.parse(json);
408
+ } catch {
409
+ return null;
410
+ }
411
+ }
412
+ function parseSummary(text, spec) {
413
+ const summary = spec.maxLength ? text.slice(0, spec.maxLength) : text;
414
+ return { format: "summary", summary };
415
+ }
416
+ function parseDiff(text) {
417
+ const obj = tryParse(text);
418
+ if (!obj || typeof obj.summary !== "string") return null;
419
+ return {
420
+ format: "diff",
421
+ summary: obj.summary,
422
+ filesChanged: Array.isArray(obj.filesChanged) ? obj.filesChanged.map((f) => ({
423
+ path: String(f.path ?? ""),
424
+ linesAdded: Number(f.linesAdded ?? 0),
425
+ linesRemoved: Number(f.linesRemoved ?? 0)
426
+ })) : []
427
+ };
428
+ }
429
+ function parseReview(text) {
430
+ const obj = tryParse(text);
431
+ if (!obj || typeof obj.summary !== "string") return null;
432
+ return {
433
+ format: "review",
434
+ summary: obj.summary,
435
+ findings: Array.isArray(obj.findings) ? obj.findings.map((f) => ({
436
+ severity: f.severity ?? "info",
437
+ location: String(f.location ?? ""),
438
+ issue: String(f.issue ?? "")
439
+ })) : [],
440
+ filesReviewed: Array.isArray(obj.filesReviewed) ? obj.filesReviewed.map(String) : []
441
+ };
442
+ }
443
+ function parseTest(text) {
444
+ const obj = tryParse(text);
445
+ if (!obj || typeof obj.summary !== "string") return null;
446
+ return {
447
+ format: "test",
448
+ summary: obj.summary,
449
+ passed: Number(obj.passed ?? 0),
450
+ failed: Number(obj.failed ?? 0),
451
+ skipped: Number(obj.skipped ?? 0),
452
+ failures: Array.isArray(obj.failures) ? obj.failures.map((f) => ({
453
+ test: String(f.test ?? ""),
454
+ error: String(f.error ?? "")
455
+ })) : []
456
+ };
457
+ }
458
+ function parseStructured(text, spec) {
459
+ const obj = tryParse(text);
460
+ if (!obj) return null;
461
+ const fields = {};
462
+ if (spec.fields?.length) {
463
+ for (const key of spec.fields) {
464
+ fields[key] = obj[key];
465
+ }
466
+ } else {
467
+ Object.assign(fields, obj);
468
+ }
469
+ return { format: "structured", fields };
470
+ }
471
+ function rawFallback(text, parseError) {
472
+ return { format: "raw", raw: text, parseError };
473
+ }
474
+ function parseOutput(rawText, spec) {
475
+ if (!rawText || rawText.trim().length === 0) {
476
+ return rawFallback(rawText, "Empty output");
477
+ }
478
+ switch (spec.format) {
479
+ case "summary":
480
+ return parseSummary(rawText, spec);
481
+ case "diff": {
482
+ const result = parseDiff(rawText);
483
+ return result ?? rawFallback(rawText, "Failed to parse diff output");
484
+ }
485
+ case "review": {
486
+ const result = parseReview(rawText);
487
+ return result ?? rawFallback(rawText, "Failed to parse review output");
488
+ }
489
+ case "test": {
490
+ const result = parseTest(rawText);
491
+ return result ?? rawFallback(rawText, "Failed to parse test output");
492
+ }
493
+ case "structured": {
494
+ const result = parseStructured(rawText, spec);
495
+ return result ?? rawFallback(rawText, "Failed to parse structured output");
496
+ }
497
+ default:
498
+ return rawFallback(rawText);
499
+ }
500
+ }
501
+
502
+ // src/adapter.ts
503
+ var OpenClawAdapter = class {
504
+ name = "openclaw";
505
+ description = "OpenClaw gateway runtime adapter";
506
+ ws;
507
+ config;
508
+ sessions = /* @__PURE__ */ new Map();
509
+ eventHandlers = [];
510
+ unsubscribeEvents = null;
511
+ constructor(config) {
512
+ this.config = config;
513
+ this.ws = new GatewayWebSocketClient({
514
+ gatewayUrl: config.gatewayUrl,
515
+ scopes: config.scopes,
516
+ reconnect: true
517
+ });
52
518
  }
53
- gatewayUrl;
54
- authToken;
55
- handlers = [];
56
519
  async connect() {
520
+ await this.ws.connect();
521
+ this.unsubscribeEvents = this.ws.onEvent(
522
+ (frame) => this.handleGatewayEvent(frame)
523
+ );
57
524
  }
58
- async disconnect() {
525
+ async spawn(opts) {
526
+ const instruction = enrichInstruction(opts.task);
527
+ const agentId = this.config.defaultAgentId ?? "test-orchestrator";
528
+ const res = await this.ws.request("sessions.create", {
529
+ agentId,
530
+ message: instruction
531
+ });
532
+ if (!res.ok || !res.payload) {
533
+ throw new Error(
534
+ `Failed to create session: ${res.error?.message ?? "unknown"}`
535
+ );
536
+ }
537
+ const sessionKey = res.payload.sessionKey;
538
+ const sessionId = res.payload.sessionId;
539
+ const handle = {
540
+ id: sessionId,
541
+ adapter: this.name,
542
+ metadata: {
543
+ sessionKey,
544
+ runId: res.payload.runId,
545
+ agentId
546
+ }
547
+ };
548
+ this.sessions.set(sessionId, {
549
+ handle,
550
+ sessionKey,
551
+ spawnOptions: opts,
552
+ startedAt: Date.now(),
553
+ accumulatedText: ""
554
+ });
555
+ await this.ws.subscribeToSession(sessionKey);
556
+ this.emitEvent({
557
+ id: (0, import_node_crypto2.randomUUID)(),
558
+ type: import_core.EventType.SessionSpawned,
559
+ source: this.name,
560
+ timestamp: Date.now(),
561
+ sessionId,
562
+ workflowId: opts.context?.workflowId,
563
+ stepId: opts.context?.stepId,
564
+ payload: {
565
+ agentId,
566
+ runtime: opts.runtime,
567
+ decision: opts.decision
568
+ }
569
+ });
570
+ return handle;
571
+ }
572
+ async send(session, message) {
573
+ const tracked = this.sessions.get(session.id);
574
+ if (!tracked) throw new Error(`Unknown session: ${session.id}`);
575
+ await this.ws.request("sessions.send", {
576
+ sessionKey: tracked.sessionKey,
577
+ message
578
+ });
579
+ }
580
+ async kill(session) {
581
+ const tracked = this.sessions.get(session.id);
582
+ if (!tracked) return;
583
+ try {
584
+ await this.ws.request("subagents", {
585
+ action: "kill",
586
+ target: session.id
587
+ });
588
+ } catch {
589
+ }
590
+ this.sessions.delete(session.id);
591
+ this.emitEvent({
592
+ id: (0, import_node_crypto2.randomUUID)(),
593
+ type: import_core.EventType.SessionKilled,
594
+ source: this.name,
595
+ timestamp: Date.now(),
596
+ sessionId: session.id,
597
+ payload: {}
598
+ });
599
+ }
600
+ async status(session) {
601
+ try {
602
+ const res = await this.ws.request("subagents", { action: "list" });
603
+ if (!res.ok) return "unknown";
604
+ const agents = res.payload?.agents ?? [];
605
+ const match = agents.find((a) => a.id === session.id);
606
+ if (!match) return this.sessions.has(session.id) ? "running" : null;
607
+ const statusMap = {
608
+ running: "running",
609
+ completed: "completed",
610
+ failed: "failed",
611
+ killed: "killed"
612
+ };
613
+ return statusMap[match.status] ?? "unknown";
614
+ } catch {
615
+ return "unknown";
616
+ }
59
617
  }
60
618
  onEvent(handler) {
61
- this.handlers.push(handler);
619
+ this.eventHandlers.push(handler);
62
620
  }
63
- /** Subscribe to events for a specific session via sessions.subscribe */
64
- async subscribeToSession(_sessionId) {
621
+ async dispose() {
622
+ for (const [, tracked] of this.sessions) {
623
+ try {
624
+ await this.kill(tracked.handle);
625
+ } catch {
626
+ }
627
+ }
628
+ this.sessions.clear();
629
+ if (this.unsubscribeEvents) {
630
+ this.unsubscribeEvents();
631
+ }
632
+ await this.ws.disconnect();
65
633
  }
66
- };
67
-
68
- // src/api.ts
69
- var GatewayApiClient = class {
70
- constructor(options) {
71
- this.options = options;
634
+ // --- Internal ---
635
+ handleGatewayEvent(frame) {
636
+ if (frame.event === "agent") {
637
+ this.handleAgentEvent(frame);
638
+ } else if (frame.event === "chat") {
639
+ this.handleChatEvent(frame);
640
+ }
72
641
  }
73
- options;
74
- /** Send a notification to a session via sessions_send */
75
- async sessionsSend(_sessionId, _message) {
642
+ handleAgentEvent(frame) {
643
+ const payload = frame.payload;
644
+ if (payload.stream === "lifecycle") {
645
+ const lifecycle = payload;
646
+ if (lifecycle.phase === "complete") {
647
+ this.handleSessionComplete(frame);
648
+ } else if (lifecycle.phase === "error") {
649
+ this.handleSessionError(frame, lifecycle.error ?? "Unknown error");
650
+ }
651
+ } else if (payload.stream === "assistant") {
652
+ const delta = payload.delta;
653
+ if (delta) {
654
+ for (const [, tracked] of this.sessions) {
655
+ tracked.accumulatedText += delta;
656
+ }
657
+ }
658
+ this.emitEvent({
659
+ id: (0, import_node_crypto2.randomUUID)(),
660
+ type: import_core.EventType.SessionMessage,
661
+ source: this.name,
662
+ timestamp: Date.now(),
663
+ payload: { stream: "assistant", delta }
664
+ });
665
+ }
76
666
  }
77
- /** List active sessions */
78
- async listSessions() {
79
- return [];
667
+ handleChatEvent(frame) {
668
+ const payload = frame.payload;
669
+ if (payload.state === "error") {
670
+ for (const [sessionId] of this.sessions) {
671
+ this.handleSessionError(
672
+ frame,
673
+ payload.errorMessage ?? "Chat error",
674
+ sessionId
675
+ );
676
+ break;
677
+ }
678
+ }
679
+ }
680
+ handleSessionComplete(frame) {
681
+ const sessionId = this.findSessionId(frame);
682
+ const tracked = sessionId ? this.sessions.get(sessionId) : null;
683
+ if (!tracked) return;
684
+ const lifecycle = frame.payload;
685
+ const durationMs = Date.now() - tracked.startedAt;
686
+ const cost = {
687
+ tokens: {
688
+ input: lifecycle.cost?.input ?? 0,
689
+ output: lifecycle.cost?.output ?? 0,
690
+ cacheRead: lifecycle.cost?.cacheRead,
691
+ cacheWrite: lifecycle.cost?.cacheWrite
692
+ },
693
+ usd: lifecycle.cost?.totalCost ?? 0,
694
+ runtime: tracked.spawnOptions.runtime
695
+ };
696
+ const output = parseOutput(
697
+ tracked.accumulatedText,
698
+ tracked.spawnOptions.task.expectedResult
699
+ );
700
+ const stepResult = {
701
+ stepId: tracked.spawnOptions.context?.stepId ?? sessionId,
702
+ status: "completed",
703
+ output,
704
+ meta: {
705
+ runtime: tracked.spawnOptions.runtime,
706
+ decision: tracked.spawnOptions.decision,
707
+ cost,
708
+ durationMs,
709
+ retries: (tracked.spawnOptions.context?.attempt ?? 1) - 1
710
+ }
711
+ };
712
+ this.sessions.delete(sessionId);
713
+ this.emitEvent({
714
+ id: (0, import_node_crypto2.randomUUID)(),
715
+ type: import_core.EventType.SessionCompleted,
716
+ source: this.name,
717
+ timestamp: Date.now(),
718
+ sessionId,
719
+ workflowId: tracked.spawnOptions.context?.workflowId,
720
+ stepId: tracked.spawnOptions.context?.stepId,
721
+ payload: { stepResult },
722
+ metadata: { cost, duration: durationMs }
723
+ });
724
+ }
725
+ handleSessionError(frame, error, overrideSessionId) {
726
+ const sessionId = overrideSessionId ?? this.findSessionId(frame);
727
+ const tracked = sessionId ? this.sessions.get(sessionId) : null;
728
+ if (!tracked) return;
729
+ const durationMs = Date.now() - tracked.startedAt;
730
+ this.sessions.delete(sessionId);
731
+ this.emitEvent({
732
+ id: (0, import_node_crypto2.randomUUID)(),
733
+ type: import_core.EventType.SessionFailed,
734
+ source: this.name,
735
+ timestamp: Date.now(),
736
+ sessionId,
737
+ workflowId: tracked.spawnOptions.context?.workflowId,
738
+ stepId: tracked.spawnOptions.context?.stepId,
739
+ payload: { error },
740
+ metadata: { duration: durationMs }
741
+ });
742
+ }
743
+ findSessionId(frame) {
744
+ const fromPayload = frame.payload.sessionId;
745
+ if (fromPayload && this.sessions.has(fromPayload)) return fromPayload;
746
+ if (this.sessions.size === 1) {
747
+ return this.sessions.keys().next().value;
748
+ }
749
+ return void 0;
750
+ }
751
+ emitEvent(event) {
752
+ for (const handler of this.eventHandlers) {
753
+ handler(event);
754
+ }
80
755
  }
81
756
  };
757
+
758
+ // src/mcp-server.ts
759
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
760
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
761
+ var import_core2 = require("@yesod/core");
762
+ function createYesodMcpServer(engine) {
763
+ const server = new import_mcp.McpServer({
764
+ name: "yesod",
765
+ version: "0.0.2"
766
+ });
767
+ server.tool(
768
+ import_core2.YESOD_TOOLS.yesod_create_workflow.name,
769
+ import_core2.YESOD_TOOLS.yesod_create_workflow.description,
770
+ import_core2.YESOD_TOOLS.yesod_create_workflow.inputSchema.properties,
771
+ async (args) => {
772
+ const name = args.name;
773
+ const steps = args.steps.map(
774
+ (s) => ({
775
+ id: s.id,
776
+ name: s.name,
777
+ task: s.task,
778
+ expectedResult: s.expectedResult,
779
+ runtime: s.runtime,
780
+ decision: s.decision,
781
+ dependsOn: s.dependsOn ?? [],
782
+ timeout: s.timeout
783
+ })
784
+ );
785
+ const workflow = {
786
+ id: "",
787
+ name,
788
+ steps,
789
+ state: "created",
790
+ createdAt: Date.now()
791
+ };
792
+ const created = engine.createWorkflow(workflow);
793
+ await engine.startWorkflow(created.id);
794
+ return {
795
+ content: [
796
+ {
797
+ type: "text",
798
+ text: JSON.stringify(
799
+ {
800
+ workflowId: created.id,
801
+ status: "started",
802
+ steps: created.steps.map((s) => s.id)
803
+ },
804
+ null,
805
+ 2
806
+ )
807
+ }
808
+ ]
809
+ };
810
+ }
811
+ );
812
+ server.tool(
813
+ import_core2.YESOD_TOOLS.yesod_workflow_status.name,
814
+ import_core2.YESOD_TOOLS.yesod_workflow_status.description,
815
+ import_core2.YESOD_TOOLS.yesod_workflow_status.inputSchema.properties,
816
+ async (args) => {
817
+ const workflowId = args.workflowId;
818
+ try {
819
+ const status = engine.getWorkflowStatus(workflowId);
820
+ return {
821
+ content: [
822
+ { type: "text", text: JSON.stringify(status, null, 2) }
823
+ ]
824
+ };
825
+ } catch (err) {
826
+ return {
827
+ content: [
828
+ {
829
+ type: "text",
830
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
831
+ }
832
+ ],
833
+ isError: true
834
+ };
835
+ }
836
+ }
837
+ );
838
+ server.tool(
839
+ import_core2.YESOD_TOOLS.yesod_cancel_workflow.name,
840
+ import_core2.YESOD_TOOLS.yesod_cancel_workflow.description,
841
+ import_core2.YESOD_TOOLS.yesod_cancel_workflow.inputSchema.properties,
842
+ async (args) => {
843
+ const workflowId = args.workflowId;
844
+ try {
845
+ await engine.cancelWorkflow(workflowId);
846
+ return {
847
+ content: [
848
+ {
849
+ type: "text",
850
+ text: JSON.stringify({ workflowId, status: "cancelled" })
851
+ }
852
+ ]
853
+ };
854
+ } catch (err) {
855
+ return {
856
+ content: [
857
+ {
858
+ type: "text",
859
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
860
+ }
861
+ ],
862
+ isError: true
863
+ };
864
+ }
865
+ }
866
+ );
867
+ server.tool(
868
+ import_core2.YESOD_TOOLS.yesod_cost_summary.name,
869
+ import_core2.YESOD_TOOLS.yesod_cost_summary.description,
870
+ import_core2.YESOD_TOOLS.yesod_cost_summary.inputSchema.properties ?? {},
871
+ async (args) => {
872
+ const workflowId = args.workflowId;
873
+ const summary = engine.getCostSummary(workflowId);
874
+ return {
875
+ content: [
876
+ { type: "text", text: JSON.stringify(summary, null, 2) }
877
+ ]
878
+ };
879
+ }
880
+ );
881
+ return server;
882
+ }
883
+ async function startMcpServer(engine) {
884
+ const server = createYesodMcpServer(engine);
885
+ const transport = new import_stdio.StdioServerTransport();
886
+ await server.connect(transport);
887
+ }
82
888
  // Annotate the CommonJS export names for ESM import in node:
83
889
  0 && (module.exports = {
84
- GatewayApiClient,
85
890
  GatewayWebSocketClient,
86
- OpenClawAdapter
891
+ OpenClawAdapter,
892
+ buildAuthPayload,
893
+ createYesodMcpServer,
894
+ deviceIdFromPrivateKey,
895
+ enrichInstruction,
896
+ generateDeviceIdentity,
897
+ loadOrCreateIdentity,
898
+ parseOutput,
899
+ signPayload,
900
+ startMcpServer
87
901
  });
88
902
  //# sourceMappingURL=index.cjs.map