@wingman-ai/gateway 0.2.1 → 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 (72) hide show
  1. package/.wingman/agents/README.md +1 -0
  2. package/.wingman/agents/coding/agent.md +179 -112
  3. package/.wingman/agents/coding/implementor.md +50 -3
  4. package/.wingman/agents/main/agent.md +4 -0
  5. package/README.md +1 -0
  6. package/dist/agent/config/agentConfig.cjs +30 -1
  7. package/dist/agent/config/agentConfig.js +30 -1
  8. package/dist/agent/config/modelFactory.cjs +22 -2
  9. package/dist/agent/config/modelFactory.d.ts +2 -0
  10. package/dist/agent/config/modelFactory.js +22 -2
  11. package/dist/agent/tests/agentConfig.test.cjs +39 -0
  12. package/dist/agent/tests/agentConfig.test.js +39 -0
  13. package/dist/agent/tests/modelFactory.test.cjs +12 -5
  14. package/dist/agent/tests/modelFactory.test.js +12 -5
  15. package/dist/cli/commands/init.cjs +7 -6
  16. package/dist/cli/commands/init.js +7 -6
  17. package/dist/cli/commands/provider.cjs +17 -3
  18. package/dist/cli/commands/provider.js +17 -3
  19. package/dist/cli/config/loader.cjs +27 -0
  20. package/dist/cli/config/loader.js +27 -0
  21. package/dist/cli/config/schema.cjs +80 -2
  22. package/dist/cli/config/schema.d.ts +88 -0
  23. package/dist/cli/config/schema.js +67 -1
  24. package/dist/cli/core/agentInvoker.cjs +242 -17
  25. package/dist/cli/core/agentInvoker.d.ts +46 -4
  26. package/dist/cli/core/agentInvoker.js +214 -13
  27. package/dist/cli/core/sessionManager.cjs +32 -5
  28. package/dist/cli/core/sessionManager.js +32 -5
  29. package/dist/cli/index.cjs +6 -5
  30. package/dist/cli/index.js +6 -5
  31. package/dist/cli/types.d.ts +32 -0
  32. package/dist/gateway/http/sessions.cjs +7 -7
  33. package/dist/gateway/http/sessions.js +7 -7
  34. package/dist/gateway/server.cjs +230 -28
  35. package/dist/gateway/server.d.ts +11 -1
  36. package/dist/gateway/server.js +230 -28
  37. package/dist/gateway/types.d.ts +5 -1
  38. package/dist/gateway/validation.cjs +1 -0
  39. package/dist/gateway/validation.d.ts +2 -0
  40. package/dist/gateway/validation.js +1 -0
  41. package/dist/providers/codex.cjs +167 -0
  42. package/dist/providers/codex.d.ts +15 -0
  43. package/dist/providers/codex.js +127 -0
  44. package/dist/providers/credentials.cjs +8 -0
  45. package/dist/providers/credentials.js +8 -0
  46. package/dist/providers/registry.cjs +11 -0
  47. package/dist/providers/registry.d.ts +1 -1
  48. package/dist/providers/registry.js +11 -0
  49. package/dist/tests/agentInvokerSummarization.test.cjs +296 -0
  50. package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
  51. package/dist/tests/agentInvokerSummarization.test.js +290 -0
  52. package/dist/tests/cli-config-loader.test.cjs +88 -0
  53. package/dist/tests/cli-config-loader.test.js +88 -0
  54. package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
  55. package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
  56. package/dist/tests/codex-credentials-precedence.test.js +88 -0
  57. package/dist/tests/codex-provider.test.cjs +186 -0
  58. package/dist/tests/codex-provider.test.d.ts +1 -0
  59. package/dist/tests/codex-provider.test.js +180 -0
  60. package/dist/tests/gateway.test.cjs +173 -1
  61. package/dist/tests/gateway.test.js +173 -1
  62. package/dist/tests/provider-command-codex.test.cjs +57 -0
  63. package/dist/tests/provider-command-codex.test.d.ts +1 -0
  64. package/dist/tests/provider-command-codex.test.js +51 -0
  65. package/dist/tests/sessionStateMessages.test.cjs +38 -0
  66. package/dist/tests/sessionStateMessages.test.js +38 -0
  67. package/dist/webui/assets/index-BVMavpud.css +11 -0
  68. package/dist/webui/assets/index-DCB2aVVf.js +182 -0
  69. package/dist/webui/index.html +2 -2
  70. package/package.json +3 -1
  71. package/dist/webui/assets/index-BytPznA_.css +0 -1
  72. package/dist/webui/assets/index-u_5qlVip.js +0 -176
