@wingman-ai/gateway 0.2.5 → 0.3.1

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 (184) hide show
  1. package/README.md +9 -0
  2. package/dist/agent/config/agentConfig.cjs +12 -0
  3. package/dist/agent/config/agentConfig.d.ts +26 -0
  4. package/dist/agent/config/agentConfig.js +10 -1
  5. package/dist/agent/config/agentLoader.cjs +9 -0
  6. package/dist/agent/config/agentLoader.js +9 -0
  7. package/dist/agent/config/mcpClientManager.cjs +44 -10
  8. package/dist/agent/config/mcpClientManager.d.ts +6 -2
  9. package/dist/agent/config/mcpClientManager.js +44 -10
  10. package/dist/agent/config/toolRegistry.cjs +20 -1
  11. package/dist/agent/config/toolRegistry.d.ts +15 -0
  12. package/dist/agent/config/toolRegistry.js +20 -1
  13. package/dist/agent/tests/agentConfig.test.cjs +6 -1
  14. package/dist/agent/tests/agentConfig.test.js +6 -1
  15. package/dist/agent/tests/browserControlHelpers.test.cjs +35 -0
  16. package/dist/agent/tests/browserControlHelpers.test.d.ts +1 -0
  17. package/dist/agent/tests/browserControlHelpers.test.js +29 -0
  18. package/dist/agent/tests/browserControlTool.test.cjs +2117 -0
  19. package/dist/agent/tests/browserControlTool.test.d.ts +1 -0
  20. package/dist/agent/tests/browserControlTool.test.js +2111 -0
  21. package/dist/agent/tests/mcpClientManager.test.cjs +124 -0
  22. package/dist/agent/tests/mcpClientManager.test.d.ts +1 -0
  23. package/dist/agent/tests/mcpClientManager.test.js +118 -0
  24. package/dist/agent/tests/toolRegistry.test.cjs +6 -0
  25. package/dist/agent/tests/toolRegistry.test.js +6 -0
  26. package/dist/agent/tools/browser_control.cjs +1282 -0
  27. package/dist/agent/tools/browser_control.d.ts +478 -0
  28. package/dist/agent/tools/browser_control.js +1242 -0
  29. package/dist/agent/tools/command_execute.cjs +1 -1
  30. package/dist/agent/tools/command_execute.js +1 -1
  31. package/dist/cli/commands/agent.cjs +16 -2
  32. package/dist/cli/commands/agent.js +16 -2
  33. package/dist/cli/commands/browser.cjs +603 -0
  34. package/dist/cli/commands/browser.d.ts +13 -0
  35. package/dist/cli/commands/browser.js +566 -0
  36. package/dist/cli/commands/gateway.cjs +18 -7
  37. package/dist/cli/commands/gateway.d.ts +5 -1
  38. package/dist/cli/commands/gateway.js +18 -7
  39. package/dist/cli/commands/init.cjs +134 -45
  40. package/dist/cli/commands/init.js +134 -45
  41. package/dist/cli/commands/skill.cjs +3 -2
  42. package/dist/cli/commands/skill.js +3 -2
  43. package/dist/cli/config/loader.cjs +15 -0
  44. package/dist/cli/config/loader.js +15 -0
  45. package/dist/cli/config/schema.cjs +51 -2
  46. package/dist/cli/config/schema.d.ts +51 -0
  47. package/dist/cli/config/schema.js +44 -1
  48. package/dist/cli/core/agentInvoker.cjs +55 -66
  49. package/dist/cli/core/agentInvoker.d.ts +10 -13
  50. package/dist/cli/core/agentInvoker.js +42 -62
  51. package/dist/cli/core/imagePersistence.cjs +125 -0
  52. package/dist/cli/core/imagePersistence.d.ts +24 -0
  53. package/dist/cli/core/imagePersistence.js +85 -0
  54. package/dist/cli/core/sessionManager.cjs +297 -40
  55. package/dist/cli/core/sessionManager.d.ts +9 -0
  56. package/dist/cli/core/sessionManager.js +297 -40
  57. package/dist/cli/core/workspace.cjs +89 -0
  58. package/dist/cli/core/workspace.d.ts +1 -0
  59. package/dist/cli/core/workspace.js +55 -0
  60. package/dist/cli/index.cjs +53 -5
  61. package/dist/cli/index.js +53 -5
  62. package/dist/cli/types/browser.cjs +18 -0
  63. package/dist/cli/types/browser.d.ts +9 -0
  64. package/dist/cli/types/browser.js +0 -0
  65. package/dist/debug/terminalProbe.cjs +57 -0
  66. package/dist/debug/terminalProbe.d.ts +10 -0
  67. package/dist/debug/terminalProbe.js +20 -0
  68. package/dist/debug/terminalProbeAuth.cjs +140 -0
  69. package/dist/debug/terminalProbeAuth.d.ts +20 -0
  70. package/dist/debug/terminalProbeAuth.js +97 -0
  71. package/dist/gateway/browserRelayServer.cjs +338 -0
  72. package/dist/gateway/browserRelayServer.d.ts +38 -0
  73. package/dist/gateway/browserRelayServer.js +301 -0
  74. package/dist/gateway/http/agents.cjs +22 -0
  75. package/dist/gateway/http/agents.js +22 -0
  76. package/dist/gateway/http/fs.cjs +76 -0
  77. package/dist/gateway/http/fs.js +77 -1
  78. package/dist/gateway/http/sessions.cjs +25 -5
  79. package/dist/gateway/http/sessions.js +25 -5
  80. package/dist/gateway/server.cjs +155 -17
  81. package/dist/gateway/server.d.ts +6 -1
  82. package/dist/gateway/server.js +148 -16
  83. package/dist/gateway/transport/websocket.cjs +45 -10
  84. package/dist/gateway/transport/websocket.d.ts +1 -0
  85. package/dist/gateway/transport/websocket.js +41 -9
  86. package/dist/gateway/types.d.ts +4 -0
  87. package/dist/tests/agentInvokerSummarization.test.cjs +56 -37
  88. package/dist/tests/agentInvokerSummarization.test.js +58 -39
  89. package/dist/tests/agentInvokerWorkdir.test.cjs +50 -0
  90. package/dist/tests/agentInvokerWorkdir.test.js +52 -2
  91. package/dist/tests/agents-api.test.cjs +52 -0
  92. package/dist/tests/agents-api.test.js +53 -1
  93. package/dist/tests/browser-command.test.cjs +264 -0
  94. package/dist/tests/browser-command.test.d.ts +1 -0
  95. package/dist/tests/browser-command.test.js +258 -0
  96. package/dist/tests/browser-relay-server.test.cjs +20 -0
  97. package/dist/tests/browser-relay-server.test.d.ts +1 -0
  98. package/dist/tests/browser-relay-server.test.js +14 -0
  99. package/dist/tests/cli-config-loader.test.cjs +43 -0
  100. package/dist/tests/cli-config-loader.test.js +43 -0
  101. package/dist/tests/cli-init.test.cjs +61 -2
  102. package/dist/tests/cli-init.test.js +61 -2
  103. package/dist/tests/cli-workspace-root.test.cjs +114 -0
  104. package/dist/tests/cli-workspace-root.test.d.ts +1 -0
  105. package/dist/tests/cli-workspace-root.test.js +108 -0
  106. package/dist/tests/falRuntime.test.cjs +78 -0
  107. package/dist/tests/falRuntime.test.d.ts +1 -0
  108. package/dist/tests/falRuntime.test.js +72 -0
  109. package/dist/tests/falSummary.test.cjs +51 -0
  110. package/dist/tests/falSummary.test.d.ts +1 -0
  111. package/dist/tests/falSummary.test.js +45 -0
  112. package/dist/tests/fs-api.test.cjs +138 -0
  113. package/dist/tests/fs-api.test.d.ts +1 -0
  114. package/dist/tests/fs-api.test.js +132 -0
  115. package/dist/tests/gateway-command-workspace.test.cjs +150 -0
  116. package/dist/tests/gateway-command-workspace.test.d.ts +1 -0
  117. package/dist/tests/gateway-command-workspace.test.js +144 -0
  118. package/dist/tests/gateway-request-execution-overrides.test.cjs +42 -0
  119. package/dist/tests/gateway-request-execution-overrides.test.d.ts +1 -0
  120. package/dist/tests/gateway-request-execution-overrides.test.js +36 -0
  121. package/dist/tests/gateway.test.cjs +140 -1
  122. package/dist/tests/gateway.test.js +140 -1
  123. package/dist/tests/imagePersistence.test.cjs +143 -0
  124. package/dist/tests/imagePersistence.test.d.ts +1 -0
  125. package/dist/tests/imagePersistence.test.js +137 -0
  126. package/dist/tests/sessionMessageAttachments.test.cjs +30 -0
  127. package/dist/tests/sessionMessageAttachments.test.js +30 -0
  128. package/dist/tests/sessionStateMessages.test.cjs +126 -0
  129. package/dist/tests/sessionStateMessages.test.js +126 -0
  130. package/dist/tests/sessions-api.test.cjs +117 -3
  131. package/dist/tests/sessions-api.test.js +118 -4
  132. package/dist/tests/terminalProbe.test.cjs +45 -0
  133. package/dist/tests/terminalProbe.test.d.ts +1 -0
  134. package/dist/tests/terminalProbe.test.js +39 -0
  135. package/dist/tests/terminalProbeAuth.test.cjs +85 -0
  136. package/dist/tests/terminalProbeAuth.test.d.ts +1 -0
  137. package/dist/tests/terminalProbeAuth.test.js +79 -0
  138. package/dist/tests/websocket-transport.test.cjs +31 -0
  139. package/dist/tests/websocket-transport.test.d.ts +1 -0
  140. package/dist/tests/websocket-transport.test.js +25 -0
  141. package/dist/tools/fal/runtime.cjs +103 -0
  142. package/dist/tools/fal/runtime.d.ts +10 -0
  143. package/dist/tools/fal/runtime.js +60 -0
  144. package/dist/tools/fal/summary.cjs +78 -0
  145. package/dist/tools/fal/summary.d.ts +22 -0
  146. package/dist/tools/fal/summary.js +41 -0
  147. package/dist/tools/mcp-fal-ai.cjs +1041 -0
  148. package/dist/tools/mcp-fal-ai.d.ts +1 -0
  149. package/dist/tools/mcp-fal-ai.js +1025 -0
  150. package/dist/types/mcp.cjs +2 -0
  151. package/dist/types/mcp.d.ts +8 -0
  152. package/dist/types/mcp.js +3 -1
  153. package/dist/webui/assets/index-BW9nM0J2.css +11 -0
  154. package/dist/webui/assets/index-C8-oboEC.js +278 -0
  155. package/dist/webui/index.html +2 -2
  156. package/extensions/wingman-browser-extension/README.md +27 -0
  157. package/extensions/wingman-browser-extension/background.js +416 -0
  158. package/extensions/wingman-browser-extension/manifest.json +19 -0
  159. package/extensions/wingman-browser-extension/options.html +156 -0
  160. package/extensions/wingman-browser-extension/options.js +106 -0
  161. package/package.json +18 -13
  162. package/{.wingman → templates}/agents/README.md +2 -1
  163. package/{.wingman → templates}/agents/coding/agent.md +5 -1
  164. package/{.wingman → templates}/agents/coding-v2/agent.md +58 -1
  165. package/templates/agents/game-dev/agent.md +101 -0
  166. package/templates/agents/game-dev/art-generation.md +38 -0
  167. package/templates/agents/game-dev/asset-refinement.md +17 -0
  168. package/templates/agents/game-dev/planning-idea.md +17 -0
  169. package/templates/agents/game-dev/ui-specialist.md +17 -0
  170. package/templates/agents/main/agent.md +29 -0
  171. package/{.wingman → templates}/agents/researcher/agent.md +9 -0
  172. package/{.wingman → templates}/agents/stock-trader/agent.md +1 -0
  173. package/.wingman/agents/main/agent.md +0 -22
  174. package/dist/webui/assets/index-C7EuTbnE.js +0 -270
  175. package/dist/webui/assets/index-DVWQluit.css +0 -11
  176. /package/{.wingman → templates}/agents/coding-v2/implementor.md +0 -0
  177. /package/{.wingman → templates}/agents/stock-trader/chain-curator.md +0 -0
  178. /package/{.wingman → templates}/agents/stock-trader/goal-translator.md +0 -0
  179. /package/{.wingman → templates}/agents/stock-trader/guardrails-veto.md +0 -0
  180. /package/{.wingman → templates}/agents/stock-trader/path-planner.md +0 -0
  181. /package/{.wingman → templates}/agents/stock-trader/regime-analyst.md +0 -0
  182. /package/{.wingman → templates}/agents/stock-trader/risk.md +0 -0
  183. /package/{.wingman → templates}/agents/stock-trader/selection.md +0 -0
  184. /package/{.wingman → templates}/agents/stock-trader/strategy-composer.md +0 -0
