@salesforce/sfdx-agent-harness-mastra 0.15.0 → 0.17.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,36 @@
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.17.0] - 2026-06-09
7
+
8
+ ### Fixes
9
+ - **harness-mastra**: drop inline WAL-pragma workaround now that LibSQLStore.close() ships stable ([#576](https://github.com/forcedotcom/agentic-dx/pull/576))
10
+
11
+ ### Chores
12
+ - **deps**: bump dependencies across packages @W-22695116@ ([#577](https://github.com/forcedotcom/agentic-dx/pull/577))
13
+ - **deps**: bump the mastra group across 1 directory with 5 updates ([#569](https://github.com/forcedotcom/agentic-dx/pull/569))
14
+ - **deps**: bump @libsql/client from 0.15.15 to 0.17.3 ([#570](https://github.com/forcedotcom/agentic-dx/pull/570))
15
+ - **deps-dev**: bump @types/node from 22.19.19 to 22.19.20 in the dev-dependencies group ([#568](https://github.com/forcedotcom/agentic-dx/pull/568))
16
+
17
+ ## [0.16.0] - 2026-06-09
18
+
19
+ ### Features
20
+ - **release**: auto-generate per-package CHANGELOG.md on publish ([#567](https://github.com/forcedotcom/agentic-dx/pull/567))
21
+ - unify tool-exposure policy under toolSearch.alwaysActive ([#566](https://github.com/forcedotcom/agentic-dx/pull/566))
22
+ - add ClaudeAgentConfig.skillSearch + decouple Mastra skillSearch from toolSearch ([#563](https://github.com/forcedotcom/agentic-dx/pull/563))
23
+ - **agent-sdk**: preserve MCP clients across updateAgentConfig ([#560](https://github.com/forcedotcom/agentic-dx/pull/560))
24
+ - **agent-sdk,harness-claude,harness-mastra**: first-class tool-result redaction ([#546](https://github.com/forcedotcom/agentic-dx/pull/546))
25
+
26
+ ### Fixes
27
+ - **harness-claude,harness-mastra**: align event/log payload shapes ([#562](https://github.com/forcedotcom/agentic-dx/pull/562))
28
+ - **harness-mastra**: honor MCPServerConfig.alwaysLoad when toolSearch is set ([#558](https://github.com/forcedotcom/agentic-dx/pull/558))
29
+
30
+ ### Chores
31
+ - **harness-claude,harness-mastra**: re-enable monorepoLintPack at four offender sites ([#561](https://github.com/forcedotcom/agentic-dx/pull/561))
32
+ - **deps-dev**: bump the eslint group across 1 directory with 2 updates ([#553](https://github.com/forcedotcom/agentic-dx/pull/553))
33
+ - **deps**: bump the mastra group across 1 directory with 3 updates ([#539](https://github.com/forcedotcom/agentic-dx/pull/539))
34
+ - **deps-dev**: bump the dev-dependencies group across 1 directory with 4 updates ([#536](https://github.com/forcedotcom/agentic-dx/pull/536))
35
+ - **deps-dev**: bump the vitest group across 1 directory with 3 updates ([#552](https://github.com/forcedotcom/agentic-dx/pull/552))
36
+
package/README.md CHANGED
@@ -26,10 +26,10 @@ internal architecture, see [ARCHITECTURE.md](ARCHITECTURE.md).
26
26
 
27
27
  ### Removing the storage directory on Windows
28
28
 
29
- `manager.shutdown()` closes the libsql client (forcedotcom/agentic-dx#429), but the underlying SQLite/WAL handle release
30
- on Windows is asynchronous at the OS level. After `shutdown()` returns, Windows can take anywhere from ~60 ms to several
31
- seconds to actually release the file lock — so a naïve `fs.rm(storageRoot)` immediately afterward will fail with
32
- `EBUSY`. Pass `maxRetries` and `retryDelay` so Node retries until the OS lets go:
29
+ `manager.shutdown()` checkpoints the WAL and closes the libsql client (forcedotcom/agentic-dx#429,
30
+ mastra-ai/mastra#17285), but the underlying SQLite handle release on Windows is asynchronous at the OS level. After
31
+ `shutdown()` returns, Windows can briefly hold the file lock — so a naïve `fs.rm(storageRoot)` immediately afterward can
32
+ fail with `EBUSY`. Pass `maxRetries` and `retryDelay` so Node retries until the OS lets go:
33
33
 
34
34
  ```ts
35
35
  import { rm } from 'node:fs/promises';
@@ -38,9 +38,7 @@ await manager.shutdown();
38
38
  await rm(storageRoot, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
39
39
  ```
40
40
 
41
- Linux and macOS release the handle eagerly; the retry options are no-ops on those platforms. Tracking upstream at
42
- [mastra-ai/mastra#17285](https://github.com/mastra-ai/mastra/issues/17285) — when `LibSQLStore` exposes a public
43
- `close()` that includes a WAL checkpoint, this delay should shrink or disappear.
41
+ Linux and macOS release the handle eagerly; the retry options are no-ops on those platforms.
44
42
 
45
43
  ## Public API
46
44
 
@@ -62,13 +60,14 @@ These types let consumers reach Mastra-specific features through the SDK's typed
62
60
  `MastraAgentHarness` from `MastraHarnessFactory.create()`, so consumers usually don't reference these types explicitly —
63
61
  they show up via inference at the call site.
64
62
 
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). |
63
+ | Export | Kind | Description |
64
+ | ------------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
65
+ | `MastraAgentHarness` | type | Branded `AgentHarness` subtype; carries the Mastra-specific `extensions` namespace and the inferred config type. |
66
+ | `MastraAgentConfig` | type | `AgentConfig` extended with Mastra-only fields (`toolSearch`, `skillSearch`). Inferred at `manager.createAgent` automatically — annotation rarely needed. |
67
+ | `MastraToolSearchConfig` | type | Shape of `MastraAgentConfig.toolSearch` (`pool`, `alwaysActive`, `topK`, `ttl`). |
68
+ | `MastraSkillSearchConfig` | type | Shape of `MastraAgentConfig.skillSearch` (`topK`). Opts into Mastra's `SkillSearchProcessor`. |
69
+ | `ToolSearchHandle` | type | Stable interface for the tool-search runtime: `id`, `getStateStats`, `clearState`, `clearAllState`, `cleanupNow`. Wraps Mastra's internal processor. |
70
+ | `SkillSearchHandle` | type | Stable interface for the skill-search runtime: `id` (Mastra's processor has no other public methods today; the harness owns lifecycle). |
72
71
 
73
72
  The Mastra-specific accessors live on `manager.extensions.mastra` (`getToolSearchProcessor(agentId)`,
74
73
  `getSkillSearchProcessor(agentId)`, `formatMcpToolName(serverName, toolName)`) and require no separate import.
@@ -90,12 +89,13 @@ const agent = await manager.createAgent(projectRoot, {
90
89
  toolSearch: {
91
90
  pool: '*', // or an explicit name list
92
91
  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'),
92
+ // Every tool from a server (post-discovery expansion).
93
+ { serverName: 'sfdx' },
94
+ // Exactly one tool on a specific server.
95
+ { serverName: 'other', toolName: 'lookup_account' },
96
+ // Any tool by this name, regardless of source — built-ins, workspace
97
+ // tools, consumer-declared tools, AND any MCP server's tool surface.
98
+ { toolName: 'resume_tool_operation' },
99
99
  ],
100
100
  topK: 5, // advisory; defaults to Mastra's 5
101
101
  ttl: 3_600_000, // ms; advisory; defaults to Mastra's 1h
@@ -105,16 +105,35 @@ const agent = await manager.createAgent(projectRoot, {
105
105
 
106
106
  When `toolSearch` is set, `createAgent` blocks on MCP discovery so MCP tools enter the searchable pool. Without
107
107
  `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.
108
+ model; everything else lives in the searchable pool.
109
+
110
+ Each `alwaysActive` entry is an `AlwaysActiveEntry` (re-exported from `@salesforce/sfdx-agent-sdk/harness` and shared
111
+ with `ClaudeAgentConfig.toolSearch.alwaysActive`). At least one of `serverName` or `toolName` must be present — `{}`
112
+ rejects at config time. Unknown `pool` names are dropped; unmatched `{ toolName }`-only entries are kept for late MCP
113
+ discovery.
110
114
 
111
115
  `MastraToolSearchConfig.topK` and `ttl` are advisory — Mastra applies its own defaults if omitted.
112
116
 
113
117
  #### Skill search
114
118
 
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.
119
+ Skills (`AgentConfig.skills`) load eagerly by default. Set `MastraAgentConfig.skillSearch` to switch to on-demand
120
+ loading via harness-injected `search_skills` / `load_skill` meta-tools (Mastra's `SkillSearchProcessor`):
121
+
122
+ ```ts
123
+ const agent = await manager.createAgent(projectRoot, {
124
+ instructions: '...',
125
+ skills: ['/path/to/skills'],
126
+ skillSearch: { topK: 5 }, // ranked, on-demand discovery; eager catalog stays out of the prompt
127
+ });
128
+ ```
129
+
130
+ `skillSearch.topK` is advisory; Mastra defaults to 5 if omitted. The same `skillSearch` field exists on
131
+ `ClaudeAgentConfig`, so cross-harness consumers can opt into on-demand skill discovery on either harness with the same
132
+ shape.
133
+
134
+ > **Breaking change (#468).** Pre-#468, on-demand skill search was implicitly gated on `skills && toolSearch`. That
135
+ > coupling has been removed: `skillSearch` is now the explicit opt-in, independent of `toolSearch`. Consumers who relied
136
+ > on the coupling must add `skillSearch: {}` to their config to keep the previous behavior.
118
137
 
119
138
  #### Operational handles
120
139
 
@@ -198,6 +217,20 @@ for await (const event of result.eventStream) {
198
217
  }
199
218
  ```
200
219
 
220
+ ### Tool-result redaction
221
+
222
+ Tool-result redaction is configured at the manager layer, not the factory. Pass a `hooksForAgent` callback to
223
+ `createAgentManager`; the SDK threads the resolved `AgentHooks` bag through `createAgent`'s `options.hooks`, and the
224
+ Mastra harness registers a `ToolResultRedactionProcessor` on the agent's `inputProcessors` chain whose
225
+ `processInputStep` walks new tool-result rows on the MessageList and replaces their content in place. The same
226
+ `ToolResultRedactor` works on the Claude harness. See the SDK README → "Tool-Result Redaction" for the full pattern
227
+ (including a `failClosed(...)` wrapper consumers can use to substitute a safe stub on throw).
228
+
229
+ The processor composes with existing harness-internal processors (`ToolSearchProcessor`, `SkillSearchProcessor`); no
230
+ consumer-supplied processor passthrough is needed. Throws propagate from `processInputStep` and the harness's approval
231
+ coordinator surfaces them as an `error` ChatEvent on the consumer's eventStream — see ARCHITECTURE.md for the full
232
+ contract.
233
+
201
234
  ### Using LLM Gateway Express
202
235
 
203
236
  ```ts
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';
@@ -2,6 +2,7 @@ import { Agent } from '@mastra/core/agent';
2
2
  import { SkillSearchProcessor, ToolSearchProcessor } from '@mastra/core/processors';
3
3
  import { MCPClient } from '@mastra/mcp';
4
4
  import { McpServerStatus, type AgentHarness, type WithAgentConfig, type ChatStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerErrorDetail, type McpServerInfo, type Message, type MessagePart, type StreamOptions, type TelemetryEventCallback, type ToolResultInfo } from '@salesforce/sfdx-agent-sdk';
5
+ import { type AgentHooks } from '@salesforce/sfdx-agent-sdk/harness';
5
6
  import { MastraApprovalCoordinator } from './mastra-approval-coordinator.js';
6
7
  import type { MastraModelConfigFactory } from './model-config-factory.js';
7
8
  import { type MastraAgentConfig, type SkillSearchHandle, type ToolSearchHandle } from './tool-search.js';
@@ -44,6 +45,12 @@ interface McpServerState {
44
45
  errorDetail?: McpServerErrorDetail;
45
46
  ready: Promise<void>;
46
47
  settled: boolean;
48
+ /**
49
+ * Discovered tools for this server, keyed by the harness's namespaced form
50
+ * `${serverName}_${toolName}`. The namespacing is applied at discovery time
51
+ * by `runMcpDiscovery` and is the contract every reader of this map relies
52
+ * on (tool merging, alwaysActive expansion, MCP-tool metadata lookup).
53
+ */
47
54
  tools: ToolsInput;
48
55
  }
49
56
  /**
@@ -51,6 +58,13 @@ interface McpServerState {
51
58
  * generic API never sees them.
52
59
  */
53
60
  interface MastraAgentState {
61
+ /**
62
+ * Project folder this agent operates within. Captured at `createAgent`
63
+ * time and reused by `updateAgent` to rebuild the `Workspace`
64
+ * (`projectRoot` is immutable post-create — `Agent.updateAgentConfig`
65
+ * does not allow changing it).
66
+ */
67
+ projectRoot: string;
54
68
  /** Mastra Agent instance. */
55
69
  agent: Agent;
56
70
  /**
@@ -99,6 +113,15 @@ interface MastraAgentState {
99
113
  toolSearchProcessor?: ToolSearchProcessor;
100
114
  /** Skill-search processor instance, when both `skills` and `toolSearch` are configured. */
101
115
  skillSearchProcessor?: SkillSearchProcessor;
116
+ /**
117
+ * Per-agent hook bag the SDK passed at `createAgent` time. The SDK
118
+ * resolves the bag once per install / restore / `updateAgentConfig` via
119
+ * `createAgentManager`'s `hooksForAgent` callback. The harness reads
120
+ * `hooks.onToolResult` at processor construction time and ignores
121
+ * unknown hook fields (forward-compat). Defaults to `{}` when the
122
+ * consumer didn't pass a `hooksForAgent`.
123
+ */
124
+ hooks: AgentHooks;
102
125
  }
103
126
  /**
104
127
  * Branded subtype consumers parameterize over to reach Mastra-specific
@@ -135,9 +158,9 @@ export type MastraAgentHarness = WithAgentConfig<AgentHarness & {
135
158
  getToolSearchProcessor(agentId: string): ToolSearchHandle | undefined;
136
159
  /**
137
160
  * Returns a {@link SkillSearchHandle} for an agent, or `undefined`
138
- * if the agent doesn't have both `skills` and `toolSearch` configured
139
- * (skill search is only enabled when both are set; without `toolSearch`,
140
- * Mastra falls back to eager skill loading).
161
+ * if the agent doesn't have both `skills` and `skillSearch` configured
162
+ * (skill search is only enabled when both are set; without `skillSearch`,
163
+ * Mastra falls back to eager skill loading via `SkillsProcessor`).
141
164
  */
142
165
  getSkillSearchProcessor(agentId: string): SkillSearchHandle | undefined;
143
166
  /**
@@ -184,18 +207,10 @@ export declare class MastraHarness implements MastraAgentHarness {
184
207
  * would mix process state.
185
208
  */
186
209
  protected readonly activeAgents: Map<string, MastraAgentState>;
187
- private readonly libsqlClient;
188
210
  private readonly storage;
189
211
  private readonly memory;
190
212
  private readonly mastra;
191
213
  private readonly buses;
192
- /**
193
- * Tracks the in-flight storage init so {@link shutdown} can settle it
194
- * before closing the libsql client. Without this, Mastra's lazy storage
195
- * init can fire after `client.close()` and surface as an unhandled
196
- * `CLIENT_CLOSED` rejection (#429 follow-up).
197
- */
198
- private readonly storageReady;
199
214
  /**
200
215
  * Mastra-specific extension surface, typed off `MastraAgentHarness`. The
201
216
  * SDK never reads this; consumers reach through `manager.extensions.mastra`.
@@ -210,10 +225,10 @@ export declare class MastraHarness implements MastraAgentHarness {
210
225
  * @requirements
211
226
  * - MUST disconnect the mcpClient for every active agent before clearing state.
212
227
  * - MUST clear the `activeAgents` map.
213
- * - MUST shut down the global `this.mastra` orchestrator.
214
- * - MUST close the libsql client so the underlying SQLite handle is released
215
- * (Windows blocks fs.rm of the storage dir while the WAL/shm files are open;
216
- * see forcedotcom/agentic-dx#429).
228
+ * - MUST shut down the global `this.mastra` orchestrator. Mastra.shutdown() awaits
229
+ * `storage.close()`, which checkpoints the WAL and unlinks the -wal/-shm sidecars
230
+ * so a subsequent `fs.rm` of the storage dir succeeds on Windows
231
+ * (forcedotcom/agentic-dx#429, mastra-ai/mastra#17285).
217
232
  */
218
233
  shutdown(): Promise<void>;
219
234
  onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
@@ -238,6 +253,7 @@ export declare class MastraHarness implements MastraAgentHarness {
238
253
  */
239
254
  createAgent(agentId: string, projectRoot: string, llmGatewayClient: LLMGatewayClient, config?: HarnessAgentConfig, options?: {
240
255
  abortSignal?: AbortSignal;
256
+ hooks?: AgentHooks;
241
257
  }): Promise<void>;
242
258
  /**
243
259
  * @requirements
@@ -264,6 +280,28 @@ export declare class MastraHarness implements MastraAgentHarness {
264
280
  * telemetry pair fires; the returned promise still resolves.
265
281
  */
266
282
  reconnectMcpServer(agentId: string, serverName: string): Promise<void>;
283
+ /**
284
+ * Implementation of {@link AgentHarness.updateAgent}. Diffs the next config
285
+ * against the agent's currently-applied state, preserves any MCP client
286
+ * whose `MCPServerConfig` is structurally equal to the previously-applied
287
+ * one, and rebuilds the Mastra `Agent` so non-MCP changes (instructions,
288
+ * model, tools, skills, rules, toolSearch) take effect.
289
+ *
290
+ * The Mastra `Agent`'s `tools: () => …` closure captures `state.mcpServers`
291
+ * by reference, so the new `Agent` constructed here observes the same Map
292
+ * the diff just mutated — preserved clients keep their discovered tool
293
+ * catalogs without a re-listing.
294
+ *
295
+ * State writes are per-server-incremental: each disconnect/connect mutates
296
+ * `state.mcpServers` synchronously after the corresponding harness work
297
+ * finishes. A re-entrant `updateAgent` call (e.g. SDK rollback against
298
+ * `previousConfig`) sees the harness's current truth and reverts only the
299
+ * actual deltas.
300
+ */
301
+ updateAgent(agentId: string, llmGatewayClient: LLMGatewayClient, config?: HarnessAgentConfig, options?: {
302
+ abortSignal?: AbortSignal;
303
+ hooks?: AgentHooks;
304
+ }): Promise<void>;
267
305
  /**
268
306
  * @requirements
269
307
  * - MUST throw an error if the specified `agentId` is not found in `activeAgents`.