@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
@@ -24,26 +24,35 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
- resolveExternalOutputMount: ()=>resolveExternalOutputMount,
28
- toWorkspaceAliasVirtualPath: ()=>toWorkspaceAliasVirtualPath,
27
+ resolveToolRetryMiddlewareSettings: ()=>resolveToolRetryMiddlewareSettings,
28
+ resolveSummarizationMiddlewareSettings: ()=>resolveSummarizationMiddlewareSettings,
29
29
  OUTPUT_VIRTUAL_PATH: ()=>OUTPUT_VIRTUAL_PATH,
30
- WORKDIR_VIRTUAL_PATH: ()=>WORKDIR_VIRTUAL_PATH,
31
- AgentInvoker: ()=>AgentInvoker,
30
+ resolveExecutionWorkspace: ()=>resolveExecutionWorkspace,
32
31
  buildUserContent: ()=>buildUserContent,
33
- resolveExecutionWorkspace: ()=>resolveExecutionWorkspace
32
+ toWorkspaceAliasVirtualPath: ()=>toWorkspaceAliasVirtualPath,
33
+ chunkHasAssistantText: ()=>chunkHasAssistantText,
34
+ resolveModelRetryMiddlewareSettings: ()=>resolveModelRetryMiddlewareSettings,
35
+ AgentInvoker: ()=>AgentInvoker,
36
+ detectToolEventContext: ()=>detectToolEventContext,
37
+ WORKDIR_VIRTUAL_PATH: ()=>WORKDIR_VIRTUAL_PATH,
38
+ configureDeepAgentSummarizationMiddleware: ()=>configureDeepAgentSummarizationMiddleware,
39
+ resolveExternalOutputMount: ()=>resolveExternalOutputMount,
40
+ resolveHumanInTheLoopSettings: ()=>resolveHumanInTheLoopSettings,
41
+ selectStreamingFallbackText: ()=>selectStreamingFallbackText
34
42
  });
35
- const external_deepagents_namespaceObject = require("deepagents");
36
43
  const external_node_fs_namespaceObject = require("node:fs");
37
44
  const external_node_path_namespaceObject = require("node:path");
45
+ const external_deepagents_namespaceObject = require("deepagents");
46
+ const external_langchain_namespaceObject = require("langchain");
38
47
  const external_uuid_namespaceObject = require("uuid");
39
- const agentLoader_cjs_namespaceObject = require("../../agent/config/agentLoader.cjs");
40
- const loader_cjs_namespaceObject = require("../config/loader.cjs");
48
+ const mcpClientManager_cjs_namespaceObject = require("../../agent/config/mcpClientManager.cjs");
41
49
  const additional_messages_cjs_namespaceObject = require("../../agent/middleware/additional-messages.cjs");
42
- const hooks_cjs_namespaceObject = require("../../agent/middleware/hooks.cjs");
43
50
  const merger_cjs_namespaceObject = require("../../agent/middleware/hooks/merger.cjs");
51
+ const hooks_cjs_namespaceObject = require("../../agent/middleware/hooks.cjs");
44
52
  const media_compat_cjs_namespaceObject = require("../../agent/middleware/media-compat.cjs");
45
- const mcpClientManager_cjs_namespaceObject = require("../../agent/config/mcpClientManager.cjs");
46
53
  const uiRegistry_cjs_namespaceObject = require("../../agent/uiRegistry.cjs");
