@wingman-ai/gateway 0.2.1 → 0.2.2

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.
@@ -116,6 +116,7 @@ Agents can have their own subagents, allowing you to create hierarchical agent s
116
116
 
117
117
  - **Maximum nesting level: 1** - Only top-level agents can have subagents
118
118
  - **Subagents cannot have their own subagents** - This prevents excessive nesting and keeps the architecture manageable
119
+ - **Subagent names must be unique and must not match the parent agent name** - Avoids ambiguous delegation routing
119
120
 
120
121
  ### Example: Agent with Subagents
121
122
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: coding
3
- description: Lead coding orchestrator that plans, parallelizes, and reviews work delegated to a focused coding subagent.
3
+ description: Lead coding orchestrator that plans, sequences, parallelizes, and reviews work delegated to focused implementation subagents.
4
4
  tools:
5
5
  - think
6
6
  - code_search
@@ -12,8 +12,8 @@ subAgents:
12
12
  - name: researcher
13
13
  description: Research subagent
14
14
  promptFile: ../researcher/agent.md
15
- - name: coding
16
- description: Executes assigned coding tasks with strict scope control and reports results clearly.
15
+ - name: implementor
16
+ description: Implements assigned coding chunks with strict scope control and concise verification output.
17
17
  tools:
18
18
  - command_execute
19
19
  - think
@@ -23,13 +23,26 @@ subAgents:
23
23
  ---
24
24
 
25
25
  You are the lead coding agent collaborating with the user as their Wingman.
26
- You plan and orchestrate work, delegate parallelizable chunks to the coding subagent, and then review everything against the plan before finalizing.
26
+ You plan and orchestrate work, sequence dependent chunks, delegate parallelizable chunks to the implementor subagent, and then review everything against the plan before finalizing.
27
27
  Use memories to preserve key context, decisions, and assumptions for future turns.
28
28
  Only provide code examples if the user explicitly asks for an "example" or "snippet".
29
29
  Any code examples must use GitHub-flavored Markdown with a language specifier.
30
30
 
31
31
  **CRITICAL - Always use file paths relative to the current working directory**
32
32
 
33
+ # Completion Contract (Non-Negotiable)
34
+ - Your objective is full task completion, not partial progress.
35
+ - Do NOT stop after completing one chunk if the user asked for broader scope.
36
+ - Keep iterating through plan items until all requested outcomes are done or you hit a real blocker.
37
+ - If blocked, report exactly what is blocked, what you already tried, and the smallest user decision needed to unblock.
38
+
39
+ # Definition of Done (Before Final Response)
40
+ - All explicitly requested outcomes are implemented.
41
+ - All planned chunks are complete, or any incomplete chunk is explicitly marked with blocker + owner.
42
+ - Relevant tests/builds are run and results are reported.
43
+ - Cross-cutting checks are done for types, configs, docs, and integration points touched by the change.
44
+ - If capability/behavior changed significantly, update relevant docs and requirements notes.
45
+
33
46
  # Memory Discipline
34
47
  - At the start, check for relevant memories and incorporate them into your plan
35
48
  - Store key decisions, constraints, and open questions in memory
@@ -63,15 +76,34 @@ Any code examples must use GitHub-flavored Markdown with a language specifier.
63
76
  - Prefer chunking by non-overlapping files or modules
64
77
  - Avoid assigning the same file to multiple subagents unless coordination is explicit
65
78
  - If dependencies require sequencing, run those chunks serially
79
+ - Track plan status explicitly (`pending`, `in_progress`, `done`) and keep driving unfinished items to completion
80
+
81
+ # Dependency-Aware Sequencing
82
+ - Build a dependency map before delegation:
83
+ - `prerequisite` chunks: unblock architecture/tooling/foundations
84
+ - `parallel` chunks: independent implementation streams
85
+ - `dependent` chunks: require outputs from earlier chunks
86
+ - Execute in waves:
87
+ 1. Complete prerequisite chunks first
88
+ 2. Run independent chunks in parallel
89
+ 3. Run dependent/integration chunks after prerequisites are done
90
+ 4. Finalize with verification and documentation updates
91
+ - Never start a dependent chunk until required prerequisite chunks are `done`.
92
+ - If a prerequisite chunk is blocked, immediately pause impacted downstream chunks, re-plan, and surface the blocker if unresolved.
66
93
 
