@mind2flow/daemon 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.
Files changed (41) hide show
  1. package/dist/command-center-client.d.ts +32 -0
  2. package/dist/command-center-client.d.ts.map +1 -0
  3. package/dist/command-center-client.js +137 -0
  4. package/dist/command-center-client.js.map +1 -0
  5. package/dist/config.d.ts +51 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +89 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/daemon.d.ts +21 -0
  10. package/dist/daemon.d.ts.map +1 -0
  11. package/dist/daemon.js +1937 -0
  12. package/dist/daemon.js.map +1 -0
  13. package/dist/gateway-client.d.ts +175 -0
  14. package/dist/gateway-client.d.ts.map +1 -0
  15. package/dist/gateway-client.js +494 -0
  16. package/dist/gateway-client.js.map +1 -0
  17. package/dist/heartbeat.d.ts +35 -0
  18. package/dist/heartbeat.d.ts.map +1 -0
  19. package/dist/heartbeat.js +116 -0
  20. package/dist/heartbeat.js.map +1 -0
  21. package/dist/openclaw-manager.d.ts +41 -0
  22. package/dist/openclaw-manager.d.ts.map +1 -0
  23. package/dist/openclaw-manager.js +199 -0
  24. package/dist/openclaw-manager.js.map +1 -0
  25. package/dist/protocols/index.d.ts +21 -0
  26. package/dist/protocols/index.d.ts.map +1 -0
  27. package/dist/protocols/index.js +30 -0
  28. package/dist/protocols/index.js.map +1 -0
  29. package/dist/protocols/jsonrpc-protocol.d.ts +24 -0
  30. package/dist/protocols/jsonrpc-protocol.d.ts.map +1 -0
  31. package/dist/protocols/jsonrpc-protocol.js +83 -0
  32. package/dist/protocols/jsonrpc-protocol.js.map +1 -0
  33. package/dist/protocols/openclaw-protocol.d.ts +33 -0
  34. package/dist/protocols/openclaw-protocol.d.ts.map +1 -0
  35. package/dist/protocols/openclaw-protocol.js +131 -0
  36. package/dist/protocols/openclaw-protocol.js.map +1 -0
  37. package/dist/protocols/types.d.ts +86 -0
  38. package/dist/protocols/types.d.ts.map +1 -0
  39. package/dist/protocols/types.js +13 -0
  40. package/dist/protocols/types.js.map +1 -0
  41. package/package.json +40 -0
