@salesforce/sfdx-agent-harness-mastra 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@salesforce/sfdx-agent-harness-mastra` are documented in this file.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [0.16.0] - 2026-06-09
7
+
8
+ ### Features
9
+ - **release**: auto-generate per-package CHANGELOG.md on publish ([#567](https://github.com/forcedotcom/agentic-dx/pull/567))
10
+ - unify tool-exposure policy under toolSearch.alwaysActive ([#566](https://github.com/forcedotcom/agentic-dx/pull/566))
11
+ - add ClaudeAgentConfig.skillSearch + decouple Mastra skillSearch from toolSearch ([#563](https://github.com/forcedotcom/agentic-dx/pull/563))
12
+ - **agent-sdk**: preserve MCP clients across updateAgentConfig ([#560](https://github.com/forcedotcom/agentic-dx/pull/560))
13
+ - **agent-sdk,harness-claude,harness-mastra**: first-class tool-result redaction ([#546](https://github.com/forcedotcom/agentic-dx/pull/546))
14
+
15
+ ### Fixes
16
+ - **harness-claude,harness-mastra**: align event/log payload shapes ([#562](https://github.com/forcedotcom/agentic-dx/pull/562))
17
+ - **harness-mastra**: honor MCPServerConfig.alwaysLoad when toolSearch is set ([#558](https://github.com/forcedotcom/agentic-dx/pull/558))
18
+
19
+ ### Chores
20
+ - **harness-claude,harness-mastra**: re-enable monorepoLintPack at four offender sites ([#561](https://github.com/forcedotcom/agentic-dx/pull/561))
21
+ - **deps-dev**: bump the eslint group across 1 directory with 2 updates ([#553](https://github.com/forcedotcom/agentic-dx/pull/553))
22
+ - **deps**: bump the mastra group across 1 directory with 3 updates ([#539](https://github.com/forcedotcom/agentic-dx/pull/539))
23
+ - **deps-dev**: bump the dev-dependencies group across 1 directory with 4 updates ([#536](https://github.com/forcedotcom/agentic-dx/pull/536))
24
+ - **deps-dev**: bump the vitest group across 1 directory with 3 updates ([#552](https://github.com/forcedotcom/agentic-dx/pull/552))
25
+
package/README.md CHANGED
@@ -62,13 +62,14 @@ These types let consumers reach Mastra-specific features through the SDK's typed
62
62
  `MastraAgentHarness` from `MastraHarnessFactory.create()`, so consumers usually don't reference these types explicitly —
63
63
  they show up via inference at the call site.
64
64
 
65
- | Export | Kind | Description |
66
- | ------------------------ | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
67
- | `MastraAgentHarness` | type | Branded `AgentHarness` subtype; carries the Mastra-specific `extensions` namespace and the inferred config type. |
68
- | `MastraAgentConfig` | type | `AgentConfig` extended with Mastra-only fields (currently `toolSearch`). Inferred at `manager.createAgent` automatically — annotation rarely needed. |
69
- | `MastraToolSearchConfig` | type | Shape of `MastraAgentConfig.toolSearch` (`pool`, `alwaysActive`, `topK`, `ttl`). |
70
- | `ToolSearchHandle` | type | Stable interface for the tool-search runtime: `id`, `getStateStats`, `clearState`, `clearAllState`, `cleanupNow`. Wraps Mastra's internal processor. |
71
- | `SkillSearchHandle` | type | Stable interface for the skill-search runtime: `id` (Mastra's processor has no other public methods today; the harness owns lifecycle). |
65
+ | Export | Kind | Description |
66
+ | ------------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
67
+ | `MastraAgentHarness` | type | Branded `AgentHarness` subtype; carries the Mastra-specific `extensions` namespace and the inferred config type. |
68
+ | `MastraAgentConfig` | type | `AgentConfig` extended with Mastra-only fields (`toolSearch`, `skillSearch`). Inferred at `manager.createAgent` automatically — annotation rarely needed. |
69
+ | `MastraToolSearchConfig` | type | Shape of `MastraAgentConfig.toolSearch` (`pool`, `alwaysActive`, `topK`, `ttl`). |
70
+ | `MastraSkillSearchConfig` | type | Shape of `MastraAgentConfig.skillSearch` (`topK`). Opts into Mastra's `SkillSearchProcessor`. |
71
+ | `ToolSearchHandle` | type | Stable interface for the tool-search runtime: `id`, `getStateStats`, `clearState`, `clearAllState`, `cleanupNow`. Wraps Mastra's internal processor. |
72
+ | `SkillSearchHandle` | type | Stable interface for the skill-search runtime: `id` (Mastra's processor has no other public methods today; the harness owns lifecycle). |
72
73
 
73
74
  The Mastra-specific accessors live on `manager.extensions.mastra` (`getToolSearchProcessor(agentId)`,
74
75
  `getSkillSearchProcessor(agentId)`, `formatMcpToolName(serverName, toolName)`) and require no separate import.
@@ -90,12 +91,13 @@ const agent = await manager.createAgent(projectRoot, {
90
91
  toolSearch: {
91
92
  pool: '*', // or an explicit name list
92
93
  alwaysActive: [
93
- 'lookup_account',
94
- // Build the Mastra-specific namespaced name for an MCP tool that hasn't
95
- // been discovered yet. The output format is harness-specific
96
- // (Mastra: `${server}_${tool}`); for cross-harness tool identification
97
- // anywhere else, prefer the `(serverName, toolName)` pair on `McpToolInfo`.
98
- manager.extensions.mastra.formatMcpToolName('sfdx', 'deploy_metadata'),
94
+ // Every tool from a server (post-discovery expansion).
95
+ { serverName: 'sfdx' },
96
+ // Exactly one tool on a specific server.
97
+ { serverName: 'other', toolName: 'lookup_account' },
98
+ // Any tool by this name, regardless of source — built-ins, workspace
99
+ // tools, consumer-declared tools, AND any MCP server's tool surface.
100
+ { toolName: 'resume_tool_operation' },
99
101
  ],
100
102
  topK: 5, // advisory; defaults to Mastra's 5
101
103
  ttl: 3_600_000, // ms; advisory; defaults to Mastra's 1h
@@ -105,16 +107,35 @@ const agent = await manager.createAgent(projectRoot, {
105
107
 
106
108
  When `toolSearch` is set, `createAgent` blocks on MCP discovery so MCP tools enter the searchable pool. Without
107
109
  `toolSearch`, MCP discovery stays non-blocking (existing behavior). `alwaysActive` tools are visible directly to the
108
- model; everything else lives in the searchable pool. Unknown `alwaysActive` names are kept (for late MCP discovery);
109
- unknown `pool` names are dropped.
110
+ model; everything else lives in the searchable pool.
111
+
112
+ Each `alwaysActive` entry is an `AlwaysActiveEntry` (re-exported from `@salesforce/sfdx-agent-sdk/harness` and shared
113
+ with `ClaudeAgentConfig.toolSearch.alwaysActive`). At least one of `serverName` or `toolName` must be present — `{}`
114
+ rejects at config time. Unknown `pool` names are dropped; unmatched `{ toolName }`-only entries are kept for late MCP
115
+ discovery.
110
116
 
111
117
  `MastraToolSearchConfig.topK` and `ttl` are advisory — Mastra applies its own defaults if omitted.
112
118
 
113
119
  #### Skill search
114
120
 
115
- Skills (`AgentConfig.skills`) load eagerly by default. When `toolSearch` is also set, skills load on demand via
116
- harness-injected `search_skills` / `load_skill` meta-tools (Mastra's `SkillSearchProcessor`). The gating is deliberate —
117
- consumers who opt into `toolSearch` get the same on-demand pattern across tools and skills.
121
+ Skills (`AgentConfig.skills`) load eagerly by default. Set `MastraAgentConfig.skillSearch` to switch to on-demand
122
+ loading via harness-injected `search_skills` / `load_skill` meta-tools (Mastra's `SkillSearchProcessor`):
123
+
124
+ ```ts
125
+ const agent = await manager.createAgent(projectRoot, {
126
+ instructions: '...',
127
+ skills: ['/path/to/skills'],
128
+ skillSearch: { topK: 5 }, // ranked, on-demand discovery; eager catalog stays out of the prompt
129
+ });
130
+ ```
131
+
132
+ `skillSearch.topK` is advisory; Mastra defaults to 5 if omitted. The same `skillSearch` field exists on
133
+ `ClaudeAgentConfig`, so cross-harness consumers can opt into on-demand skill discovery on either harness with the same
134
+ shape.
135
+
136
+ > **Breaking change (#468).** Pre-#468, on-demand skill search was implicitly gated on `skills && toolSearch`. That
137
+ > coupling has been removed: `skillSearch` is now the explicit opt-in, independent of `toolSearch`. Consumers who relied
138
+ > on the coupling must add `skillSearch: {}` to their config to keep the previous behavior.
118
139
 
119
140
  #### Operational handles
120
141
 
@@ -175,18 +196,19 @@ const harnessFactory = new MastraHarnessFactory({
175
196
  });
176
197
  ```