@@ -1,15 +1,16 @@
1
- import { CompositeBackend, FilesystemBackend, createDeepAgent } from "deepagents";
2
1
  import { existsSync } from "node:fs";
3
2
  import { isAbsolute, join, normalize, sep } from "node:path";
3
+ import { CompositeBackend, FilesystemBackend, createDeepAgent } from "deepagents";
4
+ import { modelRetryMiddleware, summarizationMiddleware, toolRetryMiddleware } from "langchain";
4
5
  import { v4 } from "uuid";
5
- import { AgentLoader } from "../../agent/config/agentLoader.js";
6
- import { WingmanConfigLoader } from "../config/loader.js";
6
+ import { MCPClientManager } from "../../agent/config/mcpClientManager.js";
7
7
  import { additionalMessageMiddleware } from "../../agent/middleware/additional-messages.js";
8
- import { createHooksMiddleware } from "../../agent/middleware/hooks.js";
9
8
  import { mergeHooks } from "../../agent/middleware/hooks/merger.js";
9
+ import { createHooksMiddleware } from "../../agent/middleware/hooks.js";
10
10
  import { mediaCompatibilityMiddleware } from "../../agent/middleware/media-compat.js";
11
- import { MCPClientManager } from "../../agent/config/mcpClientManager.js";
12
11
  import { getBundledSkillsPath } from "../../agent/uiRegistry.js";