67
94
  # Delegation Rules
68
- - Use the **coding** subagent for all code changes beyond trivial edits
95
+ - Use the **implementor** subagent for all code changes beyond trivial edits
69
96
  - Use the **researcher** subagent for external docs or API research
70
- - Provide each coding task with:
71
- - Scope and goal (1-2 sentences)
72
- - Exact files to edit or create
73
- - Acceptance criteria and edge cases
74
- - Tests to run (or ask the subagent to propose)
97
+ - Never delegate code work without an explicit chunk assignment
98
+ - Every implementor delegation MUST include this packet exactly:
99
+ - `chunk_id`: short unique id (e.g., `chunk-auth-01`)
100
+ - `goal`: 1-2 sentence objective
101
+ - `scope_paths`: exact files/packages allowed for edits
102
+ - `out_of_scope`: boundaries and files to avoid
103
+ - `acceptance_criteria`: behavior/result required
104
+ - `tests`: exact commands to run, or `propose-tests` when unknown
105
+ - If file scope is unclear, gather context first (search/read) before delegating
106
+ - Never ask the implementor to define its own chunk or select files
75
107
  - If a task expands beyond scope, pause and ask before proceeding
76
108
 
77
109
  # Review Responsibility (Top-Level Only)
@@ -79,6 +111,15 @@ Any code examples must use GitHub-flavored Markdown with a language specifier.
79
111
  - Check that every plan item is satisfied and nothing is missing
80
112
  - Re-scan for cross-cutting issues (types, configs, tests, docs)
81
113
  - Run or request any remaining tests/builds needed for confidence
114
+ - If review finds gaps, reopen delegation and resolve them before finalizing
115
+
116
+ # Verification Pipeline (End-to-End)
117
+ - For complex tasks, complete verification in this order unless constraints force otherwise:
118
+ 1. Update/add tests for changed behavior
119
+ 2. Run targeted tests for touched modules
120
+ 3. Run broader project tests as appropriate
121
+ 4. Run build/typecheck and report outcomes
122
+ - Do not mark completion until the required verification pipeline is either passing or explicitly blocked with evidence.
82
123
 
83
124
  # Output Format Standards
84
125
 
@@ -87,10 +128,31 @@ Any code examples must use GitHub-flavored Markdown with a language specifier.
87
128
  - Include column for precise locations: `src/utils.ts:42:15`
88
129
 
89
130
  ## Response Structure
131
+ - Use GitHub-flavored Markdown for user-facing responses
90
132
  - Lead with the most important information
91
133
  - Use flat bullet lists, avoid nesting
92
134
  - Code samples in fenced blocks with language specifier
93
- - Keep explanations brief - show, don't tell
135
+ - Keep explanations brief by default; expand only when complexity or risk justifies it
136
+
137
+ ## Markdown Overview Mode
138
+ - Provide a structured markdown overview when any of these are true:
139
+ - Multi-file or cross-cutting changes
140
+ - Behavior changes that can cause regressions
141
+ - Test/build failures or partial verification
142
+ - The user asks for detail, rationale, or review depth
143
+ - Use this section order for rich feedback:
144
+ 1. `Overview` (what changed and why)
145
+ 2. `Changes` (key files and decisions)
146
+ 3. `Validation` (tests/build/commands + results)
147
+ 4. `Risks` (known gaps, assumptions, follow-ups)
148
+ - For simple, low-risk tasks, use concise mode (short summary + validation line)
149
+
150
+ ## Completion Reporting
151
+ - In final responses for non-trivial tasks, include:
152
+ - `Scope Status`: requested items mapped to `done` or `blocked`
153
+ - `Validation`: exact commands run + outcome
154
+ - `Outstanding`: only true blockers or follow-ups (if none, state `None`)
155
+ - Never present an in-progress checkpoint as a final completion response
94
156
 
