@wingman-ai/gateway 0.2.2 → 0.2.3

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 (65) hide show
  1. package/.wingman/agents/coding/agent.md +174 -169
  2. package/.wingman/agents/coding/implementor.md +25 -1
  3. package/.wingman/agents/main/agent.md +4 -0
  4. package/README.md +1 -0
  5. package/dist/agent/config/agentConfig.cjs +1 -1
  6. package/dist/agent/config/agentConfig.js +1 -1
  7. package/dist/agent/config/modelFactory.cjs +22 -2
  8. package/dist/agent/config/modelFactory.d.ts +2 -0
  9. package/dist/agent/config/modelFactory.js +22 -2
  10. package/dist/agent/tests/modelFactory.test.cjs +12 -5
  11. package/dist/agent/tests/modelFactory.test.js +12 -5
  12. package/dist/cli/commands/init.cjs +7 -6
  13. package/dist/cli/commands/init.js +7 -6
  14. package/dist/cli/commands/provider.cjs +17 -3
  15. package/dist/cli/commands/provider.js +17 -3
  16. package/dist/cli/config/loader.cjs +27 -0
  17. package/dist/cli/config/loader.js +27 -0
  18. package/dist/cli/config/schema.cjs +80 -2
  19. package/dist/cli/config/schema.d.ts +88 -0
  20. package/dist/cli/config/schema.js +67 -1
  21. package/dist/cli/core/agentInvoker.cjs +191 -13
  22. package/dist/cli/core/agentInvoker.d.ts +42 -3
  23. package/dist/cli/core/agentInvoker.js +163 -9
  24. package/dist/cli/core/sessionManager.cjs +32 -5
  25. package/dist/cli/core/sessionManager.js +32 -5
  26. package/dist/cli/index.cjs +6 -5
  27. package/dist/cli/index.js +6 -5
  28. package/dist/cli/types.d.ts +32 -0
  29. package/dist/gateway/http/sessions.cjs +7 -7
  30. package/dist/gateway/http/sessions.js +7 -7
  31. package/dist/gateway/server.cjs +191 -41
  32. package/dist/gateway/server.d.ts +8 -1
  33. package/dist/gateway/server.js +191 -41
  34. package/dist/gateway/types.d.ts +1 -0
  35. package/dist/providers/codex.cjs +167 -0
  36. package/dist/providers/codex.d.ts +15 -0
  37. package/dist/providers/codex.js +127 -0
  38. package/dist/providers/credentials.cjs +8 -0
  39. package/dist/providers/credentials.js +8 -0
  40. package/dist/providers/registry.cjs +11 -0
  41. package/dist/providers/registry.d.ts +1 -1
  42. package/dist/providers/registry.js +11 -0
  43. package/dist/tests/agentInvokerSummarization.test.cjs +296 -0
  44. package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
  45. package/dist/tests/agentInvokerSummarization.test.js +290 -0
  46. package/dist/tests/cli-config-loader.test.cjs +88 -0
  47. package/dist/tests/cli-config-loader.test.js +88 -0
  48. package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
  49. package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
  50. package/dist/tests/codex-credentials-precedence.test.js +88 -0
  51. package/dist/tests/codex-provider.test.cjs +186 -0
  52. package/dist/tests/codex-provider.test.d.ts +1 -0
  53. package/dist/tests/codex-provider.test.js +180 -0
  54. package/dist/tests/gateway.test.cjs +108 -1
  55. package/dist/tests/gateway.test.js +108 -1
  56. package/dist/tests/provider-command-codex.test.cjs +57 -0
  57. package/dist/tests/provider-command-codex.test.d.ts +1 -0
  58. package/dist/tests/provider-command-codex.test.js +51 -0
  59. package/dist/tests/sessionStateMessages.test.cjs +38 -0
  60. package/dist/tests/sessionStateMessages.test.js +38 -0
  61. package/dist/webui/assets/{index-DDsMIOTX.css → index-BVMavpud.css} +1 -1
  62. package/dist/webui/assets/index-DCB2aVVf.js +182 -0
  63. package/dist/webui/index.html +2 -2
  64. package/package.json +1 -1
  65. package/dist/webui/assets/index-CPhfGPHc.js +0 -182
@@ -35,6 +35,8 @@ class SessionManager {
35
35
 
36
36
  CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at DESC);
37
37
  CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent_name);