package/dist/daemon.js ADDED
@@ -0,0 +1,1937 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Mind2Flow OpenClaw Daemon — main entry point.
4
+ *
5
+ * This is the bridge process that runs on the user's machine alongside
6
+ * their local OpenClaw Gateway instance. It:
7
+ *
8
+ * 1. Connects upstream to the Mind2Flow Command Center via WebSocket
9
+ * 2. Starts and manages the local OpenClaw Gateway subprocess
10
+ * 3. Connects locally to the Gateway's WS RPC (port 18789)
11
+ * 4. Bridges commands between Command Center ↔ Gateway
12
+ * 5. Sends periodic heartbeats with system info
13
+ *
14
+ * Usage:
15
+ * m2f-daemon # uses config from ~/.m2f-daemon/config.json
16
+ * m2f-daemon --token <install_token> # first-time registration
17
+ *
18
+ * Environment variables: see config.ts for M2F_* variables.
19
+ */
20
+ import "dotenv/config";
21
+ import { Command } from "commander";
22
+ import { loadConfig, runtimeState } from "./config.js";
23
+ import { CommandCenterClient } from "./command-center-client.js";
24
+ import { GatewayClient } from "./gateway-client.js";
25
+ import { OpenClawManager } from "./openclaw-manager.js";
26
+ import { HeartbeatService } from "./heartbeat.js";
27
+ import { createProtocol } from "./protocols/index.js";
28
+ // ── CLI ────────────────────────────────────────────────────────────
29
+ const program = new Command()
30
+ .name("m2f-daemon")
31
+ .description("Mind2Flow OpenClaw Command Center — Agent Daemon")
32
+ .version("0.1.0")
33
+ .option("--token <token>", "One-time install token for first registration")
34
+ .option("--server <url>", "Command Center WebSocket URL")
35
+ .option("--port <port>", "Local OpenClaw Gateway port", "18789")
36
+ .option("--protocol <type>", "Gateway wire protocol: openclaw or jsonrpc", "openclaw")
37
+ .option("--no-gateway", "Do not auto-start the OpenClaw Gateway")
38
+ .option("--debug", "Enable debug logging")
39
+ .parse(process.argv);
40
+ const opts = program.opts();
41
+ // ── Bootstrap ──────────────────────────────────────────────────────
42
+ async function main() {
43
+ const config = loadConfig();
44
+ // CLI overrides
45
+ if (opts.server)
46
+ config.serverUrl = opts.server;
47
+ if (opts.port)
48
+ config.gatewayPort = parseInt(opts.port, 10);
49
+ if (opts.protocol)
50
+ config.gatewayProtocol = opts.protocol;
51
+ if (opts.gateway === false)
52
+ config.autoStartGateway = false;
53
+ if (opts.debug)
54
+ config.logLevel = "debug";
55
+ // TODO: handle --token for first-time registration flow
56
+ // For now, require instanceId and apiKey to be already set
57
+ if (!config.instanceId || !config.apiKey) {
58
+ if (opts.token) {
59
+ log("info", "Registration with install token is not yet implemented.");
60
+ log("info", "Set M2F_INSTANCE_ID and M2F_API_KEY environment variables or edit ~/.m2f-daemon/config.json");
61
+ process.exit(1);
62
+ }
63
+ log("error", "Missing instanceId or apiKey. Run with --token <token> to register, or set environment variables.");
64
+ process.exit(1);
65
+ }
66
+ log("info", "╔════════════════════════════════════════════╗");
67
+ log("info", "║ Mind2Flow OpenClaw Daemon v0.1.0 ║");
68
+ log("info", "╚════════════════════════════════════════════╝");
69
+ log("info", `Instance: ${config.instanceId}`);
70
+ log("info", `Server: ${config.serverUrl}`);
71
+ log("info", `Gateway: ws://127.0.0.1:${config.gatewayPort}`);
72
+ log("info", `Protocol: ${config.gatewayProtocol}`);
73
+ // ── Initialize components ────────────────────────────────────
74
+ const protocol = createProtocol(config.gatewayProtocol);
75
+ const ccClient = new CommandCenterClient(config);
76
+ const gwClient = new GatewayClient(config.gatewayPort, protocol, config.logLevel);
77
+ const ocManager = new OpenClawManager({
78
+ port: config.gatewayPort,
79
+ openclawBin: config.openclawBin,
80
+ logLevel: config.logLevel,
81
+ });
82
+ const heartbeat = new HeartbeatService(ccClient, gwClient, ocManager, config.heartbeatIntervalMs, config.logLevel);
83
+ // ── OpenClaw Gateway lifecycle ───────────────────────────────
84
+ if (config.autoStartGateway) {
85
+ ocManager.on("started", () => {
86
+ log("info", "OpenClaw Gateway started — connecting RPC client…");
87
+ runtimeState.gatewayPid = ocManager.pid;
88
+ // Give the Gateway a moment to initialize its WS server
89
+ setTimeout(() => gwClient.connect(), 3_000);
90
+ });
91
+ ocManager.on("exited", () => {
92
+ runtimeState.gatewayPid = null;
93
+ runtimeState.gatewayReady = false;
94
+ });
95
+ await ocManager.start();
96
+ }
97
+ else {
98
+ // User-managed Gateway — connect directly
99
+ gwClient.connect();
100
+ }
101
+ gwClient.on("connected", () => {
102
+ runtimeState.gatewayReady = true;
103
+ log("info", "Gateway RPC ready");
104
+ });
105
+ gwClient.on("disconnected", () => {
106
+ runtimeState.gatewayReady = false;
107
+ });
108
+ // ── Command Center connection ────────────────────────────────
109
+ ccClient.on("connected", () => {
110
+ runtimeState.connected = true;
111
+ heartbeat.start();
112
+ heartbeat.sendSystemInfo();
113
+ });
114
+ ccClient.on("disconnected", () => {
115
+ runtimeState.connected = false;
116
+ heartbeat.stop();
117
+ });
118
+ ccClient.on("message", (msg) => {
119
+ handleServerMessage(msg, config, ccClient, gwClient, ocManager);
120
+ });
121
+ // Connect to Command Center
122
+ ccClient.connect();
123
+ // ── Graceful shutdown ────────────────────────────────────────
124
+ const shutdown = async (signal) => {
125
+ log("info", `Received ${signal} — shutting down…`);
126
+ heartbeat.stop();
127
+ ccClient.disconnect();
128
+ gwClient.disconnect();
129
+ await ocManager.stop();
130
+ // Clear LLM keys from memory
131
+ runtimeState.llmConfig = null;
132
+ log("info", "Daemon stopped.");
133
+ process.exit(0);
134
+ };
135
+ process.on("SIGINT", () => shutdown("SIGINT"));
136
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
137
+ // Keep process alive
138
+ await new Promise(() => { });
139
+ }
140
+ // ── Message handler ────────────────────────────────────────────────
141
+ async function handleServerMessage(msg, config, ccClient, gwClient, ocManager) {
142
+ switch (msg.type) {
143
+ // ── Chat instruction from user ──────────────────────────────
144
+ case "chat_instruction": {
145
+ const messageId = msg.message_id;
146
+ const content = msg.content;
147
+ const userId = msg.user_id;
148
+ const sessionId = msg.session_id || undefined;
149
+ log("info", `Chat instruction received (msg=${messageId}${sessionId ? `, session=${sessionId}` : ''}): ${content.slice(0, 80)}…`);
150
+ if (!gwClient.isConnected) {
151
+ ccClient.send({
152
+ type: "chat_response",
153
+ ref_message_id: messageId,
154
+ user_id: userId,
155
+ content: "⚠️ OpenClaw Gateway is not connected. Please wait or check the instance status.",
156
+ });
157
+ return;
158
+ }
159
+ try {
160
+ // Call the OpenClaw agent via its RPC — pass sessionId if targeting a specific session
161
+ const agentOpts = {};
162
+ if (sessionId)
163
+ agentOpts.sessionId = sessionId;
164
+ const result = await gwClient.agent(content, Object.keys(agentOpts).length ? agentOpts : undefined);
165
+ const fullText = typeof result === "string" ? result : JSON.stringify(result);
166
+ // Stream tokens back in chunks for real-time UI feel
167
+ const CHUNK_SIZE = 12; // characters per chunk
168
+ for (let i = 0; i < fullText.length; i += CHUNK_SIZE) {
169
+ const token = fullText.slice(i, i + CHUNK_SIZE);
170
+ const done = i + CHUNK_SIZE >= fullText.length;
171
+ ccClient.send({
172
+ type: "chat_stream",
173
+ ref_message_id: messageId,
174
+ user_id: userId,
175
+ token,
176
+ done,
177
+ });
178
+ // Small delay to feel natural (~30ms per chunk)
179
+ if (!done)
180
+ await new Promise(r => setTimeout(r, 30));
181
+ }
182
+ // Also send the final complete response for DB persistence
183
+ ccClient.send({
184
+ type: "chat_response",
185
+ ref_message_id: messageId,
186
+ user_id: userId,
187
+ content: fullText,
188
+ });
189
+ }
190
+ catch (err) {
191
+ const errMsg = err instanceof Error ? err.message : String(err);
192
+ log("error", `Agent RPC failed: ${errMsg}`);
193
+ ccClient.send({
194
+ type: "chat_response",
195
+ ref_message_id: messageId,
196
+ user_id: userId,
197
+ content: `❌ Agent error: ${errMsg}`,
198
+ });
199
+ }
200
+ break;
201
+ }
202
+ // ── Execute a predefined task ───────────────────────────────
203
+ case "execute_task": {
204
+ const executionId = msg.execution_id;
205
+ const taskConfig = msg.task_config;
206
+ const userId = msg.user_id;
207
+ log("info", `Executing task (execution=${executionId})`);
208
+ if (!gwClient.isConnected) {
209
+ ccClient.send({
210
+ type: "task_result",
211
+ execution_id: executionId,
212
+ user_id: userId,
213
+ status: "failed",
214
+ error: "OpenClaw Gateway is not connected",
215
+ });
216
+ return;
217
+ }
218
+ try {
219
+ const prompt = taskConfig.prompt || "";
220
+ const result = await gwClient.agent(prompt);
221
+ ccClient.send({
222
+ type: "task_result",
223
+ execution_id: executionId,
224
+ user_id: userId,
225
+ status: "completed",
226
+ output: result,
227
+ });
228
+ }
229
+ catch (err) {
230
+ const errMsg = err instanceof Error ? err.message : String(err);
231
+ ccClient.send({
232
+ type: "task_result",
233
+ execution_id: executionId,
234
+ user_id: userId,
235
+ status: "failed",
236
+ error: errMsg,
237
+ });
238
+ }
239
+ break;
240
+ }
241
+ // ── Update LLM configuration ────────────────────────────────
242
+ case "update_llm_config": {
243
+ const llmConfig = {
244
+ provider: msg.llm_provider,
245
+ apiKey: msg.llm_api_key,
246
+ model: msg.llm_model,
247
+ baseUrl: msg.base_url,
248
+ temperature: msg.temperature,
249
+ maxTokens: msg.max_tokens,
250
+ };
251
+ log("info", `LLM config update: provider=${llmConfig.provider} model=${llmConfig.model}`);
252
+ // Store in runtime memory only (never on disk)
253
+ runtimeState.llmConfig = llmConfig;
254
+ // Set environment variables for the Gateway process
255
+ ocManager.applyLLMConfig(llmConfig);
256
+ // If Gateway is connected, patch config via RPC
257
+ if (gwClient.isConnected) {
258
+ try {
259
+ const current = await gwClient.configGet();
260
+ const configRaw = `{ agent: { model: "${llmConfig.provider}/${llmConfig.model}" } }`;
261
+ await gwClient.configPatch(configRaw, current.hash);
262
+ log("info", "Gateway LLM config patched successfully");
263
+ }
264
+ catch (err) {
265
+ const errMsg = err instanceof Error ? err.message : String(err);
266
+ log("warn", `Failed to patch Gateway config (will apply on restart): ${errMsg}`);
267
+ // Restart Gateway so it picks up env vars
268
+ await ocManager.restart();
269
+ }
270
+ }
271
+ else {
272
+ // Gateway not connected — restart it with new env vars
273
+ log("info", "Gateway not connected — restarting with new LLM config…");
274
+ await ocManager.restart();
275
+ }
276
+ break;
277
+ }
278
+ // ── Gateway Config management ─────────────────────────────────
279
+ case "config_get": {
280
+ const userId = msg.user_id;
281
+ const requestId = msg.request_id;
282
+ log("info", "Gateway config get requested");
283
+ if (gwClient.isConnected) {
284
+ try {
285
+ const result = await gwClient.configGet();
286
+ ccClient.send({
287
+ type: "config_get_response",
288
+ user_id: userId,
289
+ request_id: requestId,
290
+ config: result.config,
291
+ hash: result.hash,
292
+ });
293
+ log("info", "Gateway config retrieved");
294
+ }
295
+ catch (err) {
296
+ const errMsg = err instanceof Error ? err.message : String(err);
297
+ log("warn", `Failed to get config: ${errMsg}`);
298
+ ccClient.send({
299
+ type: "config_get_response",
300
+ user_id: userId,
301
+ request_id: requestId,
302
+ error: errMsg,
303
+ });
304
+ }
305
+ }
306
+ else {
307
+ ccClient.send({
308
+ type: "config_get_response",
309
+ user_id: userId,
310
+ request_id: requestId,
311
+ error: "Gateway not connected",
312
+ });
313
+ }
314
+ break;
315
+ }
316
+ case "config_patch": {
317
+ const userId = msg.user_id;
318
+ const requestId = msg.request_id;
319
+ const raw = msg.raw;
320
+ const baseHash = msg.base_hash;
321
+ log("info", `Gateway config patch (hash=${baseHash?.slice(0, 8)}…)`);
322
+ if (gwClient.isConnected) {
323
+ try {
324
+ const result = await gwClient.configPatch(raw, baseHash);
325
+ ccClient.send({
326
+ type: "config_patch_response",
327
+ user_id: userId,
328
+ request_id: requestId,
329
+ result,
330
+ });
331
+ log("info", "Gateway config patched successfully");
332
+ }
333
+ catch (err) {
334
+ const errMsg = err instanceof Error ? err.message : String(err);
335
+ log("warn", `Failed to patch config: ${errMsg}`);
336
+ ccClient.send({
337
+ type: "config_patch_response",
338
+ user_id: userId,
339
+ request_id: requestId,
340
+ error: errMsg,
341
+ });
342
+ }
343
+ }
344
+ else {
345
+ ccClient.send({
346
+ type: "config_patch_response",
347
+ user_id: userId,
348
+ request_id: requestId,
349
+ error: "Gateway not connected",
350
+ });
351
+ }
352
+ break;
353
+ }
354
+ case "config_export": {
355
+ const userId = msg.user_id;
356
+ const requestId = msg.request_id;
357
+ log("info", "Gateway config export requested (via config.get raw)");
358
+ if (gwClient.isConnected) {
359
+ try {
360
+ // config.get returns { config, raw, hash } — use raw field as export
361
+ const result = await gwClient.configExport();
362
+ const r = result;
363
+ ccClient.send({
364
+ type: "config_export_response",
365
+ user_id: userId,
366
+ request_id: requestId,
367
+ exported: r.raw ?? JSON.stringify(r.config ?? {}, null, 2),
368
+ });
369
+ log("info", "Gateway config exported");
370
+ }
371
+ catch (err) {
372
+ const errMsg = err instanceof Error ? err.message : String(err);
373
+ log("warn", `Failed to export config: ${errMsg}`);
374
+ ccClient.send({
375
+ type: "config_export_response",
376
+ user_id: userId,
377
+ request_id: requestId,
378
+ error: errMsg,
379
+ });
380
+ }
381
+ }
382
+ else {
383
+ ccClient.send({
384
+ type: "config_export_response",
385
+ user_id: userId,
386
+ request_id: requestId,
387
+ error: "Gateway not connected",
388
+ });
389
+ }
390
+ break;
391
+ }
392
+ case "config_validate": {
393
+ const userId = msg.user_id;
394
+ const requestId = msg.request_id;
395
+ const raw = msg.raw;
396
+ log("info", "Gateway config validate requested");
397
+ if (gwClient.isConnected) {
398
+ try {
399
+ // Gateway has no explicit validate method.
400
+ // We use config.set with the raw config — if it fails validation,
401
+ // the gateway returns an error with issues. If it succeeds, the config
402
+ // is written. To avoid side effects, we do a client-side JSON parse check.
403
+ // For now, try JSON parse and report basic validity.
404
+ let valid = true;
405
+ const errors = [];
406
+ try {
407
+ JSON.parse(raw);
408
+ }
409
+ catch (parseErr) {
410
+ valid = false;
411
+ errors.push(parseErr instanceof Error ? parseErr.message : String(parseErr));
412
+ }
413
+ ccClient.send({
414
+ type: "config_validate_response",
415
+ user_id: userId,
416
+ request_id: requestId,
417
+ validation: { valid, errors },
418
+ });
419
+ log("info", "Gateway config validated (client-side)");
420
+ }
421
+ catch (err) {
422
+ const errMsg = err instanceof Error ? err.message : String(err);
423
+ log("warn", `Failed to validate config: ${errMsg}`);
424
+ ccClient.send({
425
+ type: "config_validate_response",
426
+ user_id: userId,
427
+ request_id: requestId,
428
+ error: errMsg,
429
+ });
430
+ }
431
+ }
432
+ else {
433
+ ccClient.send({
434
+ type: "config_validate_response",
435
+ user_id: userId,
436
+ request_id: requestId,
437
+ error: "Gateway not connected",
438
+ });
439
+ }
440
+ break;
441
+ }
442
+ case "config_diff": {
443
+ const userId = msg.user_id;
444
+ const requestId = msg.request_id;
445
+ const proposed = msg.proposed;
446
+ log("info", "Gateway config diff requested");
447
+ if (gwClient.isConnected) {
448
+ try {
449
+ // Gateway has no explicit diff method.
450
+ // Get current config and compute a basic diff client-side.
451
+ const current = await gwClient.configGet();
452
+ const currentRaw = current.raw ?? JSON.stringify(current.config ?? {}, null, 2);
453
+ const changed = currentRaw.trim() !== proposed.trim();
454
+ ccClient.send({
455
+ type: "config_diff_response",
456
+ user_id: userId,
457
+ request_id: requestId,
458
+ diff: {
459
+ changed,
460
+ currentLength: currentRaw.length,
461
+ proposedLength: proposed.length,
462
+ },
463
+ });
464
+ log("info", "Gateway config diff computed (client-side)");
465
+ }
466
+ catch (err) {
467
+ const errMsg = err instanceof Error ? err.message : String(err);
468
+ log("warn", `Failed to compute config diff: ${errMsg}`);
469
+ ccClient.send({
470
+ type: "config_diff_response",
471
+ user_id: userId,
472
+ request_id: requestId,
473
+ error: errMsg,
474
+ });
475
+ }
476
+ }
477
+ else {
478
+ ccClient.send({
479
+ type: "config_diff_response",
480
+ user_id: userId,
481
+ request_id: requestId,
482
+ error: "Gateway not connected",
483
+ });
484
+ }
485
+ break;
486
+ }
487
+ // ── Skills management ─────────────────────────────────────────
488
+ case "skills_status": {
489
+ const userId = msg.user_id;
490
+ const requestId = msg.request_id;
491
+ log("info", "Skills status requested");
492
+ if (gwClient.isConnected) {
493
+ try {
494
+ const result = await gwClient.skillsStatus();
495
+ ccClient.send({
496
+ type: "skills_status_response",
497
+ user_id: userId,
498
+ request_id: requestId,
499
+ skills: result,
500
+ });
501
+ log("info", "Skills status fetched from Gateway");
502
+ }
503
+ catch (err) {
504
+ const errMsg = err instanceof Error ? err.message : String(err);
505
+ log("warn", `Failed to get skills status: ${errMsg}`);
506
+ ccClient.send({
507
+ type: "skills_status_response",
508
+ user_id: userId,
509
+ request_id: requestId,
510
+ error: errMsg,
511
+ });
512
+ }
513
+ }
514
+ else {
515
+ ccClient.send({
516
+ type: "skills_status_response",
517
+ user_id: userId,
518
+ request_id: requestId,
519
+ error: "Gateway not connected",
520
+ });
521
+ }
522
+ break;
523
+ }
524
+ case "skill_update": {
525
+ const userId = msg.user_id;
526
+ const requestId = msg.request_id;
527
+ const skillKey = msg.skill_key;
528
+ const updates = msg.updates;
529
+ log("info", `Skill update: key=${skillKey}`);
530
+ if (gwClient.isConnected) {
531
+ try {
532
+ const result = await gwClient.skillsUpdate(skillKey, updates);
533
+ ccClient.send({
534
+ type: "skill_update_response",
535
+ user_id: userId,
536
+ request_id: requestId,
537
+ skill_key: skillKey,
538
+ result,
539
+ });
540
+ log("info", `Skill ${skillKey} updated`);
541
+ }
542
+ catch (err) {
543
+ const errMsg = err instanceof Error ? err.message : String(err);
544
+ log("warn", `Failed to update skill ${skillKey}: ${errMsg}`);
545
+ ccClient.send({
546
+ type: "skill_update_response",
547
+ user_id: userId,
548
+ request_id: requestId,
549
+ skill_key: skillKey,
550
+ error: errMsg,
551
+ });
552
+ }
553
+ }
554
+ else {
555
+ ccClient.send({
556
+ type: "skill_update_response",
557
+ user_id: userId,
558
+ request_id: requestId,
559
+ skill_key: skillKey,
560
+ error: "Gateway not connected",
561
+ });
562
+ }
563
+ break;
564
+ }
565
+ case "skill_install": {
566
+ const userId = msg.user_id;
567
+ const requestId = msg.request_id;
568
+ const skillKey = msg.skill_key;
569
+ const installOptionId = msg.install_option_id;
570
+ log("info", `Skill install: key=${skillKey} option=${installOptionId || "default"}`);
571
+ if (gwClient.isConnected) {
572
+ try {
573
+ const result = await gwClient.skillsInstall(skillKey, installOptionId);
574
+ ccClient.send({
575
+ type: "skill_install_response",
576
+ user_id: userId,
577
+ request_id: requestId,
578
+ skill_key: skillKey,
579
+ result,
580
+ });
581
+ log("info", `Skill ${skillKey} dependencies installed`);
582
+ }
583
+ catch (err) {
584
+ const errMsg = err instanceof Error ? err.message : String(err);
585
+ log("warn", `Failed to install skill ${skillKey}: ${errMsg}`);
586
+ ccClient.send({
587
+ type: "skill_install_response",
588
+ user_id: userId,
589
+ request_id: requestId,
590
+ skill_key: skillKey,
591
+ error: errMsg,
592
+ });
593
+ }
594
+ }
595
+ else {
596
+ ccClient.send({
597
+ type: "skill_install_response",
598
+ user_id: userId,
599
+ request_id: requestId,
600
+ skill_key: skillKey,
601
+ error: "Gateway not connected",
602
+ });
603
+ }
604
+ break;
605
+ }
606
+ case "skills_bins": {
607
+ const userId = msg.user_id;
608
+ const requestId = msg.request_id;
609
+ log("info", "Skills bins requested");
610
+ if (gwClient.isConnected) {
611
+ try {
612
+ const result = await gwClient.skillsBins();
613
+ ccClient.send({
614
+ type: "skills_bins_response",
615
+ user_id: userId,
616
+ request_id: requestId,
617
+ bins: result,
618
+ });
619
+ }
620
+ catch (err) {
621
+ const errMsg = err instanceof Error ? err.message : String(err);
622
+ ccClient.send({
623
+ type: "skills_bins_response",
624
+ user_id: userId,
625
+ request_id: requestId,
626
+ error: errMsg,
627
+ });
628
+ }
629
+ }
630
+ else {
631
+ ccClient.send({
632
+ type: "skills_bins_response",
633
+ user_id: userId,
634
+ request_id: requestId,
635
+ error: "Gateway not connected",
636
+ });
637
+ }
638
+ break;
639
+ }
640
+ // ── Cron Jobs management ──────────────────────────────────────
641
+ case "cron_list": {
642
+ const userId = msg.user_id;
643
+ const requestId = msg.request_id;
644
+ log("info", "Cron jobs list requested");
645
+ if (gwClient.isConnected) {
646
+ try {
647
+ const result = await gwClient.cronList();
648
+ ccClient.send({
649
+ type: "cron_list_response",
650
+ user_id: userId,
651
+ request_id: requestId,
652
+ jobs: result,
653
+ });
654
+ log("info", "Cron jobs listed");
655
+ }
656
+ catch (err) {
657
+ const errMsg = err instanceof Error ? err.message : String(err);
658
+ log("warn", `Failed to list cron jobs: ${errMsg}`);
659
+ ccClient.send({
660
+ type: "cron_list_response",
661
+ user_id: userId,
662
+ request_id: requestId,
663
+ error: errMsg,
664
+ });
665
+ }
666
+ }
667
+ else {
668
+ ccClient.send({
669
+ type: "cron_list_response",
670
+ user_id: userId,
671
+ request_id: requestId,
672
+ error: "Gateway not connected",
673
+ });
674
+ }
675
+ break;
676
+ }
677
+ case "cron_add": {
678
+ const userId = msg.user_id;
679
+ const requestId = msg.request_id;
680
+ const job = msg.job;
681
+ log("info", `Cron job add: ${job?.name || "unnamed"}`);
682
+ if (gwClient.isConnected) {
683
+ try {
684
+ const result = await gwClient.cronAdd(job);
685
+ ccClient.send({
686
+ type: "cron_add_response",
687
+ user_id: userId,
688
+ request_id: requestId,
689
+ result,
690
+ });
691
+ log("info", "Cron job added");
692
+ }
693
+ catch (err) {
694
+ const errMsg = err instanceof Error ? err.message : String(err);
695
+ log("warn", `Failed to add cron job: ${errMsg}`);
696
+ ccClient.send({
697
+ type: "cron_add_response",
698
+ user_id: userId,
699
+ request_id: requestId,
700
+ error: errMsg,
701
+ });
702
+ }
703
+ }
704
+ else {
705
+ ccClient.send({
706
+ type: "cron_add_response",
707
+ user_id: userId,
708
+ request_id: requestId,
709
+ error: "Gateway not connected",
710
+ });
711
+ }
712
+ break;
713
+ }
714
+ case "cron_update": {
715
+ const userId = msg.user_id;
716
+ const requestId = msg.request_id;
717
+ const jobId = msg.job_id;
718
+ const patch = msg.patch;
719
+ log("info", `Cron job update: ${jobId}`);
720
+ if (gwClient.isConnected) {
721
+ try {
722
+ const result = await gwClient.cronUpdate(jobId, patch);
723
+ ccClient.send({
724
+ type: "cron_update_response",
725
+ user_id: userId,
726
+ request_id: requestId,
727
+ job_id: jobId,
728
+ result,
729
+ });
730
+ log("info", `Cron job ${jobId} updated`);
731
+ }
732
+ catch (err) {
733
+ const errMsg = err instanceof Error ? err.message : String(err);
734
+ log("warn", `Failed to update cron job ${jobId}: ${errMsg}`);
735
+ ccClient.send({
736
+ type: "cron_update_response",
737
+ user_id: userId,
738
+ request_id: requestId,
739
+ job_id: jobId,
740
+ error: errMsg,
741
+ });
742
+ }
743
+ }
744
+ else {
745
+ ccClient.send({
746
+ type: "cron_update_response",
747
+ user_id: userId,
748
+ request_id: requestId,
749
+ job_id: jobId,
750
+ error: "Gateway not connected",
751
+ });
752
+ }
753
+ break;
754
+ }
755
+ case "cron_remove": {
756
+ const userId = msg.user_id;
757
+ const requestId = msg.request_id;
758
+ const jobId = msg.job_id;
759
+ log("info", `Cron job remove: ${jobId}`);
760
+ if (gwClient.isConnected) {
761
+ try {
762
+ const result = await gwClient.cronRemove(jobId);
763
+ ccClient.send({
764
+ type: "cron_remove_response",
765
+ user_id: userId,
766
+ request_id: requestId,
767
+ job_id: jobId,
768
+ result,
769
+ });
770
+ log("info", `Cron job ${jobId} removed`);
771
+ }
772
+ catch (err) {
773
+ const errMsg = err instanceof Error ? err.message : String(err);
774
+ log("warn", `Failed to remove cron job ${jobId}: ${errMsg}`);
775
+ ccClient.send({
776
+ type: "cron_remove_response",
777
+ user_id: userId,
778
+ request_id: requestId,
779
+ job_id: jobId,
780
+ error: errMsg,
781
+ });
782
+ }
783
+ }
784
+ else {
785
+ ccClient.send({
786
+ type: "cron_remove_response",
787
+ user_id: userId,
788
+ request_id: requestId,
789
+ job_id: jobId,
790
+ error: "Gateway not connected",
791
+ });
792
+ }
793
+ break;
794
+ }
795
+ case "cron_run": {
796
+ const userId = msg.user_id;
797
+ const requestId = msg.request_id;
798
+ const jobId = msg.job_id;
799
+ const mode = msg.mode;
800
+ log("info", `Cron job manual run: ${jobId} mode=${mode || "force"}`);
801
+ if (gwClient.isConnected) {
802
+ try {
803
+ const result = await gwClient.cronRun(jobId, mode);
804
+ ccClient.send({
805
+ type: "cron_run_response",
806
+ user_id: userId,
807
+ request_id: requestId,
808
+ job_id: jobId,
809
+ result,
810
+ });
811
+ log("info", `Cron job ${jobId} triggered`);
812
+ }
813
+ catch (err) {
814
+ const errMsg = err instanceof Error ? err.message : String(err);
815
+ log("warn", `Failed to run cron job ${jobId}: ${errMsg}`);
816
+ ccClient.send({
817
+ type: "cron_run_response",
818
+ user_id: userId,
819
+ request_id: requestId,
820
+ job_id: jobId,
821
+ error: errMsg,
822
+ });
823
+ }
824
+ }
825
+ else {
826
+ ccClient.send({
827
+ type: "cron_run_response",
828
+ user_id: userId,
829
+ request_id: requestId,
830
+ job_id: jobId,
831
+ error: "Gateway not connected",
832
+ });
833
+ }
834
+ break;
835
+ }
836
+ case "cron_runs": {
837
+ const userId = msg.user_id;
838
+ const requestId = msg.request_id;
839
+ const jobId = msg.job_id;
840
+ const limit = msg.limit;
841
+ log("info", `Cron job runs history: ${jobId}`);
842
+ if (gwClient.isConnected) {
843
+ try {
844
+ const result = await gwClient.cronRuns(jobId, limit);
845
+ ccClient.send({
846
+ type: "cron_runs_response",
847
+ user_id: userId,
848
+ request_id: requestId,
849
+ job_id: jobId,
850
+ runs: result,
851
+ });
852
+ }
853
+ catch (err) {
854
+ const errMsg = err instanceof Error ? err.message : String(err);
855
+ ccClient.send({
856
+ type: "cron_runs_response",
857
+ user_id: userId,
858
+ request_id: requestId,
859
+ job_id: jobId,
860
+ error: errMsg,
861
+ });
862
+ }
863
+ }
864
+ else {
865
+ ccClient.send({
866
+ type: "cron_runs_response",
867
+ user_id: userId,
868
+ request_id: requestId,
869
+ job_id: jobId,
870
+ error: "Gateway not connected",
871
+ });
872
+ }
873
+ break;
874
+ }
875
+ case "cron_status": {
876
+ const userId = msg.user_id;
877
+ const requestId = msg.request_id;
878
+ log("info", "Cron scheduler status requested");
879
+ if (gwClient.isConnected) {
880
+ try {
881
+ const result = await gwClient.cronStatus();
882
+ ccClient.send({
883
+ type: "cron_status_response",
884
+ user_id: userId,
885
+ request_id: requestId,
886
+ status: result,
887
+ });
888
+ log("info", "Cron scheduler status retrieved");
889
+ }
890
+ catch (err) {
891
+ const errMsg = err instanceof Error ? err.message : String(err);
892
+ log("warn", `Failed to get cron status: ${errMsg}`);
893
+ ccClient.send({
894
+ type: "cron_status_response",
895
+ user_id: userId,
896
+ request_id: requestId,
897
+ error: errMsg,
898
+ });
899
+ }
900
+ }
901
+ else {
902
+ ccClient.send({
903
+ type: "cron_status_response",
904
+ user_id: userId,
905
+ request_id: requestId,
906
+ error: "Gateway not connected",
907
+ });
908
+ }
909
+ break;
910
+ }
911
+ case "cron_toggle": {
912
+ const userId = msg.user_id;
913
+ const requestId = msg.request_id;
914
+ const enabled = msg.enabled;
915
+ log("info", `Cron scheduler toggle: enabled=${enabled}`);
916
+ if (gwClient.isConnected) {
917
+ try {
918
+ // Gateway has no native cron.toggle RPC.
919
+ // Toggle via config.patch to set cron.enabled.
920
+ const result = await gwClient.cronToggle(enabled);
921
+ ccClient.send({
922
+ type: "cron_toggle_response",
923
+ user_id: userId,
924
+ request_id: requestId,
925
+ result: { ok: true, enabled },
926
+ });
927
+ log("info", `Cron scheduler toggled to ${enabled ? "enabled" : "paused"} via config.patch`);
928
+ }
929
+ catch (err) {
930
+ const errMsg = err instanceof Error ? err.message : String(err);
931
+ log("warn", `Failed to toggle cron: ${errMsg}`);
932
+ ccClient.send({
933
+ type: "cron_toggle_response",
934
+ user_id: userId,
935
+ request_id: requestId,
936
+ error: errMsg,
937
+ });
938
+ }
939
+ }
940
+ else {
941
+ ccClient.send({
942
+ type: "cron_toggle_response",
943
+ user_id: userId,
944
+ request_id: requestId,
945
+ error: "Gateway not connected",
946
+ });
947
+ }
948
+ break;
949
+ }
950
+ // ── Usage / Token Tracking ──────────────────────────────────
951
+ case "usage_current": {
952
+ const userId = msg.user_id;
953
+ const requestId = msg.request_id;
954
+ log("info", "Usage current snapshot requested");
955
+ if (gwClient.isConnected) {
956
+ try {
957
+ const result = await gwClient.usageStatus();
958
+ ccClient.send({
959
+ type: "usage_current_response",
960
+ user_id: userId,
961
+ request_id: requestId,
962
+ usage: result,
963
+ });
964
+ log("info", "Usage current retrieved");
965
+ }
966
+ catch (err) {
967
+ const errMsg = err instanceof Error ? err.message : String(err);
968
+ log("warn", `Failed to get usage: ${errMsg}`);
969
+ ccClient.send({
970
+ type: "usage_current_response",
971
+ user_id: userId,
972
+ request_id: requestId,
973
+ error: errMsg,
974
+ });
975
+ }
976
+ }
977
+ else {
978
+ ccClient.send({
979
+ type: "usage_current_response",
980
+ user_id: userId,
981
+ request_id: requestId,
982
+ error: "Gateway not connected",
983
+ });
984
+ }
985
+ break;
986
+ }
987
+ case "usage_history": {
988
+ const userId = msg.user_id;
989
+ const requestId = msg.request_id;
990
+ const days = msg.days;
991
+ const granularity = msg.granularity;
992
+ log("info", `Usage history requested (days=${days ?? 30}, granularity=${granularity ?? "daily"})`);
993
+ if (gwClient.isConnected) {
994
+ try {
995
+ const opts = {};
996
+ if (days !== undefined)
997
+ opts.days = days;
998
+ if (granularity)
999
+ opts.granularity = granularity;
1000
+ const result = await gwClient.usageCost(Object.keys(opts).length ? opts : undefined);
1001
+ ccClient.send({
1002
+ type: "usage_history_response",
1003
+ user_id: userId,
1004
+ request_id: requestId,
1005
+ history: result,
1006
+ });
1007
+ log("info", "Usage history retrieved");
1008
+ }
1009
+ catch (err) {
1010
+ const errMsg = err instanceof Error ? err.message : String(err);
1011
+ log("warn", `Failed to get usage history: ${errMsg}`);
1012
+ ccClient.send({
1013
+ type: "usage_history_response",
1014
+ user_id: userId,
1015
+ request_id: requestId,
1016
+ error: errMsg,
1017
+ });
1018
+ }
1019
+ }
1020
+ else {
1021
+ ccClient.send({
1022
+ type: "usage_history_response",
1023
+ user_id: userId,
1024
+ request_id: requestId,
1025
+ error: "Gateway not connected",
1026
+ });
1027
+ }
1028
+ break;
1029
+ }
1030
+ // ── Channels management ─────────────────────────────────────
1031
+ case "channels_status": {
1032
+ const userId = msg.user_id;
1033
+ const requestId = msg.request_id;
1034
+ const probe = msg.probe;
1035
+ const timeoutMs = msg.timeout_ms;
1036
+ log("info", `Channels status requested (probe=${probe ?? false})`);
1037
+ if (gwClient.isConnected) {
1038
+ try {
1039
+ const result = await gwClient.channelsStatus(probe, timeoutMs);
1040
+ ccClient.send({
1041
+ type: "channels_status_response",
1042
+ user_id: userId,
1043
+ request_id: requestId,
1044
+ channels: result,
1045
+ });
1046
+ log("info", "Channels status retrieved");
1047
+ }
1048
+ catch (err) {
1049
+ const errMsg = err instanceof Error ? err.message : String(err);
1050
+ log("warn", `Failed to get channels status: ${errMsg}`);
1051
+ ccClient.send({
1052
+ type: "channels_status_response",
1053
+ user_id: userId,
1054
+ request_id: requestId,
1055
+ error: errMsg,
1056
+ });
1057
+ }
1058
+ }
1059
+ else {
1060
+ ccClient.send({
1061
+ type: "channels_status_response",
1062
+ user_id: userId,
1063
+ request_id: requestId,
1064
+ error: "Gateway not connected",
1065
+ });
1066
+ }
1067
+ break;
1068
+ }
1069
+ case "channels_logout": {
1070
+ const userId = msg.user_id;
1071
+ const requestId = msg.request_id;
1072
+ const channel = msg.channel;
1073
+ const accountId = msg.account_id;
1074
+ log("info", `Channel logout: channel=${channel} accountId=${accountId || "default"}`);
1075
+ if (gwClient.isConnected) {
1076
+ try {
1077
+ const result = await gwClient.channelsLogout(channel, accountId);
1078
+ ccClient.send({
1079
+ type: "channels_logout_response",
1080
+ user_id: userId,
1081
+ request_id: requestId,
1082
+ channel,
1083
+ result,
1084
+ });
1085
+ log("info", `Channel ${channel} logged out`);
1086
+ }
1087
+ catch (err) {
1088
+ const errMsg = err instanceof Error ? err.message : String(err);
1089
+ log("warn", `Failed to logout channel ${channel}: ${errMsg}`);
1090
+ ccClient.send({
1091
+ type: "channels_logout_response",
1092
+ user_id: userId,
1093
+ request_id: requestId,
1094
+ channel,
1095
+ error: errMsg,
1096
+ });
1097
+ }
1098
+ }
1099
+ else {
1100
+ ccClient.send({
1101
+ type: "channels_logout_response",
1102
+ user_id: userId,
1103
+ request_id: requestId,
1104
+ channel,
1105
+ error: "Gateway not connected",
1106
+ });
1107
+ }
1108
+ break;
1109
+ }
1110
+ case "web_login_start": {
1111
+ const userId = msg.user_id;
1112
+ const requestId = msg.request_id;
1113
+ const force = msg.force;
1114
+ const timeoutMs = msg.timeout_ms;
1115
+ const accountId = msg.account_id;
1116
+ log("info", `WhatsApp web login start (force=${force ?? false})`);
1117
+ if (gwClient.isConnected) {
1118
+ try {
1119
+ const result = await gwClient.webLoginStart({
1120
+ force,
1121
+ timeoutMs,
1122
+ accountId,
1123
+ });
1124
+ ccClient.send({
1125
+ type: "web_login_start_response",
1126
+ user_id: userId,
1127
+ request_id: requestId,
1128
+ result,
1129
+ });
1130
+ log("info", "WhatsApp web login started — QR generated");
1131
+ }
1132
+ catch (err) {
1133
+ const errMsg = err instanceof Error ? err.message : String(err);
1134
+ log("warn", `Failed to start web login: ${errMsg}`);
1135
+ ccClient.send({
1136
+ type: "web_login_start_response",
1137
+ user_id: userId,
1138
+ request_id: requestId,
1139
+ error: errMsg,
1140
+ });
1141
+ }
1142
+ }
1143
+ else {
1144
+ ccClient.send({
1145
+ type: "web_login_start_response",
1146
+ user_id: userId,
1147
+ request_id: requestId,
1148
+ error: "Gateway not connected",
1149
+ });
1150
+ }
1151
+ break;
1152
+ }
1153
+ case "web_login_wait": {
1154
+ const userId = msg.user_id;
1155
+ const requestId = msg.request_id;
1156
+ const timeoutMs = msg.timeout_ms;
1157
+ const accountId = msg.account_id;
1158
+ log("info", "WhatsApp web login wait…");
1159
+ if (gwClient.isConnected) {
1160
+ try {
1161
+ const result = await gwClient.webLoginWait(timeoutMs, accountId);
1162
+ ccClient.send({
1163
+ type: "web_login_wait_response",
1164
+ user_id: userId,
1165
+ request_id: requestId,
1166
+ result,
1167
+ });
1168
+ log("info", "WhatsApp web login completed");
1169
+ }
1170
+ catch (err) {
1171
+ const errMsg = err instanceof Error ? err.message : String(err);
1172
+ log("warn", `WhatsApp web login wait failed: ${errMsg}`);
1173
+ ccClient.send({
1174
+ type: "web_login_wait_response",
1175
+ user_id: userId,
1176
+ request_id: requestId,
1177
+ error: errMsg,
1178
+ });
1179
+ }
1180
+ }
1181
+ else {
1182
+ ccClient.send({
1183
+ type: "web_login_wait_response",
1184
+ user_id: userId,
1185
+ request_id: requestId,
1186
+ error: "Gateway not connected",
1187
+ });
1188
+ }
1189
+ break;
1190
+ }
1191
+ case "channels_configure": {
1192
+ const userId = msg.user_id;
1193
+ const requestId = msg.request_id;
1194
+ const patch = msg.patch;
1195
+ log("info", "Channels configure via config.patch");
1196
+ if (gwClient.isConnected) {
1197
+ try {
1198
+ // Two-step: get current config → apply patch → save
1199
+ const { config: rawConfig, hash } = await gwClient.configGet();
1200
+ let parsed;
1201
+ if (typeof rawConfig === "string") {
1202
+ parsed = JSON.parse(rawConfig);
1203
+ }
1204
+ else {
1205
+ parsed = (rawConfig ?? {});
1206
+ }
1207
+ // Deep-merge patch into parsed config
1208
+ for (const [key, value] of Object.entries(patch)) {
1209
+ if (typeof value === "object" && value !== null && !Array.isArray(value) &&
1210
+ typeof parsed[key] === "object" && parsed[key] !== null) {
1211
+ parsed[key] = { ...parsed[key], ...value };
1212
+ }
1213
+ else {
1214
+ parsed[key] = value;
1215
+ }
1216
+ }
1217
+ const result = await gwClient.configPatch(JSON.stringify(parsed, null, 2), hash);
1218
+ ccClient.send({
1219
+ type: "channels_configure_response",
1220
+ user_id: userId,
1221
+ request_id: requestId,
1222
+ result,
1223
+ });
1224
+ log("info", "Channel configuration applied");
1225
+ }
1226
+ catch (err) {
1227
+ const errMsg = err instanceof Error ? err.message : String(err);
1228
+ log("warn", `Failed to configure channel: ${errMsg}`);
1229
+ ccClient.send({
1230
+ type: "channels_configure_response",
1231
+ user_id: userId,
1232
+ request_id: requestId,
1233
+ error: errMsg,
1234
+ });
1235
+ }
1236
+ }
1237
+ else {
1238
+ ccClient.send({
1239
+ type: "channels_configure_response",
1240
+ user_id: userId,
1241
+ request_id: requestId,
1242
+ error: "Gateway not connected",
1243
+ });
1244
+ }
1245
+ break;
1246
+ }
1247
+ // ── Sessions management ─────────────────────────────────────
1248
+ case "sessions_list": {
1249
+ const userId = msg.user_id;
1250
+ const requestId = msg.request_id;
1251
+ log("info", "Sessions list requested");
1252
+ if (gwClient.isConnected) {
1253
+ try {
1254
+ const result = await gwClient.sessionsList();
1255
+ ccClient.send({
1256
+ type: "sessions_list_response",
1257
+ user_id: userId,
1258
+ request_id: requestId,
1259
+ sessions: result,
1260
+ });
1261
+ }
1262
+ catch (err) {
1263
+ const errMsg = err instanceof Error ? err.message : String(err);
1264
+ log("warn", `Sessions list failed: ${errMsg}`);
1265
+ ccClient.send({
1266
+ type: "sessions_list_response",
1267
+ user_id: userId,
1268
+ request_id: requestId,
1269
+ error: errMsg,
1270
+ });
1271
+ }
1272
+ }
1273
+ else {
1274
+ ccClient.send({
1275
+ type: "sessions_list_response",
1276
+ user_id: userId,
1277
+ request_id: requestId,
1278
+ error: "Gateway not connected",
1279
+ });
1280
+ }
1281
+ break;
1282
+ }
1283
+ case "sessions_kill": {
1284
+ const userId = msg.user_id;
1285
+ const requestId = msg.request_id;
1286
+ const sessionId = msg.session_id;
1287
+ log("info", `Session kill: session=${sessionId}`);
1288
+ if (gwClient.isConnected) {
1289
+ try {
1290
+ const result = await gwClient.sessionsKill(sessionId);
1291
+ ccClient.send({
1292
+ type: "sessions_kill_response",
1293
+ user_id: userId,
1294
+ request_id: requestId,
1295
+ result,
1296
+ });
1297
+ }
1298
+ catch (err) {
1299
+ const errMsg = err instanceof Error ? err.message : String(err);
1300
+ log("warn", `Session kill failed: ${errMsg}`);
1301
+ ccClient.send({
1302
+ type: "sessions_kill_response",
1303
+ user_id: userId,
1304
+ request_id: requestId,
1305
+ error: errMsg,
1306
+ });
1307
+ }
1308
+ }
1309
+ else {
1310
+ ccClient.send({
1311
+ type: "sessions_kill_response",
1312
+ user_id: userId,
1313
+ request_id: requestId,
1314
+ error: "Gateway not connected",
1315
+ });
1316
+ }
1317
+ break;
1318
+ }
1319
+ case "sessions_preview": {
1320
+ const userId = msg.user_id;
1321
+ const requestId = msg.request_id;
1322
+ const sessionId = msg.session_id;
1323
+ log("info", `Session preview: session=${sessionId}`);
1324
+ if (gwClient.isConnected) {
1325
+ try {
1326
+ const result = await gwClient.sessionsPreview(sessionId);
1327
+ ccClient.send({
1328
+ type: "sessions_preview_response",
1329
+ user_id: userId,
1330
+ request_id: requestId,
1331
+ result,
1332
+ });
1333
+ }
1334
+ catch (err) {
1335
+ const errMsg = err instanceof Error ? err.message : String(err);
1336
+ log("warn", `Session preview failed: ${errMsg}`);
1337
+ ccClient.send({
1338
+ type: "sessions_preview_response",
1339
+ user_id: userId,
1340
+ request_id: requestId,
1341
+ error: errMsg,
1342
+ });
1343
+ }
1344
+ }
1345
+ else {
1346
+ ccClient.send({
1347
+ type: "sessions_preview_response",
1348
+ user_id: userId,
1349
+ request_id: requestId,
1350
+ error: "Gateway not connected",
1351
+ });
1352
+ }
1353
+ break;
1354
+ }
1355
+ case "sessions_patch": {
1356
+ const userId = msg.user_id;
1357
+ const requestId = msg.request_id;
1358
+ const sessionId = msg.session_id;
1359
+ const patch = msg.patch;
1360
+ log("info", `Session patch: session=${sessionId}`);
1361
+ if (gwClient.isConnected) {
1362
+ try {
1363
+ const result = await gwClient.sessionsPatch(sessionId, patch);
1364
+ ccClient.send({
1365
+ type: "sessions_patch_response",
1366
+ user_id: userId,
1367
+ request_id: requestId,
1368
+ result,
1369
+ });
1370
+ }
1371
+ catch (err) {
1372
+ const errMsg = err instanceof Error ? err.message : String(err);
1373
+ log("warn", `Session patch failed: ${errMsg}`);
1374
+ ccClient.send({
1375
+ type: "sessions_patch_response",
1376
+ user_id: userId,
1377
+ request_id: requestId,
1378
+ error: errMsg,
1379
+ });
1380
+ }
1381
+ }
1382
+ else {
1383
+ ccClient.send({
1384
+ type: "sessions_patch_response",
1385
+ user_id: userId,
1386
+ request_id: requestId,
1387
+ error: "Gateway not connected",
1388
+ });
1389
+ }
1390
+ break;
1391
+ }
1392
+ case "sessions_reset": {
1393
+ const userId = msg.user_id;
1394
+ const requestId = msg.request_id;
1395
+ const sessionId = msg.session_id;
1396
+ log("info", `Session reset: session=${sessionId}`);
1397
+ if (gwClient.isConnected) {
1398
+ try {
1399
+ const result = await gwClient.sessionsReset(sessionId);
1400
+ ccClient.send({
1401
+ type: "sessions_reset_response",
1402
+ user_id: userId,
1403
+ request_id: requestId,
1404
+ result,
1405
+ });
1406
+ }
1407
+ catch (err) {
1408
+ const errMsg = err instanceof Error ? err.message : String(err);
1409
+ log("warn", `Session reset failed: ${errMsg}`);
1410
+ ccClient.send({
1411
+ type: "sessions_reset_response",
1412
+ user_id: userId,
1413
+ request_id: requestId,
1414
+ error: errMsg,
1415
+ });
1416
+ }
1417
+ }
1418
+ else {
1419
+ ccClient.send({
1420
+ type: "sessions_reset_response",
1421
+ user_id: userId,
1422
+ request_id: requestId,
1423
+ error: "Gateway not connected",
1424
+ });
1425
+ }
1426
+ break;
1427
+ }
1428
+ case "sessions_delete": {
1429
+ const userId = msg.user_id;
1430
+ const requestId = msg.request_id;
1431
+ const sessionId = msg.session_id;
1432
+ log("info", `Session delete: session=${sessionId}`);
1433
+ if (gwClient.isConnected) {
1434
+ try {
1435
+ const result = await gwClient.sessionsDelete(sessionId);
1436
+ ccClient.send({
1437
+ type: "sessions_delete_response",
1438
+ user_id: userId,
1439
+ request_id: requestId,
1440
+ result,
1441
+ });
1442
+ }
1443
+ catch (err) {
1444
+ const errMsg = err instanceof Error ? err.message : String(err);
1445
+ log("warn", `Session delete failed: ${errMsg}`);
1446
+ ccClient.send({
1447
+ type: "sessions_delete_response",
1448
+ user_id: userId,
1449
+ request_id: requestId,
1450
+ error: errMsg,
1451
+ });
1452
+ }
1453
+ }
1454
+ else {
1455
+ ccClient.send({
1456
+ type: "sessions_delete_response",
1457
+ user_id: userId,
1458
+ request_id: requestId,
1459
+ error: "Gateway not connected",
1460
+ });
1461
+ }
1462
+ break;
1463
+ }
1464
+ case "sessions_compact": {
1465
+ const userId = msg.user_id;
1466
+ const requestId = msg.request_id;
1467
+ const sessionId = msg.session_id;
1468
+ log("info", `Session compact: session=${sessionId}`);
1469
+ if (gwClient.isConnected) {
1470
+ try {
1471
+ const result = await gwClient.sessionsCompact(sessionId);
1472
+ ccClient.send({
1473
+ type: "sessions_compact_response",
1474
+ user_id: userId,
1475
+ request_id: requestId,
1476
+ result,
1477
+ });
1478
+ }
1479
+ catch (err) {
1480
+ const errMsg = err instanceof Error ? err.message : String(err);
1481
+ log("warn", `Session compact failed: ${errMsg}`);
1482
+ ccClient.send({
1483
+ type: "sessions_compact_response",
1484
+ user_id: userId,
1485
+ request_id: requestId,
1486
+ error: errMsg,
1487
+ });
1488
+ }
1489
+ }
1490
+ else {
1491
+ ccClient.send({
1492
+ type: "sessions_compact_response",
1493
+ user_id: userId,
1494
+ request_id: requestId,
1495
+ error: "Gateway not connected",
1496
+ });
1497
+ }
1498
+ break;
1499
+ }
1500
+ // ── Models ──────────────────────────────────────────────────
1501
+ case "models_list": {
1502
+ const userId = msg.user_id;
1503
+ const requestId = msg.request_id;
1504
+ log("info", "Models list requested");
1505
+ if (gwClient.isConnected) {
1506
+ try {
1507
+ const result = await gwClient.modelsList();
1508
+ ccClient.send({
1509
+ type: "models_list_response",
1510
+ user_id: userId,
1511
+ request_id: requestId,
1512
+ models: result,
1513
+ });
1514
+ }
1515
+ catch (err) {
1516
+ const errMsg = err instanceof Error ? err.message : String(err);
1517
+ log("warn", `Models list failed: ${errMsg}`);
1518
+ ccClient.send({
1519
+ type: "models_list_response",
1520
+ user_id: userId,
1521
+ request_id: requestId,
1522
+ error: errMsg,
1523
+ });
1524
+ }
1525
+ }
1526
+ else {
1527
+ ccClient.send({
1528
+ type: "models_list_response",
1529
+ user_id: userId,
1530
+ request_id: requestId,
1531
+ error: "Gateway not connected",
1532
+ });
1533
+ }
1534
+ break;
1535
+ }
1536
+ // ── Agents (gateway) management ─────────────────────────────
1537
+ case "agents_gw_list": {
1538
+ const userId = msg.user_id;
1539
+ const requestId = msg.request_id;
1540
+ log("info", "Agents list requested");
1541
+ if (gwClient.isConnected) {
1542
+ try {
1543
+ const result = await gwClient.agentsList();
1544
+ ccClient.send({
1545
+ type: "agents_gw_list_response",
1546
+ user_id: userId,
1547
+ request_id: requestId,
1548
+ agents: result,
1549
+ });
1550
+ }
1551
+ catch (err) {
1552
+ const errMsg = err instanceof Error ? err.message : String(err);
1553
+ log("warn", `Agents list failed: ${errMsg}`);
1554
+ ccClient.send({
1555
+ type: "agents_gw_list_response",
1556
+ user_id: userId,
1557
+ request_id: requestId,
1558
+ error: errMsg,
1559
+ });
1560
+ }
1561
+ }
1562
+ else {
1563
+ ccClient.send({
1564
+ type: "agents_gw_list_response",
1565
+ user_id: userId,
1566
+ request_id: requestId,
1567
+ error: "Gateway not connected",
1568
+ });
1569
+ }
1570
+ break;
1571
+ }
1572
+ case "agents_gw_create": {
1573
+ const userId = msg.user_id;
1574
+ const requestId = msg.request_id;
1575
+ const config = msg.config;
1576
+ log("info", "Agent create requested");
1577
+ if (gwClient.isConnected) {
1578
+ try {
1579
+ const result = await gwClient.agentsCreate(config);
1580
+ ccClient.send({
1581
+ type: "agents_gw_create_response",
1582
+ user_id: userId,
1583
+ request_id: requestId,
1584
+ result,
1585
+ });
1586
+ }
1587
+ catch (err) {
1588
+ const errMsg = err instanceof Error ? err.message : String(err);
1589
+ log("warn", `Agent create failed: ${errMsg}`);
1590
+ ccClient.send({
1591
+ type: "agents_gw_create_response",
1592
+ user_id: userId,
1593
+ request_id: requestId,
1594
+ error: errMsg,
1595
+ });
1596
+ }
1597
+ }
1598
+ else {
1599
+ ccClient.send({
1600
+ type: "agents_gw_create_response",
1601
+ user_id: userId,
1602
+ request_id: requestId,
1603
+ error: "Gateway not connected",
1604
+ });
1605
+ }
1606
+ break;
1607
+ }
1608
+ case "agents_gw_update": {
1609
+ const userId = msg.user_id;
1610
+ const requestId = msg.request_id;
1611
+ const agentId = msg.agent_id;
1612
+ const patch = msg.patch;
1613
+ log("info", `Agent update: agent=${agentId}`);
1614
+ if (gwClient.isConnected) {
1615
+ try {
1616
+ const result = await gwClient.agentsUpdate(agentId, patch);
1617
+ ccClient.send({
1618
+ type: "agents_gw_update_response",
1619
+ user_id: userId,
1620
+ request_id: requestId,
1621
+ result,
1622
+ });
1623
+ }
1624
+ catch (err) {
1625
+ const errMsg = err instanceof Error ? err.message : String(err);
1626
+ log("warn", `Agent update failed: ${errMsg}`);
1627
+ ccClient.send({
1628
+ type: "agents_gw_update_response",
1629
+ user_id: userId,
1630
+ request_id: requestId,
1631
+ error: errMsg,
1632
+ });
1633
+ }
1634
+ }
1635
+ else {
1636
+ ccClient.send({
1637
+ type: "agents_gw_update_response",
1638
+ user_id: userId,
1639
+ request_id: requestId,
1640
+ error: "Gateway not connected",
1641
+ });
1642
+ }
1643
+ break;
1644
+ }
1645
+ case "agents_gw_delete": {
1646
+ const userId = msg.user_id;
1647
+ const requestId = msg.request_id;
1648
+ const agentId = msg.agent_id;
1649
+ log("info", `Agent delete: agent=${agentId}`);
1650
+ if (gwClient.isConnected) {
1651
+ try {
1652
+ const result = await gwClient.agentsDelete(agentId);
1653
+ ccClient.send({
1654
+ type: "agents_gw_delete_response",
1655
+ user_id: userId,
1656
+ request_id: requestId,
1657
+ result,
1658
+ });
1659
+ }
1660
+ catch (err) {
1661
+ const errMsg = err instanceof Error ? err.message : String(err);
1662
+ log("warn", `Agent delete failed: ${errMsg}`);
1663
+ ccClient.send({
1664
+ type: "agents_gw_delete_response",
1665
+ user_id: userId,
1666
+ request_id: requestId,
1667
+ error: errMsg,
1668
+ });
1669
+ }
1670
+ }
1671
+ else {
1672
+ ccClient.send({
1673
+ type: "agents_gw_delete_response",
1674
+ user_id: userId,
1675
+ request_id: requestId,
1676
+ error: "Gateway not connected",
1677
+ });
1678
+ }
1679
+ break;
1680
+ }
1681
+ case "agents_gw_files_list": {
1682
+ const userId = msg.user_id;
1683
+ const requestId = msg.request_id;
1684
+ const agentId = msg.agent_id;
1685
+ log("info", `Agent files list: agent=${agentId}`);
1686
+ if (gwClient.isConnected) {
1687
+ try {
1688
+ const result = await gwClient.agentsFilesList(agentId);
1689
+ ccClient.send({
1690
+ type: "agents_gw_files_list_response",
1691
+ user_id: userId,
1692
+ request_id: requestId,
1693
+ files: result,
1694
+ });
1695
+ }
1696
+ catch (err) {
1697
+ const errMsg = err instanceof Error ? err.message : String(err);
1698
+ log("warn", `Agent files list failed: ${errMsg}`);
1699
+ ccClient.send({
1700
+ type: "agents_gw_files_list_response",
1701
+ user_id: userId,
1702
+ request_id: requestId,
1703
+ error: errMsg,
1704
+ });
1705
+ }
1706
+ }
1707
+ else {
1708
+ ccClient.send({
1709
+ type: "agents_gw_files_list_response",
1710
+ user_id: userId,
1711
+ request_id: requestId,
1712
+ error: "Gateway not connected",
1713
+ });
1714
+ }
1715
+ break;
1716
+ }
1717
+ case "agents_gw_files_get": {
1718
+ const userId = msg.user_id;
1719
+ const requestId = msg.request_id;
1720
+ const agentId = msg.agent_id;
1721
+ const filePath = msg.file_path;
1722
+ log("info", `Agent files get: agent=${agentId} path=${filePath}`);
1723
+ if (gwClient.isConnected) {
1724
+ try {
1725
+ const result = await gwClient.agentsFilesGet(agentId, filePath);
1726
+ ccClient.send({
1727
+ type: "agents_gw_files_get_response",
1728
+ user_id: userId,
1729
+ request_id: requestId,
1730
+ content: result,
1731
+ });
1732
+ }
1733
+ catch (err) {
1734
+ const errMsg = err instanceof Error ? err.message : String(err);
1735
+ log("warn", `Agent files get failed: ${errMsg}`);
1736
+ ccClient.send({
1737
+ type: "agents_gw_files_get_response",
1738
+ user_id: userId,
1739
+ request_id: requestId,
1740
+ error: errMsg,
1741
+ });
1742
+ }
1743
+ }
1744
+ else {
1745
+ ccClient.send({
1746
+ type: "agents_gw_files_get_response",
1747
+ user_id: userId,
1748
+ request_id: requestId,
1749
+ error: "Gateway not connected",
1750
+ });
1751
+ }
1752
+ break;
1753
+ }
1754
+ case "agents_gw_files_set": {
1755
+ const userId = msg.user_id;
1756
+ const requestId = msg.request_id;
1757
+ const agentId = msg.agent_id;
1758
+ const filePath = msg.file_path;
1759
+ const content = msg.content;
1760
+ log("info", `Agent files set: agent=${agentId} path=${filePath}`);
1761
+ if (gwClient.isConnected) {
1762
+ try {
1763
+ const result = await gwClient.agentsFilesSet(agentId, filePath, content);
1764
+ ccClient.send({
1765
+ type: "agents_gw_files_set_response",
1766
+ user_id: userId,
1767
+ request_id: requestId,
1768
+ result,
1769
+ });
1770
+ }
1771
+ catch (err) {
1772
+ const errMsg = err instanceof Error ? err.message : String(err);
1773
+ log("warn", `Agent files set failed: ${errMsg}`);
1774
+ ccClient.send({
1775
+ type: "agents_gw_files_set_response",
1776
+ user_id: userId,
1777
+ request_id: requestId,
1778
+ error: errMsg,
1779
+ });
1780
+ }
1781
+ }
1782
+ else {
1783
+ ccClient.send({
1784
+ type: "agents_gw_files_set_response",
1785
+ user_id: userId,
1786
+ request_id: requestId,
1787
+ error: "Gateway not connected",
1788
+ });
1789
+ }
1790
+ break;
1791
+ }
1792
+ // ── Logs tail ───────────────────────────────────────────────
1793
+ case "logs_tail": {
1794
+ const userId = msg.user_id;
1795
+ const requestId = msg.request_id;
1796
+ const cursor = msg.cursor;
1797
+ const limit = msg.limit;
1798
+ const maxBytes = msg.max_bytes;
1799
+ log("info", `Logs tail requested (cursor=${cursor ?? "start"}, limit=${limit ?? "default"})`);
1800
+ if (gwClient.isConnected) {
1801
+ try {
1802
+ const result = await gwClient.logsTail({ cursor, limit, maxBytes });
1803
+ ccClient.send({
1804
+ type: "logs_tail_response",
1805
+ user_id: userId,
1806
+ request_id: requestId,
1807
+ result,
1808
+ });
1809
+ }
1810
+ catch (err) {
1811
+ const errMsg = err instanceof Error ? err.message : String(err);
1812
+ log("warn", `Logs tail failed: ${errMsg}`);
1813
+ ccClient.send({
1814
+ type: "logs_tail_response",
1815
+ user_id: userId,
1816
+ request_id: requestId,
1817
+ error: errMsg,
1818
+ });
1819
+ }
1820
+ }
1821
+ else {
1822
+ ccClient.send({
1823
+ type: "logs_tail_response",
1824
+ user_id: userId,
1825
+ request_id: requestId,
1826
+ error: "Gateway not connected",
1827
+ });
1828
+ }
1829
+ break;
1830
+ }
1831
+ // ── Browser request ─────────────────────────────────────────
1832
+ case "browser_request": {
1833
+ const userId = msg.user_id;
1834
+ const requestId = msg.request_id;
1835
+ const method = msg.method;
1836
+ const path = msg.path;
1837
+ const query = msg.query;
1838
+ const body = msg.body;
1839
+ const timeoutMs = msg.timeout_ms;
1840
+ log("info", `Browser request: ${method} ${path}`);
1841
+ if (gwClient.isConnected) {
1842
+ try {
1843
+ const result = await gwClient.browserRequest({ method, path, query, body, timeoutMs });
1844
+ ccClient.send({
1845
+ type: "browser_request_response",
1846
+ user_id: userId,
1847
+ request_id: requestId,
1848
+ result,
1849
+ });
1850
+ }
1851
+ catch (err) {
1852
+ const errMsg = err instanceof Error ? err.message : String(err);
1853
+ log("warn", `Browser request failed: ${errMsg}`);
1854
+ ccClient.send({
1855
+ type: "browser_request_response",
1856
+ user_id: userId,
1857
+ request_id: requestId,
1858
+ error: errMsg,
1859
+ });
1860
+ }
1861
+ }
1862
+ else {
1863
+ ccClient.send({
1864
+ type: "browser_request_response",
1865
+ user_id: userId,
1866
+ request_id: requestId,
1867
+ error: "Gateway not connected",
1868
+ });
1869
+ }
1870
+ break;
1871
+ }
1872
+ // ── Screenshot request ──────────────────────────────────────
1873
+ case "screenshot_request": {
1874
+ const userId = msg.user_id;
1875
+ const requestId = msg.request_id;
1876
+ log("info", "Screenshot requested");
1877
+ try {
1878
+ // Dynamic import since screenshot-desktop is a CJS module
1879
+ // @ts-expect-error — screenshot-desktop has no type declarations
1880
+ const screenshot = (await import("screenshot-desktop")).default;
1881
+ const imgBuffer = await screenshot({ format: "png" });
1882
+ const base64 = imgBuffer.toString("base64");
1883
+ ccClient.send({
1884
+ type: "screenshot_response",
1885
+ user_id: userId,
1886
+ request_id: requestId,
1887
+ image_base64: base64,
1888
+ format: "png",
1889
+ timestamp: new Date().toISOString(),
1890
+ });
1891
+ log("info", `Screenshot captured (${Math.round(base64.length / 1024)}KB base64)`);
1892
+ }
1893
+ catch (err) {
1894
+ const errMsg = err instanceof Error ? err.message : String(err);
1895
+ log("error", `Screenshot failed: ${errMsg}`);
1896
+ ccClient.send({
1897
+ type: "screenshot_response",
1898
+ user_id: userId,
1899
+ request_id: requestId,
1900
+ error: errMsg,
1901
+ });
1902
+ }
1903
+ break;
1904
+ }
1905
+ // ── Cancel running task ─────────────────────────────────────
1906
+ case "cancel_task": {
1907
+ const executionId = msg.execution_id;
1908
+ log("info", `Cancel requested for execution=${executionId}`);
1909
+ // TODO: Implement session-based cancellation via sessions.kill
1910
+ break;
1911
+ }
1912
+ // ── Ping/pong ───────────────────────────────────────────────
1913
+ case "ping": {
1914
+ ccClient.send({ type: "pong" });
1915
+ break;
1916
+ }
1917
+ default:
1918
+ log("warn", `Unknown message type: ${msg.type}`);
1919
+ }
1920
+ }
1921
+ // ── Logging ────────────────────────────────────────────────────────
1922
+ function log(level, msg) {
1923
+ const ts = new Date().toISOString();
1924
+ const prefix = `[${ts}] [Daemon] [${level.toUpperCase()}]`;
1925
+ if (level === "error")
1926
+ console.error(`${prefix} ${msg}`);
1927
+ else if (level === "warn")
1928
+ console.warn(`${prefix} ${msg}`);
1929
+ else
1930
+ console.log(`${prefix} ${msg}`);
1931
+ }
1932
+ // ── Run ────────────────────────────────────────────────────────────
1933
+ main().catch((err) => {
1934
+ console.error("Fatal error:", err);
1935
+ process.exit(1);
1936
+ });
1937
+ //# sourceMappingURL=daemon.js.map