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