38
+ CREATE INDEX IF NOT EXISTS idx_sessions_status_updated ON sessions(status, updated_at DESC);
39
+ CREATE INDEX IF NOT EXISTS idx_sessions_status_agent_updated ON sessions(status, agent_name, updated_at DESC);
38
40
  `);
39
41
  }
40
42
  createSession(agentName, name) {
@@ -374,13 +376,38 @@ function extractContentBlocks(entry) {
374
376
  }
375
377
  function extractMessageContent(entry, blocks = []) {
376
378
  if (!entry || "object" != typeof entry) return "";
377
- if ("string" == typeof entry.content) return entry.content;
378
- if ("string" == typeof entry?.kwargs?.content) return entry.kwargs.content;
379
- if ("string" == typeof entry?.additional_kwargs?.content) return entry.additional_kwargs.content;
380
- if ("string" == typeof entry?.data?.content) return entry.data.content;
381
- if (blocks.length > 0) return blocks.filter((block)=>block && "text" === block.type && block.text).map((block)=>block.text).join("");
379
+ const candidates = [
380
+ entry.content,
381
+ entry?.kwargs?.content,
382
+ entry?.additional_kwargs?.content,
383
+ entry?.data?.content
384
+ ];
385
+ for (const candidate of candidates){
386
+ const extracted = extractTextContent(candidate);
387
+ if (extracted) return extracted;
388
+ }
389
+ if (blocks.length > 0) return extractTextContent(blocks);
390
+ return "";
391
+ }
392
+ function extractTextContent(value, depth = 0) {
393
+ if (depth > 5 || null == value) return "";
394
+ if ("string" == typeof value) return value;
395
+ if (Array.isArray(value)) return value.map((entry)=>extractTextContent(entry, depth + 1)).filter((entry)=>entry.length > 0).join("");
396
+ if ("object" != typeof value) return "";
397
+ const record = value;
398
+ if ("string" == typeof record.text) return record.text;
399
+ if (record.text && "object" == typeof record.text && "string" == typeof record.text.value) return record.text.value;
400
+ if ("string" == typeof record.output_text) return record.output_text;
401
+ if ("string" == typeof record.input_text) return record.input_text;
402
+ if ("string" == typeof record.value && isTextLikeContentType(record.type)) return record.value;
403
+ if ("content" in record) return extractTextContent(record.content, depth + 1);
382
404
  return "";
383
405
  }
406
+ function isTextLikeContentType(type) {
407
+ if ("string" != typeof type) return false;
408
+ const normalized = type.toLowerCase();
409
+ return "text" === normalized || "input_text" === normalized || "output_text" === normalized || "text_delta" === normalized;
410
+ }
384
411
  function isToolMessage(entry) {
385
412
  if (!entry || "object" != typeof entry) return false;
386
413
  const role = entry.role || entry?.kwargs?.role || entry?.additional_kwargs?.role;
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  var __webpack_exports__ = {};
4
- const external_logger_cjs_namespaceObject = require("../logger.cjs");
5
- const loader_cjs_namespaceObject = require("./config/loader.cjs");
6
4
  const env_cjs_namespaceObject = require("../gateway/env.cjs");
7
- const outputManager_cjs_namespaceObject = require("./core/outputManager.cjs");
5
+ const external_logger_cjs_namespaceObject = require("../logger.cjs");
8
6
  const agent_cjs_namespaceObject = require("./commands/agent.cjs");
9
- const skill_cjs_namespaceObject = require("./commands/skill.cjs");
10
7
  const gateway_cjs_namespaceObject = require("./commands/gateway.cjs");
11
- const provider_cjs_namespaceObject = require("./commands/provider.cjs");
12
8
  const init_cjs_namespaceObject = require("./commands/init.cjs");
9
+ const provider_cjs_namespaceObject = require("./commands/provider.cjs");
10
+ const skill_cjs_namespaceObject = require("./commands/skill.cjs");
11
+ const loader_cjs_namespaceObject = require("./config/loader.cjs");
12
+ const outputManager_cjs_namespaceObject = require("./core/outputManager.cjs");
13
13
  function parseArgs(argv) {
14
14
  const args = argv.slice(2);
15
15
  if (args.includes("--help") || args.includes("-h")) return {
@@ -114,6 +114,7 @@ Examples:
114
114
  wingman skill install pdf
115
115
  wingman skill list
116
116
  wingman provider status
117
+ wingman provider login codex
117
118
  wingman provider login copilot --token="<token>"
118
119
  wingman gateway start
119
120
  wingman gateway join ws://localhost:3000/ws --name="agent-1"
package/dist/cli/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { createLogger, getLogFilePath } from "../logger.js";
3
- import { WingmanConfigLoader } from "./config/loader.js";
4
2
  import { getGatewayTokenFromEnv } from "../gateway/env.js";
5
- import { OutputManager } from "./core/outputManager.js";
3
+ import { createLogger, getLogFilePath } from "../logger.js";
6
4
  import { executeAgentCommand } from "./commands/agent.js";
7
- import { executeSkillCommand } from "./commands/skill.js";
8
5
  import { executeGatewayCommand } from "./commands/gateway.js";
9
- import { executeProviderCommand } from "./commands/provider.js";
10
6
  import { executeInitCommand } from "./commands/init.js";
7
+ import { executeProviderCommand } from "./commands/provider.js";
8
+ import { executeSkillCommand } from "./commands/skill.js";
9
+ import { WingmanConfigLoader } from "./config/loader.js";
10
+ import { OutputManager } from "./core/outputManager.js";
11
11
  function parseArgs(argv) {
12
12
  const args = argv.slice(2);
13
13
  if (args.includes("--help") || args.includes("-h")) return {
@@ -112,6 +112,7 @@ Examples:
112
112
  wingman skill install pdf
113
113
  wingman skill list
114
114
  wingman provider status
115
+ wingman provider login codex
115
116
  wingman provider login copilot --token="<token>"
116
117
  wingman gateway start
117
118
  wingman gateway join ws://localhost:3000/ws --name="agent-1"
@@ -3,6 +3,38 @@ export type OutputMode = "interactive" | "json";
3
3
  export interface WingmanConfig {
4
4
  logLevel?: LogLevel;
5
5
  defaultAgent?: string;
6
+ summarization?: {
7
+ enabled?: boolean;
8
+ maxTokensBeforeSummary?: number;
9
+ messagesToKeep?: number;
10
+ };
11
+ modelRetry?: {
12
+ enabled?: boolean;
13
+ maxRetries?: number;
14
+ backoffFactor?: number;
15
+ initialDelayMs?: number;
16
+ maxDelayMs?: number;
17
+ jitter?: boolean;
18
+ onFailure?: "continue" | "error";
19
+ };
20
+ toolRetry?: {
21
+ enabled?: boolean;
22
+ maxRetries?: number;
23
+ backoffFactor?: number;
24
+ initialDelayMs?: number;
25
+ maxDelayMs?: number;
26
+ jitter?: boolean;
27
+ onFailure?: "continue" | "error";
28
+ tools?: string[];
29
+ };
30
+ humanInTheLoop?: {
31
+ enabled?: boolean;
32
+ interruptOn?: Record<string, boolean | {
33
+ allowedDecisions: Array<"approve" | "edit" | "reject">;
34
+ description?: string;
35
+ argsSchema?: Record<string, any>;
36
+ }>;
37
+ };
6
38
  gateway?: {
7
39
  host?: string;
8
40
  port?: number;
@@ -63,7 +63,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
63
63
  const bUpdated = "number" == typeof b.updatedAt ? b.updatedAt : 0;
64
64
  return bUpdated - aUpdated;
65
65
  });
66
- return new Response(JSON.stringify(sorted.slice(0, limit), null, 2), {
66
+ return new Response(JSON.stringify(sorted.slice(0, limit)), {
67
67
  headers: {
68
68
  "Content-Type": "application/json"
69
69
  }
@@ -87,7 +87,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
87
87
  messageCount: session.messageCount,
88
88
  lastMessagePreview: session.lastMessagePreview,
89
89
  workdir: session.metadata?.workdir ?? null
90
- }, null, 2), {
90
+ }), {
91
91
  headers: {
92
92
  "Content-Type": "application/json"
93
93
  }
@@ -107,7 +107,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
107
107
  const manager = await ctx.getSessionManager(agentId);
108
108
  if ("GET" === req.method) {
109
109
  const messages = await manager.listMessages(sessionId);
110
- return new Response(JSON.stringify(messages, null, 2), {
110
+ return new Response(JSON.stringify(messages), {
111
111
  headers: {
112
112
  "Content-Type": "application/json"
113
113
  }
@@ -124,7 +124,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
124
124
  id: sessionId,
125
125
  messageCount: updated?.messageCount ?? 0,
126
126
  lastMessagePreview: updated?.lastMessagePreview ?? null
127
- }, null, 2), {
127
+ }), {
128
128
  headers: {
129
129
  "Content-Type": "application/json"
130
130
  }
@@ -152,7 +152,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
152
152
  return new Response(JSON.stringify({
153
153
  id: session.id,
154
154
  workdir: null
155
- }, null, 2), {
155
+ }), {
156
156
  headers: {
157
157
  "Content-Type": "application/json"
158
158
  }
@@ -172,7 +172,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
172
172
  return new Response(JSON.stringify({
173
173
  id: session.id,
174
174
  workdir: resolved
175
- }, null, 2), {
175
+ }), {
176
176
  headers: {
177
177
  "Content-Type": "application/json"
178
178
  }
@@ -208,7 +208,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
208
208
  messageCount: updated?.messageCount ?? session.messageCount,
209
209
  lastMessagePreview: updated?.lastMessagePreview ?? session.lastMessagePreview,
210
210
  workdir: updated?.metadata?.workdir ?? session.metadata?.workdir ?? null
211
- }, null, 2), {
211
+ }), {
212
212
  headers: {
213
213
  "Content-Type": "application/json"
214
214
  }
@@ -35,7 +35,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
35
35
  const bUpdated = "number" == typeof b.updatedAt ? b.updatedAt : 0;
36
36
  return bUpdated - aUpdated;
37
37
  });
38
- return new Response(JSON.stringify(sorted.slice(0, limit), null, 2), {
38
+ return new Response(JSON.stringify(sorted.slice(0, limit)), {
39
39
  headers: {
40
40
  "Content-Type": "application/json"
41
41
  }
@@ -59,7 +59,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
59
59
  messageCount: session.messageCount,
60
60
  lastMessagePreview: session.lastMessagePreview,
61
61
  workdir: session.metadata?.workdir ?? null
62
- }, null, 2), {
62
+ }), {
63
63
  headers: {
64
64
  "Content-Type": "application/json"
65
65
  }
@@ -79,7 +79,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
79
79
  const manager = await ctx.getSessionManager(agentId);
80
80
  if ("GET" === req.method) {
81
81
  const messages = await manager.listMessages(sessionId);
82
- return new Response(JSON.stringify(messages, null, 2), {
82
+ return new Response(JSON.stringify(messages), {
83
83
  headers: {
84
84
  "Content-Type": "application/json"
85
85
  }
@@ -96,7 +96,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
96
96
  id: sessionId,
97
97
  messageCount: updated?.messageCount ?? 0,
98
98
  lastMessagePreview: updated?.lastMessagePreview ?? null
99
- }, null, 2), {
99
+ }), {
100
100
  headers: {
101
101
  "Content-Type": "application/json"
102
102
  }
@@ -124,7 +124,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
124
124
  return new Response(JSON.stringify({
125
125
  id: session.id,
126
126
  workdir: null
127
- }, null, 2), {
127
+ }), {
128
128
  headers: {
129
129
  "Content-Type": "application/json"
130
130
  }
@@ -144,7 +144,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
144
144
  return new Response(JSON.stringify({
145
145
  id: session.id,
146
146
  workdir: resolved
147
- }, null, 2), {
147
+ }), {
148
148
  headers: {
149
149
  "Content-Type": "application/json"
150
150
  }
@@ -180,7 +180,7 @@ const handleSessionsApi = async (ctx, req, url)=>{
180
180
  messageCount: updated?.messageCount ?? session.messageCount,
181
181
  lastMessagePreview: updated?.lastMessagePreview ?? session.lastMessagePreview,
182
182
  workdir: updated?.metadata?.workdir ?? session.metadata?.workdir ?? null
183
- }, null, 2), {
183
+ }), {
184
184
  headers: {
185
185
  "Content-Type": "application/json"
186
186
  }
@@ -29,31 +29,31 @@ __webpack_require__.r(__webpack_exports__);
29
29
  __webpack_require__.d(__webpack_exports__, {
30
30
  GatewayServer: ()=>GatewayServer
31
31
  });
32
- const external_node_cjs_namespaceObject = require("./node.cjs");
33
- const external_broadcast_cjs_namespaceObject = require("./broadcast.cjs");
34
- const external_auth_cjs_namespaceObject = require("./auth.cjs");
35
- const external_validation_cjs_namespaceObject = require("./validation.cjs");
36
- const external_env_cjs_namespaceObject = require("./env.cjs");
37
- const index_cjs_namespaceObject = require("./discovery/index.cjs");
38
- const external_logger_cjs_namespaceObject = require("../logger.cjs");
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 external_node_url_namespaceObject = require("node:url");
39
36
  const loader_cjs_namespaceObject = require("../cli/config/loader.cjs");
40
- const external_router_cjs_namespaceObject = require("./router.cjs");
41
- const outputManager_cjs_namespaceObject = require("../cli/core/outputManager.cjs");
42
37
  const agentInvoker_cjs_namespaceObject = require("../cli/core/agentInvoker.cjs");
38
+ const outputManager_cjs_namespaceObject = require("../cli/core/outputManager.cjs");
43
39
  const sessionManager_cjs_namespaceObject = require("../cli/core/sessionManager.cjs");
40
+ const external_logger_cjs_namespaceObject = require("../logger.cjs");
41
+ const discord_cjs_namespaceObject = require("./adapters/discord.cjs");
42
+ const external_auth_cjs_namespaceObject = require("./auth.cjs");
43
+ const external_broadcast_cjs_namespaceObject = require("./broadcast.cjs");
44
+ const index_cjs_namespaceObject = require("./discovery/index.cjs");
45
+ const external_env_cjs_namespaceObject = require("./env.cjs");
46
+ const registry_cjs_namespaceObject = require("./hooks/registry.cjs");
44
47
  const agents_cjs_namespaceObject = require("./http/agents.cjs");
45
48
  const fs_cjs_namespaceObject = require("./http/fs.cjs");
46
49
  const providers_cjs_namespaceObject = require("./http/providers.cjs");
47
- const voice_cjs_namespaceObject = require("./http/voice.cjs");
50
+ const routines_cjs_namespaceObject = require("./http/routines.cjs");
48
51
  const sessions_cjs_namespaceObject = require("./http/sessions.cjs");
52
+ const voice_cjs_namespaceObject = require("./http/voice.cjs");
49
53
  const webhooks_cjs_namespaceObject = require("./http/webhooks.cjs");
50
- const routines_cjs_namespaceObject = require("./http/routines.cjs");
51
- const discord_cjs_namespaceObject = require("./adapters/discord.cjs");
52
- const registry_cjs_namespaceObject = require("./hooks/registry.cjs");
53
- const external_node_os_namespaceObject = require("node:os");
54
- const external_node_path_namespaceObject = require("node:path");
55
- const external_node_fs_namespaceObject = require("node:fs");
56
- const external_node_url_namespaceObject = require("node:url");
54
+ const external_node_cjs_namespaceObject = require("./node.cjs");
55
+ const external_router_cjs_namespaceObject = require("./router.cjs");
56
+ const external_validation_cjs_namespaceObject = require("./validation.cjs");
57
57
  function _define_property(obj, key, value) {
58
58
  if (key in obj) Object.defineProperty(obj, key, {
59
59
  value: value,
@@ -350,6 +350,7 @@ class GatewayServer {
350
350
  existing?.abortController.abort();
351
351
  this.activeAgentRequests.delete(msg.id);
352
352
  }
353
+ this.removeQueuedRequestById(msg.id);
353
354
  const payload = msg.payload;
354
355
  const content = "string" == typeof payload?.content ? payload.content : "";
355
356
  const attachments = Array.isArray(payload?.attachments) ? payload.attachments : [];
@@ -359,6 +360,7 @@ class GatewayServer {
359
360
  const agentId = this.router.selectAgent(payload.agentId, payload.routing);
360
361
  if (!agentId) return void this.sendAgentError(ws, msg.id, "No agent matched the request");
361
362
  const sessionKey = payload.sessionKey || this.router.buildSessionKey(agentId, payload.routing);
363
+ const sessionQueueKey = this.buildSessionQueueKey(agentId, sessionKey);
362
364
  const sessionManager = await this.getSessionManager(agentId);
363
365
  const existingSession = sessionManager.getSession(sessionKey);
364
366
  const session = existingSession || sessionManager.getOrCreateSession(sessionKey, agentId);
@@ -409,9 +411,70 @@ class GatewayServer {
409
411
  ],
410
412
  skipSessionId: sessionKey
411
413
  });
414
+ const request = {
415
+ ws,
416
+ msg,
417
+ payload,
418
+ agentId,
419
+ sessionKey,
420
+ sessionQueueKey,
421
+ content,
422
+ attachments,
423
+ sessionManager,
424
+ workdir,
425
+ defaultOutputDir
426
+ };
427
+ this.requestSessionKeys.set(msg.id, sessionQueueKey);
428
+ const queueIfBusy = false !== payload.queueIfBusy;
429
+ const activeRequestId = this.activeSessionRequests.get(sessionQueueKey);
430
+ if (activeRequestId && queueIfBusy) {
431
+ const queued = this.queuedSessionRequests.get(sessionQueueKey) || [];
432
+ queued.push(request);
433
+ this.queuedSessionRequests.set(sessionQueueKey, queued);
434
+ const position = queued.length;
435
+ this.sendMessage(ws, {
436
+ type: "ack",
437
+ id: msg.id,
438
+ payload: {
439
+ action: "req:agent",
440
+ status: "queued",
441
+ requestId: msg.id,
442
+ sessionId: sessionKey,
443
+ agentId,
444
+ position
445
+ },
446
+ timestamp: Date.now()
447
+ });
448
+ this.sendMessage(ws, {
449
+ type: "event:agent",
450
+ id: msg.id,
451
+ clientId: ws.data.clientId,
452
+ payload: this.attachSessionContext({
453
+ type: "request-queued",
454
+ position
455
+ }, sessionKey, agentId),
456
+ timestamp: Date.now()
457
+ });
458
+ return;
459
+ }
460
+ if (activeRequestId && !queueIfBusy) {
461
+ this.requestSessionKeys.delete(msg.id);
462
+ this.sendAgentError(ws, msg.id, "Session already has an in-flight request. Set queueIfBusy=true to enqueue.", {
463
+ sessionId: sessionKey,
464
+ agentId
465
+ });
466
+ return;
467
+ }
468
+ this.executeAgentRequest(request);
469
+ }
470
+ async executeAgentRequest(request) {
471
+ const { ws, msg, agentId, sessionKey, sessionQueueKey, content, attachments, sessionManager, workdir, defaultOutputDir } = request;
472
+ this.activeSessionRequests.set(sessionQueueKey, msg.id);
412
473
  const outputManager = new outputManager_cjs_namespaceObject.OutputManager("interactive");
474
+ let emittedAgentError = false;
413
475
  const outputHandler = (event)=>{
414
476
  const payloadWithSession = this.attachSessionContext(event, sessionKey, agentId);
477
+ if (payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession) && "agent-error" === payloadWithSession.type) emittedAgentError = true;
415
478
  const baseMessage = {
416
479
  type: "event:agent",
417
480
  id: msg.id,
@@ -450,41 +513,111 @@ class GatewayServer {
450
513
  });
451
514
  } catch (error) {
452
515
  this.logger.error("Agent invocation failed", error);
516
+ if (!emittedAgentError) {
517
+ const message = error instanceof Error ? error.message : String(error);
518
+ const stack = error instanceof Error ? error.stack : void 0;
519
+ this.sendAgentError(ws, msg.id, message, {
520
+ sessionId: sessionKey,
521
+ agentId,
522
+ stack,
523
+ broadcastToSession: true,
524
+ exclude: ws
525
+ });
526
+ }
453
527
  } finally{
454
528
  this.activeAgentRequests.delete(msg.id);
529
+ this.activeSessionRequests.delete(sessionQueueKey);
530
+ this.requestSessionKeys.delete(msg.id);
455
531
  outputManager.off("output-event", outputHandler);
532
+ this.processNextQueuedAgentRequest(sessionQueueKey);
456
533
  }
457
534
  }
535
+ processNextQueuedAgentRequest(sessionQueueKey) {
536
+ if (this.activeSessionRequests.has(sessionQueueKey)) return;
537
+ const queue = this.queuedSessionRequests.get(sessionQueueKey);
538
+ if (!queue || 0 === queue.length) return void this.queuedSessionRequests.delete(sessionQueueKey);
539
+ const next = queue.shift();
540
+ if (!next) return;
541
+ if (0 === queue.length) this.queuedSessionRequests.delete(sessionQueueKey);
542
+ else this.queuedSessionRequests.set(sessionQueueKey, queue);
543
+ this.sendMessage(next.ws, {
544
+ type: "ack",
545
+ id: next.msg.id,
546
+ payload: {
547
+ action: "req:agent",
548
+ status: "dequeued",
549
+ requestId: next.msg.id,
550
+ sessionId: next.sessionKey,
551
+ agentId: next.agentId,
552
+ remaining: queue.length
553
+ },
554
+ timestamp: Date.now()
555
+ });
556
+ this.executeAgentRequest(next);
557
+ }
458
558
  handleAgentCancel(ws, msg) {
459
559
  if (!ws.data.authenticated) return void this.sendError(ws, "AUTH_FAILED", "Client is not authenticated");
460
560
  const payload = msg.payload;
461
561
  const requestId = "string" == typeof payload?.requestId && payload.requestId || void 0;
462
562
  if (!requestId) return void this.sendError(ws, "INVALID_REQUEST", "Missing requestId for cancellation");
463
563
  const active = this.activeAgentRequests.get(requestId);
464
- if (!active) return void this.sendMessage(ws, {
465
- type: "ack",
466
- id: msg.id,
467
- payload: {
468
- action: "req:agent:cancel",
469
- requestId,
470
- status: "not_found"
471
- },
472
- timestamp: Date.now()
473
- });
474
- if (active.socket !== ws) return void this.sendError(ws, "FORBIDDEN", "Cannot cancel a request started by another client");
475
- active.abortController.abort();
476
- this.activeAgentRequests.delete(requestId);
564
+ if (active) {
565
+ if (active.socket !== ws) return void this.sendError(ws, "FORBIDDEN", "Cannot cancel a request started by another client");
566
+ active.abortController.abort();
567
+ this.activeAgentRequests.delete(requestId);
568
+ this.sendMessage(ws, {
569
+ type: "ack",
570
+ id: msg.id,
571
+ payload: {
572
+ action: "req:agent:cancel",
573
+ requestId,
574
+ status: "cancelled"
575
+ },
576
+ timestamp: Date.now()
577
+ });
578
+ return;
579
+ }
580
+ const queued = this.removeQueuedRequestById(requestId);
581
+ if (queued) {
582
+ if (queued.ws !== ws) return void this.sendError(ws, "FORBIDDEN", "Cannot cancel a request started by another client");
583
+ this.sendMessage(ws, {
584
+ type: "ack",
585
+ id: msg.id,
586
+ payload: {
587
+ action: "req:agent:cancel",
588
+ requestId,
589
+ status: "cancelled_queued"
590
+ },
591
+ timestamp: Date.now()
592
+ });
593
+ return;
594
+ }
477
595
  this.sendMessage(ws, {
478
596
  type: "ack",
479
597
  id: msg.id,
480
598
  payload: {
481
599
  action: "req:agent:cancel",
482
600
  requestId,
483
- status: "cancelled"
601
+ status: "not_found"
484
602
  },
485
603
  timestamp: Date.now()
486
604
  });
487
605
  }
606
+ buildSessionQueueKey(agentId, sessionKey) {
607
+ return `${agentId}:${sessionKey}`;
608
+ }
609
+ removeQueuedRequestById(requestId) {
610
+ for (const [queueKey, queue] of this.queuedSessionRequests){
611
+ const index = queue.findIndex((item)=>item.msg.id === requestId);
612
+ if (-1 === index) continue;
613
+ const [removed] = queue.splice(index, 1);
614
+ if (0 === queue.length) this.queuedSessionRequests.delete(queueKey);
615
+ else this.queuedSessionRequests.set(queueKey, queue);
616
+ this.requestSessionKeys.delete(requestId);
617
+ return removed || null;
618
+ }
619
+ return null;
620
+ }
488
621
  handleRegister(ws, msg) {
489
622
  const payload = msg.payload;
490
623
  if (!this.auth.validate({
@@ -647,23 +780,39 @@ class GatewayServer {
647
780
  timestamp: Date.now()
648
781
  });
649
782
  }
650
- sendAgentError(ws, requestId, message) {
651
- this.sendMessage(ws, {
783
+ sendAgentError(ws, requestId, message, options) {
784
+ let payload = {
785
+ type: "agent-error",
786
+ error: message,
787
+ timestamp: new Date().toISOString()
788
+ };
789
+ if (options?.stack) payload.stack = options.stack;
790
+ if (options?.sessionId && options?.agentId) payload = this.attachSessionContext(payload, options.sessionId, options.agentId);
791
+ const baseMessage = {
652
792
  type: "event:agent",
653
793
  id: requestId,
654
- payload: {
655
- type: "agent-error",
656
- error: message,
657
- timestamp: new Date().toISOString()
658
- },
794
+ payload,
659
795
  timestamp: Date.now()
796
+ };
797
+ this.sendMessage(ws, {
798
+ ...baseMessage,
799
+ clientId: ws.data.clientId
660
800
  });
801
+ if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude);
661
802
  }
662
803
  cancelSocketAgentRequests(ws) {
663
804
  for (const [requestId, active] of this.activeAgentRequests)if (active.socket === ws) {
664
805
  active.abortController.abort();
665
806
  this.activeAgentRequests.delete(requestId);
666
807
  }
808
+ for (const [queueKey, queue] of this.queuedSessionRequests){
809
+ const nextQueue = queue.filter((request)=>request.ws !== ws);
810
+ if (nextQueue.length !== queue.length) {
811
+ for (const request of queue)if (request.ws === ws && request.msg.id) this.requestSessionKeys.delete(request.msg.id);
812
+ if (0 === nextQueue.length) this.queuedSessionRequests.delete(queueKey);
813
+ else this.queuedSessionRequests.set(queueKey, nextQueue);
814
+ }
815
+ }
667
816
  }
668
817
  attachSessionContext(event, sessionId, agentId) {
669
818
  if (event && "object" == typeof event && !Array.isArray(event)) return {
@@ -855,9 +1004,7 @@ class GatewayServer {
855
1004
  ];
856
1005
  for (const candidate of candidates)try {
857
1006
  if ((0, external_node_fs_namespaceObject.existsSync)(candidate) && (0, external_node_fs_namespaceObject.statSync)(candidate).isDirectory() && (0, external_node_fs_namespaceObject.existsSync)((0, external_node_path_namespaceObject.join)(candidate, "index.html"))) return candidate;
858
- } catch {
859
- continue;
860
- }
1007
+ } catch {}
861
1008
  return null;
862
1009
  }
863
1010
  async getSessionManager(agentId) {
@@ -1181,6 +1328,9 @@ class GatewayServer {
1181
1328
  _define_property(this, "socketSubscriptions", new Map());
1182
1329
  _define_property(this, "connectedClients", new Set());
1183
1330
  _define_property(this, "activeAgentRequests", new Map());
1331
+ _define_property(this, "activeSessionRequests", new Map());
1332
+ _define_property(this, "queuedSessionRequests", new Map());
1333
+ _define_property(this, "requestSessionKeys", new Map());
1184
1334
  _define_property(this, "bridgeQueues", new Map());
1185
1335
  _define_property(this, "bridgePollWaiters", new Map());
1186
1336
  this.workspace = config.workspace || process.cwd();
@@ -1,5 +1,5 @@
1
- import type { GatewayConfig } from "./types.js";
2
1
  import { GatewayAuth } from "./auth.js";
2
+ import type { GatewayConfig } from "./types.js";
3
3
  /**
4
4
  * Wingman Gateway Server
5
5
  * Manages WebSocket connections for AI agent swarming
@@ -34,6 +34,9 @@ export declare class GatewayServer {
34
34
  private socketSubscriptions;
35
35
  private connectedClients;
36
36
  private activeAgentRequests;
37
+ private activeSessionRequests;
38
+ private queuedSessionRequests;
39
+ private requestSessionKeys;
37
40
  private bridgeQueues;
38
41
  private bridgePollWaiters;
39
42
  constructor(config?: Partial<GatewayConfig>);
@@ -76,7 +79,11 @@ export declare class GatewayServer {
76
79
  * Handle agent execution request
77
80
  */
78
81
  private handleAgentRequest;
82
+ private executeAgentRequest;
83
+ private processNextQueuedAgentRequest;
79
84
  private handleAgentCancel;
85
+ private buildSessionQueueKey;
86
+ private removeQueuedRequestById;
80
87
  /**
81
88
  * Handle node registration
82
89
  */