@salesforce/sfdx-agent-harness-mastra 0.7.0 → 0.9.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/README.md +18 -0
- package/dist/mastra-harness.d.ts +154 -2
- package/dist/mastra-harness.js +574 -125
- package/dist/mcp-error-classifier.d.ts +34 -0
- package/dist/mcp-error-classifier.js +131 -0
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -24,6 +24,24 @@ const manager = await createAgentManager('/path/to/storage', new MastraHarnessFa
|
|
|
24
24
|
See the [`@salesforce/sfdx-agent-sdk` README](../sfdx-agent-sdk/README.md) for the full consumer-facing API. For
|
|
25
25
|
internal architecture, see [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
26
26
|
|
|
27
|
+
### Removing the storage directory on Windows
|
|
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:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { rm } from 'node:fs/promises';
|
|
36
|
+
|
|
37
|
+
await manager.shutdown();
|
|
38
|
+
await rm(storageRoot, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
39
|
+
```
|
|
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.
|
|
44
|
+
|
|
27
45
|
## Public API
|
|
28
46
|
|
|
29
47
|
### Core
|
package/dist/mastra-harness.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Agent } from '@mastra/core/agent';
|
|
|
2
2
|
import { SkillSearchProcessor, ToolSearchProcessor } from '@mastra/core/processors';
|
|
3
3
|
import type { ChunkType } from '@mastra/core/stream';
|
|
4
4
|
import { MCPClient } from '@mastra/mcp';
|
|
5
|
-
import { type AgentHarness, type WithAgentConfig, type ChatStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerInfo, type Message, type StreamOptions, type TelemetryEventCallback, type ToolResultInfo } from '@salesforce/sfdx-agent-sdk';
|
|
5
|
+
import { McpServerStatus, type AgentHarness, type WithAgentConfig, type ChatStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerErrorDetail, type McpServerInfo, type Message, type StreamOptions, type TelemetryEventCallback, type ToolResultInfo } from '@salesforce/sfdx-agent-sdk';
|
|
6
6
|
import type { MastraModelConfigFactory } from './model-config-factory.js';
|
|
7
7
|
import { type MastraAgentConfig, type SkillSearchHandle, type ToolSearchHandle } from './tool-search.js';
|
|
8
8
|
import type { LLMGatewayClient } from '@salesforce/llm-gateway-sdk';
|
|
@@ -20,18 +20,53 @@ import { Workspace } from '@mastra/core/workspace';
|
|
|
20
20
|
* if the consumer breaks out of the event stream early (common in
|
|
21
21
|
* approval UIs), we must finish reading the stream to ensure the
|
|
22
22
|
* snapshot is persisted before we attempt to resume.
|
|
23
|
+
*
|
|
24
|
+
* `disposeAbortListener` is invoked whenever the entry is replaced or
|
|
25
|
+
* removed so the per-run abort listener (installed by `stream()` against
|
|
26
|
+
* the caller's `streamOptions.abortSignal`) cannot fire on a run that is
|
|
27
|
+
* no longer the active one for the thread. Mirrors the SDK invariant
|
|
28
|
+
* "every chat turn ends with exactly one terminal `FinishEvent`" by
|
|
29
|
+
* ensuring we don't accidentally abort a continuation run that
|
|
30
|
+
* `approveToolCall` / `declineToolCall` has already authorized.
|
|
23
31
|
*/
|
|
24
32
|
interface ActiveRun {
|
|
25
33
|
runId: string;
|
|
26
34
|
fullStream: ReadableStream<ChunkType>;
|
|
35
|
+
disposeAbortListener?: () => void;
|
|
27
36
|
}
|
|
28
37
|
/**
|
|
29
38
|
* Per-server MCP state. Each enabled server gets its own MCPClient and
|
|
30
39
|
* independent discovery promise so readiness is tracked individually.
|
|
40
|
+
*
|
|
41
|
+
* `epoch` increments on every fresh discovery (initial create + each
|
|
42
|
+
* reconnect). Discovery callbacks captured against an earlier epoch drop their
|
|
43
|
+
* writes when a newer discovery has already started for the same server, so an
|
|
44
|
+
* orphaned in-flight `listToolsetsWithErrors()` cannot overwrite a fresher
|
|
45
|
+
* discovery's `tools` / `error` / `settled` state.
|
|
31
46
|
*/
|
|
32
47
|
interface McpServerState {
|
|
33
48
|
client: MCPClient;
|
|
49
|
+
/**
|
|
50
|
+
* Last `McpServerStatus` value emitted (or seeded to `Connecting` at
|
|
51
|
+
* construction). Updated synchronously by `emitMcpStatusChange()` so a
|
|
52
|
+
* follow-up `getMcpServerInfo()` call observes the world the most recent
|
|
53
|
+
* `mcp-server-status-changed` event described.
|
|
54
|
+
*/
|
|
55
|
+
currentStatus: McpServerStatus;
|
|
56
|
+
epoch: number;
|
|
57
|
+
/**
|
|
58
|
+
* Sanitized human-readable error message. V8 stack frames and file paths
|
|
59
|
+
* have been stripped — safe for UI banners and telemetry tags. The full
|
|
60
|
+
* error (with `.stack`) is preserved on the `mcp-server-discovery-failed`
|
|
61
|
+
* event's `error` field.
|
|
62
|
+
*/
|
|
34
63
|
error?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Structured failure projection. Mirrors the SDK's `McpServerErrorDetail`
|
|
66
|
+
* shape so consumers can route on category / code / retriable. Populated
|
|
67
|
+
* alongside `error`.
|
|
68
|
+
*/
|
|
69
|
+
errorDetail?: McpServerErrorDetail;
|
|
35
70
|
ready: Promise<void>;
|
|
36
71
|
settled: boolean;
|
|
37
72
|
tools: ToolsInput;
|
|
@@ -112,10 +147,18 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
112
147
|
* would mix process state.
|
|
113
148
|
*/
|
|
114
149
|
protected readonly activeAgents: Map<string, MastraAgentState>;
|
|
150
|
+
private readonly libsqlClient;
|
|
115
151
|
private readonly storage;
|
|
116
152
|
private readonly memory;
|
|
117
153
|
private readonly mastra;
|
|
118
154
|
private readonly buses;
|
|
155
|
+
/**
|
|
156
|
+
* Tracks the in-flight storage init so {@link shutdown} can settle it
|
|
157
|
+
* before closing the libsql client. Without this, Mastra's lazy storage
|
|
158
|
+
* init can fire after `client.close()` and surface as an unhandled
|
|
159
|
+
* `CLIENT_CLOSED` rejection (#429 follow-up).
|
|
160
|
+
*/
|
|
161
|
+
private readonly storageReady;
|
|
119
162
|
/**
|
|
120
163
|
* Mastra-specific extension surface, typed off `MastraAgentHarness`. The
|
|
121
164
|
* SDK never reads this; consumers reach through `manager.extensions.mastra`.
|
|
@@ -127,6 +170,9 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
127
170
|
* - MUST disconnect the mcpClient for every active agent before clearing state.
|
|
128
171
|
* - MUST clear the `activeAgents` map.
|
|
129
172
|
* - MUST shut down the global `this.mastra` orchestrator.
|
|
173
|
+
* - MUST close the libsql client so the underlying SQLite handle is released
|
|
174
|
+
* (Windows blocks fs.rm of the storage dir while the WAL/shm files are open;
|
|
175
|
+
* see forcedotcom/agentic-dx#429).
|
|
130
176
|
*/
|
|
131
177
|
shutdown(): Promise<void>;
|
|
132
178
|
onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
|
|
@@ -165,6 +211,18 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
165
211
|
*/
|
|
166
212
|
getAgentIds(): Promise<string[]>;
|
|
167
213
|
getMcpServerInfo(agentId: string): McpServerInfo[];
|
|
214
|
+
/**
|
|
215
|
+
* Eager implementation of {@link AgentHarness.reconnectMcpServer}: calls
|
|
216
|
+
* Mastra's `MCPClient.reconnectServer(name)` primitive, supersedes any
|
|
217
|
+
* in-flight discovery (replace-ready semantics — latest call wins), and
|
|
218
|
+
* awaits the new discovery before resolving.
|
|
219
|
+
*
|
|
220
|
+
* Programming errors (unknown agent / unknown server / disabled) reject.
|
|
221
|
+
* Transport / discovery failures are recorded on the per-server state
|
|
222
|
+
* (visible via {@link getMcpServerInfo}) and a `mcp-server-discovery-*`
|
|
223
|
+
* telemetry pair fires; the returned promise still resolves.
|
|
224
|
+
*/
|
|
225
|
+
reconnectMcpServer(agentId: string, serverName: string): Promise<void>;
|
|
168
226
|
/**
|
|
169
227
|
* @requirements
|
|
170
228
|
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
@@ -215,6 +273,14 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
215
273
|
* User turns are treated as authoritative input.
|
|
216
274
|
*/
|
|
217
275
|
compactThread(agentId: string, threadId: string): Promise<string>;
|
|
276
|
+
/**
|
|
277
|
+
* Best-effort wrapper around `destroyThread`: swallows the error so the
|
|
278
|
+
* caller's outer flow isn't aborted by a teardown blip, but logs the
|
|
279
|
+
* failure at warn level so a future regression in `destroyThread` is
|
|
280
|
+
* still observable. Used by `compactThread` on both the empty-source and
|
|
281
|
+
* non-empty paths.
|
|
282
|
+
*/
|
|
283
|
+
private destroyThreadBestEffort;
|
|
218
284
|
/**
|
|
219
285
|
* @requirements
|
|
220
286
|
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
@@ -223,6 +289,10 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
223
289
|
* - MUST return a `ChatStreamResult` with `textStream` and `eventStream`.
|
|
224
290
|
* - On pre-stream rejection from `agent.stream()`, MUST re-throw without emitting telemetry —
|
|
225
291
|
* the SDK's chat-session boundary catches the rejection and emits `chat-stream-error`.
|
|
292
|
+
* - MUST install a per-run abort listener on `options.abortSignal` so that aborting
|
|
293
|
+
* while a tool-approval is pending tears down the suspended Mastra run and clears
|
|
294
|
+
* the thread's `ActiveRun` entry. See ARCHITECTURE.md → "Tool Approval Flow → Abort
|
|
295
|
+
* during pending approval (#409)".
|
|
226
296
|
*/
|
|
227
297
|
stream(agentId: string, threadId: string, message: string, options?: StreamOptions): Promise<ChatStreamResult>;
|
|
228
298
|
/**
|
|
@@ -302,7 +372,6 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
302
372
|
* directly.
|
|
303
373
|
*/
|
|
304
374
|
private summarize;
|
|
305
|
-
private buildSummaryPrompt;
|
|
306
375
|
/**
|
|
307
376
|
* Two-pass summarization for transcripts above the char budget. The
|
|
308
377
|
* transcript is split into roughly-equal chunks each ≤ MAX_TRANSCRIPT_CHARS,
|
|
@@ -317,9 +386,92 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
317
386
|
*/
|
|
318
387
|
private splitIntoChunks;
|
|
319
388
|
private getAgentStateOrThrow;
|
|
389
|
+
/**
|
|
390
|
+
* Synchronously flips `serverState.currentStatus` (no-op when the next
|
|
391
|
+
* status equals the current) and returns a closure that, when invoked,
|
|
392
|
+
* emits the corresponding `mcp-server-status-changed` event. Returns
|
|
393
|
+
* `undefined` when the status didn't change — the caller has nothing
|
|
394
|
+
* to emit.
|
|
395
|
+
*
|
|
396
|
+
* Splitting the state flip from the emit lets callers update internal
|
|
397
|
+
* state synchronously alongside `settled` (so a synchronous listener
|
|
398
|
+
* reading `getMcpServerInfo()` from inside the discovery telemetry
|
|
399
|
+
* callback observes the post-settle world per issue #373) while still
|
|
400
|
+
* deferring the status-change emit until after the discovery telemetry
|
|
401
|
+
* has fired.
|
|
402
|
+
*
|
|
403
|
+
* The returned closure swallows `DISPOSED` errors the same way the
|
|
404
|
+
* discovery emit sites do: background discovery callbacks can resolve
|
|
405
|
+
* after `shutdown()` has disposed the buses.
|
|
406
|
+
*/
|
|
407
|
+
private prepareMcpStatusChange;
|
|
408
|
+
/**
|
|
409
|
+
* Runs MCP discovery for one server: emits the
|
|
410
|
+
* `mcp-server-discovery-started` / `…-completed` / `…-failed` telemetry
|
|
411
|
+
* triple and writes results into the corresponding entry in `serverStates`.
|
|
412
|
+
*
|
|
413
|
+
* The caller MUST `set()` the entry into `serverStates` either before
|
|
414
|
+
* calling this helper (the `createAgent` path constructs the entry
|
|
415
|
+
* synchronously after this returns the promise) or before any await
|
|
416
|
+
* boundary in the helper's chain — the chain reads back via
|
|
417
|
+
* `serverStates.get(serverName)!` to write `tools` / `error`. Both call
|
|
418
|
+
* sites today put the entry in place before the first microtask boundary.
|
|
419
|
+
*
|
|
420
|
+
* Errors observed during discovery are recorded on the entry's `error`
|
|
421
|
+
* field; the returned promise resolves either way (it only rejects when
|
|
422
|
+
* an aborted operation throws after we have already cleaned up). The
|
|
423
|
+
* `settled` flag is pinned BEFORE the terminal telemetry emit so a
|
|
424
|
+
* synchronous listener calling `getMcpServerInfo()` from inside the
|
|
425
|
+
* `mcp-server-discovery-{completed,failed}` callback observes the same
|
|
426
|
+
* world the event described — see issue #373.
|
|
427
|
+
*/
|
|
428
|
+
private runMcpDiscovery;
|
|
320
429
|
private toChatStreamResult;
|
|
430
|
+
/**
|
|
431
|
+
* Replace (or set) the active run for a thread, disposing any prior
|
|
432
|
+
* entry's per-run abort listener so the prior signal cannot fire on the
|
|
433
|
+
* new run.
|
|
434
|
+
*/
|
|
435
|
+
private replaceActiveRun;
|
|
436
|
+
/**
|
|
437
|
+
* Remove the active run for a thread, disposing its per-run abort listener
|
|
438
|
+
* if present. Safe to call when no entry exists.
|
|
439
|
+
*/
|
|
440
|
+
private removeActiveRun;
|
|
441
|
+
/**
|
|
442
|
+
* Wire `abortSignal.abort()` to tear down the suspended Mastra run for
|
|
443
|
+
* `threadId`. Returns a disposer that removes the listener; callers must
|
|
444
|
+
* invoke it whenever the entry is replaced or removed so a stale signal
|
|
445
|
+
* cannot abort an unrelated continuation run.
|
|
446
|
+
*
|
|
447
|
+
* Identity guard: the listener checks `agentState.activeRuns.get(threadId) === run`
|
|
448
|
+
* before calling `abortRunStream`, so a defensive race with another
|
|
449
|
+
* `stream()` call on the same thread cannot double-abort.
|
|
450
|
+
*
|
|
451
|
+
* Closes #409 — without this listener, aborting while a tool-approval is
|
|
452
|
+
* pending leaves Mastra's prepared run alive and `activeRuns[threadId]`
|
|
453
|
+
* pointing at a runId the consumer can no longer settle, so subsequent
|
|
454
|
+
* `approveToolCall` reaches Mastra and surfaces a confusing
|
|
455
|
+
* "could not find a suspended run" error instead of the SDK-level
|
|
456
|
+
* "no active run found" guard.
|
|
457
|
+
*/
|
|
458
|
+
private installPerRunAbortListener;
|
|
459
|
+
private handlePerRunAbort;
|
|
321
460
|
private getMemoryOrThrow;
|
|
322
461
|
private agentResourceId;
|
|
462
|
+
/**
|
|
463
|
+
* Build a `RequestContext` carrying the thread + resource ids under the
|
|
464
|
+
* reserved keys Mastra reads when input processors (e.g. `ToolSearchProcessor`,
|
|
465
|
+
* `SkillSearchProcessor`) call `requestContext.get(MASTRA_THREAD_ID_KEY)`.
|
|
466
|
+
*
|
|
467
|
+
* `memory: { thread, resource }` populates Mastra's memory layer but is NOT
|
|
468
|
+
* propagated into the request context, so processors that key per-thread
|
|
469
|
+
* state by `MASTRA_THREAD_ID_KEY` would otherwise see `undefined` and fall
|
|
470
|
+
* back to the `'default'` bucket — folding every consumer thread into one.
|
|
471
|
+
* Surfacing the ids here lets `ToolSearchHandle.clearState(threadId)`
|
|
472
|
+
* actually match what the processor stored.
|
|
473
|
+
*/
|
|
474
|
+
private buildRequestContext;
|
|
323
475
|
private createEventStream;
|
|
324
476
|
private readableStreamToAsyncGenerator;
|
|
325
477
|
/**
|