@wingman-ai/gateway 0.2.4 → 0.3.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 (95) hide show
  1. package/.wingman/agents/coding/agent.md +5 -0
  2. package/.wingman/agents/coding-v2/agent.md +58 -0
  3. package/.wingman/agents/game-dev/agent.md +94 -0
  4. package/.wingman/agents/game-dev/art-generation.md +37 -0
  5. package/.wingman/agents/game-dev/asset-refinement.md +17 -0
  6. package/.wingman/agents/game-dev/planning-idea.md +17 -0
  7. package/.wingman/agents/game-dev/ui-specialist.md +17 -0
  8. package/.wingman/agents/main/agent.md +2 -0
  9. package/README.md +1 -0
  10. package/dist/agent/config/agentConfig.d.ts +4 -0
  11. package/dist/agent/config/mcpClientManager.cjs +44 -10
  12. package/dist/agent/config/mcpClientManager.d.ts +6 -2
  13. package/dist/agent/config/mcpClientManager.js +44 -10
  14. package/dist/agent/config/toolRegistry.cjs +3 -1
  15. package/dist/agent/config/toolRegistry.js +3 -1
  16. package/dist/agent/tests/mcpClientManager.test.cjs +124 -0
  17. package/dist/agent/tests/mcpClientManager.test.d.ts +1 -0
  18. package/dist/agent/tests/mcpClientManager.test.js +118 -0
  19. package/dist/agent/tools/command_execute.cjs +1 -1
  20. package/dist/agent/tools/command_execute.js +1 -1
  21. package/dist/cli/config/schema.d.ts +2 -0
  22. package/dist/cli/core/agentInvoker.cjs +55 -66
  23. package/dist/cli/core/agentInvoker.d.ts +10 -13
  24. package/dist/cli/core/agentInvoker.js +42 -62
  25. package/dist/cli/core/imagePersistence.cjs +125 -0
  26. package/dist/cli/core/imagePersistence.d.ts +24 -0
  27. package/dist/cli/core/imagePersistence.js +85 -0
  28. package/dist/cli/core/sessionManager.cjs +297 -40
  29. package/dist/cli/core/sessionManager.d.ts +9 -0
  30. package/dist/cli/core/sessionManager.js +297 -40
  31. package/dist/debug/terminalProbe.cjs +57 -0
  32. package/dist/debug/terminalProbe.d.ts +10 -0
  33. package/dist/debug/terminalProbe.js +20 -0
  34. package/dist/debug/terminalProbeAuth.cjs +140 -0
  35. package/dist/debug/terminalProbeAuth.d.ts +20 -0
  36. package/dist/debug/terminalProbeAuth.js +97 -0
  37. package/dist/gateway/http/fs.cjs +19 -0
  38. package/dist/gateway/http/fs.js +19 -0
  39. package/dist/gateway/http/sessions.cjs +25 -5
  40. package/dist/gateway/http/sessions.js +25 -5
  41. package/dist/gateway/server.cjs +112 -11
  42. package/dist/gateway/server.d.ts +2 -0
  43. package/dist/gateway/server.js +112 -11
  44. package/dist/providers/codex.cjs +230 -37
  45. package/dist/providers/codex.d.ts +2 -0
  46. package/dist/providers/codex.js +231 -38
  47. package/dist/tests/agentInvokerSummarization.test.cjs +56 -37
  48. package/dist/tests/agentInvokerSummarization.test.js +58 -39
  49. package/dist/tests/agentInvokerWorkdir.test.cjs +50 -0
  50. package/dist/tests/agentInvokerWorkdir.test.js +52 -2
  51. package/dist/tests/cli-init.test.cjs +36 -0
  52. package/dist/tests/cli-init.test.js +36 -0
  53. package/dist/tests/codex-provider.test.cjs +173 -0
  54. package/dist/tests/codex-provider.test.js +174 -1
  55. package/dist/tests/falRuntime.test.cjs +78 -0
  56. package/dist/tests/falRuntime.test.d.ts +1 -0
  57. package/dist/tests/falRuntime.test.js +72 -0
  58. package/dist/tests/falSummary.test.cjs +51 -0
  59. package/dist/tests/falSummary.test.d.ts +1 -0
  60. package/dist/tests/falSummary.test.js +45 -0
  61. package/dist/tests/gateway.test.cjs +109 -1
  62. package/dist/tests/gateway.test.js +109 -1
  63. package/dist/tests/imagePersistence.test.cjs +143 -0
  64. package/dist/tests/imagePersistence.test.d.ts +1 -0
  65. package/dist/tests/imagePersistence.test.js +137 -0
  66. package/dist/tests/sessionMessageAttachments.test.cjs +30 -0
  67. package/dist/tests/sessionMessageAttachments.test.js +30 -0
  68. package/dist/tests/sessionStateMessages.test.cjs +126 -0
  69. package/dist/tests/sessionStateMessages.test.js +126 -0
  70. package/dist/tests/sessions-api.test.cjs +117 -3
  71. package/dist/tests/sessions-api.test.js +118 -4
  72. package/dist/tests/terminalProbe.test.cjs +45 -0
  73. package/dist/tests/terminalProbe.test.d.ts +1 -0
  74. package/dist/tests/terminalProbe.test.js +39 -0
  75. package/dist/tests/terminalProbeAuth.test.cjs +85 -0
  76. package/dist/tests/terminalProbeAuth.test.d.ts +1 -0
  77. package/dist/tests/terminalProbeAuth.test.js +79 -0
  78. package/dist/tools/fal/runtime.cjs +103 -0
  79. package/dist/tools/fal/runtime.d.ts +10 -0
  80. package/dist/tools/fal/runtime.js +60 -0
  81. package/dist/tools/fal/summary.cjs +78 -0
  82. package/dist/tools/fal/summary.d.ts +22 -0
  83. package/dist/tools/fal/summary.js +41 -0
  84. package/dist/tools/mcp-fal-ai.cjs +1041 -0
  85. package/dist/tools/mcp-fal-ai.d.ts +1 -0
  86. package/dist/tools/mcp-fal-ai.js +1025 -0
  87. package/dist/types/mcp.cjs +2 -0
  88. package/dist/types/mcp.d.ts +8 -0
  89. package/dist/types/mcp.js +3 -1
  90. package/dist/webui/assets/index-0nUBsUUq.js +278 -0
  91. package/dist/webui/assets/index-kk7OrD-G.css +11 -0
  92. package/dist/webui/index.html +2 -2
  93. package/package.json +16 -13
  94. package/dist/webui/assets/index-DVWQluit.css +0 -11
  95. package/dist/webui/assets/index-Dlyzwalc.js +0 -270
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ TERMINAL_PROBE_GATEWAY_TOKEN_ENV: ()=>TERMINAL_PROBE_GATEWAY_TOKEN_ENV,
28
+ resolveTerminalProbeAuth: ()=>resolveTerminalProbeAuth,
29
+ formatTerminalProbeHandshakeFailure: ()=>formatTerminalProbeHandshakeFailure,
30
+ TERMINAL_PROBE_GATEWAY_PASSWORD_ENV: ()=>TERMINAL_PROBE_GATEWAY_PASSWORD_ENV
31
+ });
32
+ const external_node_fs_namespaceObject = require("node:fs");
33
+ const external_node_os_namespaceObject = require("node:os");
34
+ const external_node_path_namespaceObject = require("node:path");
35
+ const TERMINAL_PROBE_GATEWAY_TOKEN_ENV = "WINGMAN_GATEWAY_TOKEN";
36
+ const TERMINAL_PROBE_GATEWAY_PASSWORD_ENV = "WINGMAN_GATEWAY_PASSWORD";
37
+ function normalizeSecret(raw) {
38
+ if ("string" != typeof raw) return;
39
+ const value = raw.trim();
40
+ return value.length > 0 ? value : void 0;
41
+ }
42
+ function parseGatewayAuthConfig(rawConfig) {
43
+ if (!rawConfig || "object" != typeof rawConfig || Array.isArray(rawConfig)) return null;
44
+ const config = rawConfig;
45
+ const gateway = config.gateway;
46
+ if (!gateway || "object" != typeof gateway || Array.isArray(gateway)) return null;
47
+ const auth = gateway.auth;
48
+ if (!auth || "object" != typeof auth || Array.isArray(auth)) return null;
49
+ const authConfig = auth;
50
+ const mode = "string" == typeof authConfig.mode ? authConfig.mode.toLowerCase() : "";
51
+ const token = normalizeSecret(authConfig.token);
52
+ const password = normalizeSecret(authConfig.password);
53
+ if ("token" === mode && token) return {
54
+ token
55
+ };
56
+ if ("password" === mode && password) return {
57
+ password
58
+ };
59
+ if (!mode && (token || password)) return {
60
+ token,
61
+ password
62
+ };
63
+ return null;
64
+ }
65
+ function unique(values) {
66
+ return Array.from(new Set(values));
67
+ }
68
+ function resolveConfigCandidates(options) {
69
+ if (options.configFileCandidates?.length) return unique(options.configFileCandidates);
70
+ const cwd = options.cwd ?? process.cwd();
71
+ const homeDir = options.homeDir ?? (0, external_node_os_namespaceObject.homedir)();
72
+ return unique([
73
+ (0, external_node_path_namespaceObject.join)(cwd, ".wingman", "wingman.config.json"),
74
+ (0, external_node_path_namespaceObject.join)(homeDir, ".wingman", "wingman.config.json")
75
+ ]);
76
+ }
77
+ function resolveAuthFromConfig(options) {
78
+ for (const configPath of resolveConfigCandidates(options))if ((0, external_node_fs_namespaceObject.existsSync)(configPath)) try {
79
+ const raw = (0, external_node_fs_namespaceObject.readFileSync)(configPath, "utf-8");
80
+ const parsed = JSON.parse(raw);
81
+ const auth = parseGatewayAuthConfig(parsed);
82
+ if (auth) return {
83
+ ...auth,
84
+ configPath
85
+ };
86
+ } catch {}
87
+ return null;
88
+ }
89
+ function resolveTerminalProbeAuth(options = {}) {
90
+ const cliToken = normalizeSecret(options.cliToken);
91
+ const cliPassword = normalizeSecret(options.cliPassword);
92
+ if (cliToken || cliPassword) return {
93
+ token: cliToken,
94
+ password: cliPassword,
95
+ source: "cli"
96
+ };
97
+ const env = options.env ?? process.env;
98
+ const envToken = normalizeSecret(env[TERMINAL_PROBE_GATEWAY_TOKEN_ENV]);
99
+ const envPassword = normalizeSecret(env[TERMINAL_PROBE_GATEWAY_PASSWORD_ENV]);
100
+ if (envToken || envPassword) return {
101
+ token: envToken,
102
+ password: envPassword,
103
+ source: "env"
104
+ };
105
+ const fromConfig = resolveAuthFromConfig(options);
106
+ if (fromConfig?.token || fromConfig?.password) return {
107
+ token: fromConfig.token,
108
+ password: fromConfig.password,
109
+ source: "config",
110
+ configPath: fromConfig.configPath
111
+ };
112
+ return {
113
+ source: "none"
114
+ };
115
+ }
116
+ function formatTerminalProbeHandshakeFailure(payload) {
117
+ if ("string" == typeof payload) {
118
+ const value = payload.trim();
119
+ return value.length > 0 ? value : "unknown";
120
+ }
121
+ if (null == payload) return "unknown";
122
+ try {
123
+ return JSON.stringify(payload);
124
+ } catch {
125
+ return "unknown";
126
+ }
127
+ }
128
+ exports.TERMINAL_PROBE_GATEWAY_PASSWORD_ENV = __webpack_exports__.TERMINAL_PROBE_GATEWAY_PASSWORD_ENV;
129
+ exports.TERMINAL_PROBE_GATEWAY_TOKEN_ENV = __webpack_exports__.TERMINAL_PROBE_GATEWAY_TOKEN_ENV;
130
+ exports.formatTerminalProbeHandshakeFailure = __webpack_exports__.formatTerminalProbeHandshakeFailure;
131
+ exports.resolveTerminalProbeAuth = __webpack_exports__.resolveTerminalProbeAuth;
132
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
133
+ "TERMINAL_PROBE_GATEWAY_PASSWORD_ENV",
134
+ "TERMINAL_PROBE_GATEWAY_TOKEN_ENV",
135
+ "formatTerminalProbeHandshakeFailure",
136
+ "resolveTerminalProbeAuth"
137
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
138
+ Object.defineProperty(exports, '__esModule', {
139
+ value: true
140
+ });
@@ -0,0 +1,20 @@
1
+ export declare const TERMINAL_PROBE_GATEWAY_TOKEN_ENV = "WINGMAN_GATEWAY_TOKEN";
2
+ export declare const TERMINAL_PROBE_GATEWAY_PASSWORD_ENV = "WINGMAN_GATEWAY_PASSWORD";
3
+ type TerminalProbeAuthSource = "cli" | "env" | "config" | "none";
4
+ export type TerminalProbeAuth = {
5
+ token?: string;
6
+ password?: string;
7
+ source: TerminalProbeAuthSource;
8
+ configPath?: string;
9
+ };
10
+ type ResolveTerminalProbeAuthOptions = {
11
+ cliToken?: string;
12
+ cliPassword?: string;
13
+ env?: Record<string, string | undefined>;
14
+ cwd?: string;
15
+ homeDir?: string;
16
+ configFileCandidates?: string[];
17
+ };
18
+ export declare function resolveTerminalProbeAuth(options?: ResolveTerminalProbeAuthOptions): TerminalProbeAuth;
19
+ export declare function formatTerminalProbeHandshakeFailure(payload: unknown): string;
20
+ export {};
@@ -0,0 +1,97 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const TERMINAL_PROBE_GATEWAY_TOKEN_ENV = "WINGMAN_GATEWAY_TOKEN";
5
+ const TERMINAL_PROBE_GATEWAY_PASSWORD_ENV = "WINGMAN_GATEWAY_PASSWORD";
6
+ function normalizeSecret(raw) {
7
+ if ("string" != typeof raw) return;
8
+ const value = raw.trim();
9
+ return value.length > 0 ? value : void 0;
10
+ }
11
+ function parseGatewayAuthConfig(rawConfig) {
12
+ if (!rawConfig || "object" != typeof rawConfig || Array.isArray(rawConfig)) return null;
13
+ const config = rawConfig;
14
+ const gateway = config.gateway;
15
+ if (!gateway || "object" != typeof gateway || Array.isArray(gateway)) return null;
16
+ const auth = gateway.auth;
17
+ if (!auth || "object" != typeof auth || Array.isArray(auth)) return null;
18
+ const authConfig = auth;
19
+ const mode = "string" == typeof authConfig.mode ? authConfig.mode.toLowerCase() : "";
20
+ const token = normalizeSecret(authConfig.token);
21
+ const password = normalizeSecret(authConfig.password);
22
+ if ("token" === mode && token) return {
23
+ token
24
+ };
25
+ if ("password" === mode && password) return {
26
+ password
27
+ };
28
+ if (!mode && (token || password)) return {
29
+ token,
30
+ password
31
+ };
32
+ return null;
33
+ }
34
+ function unique(values) {
35
+ return Array.from(new Set(values));
36
+ }
37
+ function resolveConfigCandidates(options) {
38
+ if (options.configFileCandidates?.length) return unique(options.configFileCandidates);
39
+ const cwd = options.cwd ?? process.cwd();
40
+ const homeDir = options.homeDir ?? homedir();
41
+ return unique([
42
+ join(cwd, ".wingman", "wingman.config.json"),
43
+ join(homeDir, ".wingman", "wingman.config.json")
44
+ ]);
45
+ }
46
+ function resolveAuthFromConfig(options) {
47
+ for (const configPath of resolveConfigCandidates(options))if (existsSync(configPath)) try {
48
+ const raw = readFileSync(configPath, "utf-8");
49
+ const parsed = JSON.parse(raw);
50
+ const auth = parseGatewayAuthConfig(parsed);
51
+ if (auth) return {
52
+ ...auth,
53
+ configPath
54
+ };
55
+ } catch {}
56
+ return null;
57
+ }
58
+ function resolveTerminalProbeAuth(options = {}) {
59
+ const cliToken = normalizeSecret(options.cliToken);
60
+ const cliPassword = normalizeSecret(options.cliPassword);
61
+ if (cliToken || cliPassword) return {
62
+ token: cliToken,
63
+ password: cliPassword,
64
+ source: "cli"
65
+ };
66
+ const env = options.env ?? process.env;
67
+ const envToken = normalizeSecret(env[TERMINAL_PROBE_GATEWAY_TOKEN_ENV]);
68
+ const envPassword = normalizeSecret(env[TERMINAL_PROBE_GATEWAY_PASSWORD_ENV]);
69
+ if (envToken || envPassword) return {
70
+ token: envToken,
71
+ password: envPassword,
72
+ source: "env"
73
+ };
74
+ const fromConfig = resolveAuthFromConfig(options);
75
+ if (fromConfig?.token || fromConfig?.password) return {
76
+ token: fromConfig.token,
77
+ password: fromConfig.password,
78
+ source: "config",
79
+ configPath: fromConfig.configPath
80
+ };
81
+ return {
82
+ source: "none"
83
+ };
84
+ }
85
+ function formatTerminalProbeHandshakeFailure(payload) {
86
+ if ("string" == typeof payload) {
87
+ const value = payload.trim();
88
+ return value.length > 0 ? value : "unknown";
89
+ }
90
+ if (null == payload) return "unknown";
91
+ try {
92
+ return JSON.stringify(payload);
93
+ } catch {
94
+ return "unknown";
95
+ }
96
+ }
97
+ export { TERMINAL_PROBE_GATEWAY_PASSWORD_ENV, TERMINAL_PROBE_GATEWAY_TOKEN_ENV, formatTerminalProbeHandshakeFailure, resolveTerminalProbeAuth };
@@ -70,6 +70,25 @@ const handleFsApi = async (ctx, req, url)=>{
70
70
  }