95
157
  ## Code Reviews (when reviewing)
96
158
  1. **Findings** (severity-ordered with file:line references)
@@ -106,13 +168,13 @@ Choose your approach based on task complexity:
106
168
  - Keep it efficient and avoid unnecessary delegation
107
169
 
108
170
  **MODERATE tasks** (new features, refactors, 50-200 lines):
109
- - Create a brief plan, then delegate chunks to **coding**
171
+ - Create a brief plan, then delegate chunks to **implementor**
110
172
  - Parallelize by file/module when possible
111
173
  - Perform the final review yourself
112
174
 
113
175
  **COMPLEX tasks** (major features, architecture changes, > 200 lines):
114
176
  - ALWAYS create a detailed plan with parallel workstreams
115
- - Delegate each stream to **coding** with clear scopes
177
+ - Delegate each stream to **implementor** with clear scopes
116
178
  - Perform a comprehensive top-level review before finalizing
117
179
 
118
180
  **Important**:
@@ -131,7 +193,7 @@ Choose your approach based on task complexity:
131
193
  7. When unexpected results occur, focus on solutions rather than apologies
132
194
  8. NEVER output code to the USER, unless requested
133
195
  9. When providing code examples, consistently use GitHub-flavored fenced markdown, specifying the appropriate programming language for syntax highlighting
134
- 10. Keep responses concise and relevant, avoiding unnecessary details
196
+ 10. Keep responses concise and relevant by default, but provide rich markdown overviews when the task complexity warrants it
135
197
 
136
198
  # Information Gathering
137
199
  If you need more context to properly address the user's request:
@@ -1,4 +1,18 @@
1
- You are the focused coding subagent. Your job is to implement the specific chunk assigned by the lead coding agent.
1
+ You are the focused implementor subagent. Your job is to implement the specific chunk assigned by the lead coding agent.
2
+
3
+ # Assignment Contract (Critical)
4
+ You should receive a task packet with:
5
+ - `chunk_id`
6
+ - `goal`
7
+ - `scope_paths`
8
+ - `out_of_scope`
9
+ - `acceptance_criteria`
10
+ - `tests`
11
+
12
+ If one or more fields are missing:
13
+ - Infer missing non-critical fields from context and proceed
14
+ - Ask for clarification only when both `goal` and `scope_paths` are missing
15
+ - Ask at most once, then stop (do not loop on "no assignment" responses)
2
16
 
3
17
  # Scope Discipline (Critical)
4
18
  - Follow the lead's plan and scope exactly
@@ -6,6 +20,13 @@ You are the focused coding subagent. Your job is to implement the specific chunk
6
20
  - If you need additional files or scope changes, pause and ask the lead
7
21
  - Avoid overlapping edits with other subagents; surface conflicts immediately
8
22
 
23
+ # Chunk Completion Standard
24
+ - Treat each chunk as incomplete until all chunk acceptance criteria are satisfied.
25
+ - Do not return early after partial edits if required tests/verification are still pending.
26
+ - If verification fails, either fix the issue within scope or return a precise blocker with evidence.
27
+ - Never hand back a chunk with vague status like "mostly done" or "ready for next step".
28
+ - If blocked by unmet prerequisite work, return `Status: Blocked` and identify the blocking `chunk_id`.
29
+
9
30
  # Implementation Standards
10
31
 
11
32
  ## Code Quality
@@ -25,6 +46,7 @@ After implementation:
25
46
  1. Run tests requested by the lead
26
47
  2. If no tests were specified, propose the most relevant tests
27
48
  3. Note any tests you could not run and why
49
+ 4. Explicitly map each acceptance criterion to `pass` or `blocked`
28
50
 
29
51
  Your responsibilities:
30
52
  - Implement the assigned chunk precisely and systematically
@@ -42,13 +64,14 @@ Workflow:
42
64
  2. Implement changes following the lead's scope
43
65
  3. Ensure code follows existing conventions (imports, formatting, naming)
44
66
  4. Run relevant tests or validation commands when appropriate
45
- 5. Summarize what was changed and why
67
+ 5. Summarize what was changed and why, grouped by `chunk_id`
46
68
 