54
+ const agentLoader_cjs_namespaceObject = require("../../agent/config/agentLoader.cjs");
55
+ const loader_cjs_namespaceObject = require("../config/loader.cjs");
47
56
  function _define_property(obj, key, value) {
48
57
  if (key in obj) Object.defineProperty(obj, key, {
49
58
  value: value,
@@ -56,6 +65,7 @@ function _define_property(obj, key, value) {
56
65
  }
57
66
  const WORKDIR_VIRTUAL_PATH = "/workdir/";
58
67
  const OUTPUT_VIRTUAL_PATH = "/output/";
68
+ const DEFAULT_DEEPAGENT_MODEL = "claude-sonnet-4-5-20250929";
59
69
  const isPathWithinRoot = (targetPath, rootPath)=>{
60
70
  const normalizedTarget = (0, external_node_path_namespaceObject.normalize)(targetPath);
61
71
  const normalizedRoot = (0, external_node_path_namespaceObject.normalize)(rootPath);
@@ -88,6 +98,101 @@ const resolveExternalOutputMount = (workspace, workdir, defaultOutputDir)=>{
88
98
  absolutePath: null
89
99
  };
90
100
  };
101
+ const resolveSummarizationMiddlewareSettings = (config)=>{
102
+ if (!config.summarization?.enabled) return null;
103
+ return {
104
+ maxTokensBeforeSummary: config.summarization.maxTokensBeforeSummary,
105
+ messagesToKeep: config.summarization.messagesToKeep
106
+ };
107
+ };
108
+ const resolveModelRetryMiddlewareSettings = (config)=>{
109
+ if (!config.modelRetry?.enabled) return null;
110
+ return {
111
+ maxRetries: config.modelRetry.maxRetries,
112
+ backoffFactor: config.modelRetry.backoffFactor,
113
+ initialDelayMs: config.modelRetry.initialDelayMs,
114
+ maxDelayMs: config.modelRetry.maxDelayMs,
115
+ jitter: config.modelRetry.jitter,
116
+ onFailure: config.modelRetry.onFailure
117
+ };
118
+ };
119
+ const resolveToolRetryMiddlewareSettings = (config)=>{
120
+ if (!config.toolRetry?.enabled) return null;
121
+ return {
122
+ maxRetries: config.toolRetry.maxRetries,
123
+ backoffFactor: config.toolRetry.backoffFactor,
124
+ initialDelayMs: config.toolRetry.initialDelayMs,
125
+ maxDelayMs: config.toolRetry.maxDelayMs,
126
+ jitter: config.toolRetry.jitter,
127
+ onFailure: config.toolRetry.onFailure,
128
+ ...config.toolRetry.tools && config.toolRetry.tools.length > 0 ? {
129
+ tools: config.toolRetry.tools
130
+ } : {}
131
+ };
132
+ };
133
+ const resolveHumanInTheLoopSettings = (config)=>{
134
+ if (!config.humanInTheLoop?.enabled) return null;
135
+ const interruptOn = config.humanInTheLoop.interruptOn || {};
136
+ if (0 === Object.keys(interruptOn).length) return null;
137
+ return {
138
+ interruptOn
139
+ };
140
+ };
141
+ const configureDeepAgentSummarizationMiddleware = (agent, settings, model)=>{
142
+ const middleware = agent?.options?.middleware;
143
+ if (!Array.isArray(middleware)) return;
144
+ const index = middleware.findIndex((entry)=>entry?.name === "SummarizationMiddleware");
145
+ if (index < 0) return;
146
+ if (!settings) return void middleware.splice(index, 1);
147
+ middleware[index] = (0, external_langchain_namespaceObject.summarizationMiddleware)({
148
+ model: model || DEFAULT_DEEPAGENT_MODEL,
149
+ trigger: {
150
+ tokens: settings.maxTokensBeforeSummary
151
+ },
152
+ keep: {
153
+ messages: settings.messagesToKeep
154
+ }
155
+ });
156
+ };
157
+ const detectToolEventContext = (chunk)=>{
158
+ if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return null;
159
+ const eventChunk = chunk;
160
+ if ("on_tool_start" !== eventChunk.event && "on_tool_end" !== eventChunk.event) return null;
161
+ const toolName = "string" == typeof eventChunk.name && eventChunk.name.trim() ? eventChunk.name.trim() : "unknown";
162
+ return {
163
+ event: eventChunk.event,
164
+ toolName
165
+ };
166
+ };
167
+ const chunkHasAssistantText = (chunk)=>{
168
+ if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return false;
169
+ const eventChunk = chunk;
170
+ const eventName = "string" == typeof eventChunk.event ? eventChunk.event : void 0;
171
+ if ("on_chat_model_stream" === eventName) {
172
+ const data = eventChunk.data && "object" == typeof eventChunk.data ? eventChunk.data : null;
173
+ const messageChunk = data?.chunk || data?.message;
174
+ const content = messageChunk?.content;
175
+ if ("string" == typeof content) return content.length > 0;
176
+ if (Array.isArray(content)) return content.some((part)=>part && "object" == typeof part && "text" === part.type && "string" == typeof part.text && part.text.length > 0);
177
+ }
178
+ if ("on_llm_stream" === eventName) {
179
+ const data = eventChunk.data && "object" == typeof eventChunk.data ? eventChunk.data : null;
180
+ const llmChunk = data?.chunk && "object" == typeof data.chunk ? data.chunk : null;
181
+ return "string" == typeof llmChunk?.text && llmChunk.text.length > 0;
182
+ }
183
+ return false;
184
+ };
185
+ const selectStreamingFallbackText = (sessionMessages, invocationStartedAt, windowMs = 1000)=>{
186
+ for(let i = sessionMessages.length - 1; i >= 0; i -= 1){
187
+ const message = sessionMessages[i];
188
+ if (!message || "object" != typeof message) continue;
189
+ if ("assistant" === message.role) {
190
+ if ("number" == typeof message.createdAt && !(message.createdAt < invocationStartedAt - windowMs)) {
191
+ if ("string" == typeof message.content && message.content.trim()) return message.content;
192
+ }
193
+ }
194
+ }
195
+ };
91
196
  class AgentInvoker {
92
197
  findAllAgents() {
93
198
  const agentConfigs = this.loader.loadAllAgentConfigs();
@@ -97,7 +202,11 @@ class AgentInvoker {
97
202
  return await this.loader.loadAgent(name);
98
203
  }
99
204
  async invokeAgent(agentName, prompt, sessionId, attachments, options) {
205
+ const invocationStartedAt = Date.now();
100
206
  let cancellationHandled = false;
207
+ let activeToolName = null;
208
+ let lastToolName = null;
209
+ let sawAssistantText = false;
101
210
  const isCancelled = ()=>options?.signal?.aborted === true;
102
211
  try {
103
212
  const executionWorkspace = resolveExecutionWorkspace(this.workspace, this.workdir);
@@ -147,6 +256,29 @@ class AgentInvoker {
147
256
  skillsDirectory
148
257
  })
149
258
  ];
259
+ const summarizationSettings = resolveSummarizationMiddlewareSettings(this.wingmanConfig);
260
+ const modelRetrySettings = resolveModelRetryMiddlewareSettings(this.wingmanConfig);
261
+ if (modelRetrySettings) middleware.push((0, external_langchain_namespaceObject.modelRetryMiddleware)({
262
+ maxRetries: modelRetrySettings.maxRetries,
263
+ backoffFactor: modelRetrySettings.backoffFactor,
264
+ initialDelayMs: modelRetrySettings.initialDelayMs,
265
+ maxDelayMs: modelRetrySettings.maxDelayMs,
266
+ jitter: modelRetrySettings.jitter,
267
+ onFailure: modelRetrySettings.onFailure
268
+ }));
269
+ const toolRetrySettings = resolveToolRetryMiddlewareSettings(this.wingmanConfig);
270
+ if (toolRetrySettings) middleware.push((0, external_langchain_namespaceObject.toolRetryMiddleware)({
271
+ maxRetries: toolRetrySettings.maxRetries,
272
+ backoffFactor: toolRetrySettings.backoffFactor,
273
+ initialDelayMs: toolRetrySettings.initialDelayMs,
274
+ maxDelayMs: toolRetrySettings.maxDelayMs,
275
+ jitter: toolRetrySettings.jitter,
276
+ onFailure: toolRetrySettings.onFailure,
277
+ ...toolRetrySettings.tools ? {
278
+ tools: toolRetrySettings.tools
279
+ } : {}
280
+ }));
281
+ const hitlSettings = resolveHumanInTheLoopSettings(this.wingmanConfig);
150
282
  if (mergedHooks) {
151
283
  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`);
152
284
  middleware.push((0, hooks_cjs_namespaceObject.createHooksMiddleware)(mergedHooks, executionWorkspace, hookSessionId, this.logger));
@@ -193,10 +325,12 @@ class AgentInvoker {
193
325
  virtualMode: true
194
326
  }), backendOverrides),
195
327
  middleware: middleware,
328
+ interruptOn: hitlSettings?.interruptOn,
196
329
  skills: skillsSources,
197
330
  subagents: targetAgent.subagents || [],
198
331
  checkpointer: checkpointer
199
332
  });
333
+ configureDeepAgentSummarizationMiddleware(standaloneAgent, summarizationSettings, targetAgent.model);
200
334
  this.logger.debug("Agent created, sending message");
201
335
  const userContent = buildUserContent(prompt, attachments, targetAgent.model);
202
336
  if (this.sessionManager && sessionId) {
@@ -217,6 +351,13 @@ class AgentInvoker {
217
351
  signal: options?.signal
218
352
  });
219
353
  for await (const chunk of stream){
354
+ if (!sawAssistantText && chunkHasAssistantText(chunk)) sawAssistantText = true;
355
+ const toolEvent = detectToolEventContext(chunk);
356
+ if (toolEvent) {
357
+ lastToolName = toolEvent.toolName;
358
+ if ("on_tool_start" === toolEvent.event) activeToolName = toolEvent.toolName;
359
+ else if (activeToolName === toolEvent.toolName) activeToolName = null;
360
+ }
220
361
  if (isCancelled()) {
221
362
  cancellationHandled = true;
222
363
  this.logger.info("Agent invocation cancelled");
@@ -237,8 +378,27 @@ class AgentInvoker {
237
378
  };
238
379
  }
239
380
  this.logger.info("Agent streaming completed successfully");
381
+ let fallbackText;
382
+ if (!sawAssistantText && this.sessionManager && sessionId) try {
383
+ const sessionMessages = await this.sessionManager.listMessages(sessionId);
384
+ fallbackText = selectStreamingFallbackText(sessionMessages, invocationStartedAt);
385
+ } catch (stateError) {
386
+ this.logger.debug("Failed to derive streaming fallback text from session state", stateError);
387
+ }
388
+ if (!sawAssistantText && !fallbackText) {
389
+ const emptyResponseMessage = "Model completed without a response. Check provider logs for request errors.";
390
+ this.logger.warn(emptyResponseMessage);
391
+ this.outputManager.emitAgentError(emptyResponseMessage);
392
+ return {
393
+ blocked: true,
394
+ reason: "empty_stream_response"
395
+ };
396
+ }
240
397
  this.outputManager.emitAgentComplete({
241
- streaming: true
398
+ streaming: true,
399
+ ...fallbackText ? {
400
+ fallbackText
401
+ } : {}
242
402
  });
243
403
  return {
244
404
  streaming: true
@@ -286,8 +446,10 @@ class AgentInvoker {
286
446
  cancelled: true
287
447
  };
288
448
  }
289
- this.logger.error(`Agent invocation failed: ${error instanceof Error ? error.message : String(error)}`);
290
- this.outputManager.emitAgentError(error);
449
+ this.logger.error(`Agent invocation failed: ${error instanceof Error ? error.message : String(error)}${activeToolName ? ` (while running tool "${activeToolName}")` : lastToolName ? ` (last tool: "${lastToolName}")` : ""}`);
450
+ const errorMessage = error instanceof Error ? error.message : String(error);
451
+ const errorWithToolContext = activeToolName ? `${errorMessage} (while running tool "${activeToolName}")` : lastToolName ? `${errorMessage} (last tool: "${lastToolName}")` : errorMessage;
452
+ this.outputManager.emitAgentError(errorWithToolContext);
291
453
  throw error;
292
454
  } finally{
293
455
  if (this.mcpManager) {
@@ -516,16 +678,32 @@ exports.AgentInvoker = __webpack_exports__.AgentInvoker;
516
678
  exports.OUTPUT_VIRTUAL_PATH = __webpack_exports__.OUTPUT_VIRTUAL_PATH;
517
679
  exports.WORKDIR_VIRTUAL_PATH = __webpack_exports__.WORKDIR_VIRTUAL_PATH;
518
680
  exports.buildUserContent = __webpack_exports__.buildUserContent;
681
+ exports.chunkHasAssistantText = __webpack_exports__.chunkHasAssistantText;
682
+ exports.configureDeepAgentSummarizationMiddleware = __webpack_exports__.configureDeepAgentSummarizationMiddleware;
683
+ exports.detectToolEventContext = __webpack_exports__.detectToolEventContext;
519
684
  exports.resolveExecutionWorkspace = __webpack_exports__.resolveExecutionWorkspace;
520
685
  exports.resolveExternalOutputMount = __webpack_exports__.resolveExternalOutputMount;
686
+ exports.resolveHumanInTheLoopSettings = __webpack_exports__.resolveHumanInTheLoopSettings;
687
+ exports.resolveModelRetryMiddlewareSettings = __webpack_exports__.resolveModelRetryMiddlewareSettings;
688
+ exports.resolveSummarizationMiddlewareSettings = __webpack_exports__.resolveSummarizationMiddlewareSettings;
689
+ exports.resolveToolRetryMiddlewareSettings = __webpack_exports__.resolveToolRetryMiddlewareSettings;
690
+ exports.selectStreamingFallbackText = __webpack_exports__.selectStreamingFallbackText;
521
691
  exports.toWorkspaceAliasVirtualPath = __webpack_exports__.toWorkspaceAliasVirtualPath;
522
692
  for(var __rspack_i in __webpack_exports__)if (-1 === [
523
693
  "AgentInvoker",
524
694
  "OUTPUT_VIRTUAL_PATH",
525
695
  "WORKDIR_VIRTUAL_PATH",
526
696
  "buildUserContent",
697
+ "chunkHasAssistantText",
698
+ "configureDeepAgentSummarizationMiddleware",
699
+ "detectToolEventContext",
527
700
  "resolveExecutionWorkspace",
528
701
  "resolveExternalOutputMount",
702
+ "resolveHumanInTheLoopSettings",
703
+ "resolveModelRetryMiddlewareSettings",
704
+ "resolveSummarizationMiddlewareSettings",
705
+ "resolveToolRetryMiddlewareSettings",
706
+ "selectStreamingFallbackText",
529
707
  "toWorkspaceAliasVirtualPath"
530
708
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
531
709
  Object.defineProperty(exports, '__esModule', {
@@ -1,8 +1,9 @@
1
- import type { OutputManager } from "./outputManager.js";
2
- import type { Logger } from "../../logger.js";
3
1
  import type { WingmanAgentConfig } from "@/agent/config/agentConfig.js";
4
2
  import type { WingmanAgent } from "@/types/agents.js";
5
- import { SessionManager } from "./sessionManager.js";
3
+ import type { Logger } from "../../logger.js";
4
+ import type { WingmanConfigType } from "../config/schema.js";
5
+ import type { OutputManager } from "./outputManager.js";
6
+ import type { SessionManager } from "./sessionManager.js";
6
7
  export interface AgentInvokerOptions {
7
8
  workspace?: string;
8
9
  configDir?: string;
@@ -79,9 +80,47 @@ export type ExternalOutputMount = {
79
80
  virtualPath: string | null;
80
81
  absolutePath: string | null;
81
82
  };
83
+ export type SummarizationMiddlewareSettings = {
84
+ maxTokensBeforeSummary: number;
85
+ messagesToKeep: number;
86
+ };
87
+ export type ModelRetryMiddlewareSettings = {
88
+ maxRetries: number;
89
+ backoffFactor: number;
90
+ initialDelayMs: number;
91
+ maxDelayMs: number;
92
+ jitter: boolean;
93
+ onFailure: "continue" | "error";
94
+ };
95
+ export type ToolRetryMiddlewareSettings = ModelRetryMiddlewareSettings & {
96
+ tools?: string[];
97
+ };
98
+ export type HumanInTheLoopSettings = {
99
+ interruptOn: Record<string, boolean | {
100
+ allowedDecisions: Array<"approve" | "edit" | "reject">;
101
+ description?: string;
102
+ argsSchema?: Record<string, any>;
103
+ }>;
104
+ };
82
105
  export declare const resolveExecutionWorkspace: (workspace: string, workdir?: string | null) => string;
83
106
  export declare const toWorkspaceAliasVirtualPath: (absolutePath: string) => string | null;
84
107
  export declare const resolveExternalOutputMount: (workspace: string, workdir?: string | null, defaultOutputDir?: string | null) => ExternalOutputMount;
108
+ export declare const resolveSummarizationMiddlewareSettings: (config: WingmanConfigType) => SummarizationMiddlewareSettings | null;
109
+ export declare const resolveModelRetryMiddlewareSettings: (config: WingmanConfigType) => ModelRetryMiddlewareSettings | null;
110
+ export declare const resolveToolRetryMiddlewareSettings: (config: WingmanConfigType) => ToolRetryMiddlewareSettings | null;
111
+ export declare const resolveHumanInTheLoopSettings: (config: WingmanConfigType) => HumanInTheLoopSettings | null;
112
+ export declare const configureDeepAgentSummarizationMiddleware: (agent: any, settings: SummarizationMiddlewareSettings | null, model?: any) => void;
113
+ type ToolEventContext = {
114
+ event: "on_tool_start" | "on_tool_end";
115
+ toolName: string;
116
+ };
117
+ export declare const detectToolEventContext: (chunk: unknown) => ToolEventContext | null;
118
+ export declare const chunkHasAssistantText: (chunk: unknown) => boolean;
119
+ export declare const selectStreamingFallbackText: (sessionMessages: Array<{
120
+ role?: unknown;
121
+ createdAt?: unknown;
122
+ content?: unknown;
123
+ }>, invocationStartedAt: number, windowMs?: number) => string | undefined;
85
124
  export declare class AgentInvoker {
86
125
  private loader;
87
126
  private outputManager;
@@ -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();
@@ -63,7 +160,11 @@ class AgentInvoker {
63
160
  return await this.loader.loadAgent(name);
64
161
  }
65
162
  async invokeAgent(agentName, prompt, sessionId, attachments, options) {
163
+ const invocationStartedAt = Date.now();
66
164
  let cancellationHandled = false;
165
+ let activeToolName = null;
166
+ let lastToolName = null;
167
+ let sawAssistantText = false;
67
168
  const isCancelled = ()=>options?.signal?.aborted === true;
68
169
  try {
69
170
  const executionWorkspace = resolveExecutionWorkspace(this.workspace, this.workdir);
@@ -113,6 +214,29 @@ class AgentInvoker {
113
214
  skillsDirectory
114
215
  })
115
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);
116
240
  if (mergedHooks) {
117
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`);
118
242
  middleware.push(createHooksMiddleware(mergedHooks, executionWorkspace, hookSessionId, this.logger));
@@ -159,10 +283,12 @@ class AgentInvoker {
159
283
  virtualMode: true
160
284
  }), backendOverrides),
161
285
  middleware: middleware,
286
+ interruptOn: hitlSettings?.interruptOn,
162
287
  skills: skillsSources,
163
288
  subagents: targetAgent.subagents || [],
164
289
  checkpointer: checkpointer
165
290
  });
291
+ configureDeepAgentSummarizationMiddleware(standaloneAgent, summarizationSettings, targetAgent.model);
166
292
  this.logger.debug("Agent created, sending message");
167
293
  const userContent = buildUserContent(prompt, attachments, targetAgent.model);
168
294
  if (this.sessionManager && sessionId) {
@@ -183,6 +309,13 @@ class AgentInvoker {
183
309
  signal: options?.signal
184
310
  });
185
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
+ }
186
319
  if (isCancelled()) {
187
320
  cancellationHandled = true;
188
321
  this.logger.info("Agent invocation cancelled");
@@ -203,8 +336,27 @@ class AgentInvoker {
203
336
  };
204
337
  }
205
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
+ }
206
355
  this.outputManager.emitAgentComplete({
207
- streaming: true
356
+ streaming: true,
357
+ ...fallbackText ? {
358
+ fallbackText
359
+ } : {}
208
360
  });
209
361
  return {
210
362
  streaming: true
@@ -252,8 +404,10 @@ class AgentInvoker {
252
404
  cancelled: true
253
405
  };
254
406
  }
255
- this.logger.error(`Agent invocation failed: ${error instanceof Error ? error.message : String(error)}`);
256
- this.outputManager.emitAgentError(error);
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);
257
411
  throw error;
258
412
  } finally{
259
413
  if (this.mcpManager) {
@@ -478,4 +632,4 @@ function buildAttachmentPreview(attachments) {
478
632
  if (hasImage) return "[image]";
479
633
  return "";
480
634
  }
481
- 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;