71
71
  });
72
72
  }
73
+ if ("/api/fs/file" === url.pathname && "GET" === req.method) {
74
+ const rawPath = url.searchParams.get("path");
75
+ if (!rawPath) return new Response("path required", {
76
+ status: 400
77
+ });
78
+ const resolved = ctx.resolveFsPath(rawPath);
79
+ const roots = ctx.resolveFsRoots();
80
+ if (!ctx.isPathWithinRoots(resolved, roots)) return new Response("path not allowed", {
81
+ status: 403
82
+ });
83
+ if (!(0, external_node_fs_namespaceObject.existsSync)(resolved) || !(0, external_node_fs_namespaceObject.statSync)(resolved).isFile()) return new Response("path not found", {
84
+ status: 404
85
+ });
86
+ return new Response(Bun.file(resolved), {
87
+ headers: {
88
+ "Cache-Control": "no-store"
89
+ }
90
+ });
91
+ }
73
92
  return null;
74
93
  };
75
94
  exports.handleFsApi = __webpack_exports__.handleFsApi;
@@ -42,6 +42,25 @@ const handleFsApi = async (ctx, req, url)=>{
42
42
  }
43
43
  });
44
44
  }
45
+ if ("/api/fs/file" === url.pathname && "GET" === req.method) {
46
+ const rawPath = url.searchParams.get("path");
47
+ if (!rawPath) return new Response("path required", {
48
+ status: 400
49
+ });
50
+ const resolved = ctx.resolveFsPath(rawPath);
51
+ const roots = ctx.resolveFsRoots();
52
+ if (!ctx.isPathWithinRoots(resolved, roots)) return new Response("path not allowed", {
53
+ status: 403
54
+ });
55
+ if (!existsSync(resolved) || !statSync(resolved).isFile()) return new Response("path not found", {
56
+ status: 404
57
+ });
58
+ return new Response(Bun.file(resolved), {
59
+ headers: {
60
+ "Cache-Control": "no-store"
61
+ }
62
+ });
63
+ }
45
64
  return null;