47
69
  IMPORTANT:
48
70
  - Return summaries of changes made, NOT full file contents
49
71
  - Keep responses under 500 words - be concise
50
72
  - If you encounter issues or blockers, report them clearly
51
73
  - Don't add unnecessary features beyond the assigned task
74
+ - End with a clear chunk status line: `Status: Done` or `Status: Blocked (<reason>)`
52
75
 
53
76
  Example summary format:
54
77
  "Modified [file]: [brief description of changes]
@@ -72,6 +72,35 @@ const BaseAgentConfigSchema = external_zod_namespaceObject.z.object({
72
72
  });
73
73
  const AgentConfigSchema = BaseAgentConfigSchema.extend({
74
74
  subAgents: external_zod_namespaceObject.z.array(BaseAgentConfigSchema).optional().describe("List of sub-agents that this agent can delegate to (each may include its own model override)")
75
+ }).superRefine((config, ctx)=>{
76
+ if (!config.subAgents || 0 === config.subAgents.length) return;
77
+ const parentName = config.name.trim().toLowerCase();
78
+ const seenSubAgentNames = new Set();
79
+ for (const [index, subAgent] of config.subAgents.entries()){
80
+ const normalizedName = subAgent.name.trim().toLowerCase();
81
+ if (normalizedName === parentName) ctx.addIssue({
82
+ code: "custom",
83
+ path: [
84
+ "subAgents",
85
+ index,
86
+ "name"
87
+ ],
88
+ message: "Sub-agent name must be different from parent agent name"
89
+ });
90
+ if (seenSubAgentNames.has(normalizedName)) {
91
+ ctx.addIssue({
92
+ code: "custom",
93
+ path: [
94
+ "subAgents",
95
+ index,
96
+ "name"
97
+ ],
98
+ message: "Sub-agent names must be unique within the same parent agent"
99
+ });
100
+ continue;
101
+ }
102
+ seenSubAgentNames.add(normalizedName);
103
+ }
75
104
  });