177
198
 
178
- When the timer fires, the harness aborts the suspended Mastra run, drops the `activeRuns` entry for the thread, and
179
- yields a synthetic terminal pair on the active `eventStream`:
199
+ When the timer fires, the harness aborts the suspended Mastra run via the per-`(agentId, threadId)`
200
+ `MastraApprovalCoordinator`, marks every emitted approval as auto-resolved, and yields a synthetic terminal pair on the
201
+ active `eventStream`:
180
202
 
181
203
  ```ts
182
204
  { type: 'error', error: Error('Tool approval timed out after 60000ms.'), code: 'tool-approval-timeout' }
183
205
  { type: 'finish', finishReason: 'error' }
184
206
  ```
185
207
 
186
- Subsequent `approveToolCall` / `declineToolCall` / `submitToolResult` calls for the timed-out tool reject with
187
- `no active run found for thread "<id>"`. The granularity is per thread — each suspended `ActiveRun` arms its own timer
188
- because Mastra suspends one tool approval at a time per thread; multi-tool turns get a fresh timer with each
189
- continuation. To observe the timeout, keep iterating the `eventStream` past the `tool-approval-request` event so the
208
+ Subsequent `approveToolCall` / `declineToolCall` / `submitToolResult` calls for the timed-out tool throw because the
209
+ coordinator has already auto-resolved them. The granularity is per `toolCallId` — each emitted approval arms its own
210
+ timer; the first one to fire tears down the whole turn (since Mastra can't continue the suspended run after timeout
211
+ anyway). To observe the timeout, keep iterating the `eventStream` past the `tool-approval-request` event so the
190
212
  synthetic terminal pair lands in your loop:
191
213
 
192
214
  ```ts
@@ -197,6 +219,20 @@ for await (const event of result.eventStream) {
197
219
  }
198
220
  ```
199
221
 
222
+ ### Tool-result redaction
223
+
224
+ Tool-result redaction is configured at the manager layer, not the factory. Pass a `hooksForAgent` callback to
225
+ `createAgentManager`; the SDK threads the resolved `AgentHooks` bag through `createAgent`'s `options.hooks`, and the
226
+ Mastra harness registers a `ToolResultRedactionProcessor` on the agent's `inputProcessors` chain whose
227
+ `processInputStep` walks new tool-result rows on the MessageList and replaces their content in place. The same
228
+ `ToolResultRedactor` works on the Claude harness. See the SDK README → "Tool-Result Redaction" for the full pattern
229
+ (including a `failClosed(...)` wrapper consumers can use to substitute a safe stub on throw).
230
+
231
+ The processor composes with existing harness-internal processors (`ToolSearchProcessor`, `SkillSearchProcessor`); no
232
+ consumer-supplied processor passthrough is needed. Throws propagate from `processInputStep` and the harness's approval
233
+ coordinator surfaces them as an `error` ChatEvent on the consumer's eventStream — see ARCHITECTURE.md for the full
234
+ contract.
235
+
200
236
  ### Using LLM Gateway Express