12
+ import { AgentLoader } from "../../agent/config/agentLoader.js";
13
+ import { WingmanConfigLoader } from "../config/loader.js";
13
14
  function _define_property(obj, key, value) {
14
15
  if (key in obj) Object.defineProperty(obj, key, {
15
16
  value: value,
@@ -22,6 +23,7 @@ function _define_property(obj, key, value) {
22
23
  }
23
24
  const WORKDIR_VIRTUAL_PATH = "/workdir/";
24
25
  const OUTPUT_VIRTUAL_PATH = "/output/";
26
+ const DEFAULT_DEEPAGENT_MODEL = "claude-sonnet-4-5-20250929";
25
27
  const isPathWithinRoot = (targetPath, rootPath)=>{
26
28
  const normalizedTarget = normalize(targetPath);
27
29
  const normalizedRoot = normalize(rootPath);
@@ -54,6 +56,101 @@ const resolveExternalOutputMount = (workspace, workdir, defaultOutputDir)=>{
54
56
  absolutePath: null
55
57
  };
56
58
  };
59
+ const resolveSummarizationMiddlewareSettings = (config)=>{
60
+ if (!config.summarization?.enabled) return null;
61
+ return {
62
+ maxTokensBeforeSummary: config.summarization.maxTokensBeforeSummary,
63
+ messagesToKeep: config.summarization.messagesToKeep
64
+ };
65
+ };
66
+ const resolveModelRetryMiddlewareSettings = (config)=>{
67
+ if (!config.modelRetry?.enabled) return null;
68
+ return {
69
+ maxRetries: config.modelRetry.maxRetries,
70
+ backoffFactor: config.modelRetry.backoffFactor,
71
+ initialDelayMs: config.modelRetry.initialDelayMs,
72
+ maxDelayMs: config.modelRetry.maxDelayMs,
73
+ jitter: config.modelRetry.jitter,
74
+ onFailure: config.modelRetry.onFailure
75
+ };
76
+ };
77
+ const resolveToolRetryMiddlewareSettings = (config)=>{
78
+ if (!config.toolRetry?.enabled) return null;
79
+ return {
80
+ maxRetries: config.toolRetry.maxRetries,
81
+ backoffFactor: config.toolRetry.backoffFactor,
82
+ initialDelayMs: config.toolRetry.initialDelayMs,
83
+ maxDelayMs: config.toolRetry.maxDelayMs,
84
+ jitter: config.toolRetry.jitter,
85
+ onFailure: config.toolRetry.onFailure,
86
+ ...config.toolRetry.tools && config.toolRetry.tools.length > 0 ? {
87
+ tools: config.toolRetry.tools
88
+ } : {}
89
+ };
90
+ };
91
+ const resolveHumanInTheLoopSettings = (config)=>{
92
+ if (!config.humanInTheLoop?.enabled) return null;
93
+ const interruptOn = config.humanInTheLoop.interruptOn || {};
94
+ if (0 === Object.keys(interruptOn).length) return null;
95
+ return {
96
+ interruptOn
97
+ };
98
+ };
99
+ const configureDeepAgentSummarizationMiddleware = (agent, settings, model)=>{
100
+ const middleware = agent?.options?.middleware;
101
+ if (!Array.isArray(middleware)) return;
102
+ const index = middleware.findIndex((entry)=>entry?.name === "SummarizationMiddleware");
103
+ if (index < 0) return;
104
+ if (!settings) return void middleware.splice(index, 1);
105
+ middleware[index] = summarizationMiddleware({
106
+ model: model || DEFAULT_DEEPAGENT_MODEL,
107
+ trigger: {
108
+ tokens: settings.maxTokensBeforeSummary
109
+ },
110
+ keep: {
111
+ messages: settings.messagesToKeep
112
+ }
113
+ });
114
+ };
115
+ const detectToolEventContext = (chunk)=>{
116
+ if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return null;
117
+ const eventChunk = chunk;
118
+ if ("on_tool_start" !== eventChunk.event && "on_tool_end" !== eventChunk.event) return null;
119
+ const toolName = "string" == typeof eventChunk.name && eventChunk.name.trim() ? eventChunk.name.trim() : "unknown";
120
+ return {
121
+ event: eventChunk.event,
122
+ toolName
123
+ };
124
+ };
125
+ const chunkHasAssistantText = (chunk)=>{
126
+ if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return false;
127
+ const eventChunk = chunk;
128
+ const eventName = "string" == typeof eventChunk.event ? eventChunk.event : void 0;
129
+ if ("on_chat_model_stream" === eventName) {
130
+ const data = eventChunk.data && "object" == typeof eventChunk.data ? eventChunk.data : null;
131
+ const messageChunk = data?.chunk || data?.message;
132
+ const content = messageChunk?.content;
133
+ if ("string" == typeof content) return content.length > 0;
134
+ if (Array.isArray(content)) return content.some((part)=>part && "object" == typeof part && "text" === part.type && "string" == typeof part.text && part.text.length > 0);
135
+ }
136
+ if ("on_llm_stream" === eventName) {
137
+ const data = eventChunk.data && "object" == typeof eventChunk.data ? eventChunk.data : null;
138
+ const llmChunk = data?.chunk && "object" == typeof data.chunk ? data.chunk : null;
139
+ return "string" == typeof llmChunk?.text && llmChunk.text.length > 0;
140
+ }
141
+ return false;
142
+ };
143
+ const selectStreamingFallbackText = (sessionMessages, invocationStartedAt, windowMs = 1000)=>{
144
+ for(let i = sessionMessages.length - 1; i >= 0; i -= 1){
145
+ const message = sessionMessages[i];
146
+ if (!message || "object" != typeof message) continue;
147
+ if ("assistant" === message.role) {
148
+ if ("number" == typeof message.createdAt && !(message.createdAt < invocationStartedAt - windowMs)) {
149
+ if ("string" == typeof message.content && message.content.trim()) return message.content;
150
+ }
151
+ }
152
+ }
153
+ };
57
154
  class AgentInvoker {
58
155
  findAllAgents() {
59
156
  const agentConfigs = this.loader.loadAllAgentConfigs();
@@ -62,7 +159,13 @@ class AgentInvoker {
62
159
  async findAgent(name) {
63
160
  return await this.loader.loadAgent(name);
64
161
  }
65
- async invokeAgent(agentName, prompt, sessionId, attachments) {
162
+ async invokeAgent(agentName, prompt, sessionId, attachments, options) {
163
+ const invocationStartedAt = Date.now();
164
+ let cancellationHandled = false;
165
+ let activeToolName = null;
166
+ let lastToolName = null;
167
+ let sawAssistantText = false;
168
+ const isCancelled = ()=>options?.signal?.aborted === true;
66
169
  try {
67
170
  const executionWorkspace = resolveExecutionWorkspace(this.workspace, this.workdir);
68
171
  const effectiveWorkdir = this.workdir ? executionWorkspace : null;
@@ -111,6 +214,29 @@ class AgentInvoker {
111
214
  skillsDirectory
112
215
  })
113
216
  ];
217
+ const summarizationSettings = resolveSummarizationMiddlewareSettings(this.wingmanConfig);
218
+ const modelRetrySettings = resolveModelRetryMiddlewareSettings(this.wingmanConfig);
219
+ if (modelRetrySettings) middleware.push(modelRetryMiddleware({
220
+ maxRetries: modelRetrySettings.maxRetries,
221
+ backoffFactor: modelRetrySettings.backoffFactor,
222
+ initialDelayMs: modelRetrySettings.initialDelayMs,
223
+ maxDelayMs: modelRetrySettings.maxDelayMs,
224
+ jitter: modelRetrySettings.jitter,
225
+ onFailure: modelRetrySettings.onFailure
226
+ }));
227
+ const toolRetrySettings = resolveToolRetryMiddlewareSettings(this.wingmanConfig);
228
+ if (toolRetrySettings) middleware.push(toolRetryMiddleware({
229
+ maxRetries: toolRetrySettings.maxRetries,
230
+ backoffFactor: toolRetrySettings.backoffFactor,
231
+ initialDelayMs: toolRetrySettings.initialDelayMs,
232
+ maxDelayMs: toolRetrySettings.maxDelayMs,
233
+ jitter: toolRetrySettings.jitter,
234
+ onFailure: toolRetrySettings.onFailure,
235
+ ...toolRetrySettings.tools ? {
236
+ tools: toolRetrySettings.tools
237
+ } : {}
238
+ }));
239
+ const hitlSettings = resolveHumanInTheLoopSettings(this.wingmanConfig);
114
240
  if (mergedHooks) {
115
241
  this.logger.debug(`Adding hooks middleware with ${mergedHooks.PreToolUse?.length || 0} PreToolUse hooks, ${mergedHooks.PostToolUse?.length || 0} PostToolUse hooks, and ${mergedHooks.Stop?.length || 0} Stop hooks`);
116
242
  middleware.push(createHooksMiddleware(mergedHooks, executionWorkspace, hookSessionId, this.logger));
@@ -157,10 +283,12 @@ class AgentInvoker {
157
283
  virtualMode: true
158
284
  }), backendOverrides),
159
285
  middleware: middleware,
286
+ interruptOn: hitlSettings?.interruptOn,
160
287
  skills: skillsSources,
161
288
  subagents: targetAgent.subagents || [],
162
289
  checkpointer: checkpointer
163
290
  });
291
+ configureDeepAgentSummarizationMiddleware(standaloneAgent, summarizationSettings, targetAgent.model);
164
292
  this.logger.debug("Agent created, sending message");
165
293
  const userContent = buildUserContent(prompt, attachments, targetAgent.model);
166
294
  if (this.sessionManager && sessionId) {
@@ -177,12 +305,58 @@ class AgentInvoker {
177
305
  configurable: {
178
306
  thread_id: sessionId
179
307
  },
180
- version: "v2"
308
+ version: "v2",
309
+ signal: options?.signal
181
310
  });
182
- for await (const chunk of stream)this.outputManager.emitAgentStream(chunk);
311
+ for await (const chunk of stream){
312
+ if (!sawAssistantText && chunkHasAssistantText(chunk)) sawAssistantText = true;
313
+ const toolEvent = detectToolEventContext(chunk);
314
+ if (toolEvent) {
315
+ lastToolName = toolEvent.toolName;
316
+ if ("on_tool_start" === toolEvent.event) activeToolName = toolEvent.toolName;
317
+ else if (activeToolName === toolEvent.toolName) activeToolName = null;
318
+ }
319
+ if (isCancelled()) {
320
+ cancellationHandled = true;
321
+ this.logger.info("Agent invocation cancelled");
322
+ this.outputManager.emitAgentError("Request cancelled");
323
+ if ("function" == typeof stream?.return) await stream.return();
324
+ return {
325
+ cancelled: true
326
+ };
327
+ }
328
+ this.outputManager.emitAgentStream(chunk);
329
+ }
330
+ if (isCancelled()) {
331
+ cancellationHandled = true;
332
+ this.logger.info("Agent invocation cancelled");
333
+ this.outputManager.emitAgentError("Request cancelled");
334
+ return {
335
+ cancelled: true
336
+ };
337
+ }
183
338
  this.logger.info("Agent streaming completed successfully");
339
+ let fallbackText;
340
+ if (!sawAssistantText && this.sessionManager && sessionId) try {
341
+ const sessionMessages = await this.sessionManager.listMessages(sessionId);
342
+ fallbackText = selectStreamingFallbackText(sessionMessages, invocationStartedAt);
343
+ } catch (stateError) {
344
+ this.logger.debug("Failed to derive streaming fallback text from session state", stateError);
345
+ }
346
+ if (!sawAssistantText && !fallbackText) {
347
+ const emptyResponseMessage = "Model completed without a response. Check provider logs for request errors.";
348
+ this.logger.warn(emptyResponseMessage);
349
+ this.outputManager.emitAgentError(emptyResponseMessage);
350
+ return {
351
+ blocked: true,
352
+ reason: "empty_stream_response"
353
+ };
354
+ }
184
355
  this.outputManager.emitAgentComplete({
185
- streaming: true
356
+ streaming: true,
357
+ ...fallbackText ? {
358
+ fallbackText
359
+ } : {}
186
360
  });
187
361
  return {
188
362
  streaming: true
@@ -190,6 +364,14 @@ class AgentInvoker {
190
364
  }
191
365
  {
192
366
  this.logger.debug("Using blocking invoke (no session manager)");
367
+ if (isCancelled()) {
368
+ cancellationHandled = true;
369
+ this.logger.info("Agent invocation cancelled");
370
+ this.outputManager.emitAgentError("Request cancelled");
371
+ return {
372
+ cancelled: true
373
+ };
374
+ }
193
375
  const result = await standaloneAgent.invoke({
194
376
  messages: [
195
377
  {
@@ -198,15 +380,34 @@ class AgentInvoker {
198
380
  }
199
381
  ]
200
382
  }, {
201
- recursionLimit: this.wingmanConfig.recursionLimit
383
+ recursionLimit: this.wingmanConfig.recursionLimit,
384
+ signal: options?.signal
202
385
  });
386
+ if (isCancelled()) {
387
+ cancellationHandled = true;
388
+ this.logger.info("Agent invocation cancelled");
389
+ this.outputManager.emitAgentError("Request cancelled");
390
+ return {
391
+ cancelled: true
392
+ };
393
+ }
203
394
  this.logger.info("Agent completed successfully");
204
395
  this.outputManager.emitAgentComplete(result);
205
396
  return result;
206
397
  }
207
398
  } catch (error) {
208
- this.logger.error(`Agent invocation failed: ${error instanceof Error ? error.message : String(error)}`);
209
- this.outputManager.emitAgentError(error);
399
+ const abortError = isCancelled() || error instanceof Error && ("AbortError" === error.name || "CancelledError" === error.name || /abort|cancel/i.test(error.message));
400
+ if (abortError) {
401
+ if (!cancellationHandled) this.outputManager.emitAgentError("Request cancelled");
402
+ this.logger.info("Agent invocation cancelled");
403
+ return {
404
+ cancelled: true
405
+ };
406
+ }
407
+ this.logger.error(`Agent invocation failed: ${error instanceof Error ? error.message : String(error)}${activeToolName ? ` (while running tool "${activeToolName}")` : lastToolName ? ` (last tool: "${lastToolName}")` : ""}`);
408
+ const errorMessage = error instanceof Error ? error.message : String(error);
409
+ const errorWithToolContext = activeToolName ? `${errorMessage} (while running tool "${activeToolName}")` : lastToolName ? `${errorMessage} (last tool: "${lastToolName}")` : errorMessage;
410
+ this.outputManager.emitAgentError(errorWithToolContext);
210
411
  throw error;
211
412
  } finally{
212
413
  if (this.mcpManager) {
@@ -431,4 +632,4 @@ function buildAttachmentPreview(attachments) {
431
632
  if (hasImage) return "[image]";
432
633
  return "";
433
634
  }
434
- export { AgentInvoker, OUTPUT_VIRTUAL_PATH, WORKDIR_VIRTUAL_PATH, buildUserContent, resolveExecutionWorkspace, resolveExternalOutputMount, toWorkspaceAliasVirtualPath };
635
+ export { AgentInvoker, OUTPUT_VIRTUAL_PATH, WORKDIR_VIRTUAL_PATH, buildUserContent, chunkHasAssistantText, configureDeepAgentSummarizationMiddleware, detectToolEventContext, resolveExecutionWorkspace, resolveExternalOutputMount, resolveHumanInTheLoopSettings, resolveModelRetryMiddlewareSettings, resolveSummarizationMiddlewareSettings, resolveToolRetryMiddlewareSettings, selectStreamingFallbackText, toWorkspaceAliasVirtualPath };
@@ -68,6 +68,8 @@ class SessionManager {
68
68
 
69
69
  CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at DESC);
70
70
  CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent_name);
71
+ CREATE INDEX IF NOT EXISTS idx_sessions_status_updated ON sessions(status, updated_at DESC);
72
+ CREATE INDEX IF NOT EXISTS idx_sessions_status_agent_updated ON sessions(status, agent_name, updated_at DESC);
71
73
  `);
72
74
  }
73
75
  createSession(agentName, name) {
@@ -407,13 +409,38 @@ function extractContentBlocks(entry) {
407
409
  }
408
410
  function extractMessageContent(entry, blocks = []) {
409
411
  if (!entry || "object" != typeof entry) return "";
410
- if ("string" == typeof entry.content) return entry.content;
411
- if ("string" == typeof entry?.kwargs?.content) return entry.kwargs.content;
412
- if ("string" == typeof entry?.additional_kwargs?.content) return entry.additional_kwargs.content;
413
- if ("string" == typeof entry?.data?.content) return entry.data.content;
414
- if (blocks.length > 0) return blocks.filter((block)=>block && "text" === block.type && block.text).map((block)=>block.text).join("");
412
+ const candidates = [
413
+ entry.content,
414
+ entry?.kwargs?.content,
415
+ entry?.additional_kwargs?.content,
416
+ entry?.data?.content
417
+ ];
418
+ for (const candidate of candidates){
419
+ const extracted = extractTextContent(candidate);
420
+ if (extracted) return extracted;
421
+ }
422
+ if (blocks.length > 0) return extractTextContent(blocks);
423
+ return "";
424
+ }
425
+ function extractTextContent(value, depth = 0) {
426
+ if (depth > 5 || null == value) return "";
427
+ if ("string" == typeof value) return value;
428
+ if (Array.isArray(value)) return value.map((entry)=>extractTextContent(entry, depth + 1)).filter((entry)=>entry.length > 0).join("");
429
+ if ("object" != typeof value) return "";
430
+ const record = value;
431
+ if ("string" == typeof record.text) return record.text;
432
+ if (record.text && "object" == typeof record.text && "string" == typeof record.text.value) return record.text.value;
433
+ if ("string" == typeof record.output_text) return record.output_text;
434
+ if ("string" == typeof record.input_text) return record.input_text;
435
+ if ("string" == typeof record.value && isTextLikeContentType(record.type)) return record.value;
436
+ if ("content" in record) return extractTextContent(record.content, depth + 1);
415
437
  return "";
416
438
  }
439
+ function isTextLikeContentType(type) {
440
+ if ("string" != typeof type) return false;
441
+ const normalized = type.toLowerCase();
442
+ return "text" === normalized || "input_text" === normalized || "output_text" === normalized || "text_delta" === normalized;
443
+ }
417
444
  function isToolMessage(entry) {
418
445
  if (!entry || "object" != typeof entry) return false;
419
446
  const role = entry.role || entry?.kwargs?.role || entry?.additional_kwargs?.role;
@@ -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
  }