76
105
  function validateAgentConfig(config) {
77
106
  try {
@@ -41,6 +41,35 @@ const BaseAgentConfigSchema = z.object({
41
41
  });
42
42
  const AgentConfigSchema = BaseAgentConfigSchema.extend({
43
43
  subAgents: z.array(BaseAgentConfigSchema).optional().describe("List of sub-agents that this agent can delegate to (each may include its own model override)")
44
+ }).superRefine((config, ctx)=>{
45
+ if (!config.subAgents || 0 === config.subAgents.length) return;
46
+ const parentName = config.name.trim().toLowerCase();
47
+ const seenSubAgentNames = new Set();
48
+ for (const [index, subAgent] of config.subAgents.entries()){
49
+ const normalizedName = subAgent.name.trim().toLowerCase();
50
+ if (normalizedName === parentName) ctx.addIssue({
51
+ code: "custom",
52
+ path: [
53
+ "subAgents",
54
+ index,
55
+ "name"
56
+ ],
57
+ message: "Sub-agent name must be different from parent agent name"
58
+ });
59
+ if (seenSubAgentNames.has(normalizedName)) {
60
+ ctx.addIssue({
61
+ code: "custom",
62
+ path: [
63
+ "subAgents",
64
+ index,
65
+ "name"
66
+ ],
67
+ message: "Sub-agent names must be unique within the same parent agent"
68
+ });
69
+ continue;
70
+ }
71
+ seenSubAgentNames.add(normalizedName);
72
+ }
44
73
  });
45
74
  function validateAgentConfig(config) {
46
75
  try {
@@ -69,6 +69,45 @@ const agentConfig_cjs_namespaceObject = require("../config/agentConfig.cjs");
69
69
  (0, external_vitest_namespaceObject.expect)(result.success).toBe(true);
70
70
  if (result.success) (0, external_vitest_namespaceObject.expect)(result.data.subAgents?.[0].model).toBe("openai:gpt-4o");
71
71
  });
72
+ (0, external_vitest_namespaceObject.it)("should fail when a sub-agent shares the same name as its parent", ()=>{
73
+ const config = {
74
+ name: "coding",
75
+ description: "Parent coding agent",
76
+ systemPrompt: "You are the parent coding agent",
77
+ subAgents: [
78
+ {
79
+ name: "coding",
80
+ description: "Nested coding worker",
81
+ systemPrompt: "You are a worker"
82
+ }
83
+ ]
84
+ };
85
+ const result = (0, agentConfig_cjs_namespaceObject.validateAgentConfig)(config);
86
+ (0, external_vitest_namespaceObject.expect)(result.success).toBe(false);
87
+ if (!result.success) (0, external_vitest_namespaceObject.expect)(result.error).toContain("Sub-agent name must be different from parent agent name");
88
+ });
89
+ (0, external_vitest_namespaceObject.it)("should fail when sub-agent names are duplicated", ()=>{
90
+ const config = {
91
+ name: "parent-agent",
92
+ description: "Parent agent",
93
+ systemPrompt: "You are the parent agent",
94
+ subAgents: [
95
+ {
96
+ name: "implementor",
97
+ description: "First implementor",
98
+ systemPrompt: "You implement changes"
99
+ },
100
+ {
101
+ name: "IMPLEMENTOR",
102
+ description: "Duplicate implementor",
103
+ systemPrompt: "You implement more changes"
104
+ }
105
+ ]
106
+ };
107
+ const result = (0, agentConfig_cjs_namespaceObject.validateAgentConfig)(config);
108
+ (0, external_vitest_namespaceObject.expect)(result.success).toBe(false);
109
+ if (!result.success) (0, external_vitest_namespaceObject.expect)(result.error).toContain("Sub-agent names must be unique within the same parent agent");
110
+ });
72
111
  (0, external_vitest_namespaceObject.it)("should fail validation for missing required fields", ()=>{
73
112
  const config = {
74
113
  name: "test-agent"
@@ -67,6 +67,45 @@ describe("Agent Configuration Schema", ()=>{
67
67
  expect(result.success).toBe(true);
68
68
  if (result.success) expect(result.data.subAgents?.[0].model).toBe("openai:gpt-4o");
69
69
  });
70
+ it("should fail when a sub-agent shares the same name as its parent", ()=>{
71
+ const config = {
72
+ name: "coding",
73
+ description: "Parent coding agent",
74
+ systemPrompt: "You are the parent coding agent",
75
+ subAgents: [
76
+ {
77
+ name: "coding",
78
+ description: "Nested coding worker",
79
+ systemPrompt: "You are a worker"
80
+ }
81
+ ]
82
+ };
83
+ const result = validateAgentConfig(config);
84
+ expect(result.success).toBe(false);
85
+ if (!result.success) expect(result.error).toContain("Sub-agent name must be different from parent agent name");
86
+ });
87
+ it("should fail when sub-agent names are duplicated", ()=>{
88
+ const config = {
89
+ name: "parent-agent",
90
+ description: "Parent agent",
91
+ systemPrompt: "You are the parent agent",
92
+ subAgents: [
93
+ {
94
+ name: "implementor",
95
+ description: "First implementor",
96
+ systemPrompt: "You implement changes"
97
+ },
98
+ {
99
+ name: "IMPLEMENTOR",
100
+ description: "Duplicate implementor",
101
+ systemPrompt: "You implement more changes"
102
+ }
103
+ ]
104
+ };
105
+ const result = validateAgentConfig(config);
106
+ expect(result.success).toBe(false);
107
+ if (!result.success) expect(result.error).toContain("Sub-agent names must be unique within the same parent agent");
108
+ });
70
109
  it("should fail validation for missing required fields", ()=>{
71
110
  const config = {
72
111
  name: "test-agent"
@@ -96,7 +96,9 @@ class AgentInvoker {
96
96
  async findAgent(name) {
97
97
  return await this.loader.loadAgent(name);
98
98
  }
99
- async invokeAgent(agentName, prompt, sessionId, attachments) {
99
+ async invokeAgent(agentName, prompt, sessionId, attachments, options) {
100
+ let cancellationHandled = false;
101
+ const isCancelled = ()=>options?.signal?.aborted === true;
100
102
  try {
101
103
  const executionWorkspace = resolveExecutionWorkspace(this.workspace, this.workdir);
102
104
  const effectiveWorkdir = this.workdir ? executionWorkspace : null;
@@ -211,9 +213,29 @@ class AgentInvoker {
211
213
  configurable: {
212
214
  thread_id: sessionId
213
215
  },
214
- version: "v2"
216
+ version: "v2",
217
+ signal: options?.signal
215
218
  });
216
- for await (const chunk of stream)this.outputManager.emitAgentStream(chunk);
219
+ for await (const chunk of stream){
220
+ if (isCancelled()) {
221
+ cancellationHandled = true;
222
+ this.logger.info("Agent invocation cancelled");
223
+ this.outputManager.emitAgentError("Request cancelled");
224
+ if ("function" == typeof stream?.return) await stream.return();
225
+ return {
226
+ cancelled: true
227
+ };
228
+ }
229
+ this.outputManager.emitAgentStream(chunk);
230
+ }
231
+ if (isCancelled()) {
232
+ cancellationHandled = true;
233
+ this.logger.info("Agent invocation cancelled");
234
+ this.outputManager.emitAgentError("Request cancelled");
235
+ return {
236
+ cancelled: true
237
+ };
238
+ }
217
239
  this.logger.info("Agent streaming completed successfully");
218
240
  this.outputManager.emitAgentComplete({
219
241
  streaming: true
@@ -224,6 +246,14 @@ class AgentInvoker {
224
246
  }
225
247
  {
226
248
  this.logger.debug("Using blocking invoke (no session manager)");
249
+ if (isCancelled()) {
250
+ cancellationHandled = true;
251
+ this.logger.info("Agent invocation cancelled");
252
+ this.outputManager.emitAgentError("Request cancelled");
253
+ return {
254
+ cancelled: true
255
+ };
256
+ }
227
257
  const result = await standaloneAgent.invoke({
228
258
  messages: [
229
259
  {
@@ -232,13 +262,30 @@ class AgentInvoker {
232
262
  }
233
263
  ]
234
264
  }, {
235
- recursionLimit: this.wingmanConfig.recursionLimit
265
+ recursionLimit: this.wingmanConfig.recursionLimit,
266
+ signal: options?.signal
236
267
  });
268
+ if (isCancelled()) {
269
+ cancellationHandled = true;
270
+ this.logger.info("Agent invocation cancelled");
271
+ this.outputManager.emitAgentError("Request cancelled");
272
+ return {
273
+ cancelled: true
274
+ };
275
+ }
237
276
  this.logger.info("Agent completed successfully");
238
277
  this.outputManager.emitAgentComplete(result);
239
278
  return result;
240
279
  }
241
280
  } catch (error) {
281
+ const abortError = isCancelled() || error instanceof Error && ("AbortError" === error.name || "CancelledError" === error.name || /abort|cancel/i.test(error.message));
282
+ if (abortError) {
283
+ if (!cancellationHandled) this.outputManager.emitAgentError("Request cancelled");
284
+ this.logger.info("Agent invocation cancelled");
285
+ return {
286
+ cancelled: true
287
+ };
288
+ }
242
289
  this.logger.error(`Agent invocation failed: ${error instanceof Error ? error.message : String(error)}`);
243
290
  this.outputManager.emitAgentError(error);
244
291
  throw error;
@@ -12,6 +12,9 @@ export interface AgentInvokerOptions {
12
12
  workdir?: string | null;
13
13
  defaultOutputDir?: string | null;
14
14
  }
15
+ export interface InvokeAgentOptions {
16
+ signal?: AbortSignal;
17
+ }
15
18
  export type ImageAttachment = {
16
19
  kind?: "image";
17
20
  dataUrl: string;
@@ -99,7 +102,7 @@ export declare class AgentInvoker {
99
102
  /**
100
103
  * Invoke a specific agent directly (bypassing main orchestration)
101
104
  */
102
- invokeAgent(agentName: string, prompt: string, sessionId?: string, attachments?: MediaAttachment[]): Promise<any>;
105
+ invokeAgent(agentName: string, prompt: string, sessionId?: string, attachments?: MediaAttachment[], options?: InvokeAgentOptions): Promise<any>;
103
106
  /**
104
107
  * List all available agents with their descriptions
105
108
  */
@@ -62,7 +62,9 @@ class AgentInvoker {
62
62
  async findAgent(name) {
63
63
  return await this.loader.loadAgent(name);
64
64
  }
65
- async invokeAgent(agentName, prompt, sessionId, attachments) {
65
+ async invokeAgent(agentName, prompt, sessionId, attachments, options) {
66
+ let cancellationHandled = false;
67
+ const isCancelled = ()=>options?.signal?.aborted === true;
66
68
  try {
67
69
  const executionWorkspace = resolveExecutionWorkspace(this.workspace, this.workdir);
68
70
  const effectiveWorkdir = this.workdir ? executionWorkspace : null;
@@ -177,9 +179,29 @@ class AgentInvoker {
177
179
  configurable: {
178
180
  thread_id: sessionId
179
181
  },
180
- version: "v2"
182
+ version: "v2",
183
+ signal: options?.signal
181
184
  });
182
- for await (const chunk of stream)this.outputManager.emitAgentStream(chunk);
185
+ for await (const chunk of stream){
186
+ if (isCancelled()) {
187
+ cancellationHandled = true;
188
+ this.logger.info("Agent invocation cancelled");
189
+ this.outputManager.emitAgentError("Request cancelled");
190
+ if ("function" == typeof stream?.return) await stream.return();
191
+ return {
192
+ cancelled: true
193
+ };
194
+ }
195
+ this.outputManager.emitAgentStream(chunk);
196
+ }
197
+ if (isCancelled()) {
198
+ cancellationHandled = true;
199
+ this.logger.info("Agent invocation cancelled");
200
+ this.outputManager.emitAgentError("Request cancelled");
201
+ return {
202
+ cancelled: true
203
+ };
204
+ }
183
205
  this.logger.info("Agent streaming completed successfully");
184
206
  this.outputManager.emitAgentComplete({
185
207
  streaming: true
@@ -190,6 +212,14 @@ class AgentInvoker {
190
212
  }
191
213
  {
192
214
  this.logger.debug("Using blocking invoke (no session manager)");
215
+ if (isCancelled()) {
216
+ cancellationHandled = true;
217
+ this.logger.info("Agent invocation cancelled");
218
+ this.outputManager.emitAgentError("Request cancelled");
219
+ return {
220
+ cancelled: true
221
+ };
222
+ }
193
223
  const result = await standaloneAgent.invoke({
194
224
  messages: [
195
225
  {
@@ -198,13 +228,30 @@ class AgentInvoker {
198
228
  }
199
229
  ]
200
230
  }, {
201
- recursionLimit: this.wingmanConfig.recursionLimit
231
+ recursionLimit: this.wingmanConfig.recursionLimit,
232
+ signal: options?.signal
202
233
  });
234
+ if (isCancelled()) {
235
+ cancellationHandled = true;
236
+ this.logger.info("Agent invocation cancelled");
237
+ this.outputManager.emitAgentError("Request cancelled");
238
+ return {
239
+ cancelled: true
240
+ };
241
+ }
203
242
  this.logger.info("Agent completed successfully");
204
243
  this.outputManager.emitAgentComplete(result);
205
244
  return result;
206
245
  }
207
246
  } catch (error) {
247
+ const abortError = isCancelled() || error instanceof Error && ("AbortError" === error.name || "CancelledError" === error.name || /abort|cancel/i.test(error.message));
248
+ if (abortError) {
249
+ if (!cancellationHandled) this.outputManager.emitAgentError("Request cancelled");
250
+ this.logger.info("Agent invocation cancelled");
251
+ return {
252
+ cancelled: true
253
+ };
254
+ }
208
255
  this.logger.error(`Agent invocation failed: ${error instanceof Error ? error.message : String(error)}`);
209
256
  this.outputManager.emitAgentError(error);
210
257
  throw error;