46
65
  };
47
66
  export { handleFsApi };
@@ -28,17 +28,37 @@ __webpack_require__.d(__webpack_exports__, {
28
28
  });
29
29
  const external_node_crypto_namespaceObject = require("node:crypto");
30
30
  const external_node_fs_namespaceObject = require("node:fs");
31
+ const agentLoader_cjs_namespaceObject = require("../../agent/config/agentLoader.cjs");
32
+ const getSessionAgents = (ctx, explicitAgentId)=>{
33
+ if (explicitAgentId) return [
34
+ explicitAgentId
35
+ ];
36
+ const config = ctx.getWingmanConfig();
37
+ const configuredAgents = config.agents?.list?.map((agent)=>agent.id?.trim()).filter((id)=>Boolean(id)) ?? [];
38
+ let discoveredAgents = [];
39
+ try {
40
+ const loader = new agentLoader_cjs_namespaceObject.AgentLoader(ctx.configDir, ctx.workspace, config);
41
+ discoveredAgents = loader.loadAllAgentConfigs().map((agent)=>agent.name?.trim()).filter((id)=>Boolean(id));
42
+ } catch (error) {
43
+ if ("function" == typeof ctx.logger?.warn) ctx.logger.warn("Failed to load discovered agents while listing sessions", error);
44
+ }
45
+ const agents = [
46
+ ...new Set([
47
+ ...configuredAgents,
48
+ ...discoveredAgents
49
+ ])
50
+ ];
51
+ return agents.length > 0 ? agents : [
52
+ "main"
53
+ ];
54
+ };
31
55
  const handleSessionsApi = async (ctx, req, url)=>{
32
56
  if ("/api/sessions" === url.pathname) {
33
57
  if ("GET" === req.method) {
34
58
  const limit = Number(url.searchParams.get("limit") || "100");
35
59
  const status = url.searchParams.get("status") || "active";
36
60
  const agentId = url.searchParams.get("agentId") || void 0;
37
- const agents = agentId ? [
38
- agentId
39
- ] : ctx.getWingmanConfig().agents?.list?.map((agent)=>agent.id) || [
40
- "main"
41
- ];
61
+ const agents = getSessionAgents(ctx, agentId);
42
62
  const sessions = [];
43
63
  for (const agent of agents){
44
64
  const manager = await ctx.getSessionManager(agent);
@@ -1,16 +1,36 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { existsSync, statSync } from "node:fs";
3
+ import { AgentLoader } from "../../agent/config/agentLoader.js";
4
+ const getSessionAgents = (ctx, explicitAgentId)=>{
5
+ if (explicitAgentId) return [
6
+ explicitAgentId
7
+ ];
8
+ const config = ctx.getWingmanConfig();
9
+ const configuredAgents = config.agents?.list?.map((agent)=>agent.id?.trim()).filter((id)=>Boolean(id)) ?? [];
10
+ let discoveredAgents = [];
11
+ try {
12
+ const loader = new AgentLoader(ctx.configDir, ctx.workspace, config);
13
+ discoveredAgents = loader.loadAllAgentConfigs().map((agent)=>agent.name?.trim()).filter((id)=>Boolean(id));
14
+ } catch (error) {
15
+ if ("function" == typeof ctx.logger?.warn) ctx.logger.warn("Failed to load discovered agents while listing sessions", error);
16
+ }
17
+ const agents = [
18
+ ...new Set([
19
+ ...configuredAgents,
20
+ ...discoveredAgents
21
+ ])
22
+ ];
23
+ return agents.length > 0 ? agents : [
24
+ "main"
25
+ ];
26
+ };
3
27
  const handleSessionsApi = async (ctx, req, url)=>{
4
28
  if ("/api/sessions" === url.pathname) {
5
29
  if ("GET" === req.method) {
6
30
  const limit = Number(url.searchParams.get("limit") || "100");
7
31
  const status = url.searchParams.get("status") || "active";
8
32
  const agentId = url.searchParams.get("agentId") || void 0;
9
- const agents = agentId ? [
10
- agentId
11
- ] : ctx.getWingmanConfig().agents?.list?.map((agent)=>agent.id) || [
12
- "main"
13
- ];
33
+ const agents = getSessionAgents(ctx, agentId);
14
34
  const sessions = [];
15
35
  for (const agent of agents){
16
36
  const manager = await ctx.getSessionManager(agent);
@@ -367,12 +367,29 @@ class GatewayServer {
367
367
  const sessionManager = await this.getSessionManager(agentId);
368
368
  const existingSession = sessionManager.getSession(sessionKey);
369
369
  const session = existingSession || sessionManager.getOrCreateSession(sessionKey, agentId);
370
+ const requestId = msg.id || `req-${Date.now()}`;
370
371
  const workdir = session.metadata?.workdir ?? null;
371
372
  const defaultOutputDir = this.resolveDefaultOutputDir(agentId);
372
373
  const preview = hasContent ? content.trim() : buildAttachmentPreview(attachments);
373
374
  sessionManager.updateSession(session.id, {
375
+ messageCount: (session.messageCount ?? 0) + 1,
374
376
  lastMessagePreview: preview.substring(0, 200)
375
377
  });
378
+ try {
379
+ sessionManager.persistPendingMessage({
380
+ sessionId: sessionKey,
381
+ requestId,
382
+ message: {
383
+ id: `user-${requestId}`,
384
+ role: "user",
385
+ content,
386
+ attachments: attachments.length > 0 ? mapAttachmentsForPendingMessage(attachments) : void 0,
387
+ createdAt: Date.now()
388
+ }
389
+ });
390
+ } catch (error) {
391
+ this.logger.warn("Failed to persist pending user message", error);
392
+ }
376
393
  if (!existingSession) this.internalHooks?.emit({
377
394
  type: "session",
378
395
  action: "start",
@@ -475,9 +492,15 @@ class GatewayServer {
475
492
  this.activeSessionRequests.set(sessionQueueKey, msg.id);
476
493
  const outputManager = new outputManager_cjs_namespaceObject.OutputManager("interactive");
477
494
  let emittedAgentError = false;
495
+ let streamedCompletionResult;
478
496
  const outputHandler = (event)=>{
479
497
  const payloadWithSession = this.attachSessionContext(event, sessionKey, agentId);
480
- if (payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession) && "agent-error" === payloadWithSession.type) emittedAgentError = true;
498
+ const payloadType = payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession) && "string" == typeof payloadWithSession.type ? payloadWithSession.type : "";
499
+ if ("agent-complete" === payloadType) {
500
+ if (payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession)) streamedCompletionResult = payloadWithSession.result;
501
+ return;
502
+ }
503
+ if ("agent-error" === payloadType) emittedAgentError = true;
481
504
  const baseMessage = {
482
505
  type: "event:agent",
483
506
  id: msg.id,
@@ -508,17 +531,43 @@ class GatewayServer {
508
531
  abortController
509
532
  });
510
533
  try {
511
- await invoker.invokeAgent(agentId, content, sessionKey, attachments, {
534
+ const invocationResult = await invoker.invokeAgent(agentId, content, sessionKey, attachments, {
512
535
  signal: abortController.signal
513
536
  });
514
- const updated = sessionManager.getSession(sessionKey);
515
- if (updated) sessionManager.updateSession(sessionKey, {
516
- messageCount: updated.messageCount + 1
537
+ if (msg.id) sessionManager.clearPendingMessagesForRequest(sessionKey, msg.id);
538
+ if (emittedAgentError) return;
539
+ const invocationCancelled = abortController.signal.aborted || "object" == typeof invocationResult && null !== invocationResult && !Array.isArray(invocationResult) && true === invocationResult.cancelled;
540
+ if (invocationCancelled) return void this.sendAgentError(ws, msg.id, "Request cancelled", {
541
+ sessionId: sessionKey,
542
+ agentId,
543
+ broadcastToSession: true,
544
+ exclude: ws
545
+ });
546
+ const completionResult = void 0 === streamedCompletionResult ? invocationResult : streamedCompletionResult;
547
+ this.sendAgentComplete(ws, msg.id, completionResult, {
548
+ sessionId: sessionKey,
549
+ agentId,
550
+ broadcastToSession: true,
551
+ exclude: ws
517
552
  });
518
553
  } catch (error) {
519
554
  this.logger.error("Agent invocation failed", error);
555
+ const message = error instanceof Error ? error.message : String(error);
556
+ if (msg.id) try {
557
+ sessionManager.persistPendingMessage({
558
+ sessionId: sessionKey,
559
+ requestId: msg.id,
560
+ message: {
561
+ id: msg.id,
562
+ role: "assistant",
563
+ content: message,
564
+ createdAt: Date.now()
565
+ }
566
+ });
567
+ } catch (persistError) {
568
+ this.logger.warn("Failed to persist pending assistant error message", persistError);
569
+ }
520
570
  if (!emittedAgentError) {
521
- const message = error instanceof Error ? error.message : String(error);
522
571
  const stack = error instanceof Error ? error.stack : void 0;
523
572
  this.sendAgentError(ws, msg.id, message, {
524
573
  sessionId: sessionKey,
@@ -594,6 +643,12 @@ class GatewayServer {
594
643
  },
595
644
  timestamp: Date.now()
596
645
  });
646
+ this.sendAgentError(ws, requestId, "Request cancelled", {
647
+ sessionId: queued.sessionKey,
648
+ agentId: queued.agentId,
649
+ broadcastToSession: true,
650
+ exclude: ws
651
+ });
597
652
  return;
598
653
  }
599
654
  this.sendMessage(ws, {
@@ -768,11 +823,25 @@ class GatewayServer {
768
823
  }
769
824
  sendMessage(ws, message) {
770
825
  try {
771
- ws.send(JSON.stringify(message));
826
+ const result = ws.send(JSON.stringify(message));
827
+ if ("number" == typeof result && result <= 0) return false;
828
+ return true;
772
829
  } catch (error) {
773
830
  this.log("error", "Failed to send message", error);
831
+ return false;
774
832
  }
775
833
  }
834
+ sendMessageWithRetry(ws, message, attempt = 0) {
835
+ if (this.sendMessage(ws, message)) return;
836
+ if (attempt >= 2) return void this.log("warn", "Dropping websocket message after retry attempts", {
837
+ type: message.type,
838
+ id: message.id
839
+ });
840
+ const delayMs = 25 * (attempt + 1);
841
+ setTimeout(()=>{
842
+ this.sendMessageWithRetry(ws, message, attempt + 1);
843
+ }, delayMs);
844
+ }
776
845
  sendError(ws, code, message) {
777
846
  const errorPayload = {
778
847
  code,
@@ -784,6 +853,25 @@ class GatewayServer {
784
853
  timestamp: Date.now()
785
854
  });
786
855
  }
856
+ sendAgentComplete(ws, requestId, result, options) {
857
+ let payload = {
858
+ type: "agent-complete",
859
+ result: result ?? null,
860
+ timestamp: new Date().toISOString()
861
+ };
862
+ if (options?.sessionId && options?.agentId) payload = this.attachSessionContext(payload, options.sessionId, options.agentId);
863
+ const baseMessage = {
864
+ type: "event:agent",
865
+ id: requestId,
866
+ payload,
867
+ timestamp: Date.now()
868
+ };
869
+ this.sendMessageWithRetry(ws, {
870
+ ...baseMessage,
871
+ clientId: ws.data.clientId
872
+ });
873
+ if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude, true);
874
+ }
787
875
  sendAgentError(ws, requestId, message, options) {
788
876
  let payload = {
789
877
  type: "agent-error",
@@ -798,11 +886,11 @@ class GatewayServer {
798
886
  payload,
799
887
  timestamp: Date.now()
800
888
  };
801
- this.sendMessage(ws, {
889
+ this.sendMessageWithRetry(ws, {
802
890
  ...baseMessage,
803
891
  clientId: ws.data.clientId
804
892
  });
805
- if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude);
893
+ if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude, true);
806
894
  }
807
895
  cancelSocketAgentRequests(ws) {
808
896
  for (const [requestId, active] of this.activeAgentRequests)if (active.socket === ws) {
@@ -831,12 +919,13 @@ class GatewayServer {
831
919
  agentId
832
920
  };
833
921
  }
834
- broadcastSessionEvent(sessionId, message, exclude) {
922
+ broadcastSessionEvent(sessionId, message, exclude, reliable = false) {
835
923
  const subscribers = this.sessionSubscriptions.get(sessionId);
836
924
  if (!subscribers || 0 === subscribers.size) return 0;
837
925
  let sent = 0;
838
926
  for (const ws of subscribers)if (!exclude || ws !== exclude) {
839
- this.sendMessage(ws, message);
927
+ if (reliable) this.sendMessageWithRetry(ws, message);
928
+ else this.sendMessage(ws, message);
840
929
  sent++;
841
930
  }
842
931
  return sent;
@@ -1410,6 +1499,18 @@ function buildAttachmentPreview(attachments) {
1410
1499
  if (hasAudio) return count > 1 ? "Audio attachments" : "Audio attachment";
1411
1500
  return count > 1 ? "Image attachments" : "Image attachment";
1412
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
+ }
1413
1514
  function isAudioAttachment(attachment) {
1414
1515
  if ("audio" === attachment.kind) return true;
1415
1516
  if (attachment.mimeType?.startsWith("audio/")) return true;
@@ -129,10 +129,12 @@ export declare class GatewayServer {
129
129
  * Send a message to a WebSocket
130
130
  */
131
131
  private sendMessage;
132
+ private sendMessageWithRetry;
132
133
  /**
133
134
  * Send an error message
134
135
  */
135
136
  private sendError;
137
+ private sendAgentComplete;
136
138
  private sendAgentError;
137
139
  private cancelSocketAgentRequests;
138
140
  private attachSessionContext;