@@ -10,6 +10,7 @@ import { SessionManager } from "../cli/core/sessionManager.js";
10
10
  import { createLogger } from "../logger.js";
11
11
  import { DiscordGatewayAdapter } from "./adapters/discord.js";
12
12
  import { GatewayAuth } from "./auth.js";
13
+ import { BrowserRelayServer } from "./browserRelayServer.js";
13
14
  import { BroadcastGroupManager } from "./broadcast.js";
14
15
  import { MDNSDiscoveryService, TailscaleDiscoveryService } from "./discovery/index.js";
15
16
  import { getGatewayTokenFromEnv } from "./env.js";
@@ -49,6 +50,19 @@ function withApiCors(response) {
49
50
  headers
50
51
  });
51
52
  }
53
+ function resolveExecutionWorkspaceOverride(payload) {
54
+ const rawWorkspace = payload?.execution?.workspace;
55
+ if ("string" != typeof rawWorkspace) return null;
56
+ const trimmed = rawWorkspace.trim();
57
+ if (!trimmed || !isAbsolute(trimmed)) return null;
58
+ return normalize(trimmed);
59
+ }
60
+ function resolveExecutionConfigDirOverride(payload) {
61
+ const rawConfigDir = payload?.execution?.configDir;
62
+ if ("string" != typeof rawConfigDir) return null;
63
+ const trimmed = rawConfigDir.trim();
64
+ return trimmed || null;
65
+ }
52
66
  class GatewayServer {
53
67
  async start() {
54
68
  if (void 0 === globalThis.Bun) throw new Error("Gateway server requires Bun runtime. Start with `bun ./bin/wingman gateway start`.");
@@ -106,6 +120,7 @@ class GatewayServer {
106
120
  if (!this.uiDistDir) this.log("warn", "Control UI is enabled but build assets were not found. Run `bun run webui:build`.");
107
121
  this.log("info", `Control UI available on ${this.config.host}:${this.config.port}`);
108
122
  }
123
+ this.browserRelayServer?.start();
109
124
  if (this.config.discovery?.enabled) await this.startDiscovery();
110
125
  await this.startAdapters();
111
126
  this.log("info", `Gateway started on ${this.config.host}:${this.config.port}`);
@@ -134,6 +149,7 @@ class GatewayServer {
134
149
  this.uiServer.stop();
135
150
  this.uiServer = null;
136
151
  }
152
+ if (this.browserRelayServer) this.browserRelayServer.stop();
137
153
  this.terminalSessionManager.dispose();
138
154
  this.log("info", "Gateway stopped");
139
155
  }
@@ -326,6 +342,8 @@ class GatewayServer {
326
342
  const payload = msg.payload;
327
343
  const content = "string" == typeof payload?.content ? payload.content : "";
328
344
  const attachments = Array.isArray(payload?.attachments) ? payload.attachments : [];
345
+ const workspaceOverride = resolveExecutionWorkspaceOverride(payload);
346
+ const configDirOverride = resolveExecutionConfigDirOverride(payload);
329
347
  const hasContent = content.trim().length > 0;
330
348
  const hasAttachments = attachments.length > 0;
331
349
  if (!hasContent && !hasAttachments) return void this.sendAgentError(ws, msg.id, "Missing agent content");
@@ -336,12 +354,29 @@ class GatewayServer {
336
354
  const sessionManager = await this.getSessionManager(agentId);
337
355
  const existingSession = sessionManager.getSession(sessionKey);
338
356
  const session = existingSession || sessionManager.getOrCreateSession(sessionKey, agentId);
357
+ const requestId = msg.id || `req-${Date.now()}`;
339
358
  const workdir = session.metadata?.workdir ?? null;
340
359
  const defaultOutputDir = this.resolveDefaultOutputDir(agentId);
341
360
  const preview = hasContent ? content.trim() : buildAttachmentPreview(attachments);
342
361
  sessionManager.updateSession(session.id, {
362
+ messageCount: (session.messageCount ?? 0) + 1,
343
363
  lastMessagePreview: preview.substring(0, 200)
344
364
  });
365
+ try {
366
+ sessionManager.persistPendingMessage({
367
+ sessionId: sessionKey,
368
+ requestId,
369
+ message: {
370
+ id: `user-${requestId}`,
371
+ role: "user",
372
+ content,
373
+ attachments: attachments.length > 0 ? mapAttachmentsForPendingMessage(attachments) : void 0,
374
+ createdAt: Date.now()
375
+ }
376
+ });
377
+ } catch (error) {
378
+ this.logger.warn("Failed to persist pending user message", error);
379
+ }
345
380
  if (!existingSession) this.internalHooks?.emit({
346
381
  type: "session",
347
382
  action: "start",
@@ -394,7 +429,9 @@ class GatewayServer {
394
429
  attachments,
395
430
  sessionManager,
396
431
  workdir,
397
- defaultOutputDir
432
+ defaultOutputDir,
433
+ workspaceOverride,
434
+ configDirOverride
398
435
  };
399
436
  this.requestSessionKeys.set(msg.id, sessionQueueKey);
400
437
  const queueIfBusy = false !== payload.queueIfBusy;
@@ -440,13 +477,19 @@ class GatewayServer {
440
477
  this.executeAgentRequest(request);
441
478
  }
442
479
  async executeAgentRequest(request) {
443
- const { ws, msg, agentId, sessionKey, sessionQueueKey, content, attachments, sessionManager, workdir, defaultOutputDir } = request;
480
+ const { ws, msg, agentId, sessionKey, sessionQueueKey, content, attachments, sessionManager, workdir, defaultOutputDir, workspaceOverride, configDirOverride } = request;
444
481
  this.activeSessionRequests.set(sessionQueueKey, msg.id);
445
482
  const outputManager = new OutputManager("interactive");
446
483
  let emittedAgentError = false;
484
+ let streamedCompletionResult;
447
485
  const outputHandler = (event)=>{
448
486
  const payloadWithSession = this.attachSessionContext(event, sessionKey, agentId);
449
- if (payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession) && "agent-error" === payloadWithSession.type) emittedAgentError = true;
487
+ const payloadType = payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession) && "string" == typeof payloadWithSession.type ? payloadWithSession.type : "";
488
+ if ("agent-complete" === payloadType) {
489
+ if (payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession)) streamedCompletionResult = payloadWithSession.result;
490
+ return;
491
+ }
492
+ if ("agent-error" === payloadType) emittedAgentError = true;
450
493
  const baseMessage = {
451
494
  type: "event:agent",
452
495
  id: msg.id,
@@ -460,10 +503,11 @@ class GatewayServer {
460
503
  this.broadcastSessionEvent(sessionKey, baseMessage, ws);
461
504
  };
462
505
  outputManager.on("output-event", outputHandler);
463
- const workspace = this.resolveAgentWorkspace(agentId);
506
+ const workspace = workspaceOverride || this.resolveAgentWorkspace(agentId);
507
+ const configDir = configDirOverride || this.configDir;
464
508
  const invoker = new AgentInvoker({
465
509
  workspace,
466
- configDir: this.configDir,
510
+ configDir,
467
511
  outputManager,
468
512
  logger: this.logger,
469
513
  sessionManager,
@@ -477,17 +521,43 @@ class GatewayServer {
477
521
  abortController
478
522
  });
479
523
  try {
480
- await invoker.invokeAgent(agentId, content, sessionKey, attachments, {
524
+ const invocationResult = await invoker.invokeAgent(agentId, content, sessionKey, attachments, {
481
525
  signal: abortController.signal
482
526
  });
483
- const updated = sessionManager.getSession(sessionKey);
484
- if (updated) sessionManager.updateSession(sessionKey, {
485
- messageCount: updated.messageCount + 1
527
+ if (msg.id) sessionManager.clearPendingMessagesForRequest(sessionKey, msg.id);
528
+ if (emittedAgentError) return;
529
+ const invocationCancelled = abortController.signal.aborted || "object" == typeof invocationResult && null !== invocationResult && !Array.isArray(invocationResult) && true === invocationResult.cancelled;
530
+ if (invocationCancelled) return void this.sendAgentError(ws, msg.id, "Request cancelled", {
531
+ sessionId: sessionKey,
532
+ agentId,
533
+ broadcastToSession: true,
534
+ exclude: ws
535
+ });
536
+ const completionResult = void 0 === streamedCompletionResult ? invocationResult : streamedCompletionResult;
537
+ this.sendAgentComplete(ws, msg.id, completionResult, {
538
+ sessionId: sessionKey,
539
+ agentId,
540
+ broadcastToSession: true,
541
+ exclude: ws
486
542
  });
487
543
  } catch (error) {
488
544
  this.logger.error("Agent invocation failed", error);
545
+ const message = error instanceof Error ? error.message : String(error);
546
+ if (msg.id) try {
547
+ sessionManager.persistPendingMessage({
548
+ sessionId: sessionKey,
549
+ requestId: msg.id,
550
+ message: {
551
+ id: msg.id,
552
+ role: "assistant",
553
+ content: message,
554
+ createdAt: Date.now()
555
+ }
556
+ });
557
+ } catch (persistError) {
558
+ this.logger.warn("Failed to persist pending assistant error message", persistError);
559
+ }
489
560
  if (!emittedAgentError) {
490
- const message = error instanceof Error ? error.message : String(error);
491
561
  const stack = error instanceof Error ? error.stack : void 0;
492
562
  this.sendAgentError(ws, msg.id, message, {
493
563
  sessionId: sessionKey,
@@ -563,6 +633,12 @@ class GatewayServer {
563
633
  },
564
634
  timestamp: Date.now()
565
635
  });
636
+ this.sendAgentError(ws, requestId, "Request cancelled", {
637
+ sessionId: queued.sessionKey,
638
+ agentId: queued.agentId,
639
+ broadcastToSession: true,
640
+ exclude: ws
641
+ });
566
642
  return;
567
643
  }
568
644
  this.sendMessage(ws, {
@@ -737,11 +813,25 @@ class GatewayServer {
737
813
  }
738
814
  sendMessage(ws, message) {
739
815
  try {
740
- ws.send(JSON.stringify(message));
816
+ const result = ws.send(JSON.stringify(message));
817
+ if ("number" == typeof result && result <= 0) return false;
818
+ return true;
741
819
  } catch (error) {
742
820
  this.log("error", "Failed to send message", error);
821
+ return false;
743
822
  }
744
823
  }
824
+ sendMessageWithRetry(ws, message, attempt = 0) {
825
+ if (this.sendMessage(ws, message)) return;
826
+ if (attempt >= 2) return void this.log("warn", "Dropping websocket message after retry attempts", {
827
+ type: message.type,
828
+ id: message.id
829
+ });
830
+ const delayMs = 25 * (attempt + 1);
831
+ setTimeout(()=>{
832
+ this.sendMessageWithRetry(ws, message, attempt + 1);
833
+ }, delayMs);
834
+ }
745
835
  sendError(ws, code, message) {
746
836
  const errorPayload = {
747
837
  code,
@@ -753,6 +843,25 @@ class GatewayServer {
753
843
  timestamp: Date.now()
754
844
  });
755
845
  }
846
+ sendAgentComplete(ws, requestId, result, options) {
847
+ let payload = {
848
+ type: "agent-complete",
849
+ result: result ?? null,
850
+ timestamp: new Date().toISOString()
851
+ };
852
+ if (options?.sessionId && options?.agentId) payload = this.attachSessionContext(payload, options.sessionId, options.agentId);
853
+ const baseMessage = {
854
+ type: "event:agent",
855
+ id: requestId,
856
+ payload,
857
+ timestamp: Date.now()
858
+ };
859
+ this.sendMessageWithRetry(ws, {
860
+ ...baseMessage,
861
+ clientId: ws.data.clientId
862
+ });
863
+ if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude, true);
864
+ }
756
865
  sendAgentError(ws, requestId, message, options) {
757
866
  let payload = {
758
867
  type: "agent-error",
@@ -767,11 +876,11 @@ class GatewayServer {
767
876
  payload,
768
877
  timestamp: Date.now()
769
878
  };
770
- this.sendMessage(ws, {
879
+ this.sendMessageWithRetry(ws, {
771
880
  ...baseMessage,
772
881
  clientId: ws.data.clientId
773
882
  });
774
- if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude);
883
+ if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude, true);
775
884
  }
776
885
  cancelSocketAgentRequests(ws) {
777
886
  for (const [requestId, active] of this.activeAgentRequests)if (active.socket === ws) {
@@ -800,12 +909,13 @@ class GatewayServer {
800
909
  agentId
801
910
  };
802
911
  }
803
- broadcastSessionEvent(sessionId, message, exclude) {
912
+ broadcastSessionEvent(sessionId, message, exclude, reliable = false) {
804
913
  const subscribers = this.sessionSubscriptions.get(sessionId);
805
914
  if (!subscribers || 0 === subscribers.size) return 0;
806
915
  let sent = 0;
807
916
  for (const ws of subscribers)if (!exclude || ws !== exclude) {
808
- this.sendMessage(ws, message);
917
+ if (reliable) this.sendMessageWithRetry(ws, message);
918
+ else this.sendMessage(ws, message);
809
919
  sent++;
810
920
  }
811
921
  return sent;
@@ -1294,6 +1404,7 @@ class GatewayServer {
1294
1404
  _define_property(this, "controlUiPort", 18790);
1295
1405
  _define_property(this, "controlUiSamePort", false);
1296
1406
  _define_property(this, "uiDistDir", null);
1407
+ _define_property(this, "browserRelayServer", null);
1297
1408
  _define_property(this, "webhookStore", void 0);
1298
1409
  _define_property(this, "routineStore", void 0);
1299
1410
  _define_property(this, "internalHooks", null);
@@ -1356,6 +1467,15 @@ class GatewayServer {
1356
1467
  this.controlUiPort = controlUi?.port || 18790;
1357
1468
  this.controlUiSamePort = this.controlUiEnabled && this.controlUiPort === this.config.port;
1358
1469
  this.uiDistDir = this.controlUiEnabled ? this.resolveControlUiDir() : null;
1470
+ const relayConfig = this.wingmanConfig.browser?.relay;
1471
+ if (relayConfig?.enabled) this.browserRelayServer = new BrowserRelayServer({
1472
+ enabled: relayConfig.enabled,
1473
+ host: relayConfig.host || "127.0.0.1",
1474
+ port: relayConfig.port || 18792,
1475
+ requireAuth: relayConfig.requireAuth ?? true,
1476
+ authToken: relayConfig.authToken,
1477
+ maxMessageBytes: relayConfig.maxMessageBytes || 262144
1478
+ }, this.logger);
1359
1479
  this.terminalSessionManager = new TerminalSessionManager();
1360
1480
  }
1361
1481
  }
@@ -1379,6 +1499,18 @@ function buildAttachmentPreview(attachments) {
1379
1499
  if (hasAudio) return count > 1 ? "Audio attachments" : "Audio attachment";
1380
1500
  return count > 1 ? "Image attachments" : "Image attachment";
1381
1501
  }
1502
+ function mapAttachmentsForPendingMessage(attachments) {
1503
+ return attachments.map((attachment)=>{
1504
+ const kind = isFileAttachment(attachment) ? "file" : isAudioAttachment(attachment) ? "audio" : "image";
1505
+ return {
1506
+ kind,
1507
+ dataUrl: attachment.dataUrl,
1508
+ name: attachment.name,
1509
+ mimeType: attachment.mimeType,
1510
+ size: attachment.size
1511
+ };
1512
+ });
1513
+ }
1382
1514
  function isAudioAttachment(attachment) {
1383
1515
  if ("audio" === attachment.kind) return true;
1384
1516
  if (attachment.mimeType?.startsWith("audio/")) return true;
@@ -1389,4 +1521,4 @@ function isFileAttachment(attachment) {
1389
1521
  if ("file" === attachment.kind) return true;
1390
1522
  return "string" == typeof attachment.textContent;
1391
1523
  }
1392
- export { GatewayServer };
1524
+ export { GatewayServer, resolveExecutionConfigDirOverride, resolveExecutionWorkspaceOverride };
@@ -24,7 +24,8 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
- WebSocketTransport: ()=>WebSocketTransport
27
+ WebSocketTransport: ()=>WebSocketTransport,
28
+ describeWebSocketError: ()=>describeWebSocketError
28
29
  });
29
30
  const external_logger_cjs_namespaceObject = require("../../logger.cjs");
30
31
  function _define_property(obj, key, value) {
@@ -38,19 +39,47 @@ function _define_property(obj, key, value) {
38
39
  return obj;
39
40
  }
40
41
  const logger = (0, external_logger_cjs_namespaceObject.createLogger)();
42
+ function describeWebSocketError(error, url) {
43
+ if (error instanceof Error) {
44
+ const message = error.message.trim();
45
+ return message ? `WebSocket connection failed to ${url}: ${message}` : `WebSocket connection failed to ${url}.`;
46
+ }
47
+ if ("string" == typeof error && error.trim()) return `WebSocket connection failed to ${url}: ${error.trim()}`;
48
+ if (error && "object" == typeof error) {
49
+ const typed = error;
50
+ const eventType = "string" == typeof typed.type && typed.type.trim() ? typed.type.trim() : "error";
51
+ const message = "string" == typeof typed.message && typed.message.trim() ? typed.message.trim() : null;
52
+ const reason = "string" == typeof typed.reason && typed.reason.trim() ? typed.reason.trim() : null;
53
+ const detail = message || reason;
54
+ return detail ? `WebSocket ${eventType} while connecting to ${url}: ${detail}` : `WebSocket ${eventType} while connecting to ${url}.`;
55
+ }
56
+ return `WebSocket connection failed to ${url}.`;
57
+ }
41
58
  class WebSocketTransport {
42
59
  async connect() {
43
60
  return new Promise((resolve, reject)=>{
61
+ let settled = false;
62
+ const rejectOnce = (reason)=>{
63
+ if (settled) return;
64
+ settled = true;
65
+ clearTimeout(timeout);
66
+ reject(reason);
67
+ };
68
+ const resolveOnce = ()=>{
69
+ if (settled) return;
70
+ settled = true;
71
+ clearTimeout(timeout);
72
+ resolve();
73
+ };
44
74
  const timeout = setTimeout(()=>{
45
75
  if (this.ws) this.ws.close();
46
- reject(new Error("Connection timeout"));
76
+ rejectOnce(new Error(`WebSocket connection timeout after ${this.options.connectionTimeout}ms (${this.url}).`));
47
77
  }, this.options.connectionTimeout);
48
78
  try {
49
79
  this.ws = new WebSocket(this.url);
50
80
  this.ws.onopen = ()=>{
51
- clearTimeout(timeout);
52
81
  this.reconnectAttempts = 0;
53
- resolve();
82
+ resolveOnce();
54
83
  };
55
84
  this.ws.onmessage = (event)=>{
56
85
  try {
@@ -61,16 +90,20 @@ class WebSocketTransport {
61
90
  }
62
91
  };
63
92
  this.ws.onerror = (error)=>{
64
- clearTimeout(timeout);
65
- reject(error);
93
+ rejectOnce(new Error(describeWebSocketError(error, this.url)));
66
94
  };
67
- this.ws.onclose = ()=>{
95
+ this.ws.onclose = (event)=>{
96
+ if (!settled) {
97
+ const code = event && "number" == typeof event.code ? String(event.code) : "unknown";
98
+ const reason = event && "string" == typeof event.reason && event.reason.trim() ? ` (${event.reason.trim()})` : "";
99
+ rejectOnce(new Error(`WebSocket closed before connection was established (${this.url}, code=${code})${reason}.`));
100
+ return;
101
+ }
68
102
  this.ws = null;
69
103
  if (this.options.autoReconnect && this.reconnectAttempts < (this.options.maxReconnectAttempts || 5)) this.scheduleReconnect();
70
104
  };
71
105
  } catch (error) {
72
- clearTimeout(timeout);
73
- reject(error);
106
+ rejectOnce(new Error(describeWebSocketError(error, this.url)));
74
107
  }
75
108
  });
76
109
  }
@@ -124,8 +157,10 @@ class WebSocketTransport {
124
157
  }
125
158
  }
126
159
  exports.WebSocketTransport = __webpack_exports__.WebSocketTransport;
160
+ exports.describeWebSocketError = __webpack_exports__.describeWebSocketError;
127
161
  for(var __rspack_i in __webpack_exports__)if (-1 === [
128
- "WebSocketTransport"
162
+ "WebSocketTransport",
163
+ "describeWebSocketError"
129
164
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
130
165
  Object.defineProperty(exports, '__esModule', {
131
166
  value: true
@@ -1,5 +1,6 @@
1
1
  import type { GatewayMessage } from "../types.js";
2
2
  import type { TransportClient, TransportOptions } from "./types.js";
3
+ export declare function describeWebSocketError(error: unknown, url: string): string;
3
4
  /**
4
5
  * WebSocket transport client
5
6
  */
@@ -10,19 +10,47 @@ function _define_property(obj, key, value) {
10
10
  return obj;
11
11
  }
12
12
  const logger = createLogger();
13
+ function describeWebSocketError(error, url) {
14
+ if (error instanceof Error) {
15
+ const message = error.message.trim();
16
+ return message ? `WebSocket connection failed to ${url}: ${message}` : `WebSocket connection failed to ${url}.`;
17
+ }
18
+ if ("string" == typeof error && error.trim()) return `WebSocket connection failed to ${url}: ${error.trim()}`;
19
+ if (error && "object" == typeof error) {
20
+ const typed = error;
21
+ const eventType = "string" == typeof typed.type && typed.type.trim() ? typed.type.trim() : "error";
22
+ const message = "string" == typeof typed.message && typed.message.trim() ? typed.message.trim() : null;
23
+ const reason = "string" == typeof typed.reason && typed.reason.trim() ? typed.reason.trim() : null;
24
+ const detail = message || reason;
25
+ return detail ? `WebSocket ${eventType} while connecting to ${url}: ${detail}` : `WebSocket ${eventType} while connecting to ${url}.`;
26
+ }
27
+ return `WebSocket connection failed to ${url}.`;
28
+ }
13
29
  class WebSocketTransport {
14
30
  async connect() {
15
31
  return new Promise((resolve, reject)=>{
32
+ let settled = false;
33
+ const rejectOnce = (reason)=>{
34
+ if (settled) return;
35
+ settled = true;
36
+ clearTimeout(timeout);
37
+ reject(reason);
38
+ };
39
+ const resolveOnce = ()=>{
40
+ if (settled) return;
41
+ settled = true;
42
+ clearTimeout(timeout);
43
+ resolve();
44
+ };
16
45
  const timeout = setTimeout(()=>{
17
46
  if (this.ws) this.ws.close();
18
- reject(new Error("Connection timeout"));
47
+ rejectOnce(new Error(`WebSocket connection timeout after ${this.options.connectionTimeout}ms (${this.url}).`));
19
48
  }, this.options.connectionTimeout);
20
49
  try {
21
50
  this.ws = new WebSocket(this.url);
22
51
  this.ws.onopen = ()=>{
23
- clearTimeout(timeout);
24
52
  this.reconnectAttempts = 0;
25
- resolve();
53
+ resolveOnce();
26
54
  };
27
55
  this.ws.onmessage = (event)=>{
28
56
  try {
@@ -33,16 +61,20 @@ class WebSocketTransport {
33
61
  }
34
62
  };
35
63
  this.ws.onerror = (error)=>{
36
- clearTimeout(timeout);
37
- reject(error);
64
+ rejectOnce(new Error(describeWebSocketError(error, this.url)));
38
65
  };
39
- this.ws.onclose = ()=>{
66
+ this.ws.onclose = (event)=>{
67
+ if (!settled) {
68
+ const code = event && "number" == typeof event.code ? String(event.code) : "unknown";
69
+ const reason = event && "string" == typeof event.reason && event.reason.trim() ? ` (${event.reason.trim()})` : "";
70
+ rejectOnce(new Error(`WebSocket closed before connection was established (${this.url}, code=${code})${reason}.`));
71
+ return;
72
+ }
40
73
  this.ws = null;
41
74
  if (this.options.autoReconnect && this.reconnectAttempts < (this.options.maxReconnectAttempts || 5)) this.scheduleReconnect();
42
75
  };
43
76
  } catch (error) {
44
- clearTimeout(timeout);
45
- reject(error);
77
+ rejectOnce(new Error(describeWebSocketError(error, this.url)));
46
78
  }
47
79
  });
48
80
  }
@@ -95,4 +127,4 @@ class WebSocketTransport {
95
127
  };
96
128
  }
97
129
  }
98
- export { WebSocketTransport };
130
+ export { WebSocketTransport, describeWebSocketError };
@@ -58,6 +58,10 @@ export interface AgentRequestPayload {
58
58
  agentId?: string;
59
59
  content?: string;
60
60
  attachments?: MediaAttachment[];
61
+ execution?: {
62
+ workspace?: string;
63
+ configDir?: string;
64
+ };
61
65
  routing?: RoutingInfo;
62
66
  sessionKey?: string;
63
67
  queueIfBusy?: boolean;
@@ -348,42 +348,6 @@ const parseConfig = (input)=>{
348
348
  })).toBeUndefined();
349
349
  });
350
350
  });
351
- (0, external_vitest_namespaceObject.describe)("evaluateStreamingCompletion", ()=>{
352
- (0, external_vitest_namespaceObject.it)("blocks with stream_error when no assistant text and stream error exists", ()=>{
353
- const result = (0, agentInvoker_cjs_namespaceObject.evaluateStreamingCompletion)({
354
- sawAssistantText: false,
355
- fallbackText: void 0,
356
- streamErrorMessage: "provider timeout"
357
- });
358
- (0, external_vitest_namespaceObject.expect)(result).toEqual({
359
- status: "blocked",
360
- reason: "stream_error",
361
- message: "Model call failed: provider timeout"
362
- });
363
- });
364
- (0, external_vitest_namespaceObject.it)("blocks with empty_stream_response when no text or fallback is present", ()=>{
365
- const result = (0, agentInvoker_cjs_namespaceObject.evaluateStreamingCompletion)({
366
- sawAssistantText: false,
367
- fallbackText: void 0,
368
- streamErrorMessage: void 0
369
- });
370
- (0, external_vitest_namespaceObject.expect)(result).toEqual({
371
- status: "blocked",
372
- reason: "empty_stream_response",
373
- message: "Model completed without a response. Check provider logs for request errors."
374
- });
375
- });
376
- (0, external_vitest_namespaceObject.it)("returns ok when assistant text exists", ()=>{
377
- const result = (0, agentInvoker_cjs_namespaceObject.evaluateStreamingCompletion)({
378
- sawAssistantText: true,
379
- fallbackText: void 0,
380
- streamErrorMessage: void 0
381
- });
382
- (0, external_vitest_namespaceObject.expect)(result).toEqual({
383
- status: "ok"
384
- });
385
- });
386
- });
387
351
  (0, external_vitest_namespaceObject.describe)("LangGraph lifecycle termination", ()=>{
388
352
  (0, external_vitest_namespaceObject.it)("tracks root LangGraph run id from parentless on_chain_start", ()=>{
389
353
  const rootRunId = (0, agentInvoker_cjs_namespaceObject.trackRootLangGraphRunId)(void 0, {
@@ -441,12 +405,67 @@ const parseConfig = (input)=>{
441
405
  run_id: "root-run",
442
406
  parent_ids: []
443
407
  }, "root-run")).toBe(false);
408
+ });
409
+ (0, external_vitest_namespaceObject.it)("treats root LangGraph on_chain_end as terminal even without tracked run id", ()=>{
444
410
  (0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.isRootLangGraphTerminalEvent)({
445
411
  event: "on_chain_end",
446
412
  name: "LangGraph",
447
413
  run_id: "root-run",
448
414
  parent_ids: []
449
- }, void 0)).toBe(false);
415
+ }, void 0)).toBe(true);
416
+ });
417
+ (0, external_vitest_namespaceObject.it)("treats root LangGraph on_chain_end as terminal when run id is missing", ()=>{
418
+ (0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.isRootLangGraphTerminalEvent)({
419
+ event: "on_chain_end",
420
+ name: "LangGraph",
421
+ parent_ids: []
422
+ }, "root-run")).toBe(true);
423
+ });
424
+ });
425
+ (0, external_vitest_namespaceObject.describe)("emitCompletionAndContinuePostProcessing", ()=>{
426
+ (0, external_vitest_namespaceObject.it)("emits completion before post-processing resolves", ()=>{
427
+ const callOrder = [];
428
+ let resolvePostProcess;
429
+ const postProcess = ()=>new Promise((resolve)=>{
430
+ callOrder.push("post-process-start");
431
+ resolvePostProcess = resolve;
432
+ });
433
+ (0, agentInvoker_cjs_namespaceObject.emitCompletionAndContinuePostProcessing)({
434
+ outputManager: {
435
+ emitAgentComplete: ()=>{
436
+ callOrder.push("emit-complete");
437
+ }
438
+ },
439
+ result: {
440
+ ok: true
441
+ },
442
+ postProcess
443
+ });
444
+ (0, external_vitest_namespaceObject.expect)(callOrder).toEqual([
445
+ "emit-complete",
446
+ "post-process-start"
447
+ ]);
448
+ resolvePostProcess?.();
449
+ });
450
+ (0, external_vitest_namespaceObject.it)("logs and swallows post-processing failures", async ()=>{
451
+ const debug = external_vitest_namespaceObject.vi.fn();
452
+ (0, agentInvoker_cjs_namespaceObject.emitCompletionAndContinuePostProcessing)({
453
+ outputManager: {
454
+ emitAgentComplete: external_vitest_namespaceObject.vi.fn()
455
+ },
456
+ result: {
457
+ ok: true
458
+ },
459
+ postProcess: async ()=>{
460
+ throw new Error("materialization failed");
461
+ },
462
+ logger: {
463
+ debug
464
+ }
465
+ });
466
+ await Promise.resolve();
467
+ await Promise.resolve();
468
+ (0, external_vitest_namespaceObject.expect)(debug).toHaveBeenCalledWith("Failed post-completion processing for streamed agent response", external_vitest_namespaceObject.expect.any(Error));
450
469
  });
451
470
  });
452
471
  for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];