@salesforce/sfdx-agent-harness-mastra 0.58.0 → 0.60.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 CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to `@salesforce/sfdx-agent-harness-mastra` are documented in this file.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.60.0] - 2026-09-01
7
+
8
+ ### Features
9
+ - **agent-sdk**: add policy-aware tool declines @W-24048333@ ([#776](https://github.com/forcedotcom/agentic-dx/pull/776))
10
+
11
+ ### Chores
12
+ - **deps-dev**: bump typescript-eslint from 8.67.0 to 8.68.0 in the dev-dependencies group across 1 directory ([#777](https://github.com/forcedotcom/agentic-dx/pull/777))
13
+ - **deps-dev**: bump eslint from 10.8.1 to 10.9.1 in the eslint group ([#778](https://github.com/forcedotcom/agentic-dx/pull/778))
14
+
15
+ ## [0.59.0] - 2026-08-28
16
+
17
+ ### Fixes
18
+ - **harness-mastra**: persist in-flight transcript on Stop/cancel via @mastra/core ^1.62 bump @W-24001006@ ([#772](https://github.com/forcedotcom/agentic-dx/pull/772))
19
+
6
20
  ## [0.58.0] - 2026-08-28
7
21
 
8
22
  _No changes — released alongside dependent packages._
package/README.md CHANGED
@@ -249,6 +249,11 @@ Consumer-executed tools (`AgentConfig.tools`) always bypass the gate — their e
249
249
  via `submitToolResult`. Set `StreamOptions.batchApprovals: true` to surface parallel `require-approval` requests on one
250
250
  stream for a batch-approval UI.
251
251
 
252
+ With `@mastra/core` 1.62.0, an organization-policy decline is passed through `agent.declineToolCall({ reason })`. Mastra
253
+ emits one native `tool-output-denied` chunk; the adapter maps it to one `tool-result(isError=true)` carrying the same
254
+ exact message the model sees. Automatic policy denies use `Denied by tool policy.`; reasonless consumer declines retain
255
+ Mastra's `Tool call was not approved by the user` default.
256
+
252
257
  ### Tool approval timeout
253
258
 
254
259
  When the approval gate is engaged and a tool resolves to `require-approval`, the harness suspends the workflow on each
@@ -1,7 +1,7 @@
1
1
  import type { Agent } from '@mastra/core/agent';
2
2
  import type { RequestContext } from '@mastra/core/request-context';
3
3
  import type { ChunkType } from '@mastra/core/stream';
4
- import { type ChatEvent, type Decision as PolicyDecision, type McpToolAnnotations, type ToolInvocation, type WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
4
+ import { type ChatEvent, type Decision as PolicyDecision, type McpToolAnnotations, type ToolDeclineReason, type ToolInvocation, type WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
5
5
  import { GenSink } from '@salesforce/sfdx-agent-sdk/harness';
6
6
  import type { Clock, LogBus } from '@salesforce/agentic-common';
7
7
  /**
@@ -125,8 +125,8 @@ export interface MastraApprovalCoordinatorOptions {
125
125
  * Responsibilities:
126
126
  * - Policy-driven approval gating (Phase 2a): for each non-consumer tool,
127
127
  * consult `lookupPolicy` — `allow` silently approves, `deny` silently
128
- * declines (the coordinator synthesizes a `tool-result(isError=true)` and
129
- * drives `agent.declineToolCall` so the model sees the failed call),
128
+ * declines through `agent.declineToolCall` so Mastra emits one native
129
+ * `tool-output-denied` result for the model and event adapter,
130
130
  * `require-approval` emits a `tool-approval-request` and drives
131
131
  * `agent.approveToolCall(...)` / `declineToolCall(...)` when the consumer
132
132
  * settles. `batchApprovals` controls whether parallel `require-approval`
@@ -337,7 +337,7 @@ export declare class MastraApprovalCoordinator {
337
337
  */
338
338
  private teardown;
339
339
  approve(toolCallId: string): void;
340
- decline(toolCallId: string): void;
340
+ decline(toolCallId: string, reason?: ToolDeclineReason): void;
341
341
  private recordDecision;
342
342
  /**
343
343
  * Parker callback the consumer-tool bridge `execute` handler invokes
@@ -3,7 +3,7 @@
3
3
  * See LICENSE.txt for license terms.
4
4
  */
5
5
  import { AgentSDKError, AgentSDKErrorType, } from '@salesforce/sfdx-agent-sdk';
6
- import { GenSink } from '@salesforce/sfdx-agent-sdk/harness';
6
+ import { GenSink, resolveToolDeclineModelMessage } from '@salesforce/sfdx-agent-sdk/harness';
7
7
  import { MastraEventAdapter } from './mastra-event-adapter.js';
8
8
  import { SKILL_BRIDGE_SERVER_ID } from './skill-bridge.js';
9
9
  /**
@@ -22,13 +22,7 @@ function pickIdWithDecision(pendingIds, decisions) {
22
22
  }
23
23
  return undefined;
24
24
  }
25
- /**
26
- * Result text the coordinator stamps on the synthesized `tool-result` for a
27
- * policy-denied tool call. Matches the text Mastra folds into the model's
28
- * context on decline ("Tool call was not approved by the user") so the
29
- * consumer-facing event and the model's transcript stay consistent.
30
- */
31
- const POLICY_DENIED_RESULT_TEXT = 'Tool call was not approved by the user';
25
+ const POLICY_DENIED_RESULT_TEXT = 'Denied by tool policy.';
32
26
  /**
33
27
  * Per-`(agentId, threadId)` coordinator that subsumes the pre-#529
34
28
  * `MastraStreamBroker` + `activeRuns` pair into one object owning a single
@@ -37,8 +31,8 @@ const POLICY_DENIED_RESULT_TEXT = 'Tool call was not approved by the user';
37
31
  * Responsibilities:
38
32
  * - Policy-driven approval gating (Phase 2a): for each non-consumer tool,
39
33
  * consult `lookupPolicy` — `allow` silently approves, `deny` silently
40
- * declines (the coordinator synthesizes a `tool-result(isError=true)` and
41
- * drives `agent.declineToolCall` so the model sees the failed call),
34
+ * declines through `agent.declineToolCall` so Mastra emits one native
35
+ * `tool-output-denied` result for the model and event adapter,
42
36
  * `require-approval` emits a `tool-approval-request` and drives
43
37
  * `agent.approveToolCall(...)` / `declineToolCall(...)` when the consumer
44
38
  * settles. `batchApprovals` controls whether parallel `require-approval`
@@ -353,8 +347,11 @@ export class MastraApprovalCoordinator {
353
347
  approve(toolCallId) {
354
348
  this.recordDecision('approve', toolCallId, { kind: 'approve' });
355
349
  }
356
- decline(toolCallId) {
357
- this.recordDecision('decline', toolCallId, { kind: 'decline' });
350
+ decline(toolCallId, reason) {
351
+ this.recordDecision('decline', toolCallId, {
352
+ kind: 'decline',
353
+ ...(reason ? { modelMessage: resolveToolDeclineModelMessage(reason) } : {}),
354
+ });
358
355
  }
359
356
  recordDecision(method, toolCallId, decision) {
360
357
  // Issue #589: idempotent settle. A tool call the coordinator
@@ -618,36 +615,12 @@ export class MastraApprovalCoordinator {
618
615
  this.decisions.set(tc.toolCallId, { kind: 'approve' });
619
616
  }
620
617
  else if (decision === 'deny') {
621
- // Silent decline — no `tool-approval-request` surfaces.
622
- // The pump drives `agent.declineToolCall` so Mastra folds
623
- // the denial into the model's context (persisted as the
624
- // `role: 'tool'` message "Tool call was not approved by the
625
- // user"), letting the model recover on the resume stream.
626
- //
627
- // We synthesize the consumer-facing `tool-result(isError=true)`
628
- // ourselves rather than relying on Mastra to emit one:
629
- // @mastra/core 1.49 no longer emits a standalone
630
- // `tool-result` chunk on the decline-resume `fullStream`
631
- // (the denial text lives only inside the persisted
632
- // `messages.all` payload embedded in `step-finish`), so the
633
- // old "re-stamp Mastra's native result" path never fired and
634
- // the SDK `Decision` contract "a denied tool surfaces as
635
- // `tool-result(isError=true)`" was silently broken. Routing
636
- // it here — immediately after the already-routed `tool-call`
637
- // and before the decline resume — keeps the natural
638
- // tool-call → tool-result ordering the SDK telemetry deriver
639
- // matches on. The un-emitted approval is marked
640
- // coordinator-settled so a (spurious) consumer
641
- // `approveToolCall` for this id is a no-op rather than a
642
- // TOOL_CALL_NOT_FOUND throw.
618
+ // Mastra emits the native `tool-output-denied` chunk
619
+ // carrying this same reason on the resumed stream.
643
620
  this.settledByCoordinator.add(tc.toolCallId);
644
- this.decisions.set(tc.toolCallId, { kind: 'decline' });
645
- this.routeEnriched({
646
- type: 'tool-result',
647
- toolCallId: tc.toolCallId,
648
- toolName: tc.toolName,
649
- result: POLICY_DENIED_RESULT_TEXT,
650
- isError: true,
621
+ this.decisions.set(tc.toolCallId, {
622
+ kind: 'decline',
623
+ modelMessage: POLICY_DENIED_RESULT_TEXT,
651
624
  });
652
625
  }
653
626
  else if (!this.batchApprovals && !this.emittedApprovals.has(tc.toolCallId)) {
@@ -710,6 +683,7 @@ export class MastraApprovalCoordinator {
710
683
  toolCallId,
711
684
  memory: this.memory,
712
685
  requestContext: this.requestContext,
686
+ ...(decision.modelMessage ? { reason: decision.modelMessage } : {}),
713
687
  });
714
688
  // Post-await guard: abort/dispose may have fired during the
715
689
  // round-trip. Release the new run if so.
@@ -33,6 +33,7 @@ import type { ChatEvent } from '@salesforce/sfdx-agent-sdk';
33
33
  * | `tool-call-approval` | `tool-approval-request` | `{ toolCall: { toolCallId, toolName, args } }` — stream suspends |
34
34
  * | `tool-result` | `tool-result` | `{ toolCallId, toolName, result, isError: false }` |
35
35
  * | `tool-error` | `tool-result` | `{ toolCallId, toolName, result: error, isError: true}`|
36
+ * | `tool-output-denied` | `tool-result` | `{ toolCallId, toolName, result: reason, isError: true}`|
36
37
  * | `tool-output` | `tool-progress` | `{ toolCallId, toolName, output }` |
37
38
  * | `tool-call-delta` | `tool-call-delta` | `{ toolCallId, toolName?, argsTextDelta }` |
38
39
  * | `step-start` | `step-start` | `{ stepIndex }` from step metadata |
@@ -35,6 +35,7 @@ import { isNetworkStreamError } from './stream-errors.js';
35
35
  * | `tool-call-approval` | `tool-approval-request` | `{ toolCall: { toolCallId, toolName, args } }` — stream suspends |
36
36
  * | `tool-result` | `tool-result` | `{ toolCallId, toolName, result, isError: false }` |
37
37
  * | `tool-error` | `tool-result` | `{ toolCallId, toolName, result: error, isError: true}`|
38
+ * | `tool-output-denied` | `tool-result` | `{ toolCallId, toolName, result: reason, isError: true}`|
38
39
  * | `tool-output` | `tool-progress` | `{ toolCallId, toolName, output }` |
39
40
  * | `tool-call-delta` | `tool-call-delta` | `{ toolCallId, toolName?, argsTextDelta }` |
40
41
  * | `step-start` | `step-start` | `{ stepIndex }` from step metadata |
@@ -117,6 +118,14 @@ export class MastraEventAdapter {
117
118
  error,
118
119
  };
119
120
  }
121
+ case 'tool-output-denied':
122
+ return {
123
+ type: 'tool-result',
124
+ toolCallId: chunk.payload.toolCallId,
125
+ toolName: chunk.payload.toolName,
126
+ result: chunk.payload.approval.reason ?? 'Tool call was not approved by the user',
127
+ isError: true,
128
+ };
120
129
  case 'step-start':
121
130
  return { type: 'step-start', stepIndex: this.stepIndex++ };
122
131
  case 'step-finish':
@@ -1,7 +1,7 @@
1
1
  import { Agent } from '@mastra/core/agent';
2
2
  import { SkillSearchProcessor, ToolSearchProcessor } from '@mastra/core/processors';
3
3
  import { MCPClient } from '@mastra/mcp';
4
- import { McpServerStatus, type AgentHarness, type WithAgentConfig, type ChatStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerErrorDetail, type McpServerInfo, type Message, type MessagePart, type McpAuthProviders, type ModelConnectivityInfo, type SessionContext, type StreamOptions, type TelemetryEventCallback, type ToolResultInfo, type WireCommunicationEventCallback } from '@salesforce/sfdx-agent-sdk';
4
+ import { McpServerStatus, type AgentHarness, type WithAgentConfig, type ChatStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerErrorDetail, type McpServerInfo, type Message, type MessagePart, type McpAuthProviders, type ModelConnectivityInfo, type SessionContext, type StreamOptions, type TelemetryEventCallback, type ToolDeclineReason, type ToolResultInfo, type WireCommunicationEventCallback } from '@salesforce/sfdx-agent-sdk';
5
5
  import { type AgentHooks } from '@salesforce/sfdx-agent-sdk/harness';
6
6
  import type { MastraLanguageModelBuilderContext } from './index.js';
7
7
  import { MastraApprovalCoordinator } from './mastra-approval-coordinator.js';
@@ -563,7 +563,7 @@ export declare class MastraHarness implements MastraAgentHarness {
563
563
  * except the coordinator drives `agent.declineToolCall()` instead of
564
564
  * `agent.approveToolCall()`.
565
565
  */
566
- declineToolCall(agentId: string, threadId: string, toolCallId: string): Promise<void>;
566
+ declineToolCall(agentId: string, threadId: string, toolCallId: string, reason?: ToolDeclineReason): Promise<void>;
567
567
  private getCoordinatorOrThrow;
568
568
  /**
569
569
  * @requirements
@@ -1751,9 +1751,12 @@ export class MastraHarness {
1751
1751
  * except the coordinator drives `agent.declineToolCall()` instead of
1752
1752
  * `agent.approveToolCall()`.
1753
1753
  */
1754
- async declineToolCall(agentId, threadId, toolCallId) {
1754
+ async declineToolCall(agentId, threadId, toolCallId, reason) {
1755
1755
  const coordinator = this.getCoordinatorOrThrow(agentId, threadId, 'declineToolCall');
1756
- coordinator.decline(toolCallId);
1756
+ if (reason)
1757
+ coordinator.decline(toolCallId, reason);
1758
+ else
1759
+ coordinator.decline(toolCallId);
1757
1760
  }
1758
1761
  getCoordinatorOrThrow(agentId, threadId, method) {
1759
1762
  const agentState = this.getAgentStateOrThrow(agentId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-harness-mastra",
3
- "version": "0.58.0",
3
+ "version": "0.60.0",
4
4
  "description": "Mastra-backed AgentHarness implementation for @salesforce/sfdx-agent-sdk",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,7 +32,7 @@
32
32
  "@ai-sdk/provider": "^4.0.5",
33
33
  "@anthropic-ai/bedrock-sdk": "^0.32.0",
34
34
  "@anthropic-ai/sdk": "^0.115.0",
35
- "@mastra/core": "^1.55.0",
35
+ "@mastra/core": "^1.62.0",
36
36
  "@mastra/libsql": "^1.18.0",
37
37
  "@mastra/mcp": "^1.15.0",
38
38
  "@mastra/memory": "^1.24.0",
@@ -42,16 +42,16 @@
42
42
  "openai": "^7.0.0"
43
43
  },
44
44
  "peerDependencies": {
45
- "@salesforce/sfdx-agent-sdk": "0.59.0"
45
+ "@salesforce/sfdx-agent-sdk": "0.61.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@eslint/js": "^10.0.1",
49
49
  "@salesforce/harness-conformance": "0.0.1",
50
- "@salesforce/sfdx-agent-sdk": "0.59.0",
50
+ "@salesforce/sfdx-agent-sdk": "0.61.0",
51
51
  "@types/node": "^22.20.1",
52
52
  "@vitest/coverage-istanbul": "^4.1.10",
53
53
  "@vitest/eslint-plugin": "^1.6.27",
54
- "eslint": "^10.8.1",
54
+ "eslint": "^10.9.1",
55
55
  "eslint-config-prettier": "^10.1.8",
56
56
  "eslint-import-resolver-typescript": "^4.4.5",
57
57
  "eslint-plugin-import": "^2.32.0",
@@ -62,7 +62,7 @@
62
62
  "rimraf": "^6.1.3",
63
63
  "tsx": "^4.23.12",
64
64
  "typescript": "^7.0.2",
65
- "typescript-eslint": "^8.67.0",
65
+ "typescript-eslint": "^8.68.0",
66
66
  "vitest": "^4.1.8"
67
67
  },
68
68
  "engines": {