@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 +25 -0
- package/README.md +60 -24
- package/dist/gen-sink.d.ts +6 -21
- package/dist/gen-sink.js +6 -49
- package/dist/index.d.ts +1 -1
- package/dist/mastra-approval-coordinator.d.ts +273 -0
- package/dist/mastra-approval-coordinator.js +640 -0
- package/dist/mastra-harness.d.ts +90 -213
- package/dist/mastra-harness.js +370 -589
- package/dist/mastra-tool-mapper.d.ts +36 -22
- package/dist/mastra-tool-mapper.js +74 -30
- package/dist/mastra-tool-result-redaction-processor.d.ts +54 -0
- package/dist/mastra-tool-result-redaction-processor.js +136 -0
- package/dist/mcp-error-classifier.js +2 -2
- package/dist/tool-search.d.ts +87 -16
- package/dist/tool-search.js +58 -7
- package/package.json +17 -16
- package/dist/mastra-stream-broker.d.ts +0 -210
- package/dist/mastra-stream-broker.js +0 -563
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
|
|
66
|
-
|
|
|
67
|
-
| `MastraAgentHarness`
|
|
68
|
-
| `MastraAgentConfig`
|
|
69
|
-
| `MastraToolSearchConfig`
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
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.
|
|
109
|
-
|
|
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.
|
|
116
|
-
harness-injected `search_skills` / `load_skill` meta-tools (Mastra's `SkillSearchProcessor`)
|
|
117
|
-
|
|
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
|
|
179
|
-
yields a synthetic terminal pair on the
|
|
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
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
package/dist/gen-sink.d.ts
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* `
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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
|
+
}
|