201
237
 
202
238
  ```ts
@@ -1,23 +1,8 @@
1
1
  /**
2
- * Buffered async generator wrapper used by `MastraStreamBroker` to deliver
3
- * `ChatEvent`s to a consumer's `ChatStreamResult.eventStream`.
4
- *
5
- * The buffer holds events pushed before the consumer starts iterating, so the
6
- * broker can begin populating the sink eagerly without coordinating with the
7
- * consumer's `for await` loop. Events pushed after iteration starts unblock
8
- * the awaiting consumer via an internal Promise waiter.
9
- *
10
- * Internal to `@salesforce/sfdx-agent-harness-mastra`; not part of the public
11
- * harness surface. Mirrors the same primitive in `@salesforce/sfdx-agent-harness-claude`
12
- * to keep each harness's broker self-contained without forcing a shared
13
- * cross-package surface.
2
+ * Re-export of the `GenSink` primitive from the SDK's harness-implementation
3
+ * subpath (`@salesforce/sfdx-agent-sdk/harness`). See that surface for the
4
+ * single-iteration invariant; issue #529 is the rationale for the
5
+ * cross-harness lift and for the subpath split (consumer applications can no
6
+ * longer import `GenSink` from the bare specifier — they have no use for it).
14
7
  */
15
- export declare class GenSink<T> {
16
- private readonly buffered;
17
- private ended;
18
- private waiter;
19
- push(event: T): void;
20
- end(): void;
21
- generator(): AsyncGenerator<T, void>;
22
- private wake;
23
- }
8
+ export { GenSink } from '@salesforce/sfdx-agent-sdk/harness';
package/dist/gen-sink.js CHANGED
@@ -3,54 +3,11 @@
3
3
  * See LICENSE.txt for license terms.
4
4
  */
5
5
  /**
6
- * Buffered async generator wrapper used by `MastraStreamBroker` to deliver
7
- * `ChatEvent`s to a consumer's `ChatStreamResult.eventStream`.
8
- *
9
- * The buffer holds events pushed before the consumer starts iterating, so the
10
- * broker can begin populating the sink eagerly without coordinating with the
11
- * consumer's `for await` loop. Events pushed after iteration starts unblock
12
- * the awaiting consumer via an internal Promise waiter.
13
- *
14
- * Internal to `@salesforce/sfdx-agent-harness-mastra`; not part of the public
15
- * harness surface. Mirrors the same primitive in `@salesforce/sfdx-agent-harness-claude`
16
- * to keep each harness's broker self-contained without forcing a shared
17
- * cross-package surface.
6
+ * Re-export of the `GenSink` primitive from the SDK's harness-implementation
7
+ * subpath (`@salesforce/sfdx-agent-sdk/harness`). See that surface for the
8
+ * single-iteration invariant; issue #529 is the rationale for the
9
+ * cross-harness lift and for the subpath split (consumer applications can no
10
+ * longer import `GenSink` from the bare specifier — they have no use for it).
18
11
  */
19
- export class GenSink {
20
- buffered = [];
21
- ended = false;
22
- waiter = null;
23
- push(event) {
24
- if (this.ended)
25
- return;
26
- this.buffered.push(event);
27
- this.wake();
28
- }
29
- end() {
30
- if (this.ended)
31
- return;
32
- this.ended = true;
33
- this.wake();
34
- }
35
- async *generator() {
36
- while (true) {
37
- if (this.buffered.length > 0) {
38
- yield this.buffered.shift();
39
- continue;
40
- }
41
- if (this.ended)
42
- return;
43
- await new Promise((resolve) => {
44
- this.waiter = { resolve };
45
- });
46
- }
47
- }
48
- wake() {
49
- const w = this.waiter;
50
- if (w) {
51
- this.waiter = null;
52
- w.resolve();
53
- }
54
- }
55
- }
12
+ export { GenSink } from '@salesforce/sfdx-agent-sdk/harness';
56
13
  //# sourceMappingURL=gen-sink.js.map
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { MastraHarnessFactory, type MastraHarnessFactoryConfig } from './mastra-harness-factory.js';
2
2
  export type { MastraAgentHarness } from './mastra-harness.js';
3
- export { type MastraAgentConfig, type MastraToolSearchConfig, type SkillSearchHandle, type ToolSearchHandle, } from './tool-search.js';
3
+ export { type MastraAgentConfig, type MastraSkillSearchConfig, type MastraToolSearchConfig, type SkillSearchHandle, type ToolSearchHandle, } from './tool-search.js';
4
4
  export type { MastraModelConfigFactory, ResolvedModelConfig } from './model-config-factory.js';
5
5
  export { DefaultMastraModelConfigFactory } from './default-model-config-factory.js';
6
6
  export { LLMGExpressMastraModelConfigFactory, type LLMGExpressConfig } from './llmg-express-model-config-factory.js';
@@ -0,0 +1,273 @@
1
+ import type { Agent } from '@mastra/core/agent';
2
+ import type { RequestContext } from '@mastra/core/request-context';
3
+ import type { ChunkType } from '@mastra/core/stream';
4
+ import { type ChatEvent, type McpToolAnnotations } from '@salesforce/sfdx-agent-sdk';
5
+ import { GenSink } from '@salesforce/sfdx-agent-sdk/harness';
6
+ import type { LogBus } from '@salesforce/agentic-common';
7
+ /**
8
+ * Output shape from `agent.stream()` / `agent.approveToolCall()` /
9
+ * `agent.declineToolCall()`. Captured as a structural type so the
10
+ * coordinator doesn't depend on Mastra's exact `StreamOutput` generic.
11
+ *
12
+ * Mastra's actual output also carries `textStream`; the coordinator doesn't
13
+ * need it (it derives text deltas from `fullStream` via the event adapter)
14
+ * and structural typing accepts the wider value at the call site.
15
+ */
16
+ export interface MastraStreamOutput {
17
+ runId: string;
18
+ fullStream: ReadableStream<ChunkType>;
19
+ }
20
+ /**
21
+ * Looks up MCP-server enrichment (`serverName` / `annotations`) for a given
22
+ * Mastra-namespaced tool name (`${serverName}_${toolName}`). Returns
23
+ * `undefined` for non-MCP tools (consumer-declared, workspace, built-in).
24
+ */
25
+ export type MastraMcpToolMetadataLookup = (namespacedToolName: string) => {
26
+ serverName: string;
27
+ annotations?: McpToolAnnotations;
28
+ } | undefined;
29
+ export interface MastraApprovalCoordinatorOptions {
30
+ agent: Agent;
31
+ threadId: string;
32
+ memory: {
33
+ thread: string;
34
+ resource: string;
35
+ };
36
+ requestContext: RequestContext;
37
+ toolApprovalTimeoutMs: number;
38
+ /**
39
+ * Approval-emission mode (#447). Defaults to `undefined` (no gating).
40
+ *
41
+ * - `undefined`: stream is run without `requireToolApproval`. Mastra
42
+ * suspends only on tools whose per-tool `requireApproval` is `true`
43
+ * (consumer tools post-2c are `false` so they don't suspend; the
44
+ * bridge's parker `execute` handles their lifecycle natively).
45
+ * - `'serial'`: stream runs with `requireToolApproval: true`. The
46
+ * coordinator emits one `tool-approval-request` per natural
47
+ * `tool-call-approval` chunk — Mastra serializes one approval per
48
+ * suspension.
49
+ * - `'batch'`: same as serial except `tool-approval-request` is
50
+ * front-loaded off the adapter's `tool-call` events so parallel
51
+ * `tool_use` batches all surface their approval-requests on the
52
+ * single per-turn stream.
53
+ *
54
+ * Note: in approval mode, consumer-executed tools still bypass the
55
+ * approval gate (per the `StreamOptions.requireToolApproval` JSDoc) —
56
+ * the coordinator silently calls `agent.approveToolCall(id)` for them
57
+ * without surfacing a `tool-approval-request` ChatEvent.
58
+ */
59
+ mode?: 'serial' | 'batch';
60
+ /**
61
+ * Set of tool names registered via `AgentConfig.tools`. The coordinator
62
+ * uses this to route `tool-call-approval` chunks: if the toolName is in
63
+ * the set, auto-approve silently (consumer tools bypass the gate); else
64
+ * surface a `tool-approval-request` ChatEvent and park.
65
+ *
66
+ * Empty set when no consumer tools are configured — every approval
67
+ * chunk surfaces to the consumer in approval mode.
68
+ */
69
+ consumerToolNames: ReadonlySet<string>;
70
+ /**
71
+ * Resolves MCP enrichment for a Mastra-namespaced tool name. Wired to
72
+ * `lookupMcpToolMetadata` in {@link MastraHarness} so the same lookup
73
+ * the legacy `createEventStream` used post-enriches `tool-call` /
74
+ * `tool-result` / `tool-approval-request` events.
75
+ */
76
+ lookupMcpToolMetadata?: MastraMcpToolMetadataLookup;
77
+ logBus?: LogBus;
78
+ }
79
+ /**
80
+ * Per-`(agentId, threadId)` coordinator that subsumes the pre-#529
81
+ * `MastraStreamBroker` + `activeRuns` pair into one object owning a single
82
+ * sink, single multi-source pump loop, and single set of teardown paths.
83
+ *
84
+ * Responsibilities:
85
+ * - Approval gating in `'serial'` / `'batch'` mode: emit
86
+ * `tool-approval-request` for non-consumer tools, drive
87
+ * `agent.approveToolCall(...)` / `declineToolCall(...)` when the
88
+ * consumer settles.
89
+ * - Consumer-tool plumbing: every `AgentConfig.tools` entry has
90
+ * `requireApproval: false` and a Promise-returning `execute` that parks
91
+ * on the coordinator's `parkConsumerToolCall(...)`. Mastra natively
92
+ * suspends the agentic loop while the Promise pends, so consumer tools
93
+ * don't go through `agent.approveToolCall(...)` in non-approval mode.
94
+ * In approval mode the coordinator auto-approves them silently so the
95
+ * bridge `execute` parker still runs on the resumed stream.
96
+ * - Multi-source pump: Mastra returns a fresh `fullStream` per resume
97
+ * call; the coordinator pumps each one into the same `initial` sink so
98
+ * the consumer iterates one stream per turn.
99
+ * - Snapshot-drain before each resume call (Mastra-specific: workflow
100
+ * snapshots persist asynchronously after `tool-call-approval`).
101
+ * - Approval-timeout / abort / dispose teardown — single helper drains
102
+ * timers, rejects pending parkers, marks emitted approvals as
103
+ * coordinator-settled, and pushes the synthetic terminal pair on the
104
+ * sink.
105
+ *
106
+ * Issue #529 contract: settle calls (`approveToolCall` / `declineToolCall` /
107
+ * `submitToolResult`) are control messages on the chat() turn's existing
108
+ * eventStream. There is exactly one sink (`initial`) per turn; post-resume
109
+ * events flow through the same sink the consumer is iterating from
110
+ * `chat()`. There are no per-tool sinks and no `lastGenSinkStale` flag.
111
+ */
112
+ export declare class MastraApprovalCoordinator {
113
+ private readonly agent;
114
+ private readonly memory;
115
+ private readonly requestContext;
116
+ private readonly toolApprovalTimeoutMs;
117
+ private readonly mode;
118
+ private readonly consumerToolNames;
119
+ private readonly lookupMcpToolMetadata?;
120
+ private readonly adapter;
121
+ /** Single per-turn sink. Issue #529 contract: one stream per turn. */
122
+ private readonly initial;
123
+ /** Buffered consumer decisions keyed by `toolCallId`. */
124
+ private readonly decisions;
125
+ /** ToolCallIds for which a `tool-approval-request` ChatEvent has been emitted. */
126
+ private readonly emittedApprovals;
127
+ /** ToolCallIds the consumer has settled. Rejects double-settle. */
128
+ private readonly consumerSettled;
129
+ /**
130
+ * ToolCallIds the coordinator auto-resolved (timeout / abort / dispose
131
+ * / consumer-tool auto-approve). A subsequent settle for these ids
132
+ * rejects with "already auto-resolved by the coordinator".
133
+ */
134
+ private readonly settledByCoordinator;
135
+ /** Per-toolCallId approval-timeout timers (armed when an approval is emitted). */
136
+ private readonly approvalTimers;
137
+ /** Args from the adapter's `tool-call` ChatEvent keyed by `toolCallId`. */
138
+ private readonly toolCallArgs;
139
+ /**
140
+ * Mastra's current `runId` after the latest stream/resume. Updated on
141
+ * each successful pump entry. Needed for the next
142
+ * `agent.approveToolCall(...)` / `declineToolCall(...)`.
143
+ */
144
+ private currentRunId;
145
+ /**
146
+ * The toolCallId Mastra is currently suspended on (after the natural
147
+ * `tool-call-approval` chunk). `null` when no suspension. The
148
+ * coordinator's pump loop reads this when the current `fullStream`
149
+ * closes to decide whether to drive a resume.
150
+ */
151
+ private currentSuspendId;
152
+ /** Consumer-tool: bound parkers, keyed by `toolCallId`. `submitToolResult` resolves these. */
153
+ private readonly pendingByToolCallId;
154
+ /**
155
+ * Consumer-tool: results submitted before the bridge `execute` handler
156
+ * parked. Keyed by `toolCallId`. Used when `submitToolResult` arrives
157
+ * during the gap between the `tool-call` ChatEvent and the bridge
158
+ * handler's actual invocation — the staged result resolves the next
159
+ * parker invocation for the same id.
160
+ */
161
+ private readonly preSubmittedByToolCallId;
162
+ /**
163
+ * Callbacks invoked exactly once during teardown (`abort` or `dispose`,
164
+ * whichever fires first, or natural pump exit).
165
+ */
166
+ private readonly teardownCallbacks;
167
+ /**
168
+ * Resolved by `recordDecision` (consumer settled) or `teardown`. The
169
+ * outer pump loop awaits this when it sees a suspension chunk before
170
+ * the consumer has settled. `'continued'` means a decision is now in
171
+ * `decisions`; `'done'` means the pump should exit (teardown fired).
172
+ */
173
+ private continuationResolver?;
174
+ private aborted;
175
+ private disposed;
176
+ private timedOut;
177
+ constructor(opts: MastraApprovalCoordinatorOptions);
178
+ initialSink(): GenSink<ChatEvent>;
179
+ onTeardown(callback: () => void): void;
180
+ private runTeardownCallbacks;
181
+ /**
182
+ * Begin pumping the initial Mastra `fullStream`. Stores `runId` and
183
+ * spawns the multi-source pump loop. The consumer iterates
184
+ * `initialSink().generator()` to observe events.
185
+ */
186
+ start(output: MastraStreamOutput): void;
187
+ abort(): void;
188
+ dispose(): void;
189
+ /**
190
+ * Single teardown path for abort / dispose / approval-timeout / pump-
191
+ * error. Drains timers, rejects pending consumer-tool parkers, marks
192
+ * every emitted approval as coordinator-settled, releases Mastra's
193
+ * suspended run, pushes the synthetic terminal pair on the sink, and
194
+ * fires teardown callbacks.
195
+ *
196
+ * The `reason` parameter only changes the sink's terminal shape:
197
+ * `'dispose'` pushes error-only (matches Claude's dispose contract);
198
+ * everything else pushes error+finish.
199
+ */
200
+ private teardown;
201
+ approve(toolCallId: string): void;
202
+ decline(toolCallId: string): void;
203
+ private recordDecision;
204
+ /**
205
+ * Parker callback the consumer-tool bridge `execute` handler invokes
206
+ * when Mastra calls the tool. Returns a Promise the consumer settles
207
+ * via {@link submitToolResult}.
208
+ *
209
+ * Mastra's bridge `execute` knows the `toolCallId` from its execution
210
+ * context, so binding is direct (no FIFO match by name). The
211
+ * pre-submit ladder handles the rare case where `submitToolResult`
212
+ * lands before the bridge handler's `execute` actually runs:
213
+ *
214
+ * 1. A pre-submitted result is staged for this `toolCallId` →
215
+ * resolve immediately.
216
+ * 2. Otherwise → bind under `toolCallId`; the eventual
217
+ * `submitToolResult` resolves us.
218
+ */
219
+ readonly parkConsumerToolCall: (toolCallId: string) => Promise<{
220
+ result: unknown;
221
+ isError?: boolean;
222
+ }>;
223
+ /**
224
+ * Settle a parked consumer-tool call. Issue #529 contract: returns
225
+ * `void`; post-resume events flow through the same `initial` sink the
226
+ * consumer is iterating from `chat()`. Mastra natively continues the
227
+ * agentic loop on the same `fullStream` once the parker Promise
228
+ * resolves (probe-validated, see #529 step 2c).
229
+ */
230
+ submitToolResult(toolCallId: string, result: unknown, isError?: boolean): void;
231
+ /**
232
+ * Multi-source pump loop. Drains the initial Mastra `fullStream` plus
233
+ * every continuation stream returned by `agent.approveToolCall(...)` /
234
+ * `declineToolCall(...)` into the same `initial` sink.
235
+ *
236
+ * Termination conditions:
237
+ * - Saw `finish` chunk on a stream and no further suspension awaits a
238
+ * decision.
239
+ * - Saw an `error` chunk (terminal under SDK contract).
240
+ * - `abort()` / `dispose()` / approval-timeout fired.
241
+ */
242
+ private runPumpLoop;
243
+ /**
244
+ * Pump one `ReadableStream<ChunkType>` into the sink until one of:
245
+ * - `'terminal'`: saw `finish` chunk; sink ended.
246
+ * - `'suspended'`: saw `tool-call-approval` chunk on a non-consumer
247
+ * tool (or auto-approved consumer tool didn't suspend on it). The
248
+ * stream may still be pumping — drain remaining chunks for the
249
+ * Mastra-snapshot-persistence guarantee, then return.
250
+ * - `'pump-error'`: stream threw; sink ended with error+finish.
251
+ */
252
+ private pumpUntilSuspendOrTerminal;
253
+ /**
254
+ * Drive a resume call and set up the next stream for the pump loop.
255
+ * Returns `'done'` if the resume rejected (turn over, error already
256
+ * pushed) or the new `fullStream` to pump next.
257
+ */
258
+ private driveResumeReturning;
259
+ private awaitContinuation;
260
+ /**
261
+ * Push an event onto the sink with MCP-server enrichment applied (when
262
+ * the toolName resolves to a known MCP catalog entry). Mirrors the
263
+ * pre-#529 `createEventStream`'s post-enrichment of `tool-call` /
264
+ * `tool-result` / `tool-approval-request`.
265
+ */
266
+ private routeEnriched;
267
+ private maybeEnrich;
268
+ private emitApprovalRequest;
269
+ private armApprovalTimer;
270
+ private cancelTimer;
271
+ private cancelAllTimers;
272
+ private handleApprovalTimeout;
273
+ }