@salesforce/sfdx-agent-sdk 0.77.0 → 0.79.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,15 @@
3
3
  All notable changes to `@salesforce/sfdx-agent-sdk` are documented in this file.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.79.0] - 2026-09-14
7
+
8
+ ### Features
9
+ - add ChatSession.cancelTurn() with abort-persistence barrier @W-23559264@ ([#810](https://github.com/forcedotcom/agentic-dx/pull/810))
10
+
11
+ ## [0.78.0] - 2026-09-09
12
+
13
+ _No changes — released alongside dependent packages._
14
+
6
15
  ## [0.77.0] - 2026-09-08
7
16
 
8
17
  ### Fixes
package/README.md CHANGED
@@ -170,6 +170,7 @@ A single conversation thread.
170
170
  | `submitToolResult` | `(toolResult: ToolResultInfo) => Promise<void>` | Return a consumer-executed tool result. Control message on the existing turn — post-resume events flow on the same stream. |
171
171
  | `approveToolCall` | `(toolCallId: string, options?: { remember?: boolean }) => Promise<void>` | Approve a pending tool call. `{ remember: true }` ("Allow always") appends an `allow` rule to `AgentConfig.toolPolicies` and persists it before settling. Control message on the existing turn. |
172
172
  | `declineToolCall` | `(toolCallId: string, options?: DeclineToolCallOptions) => Promise<void>` | Decline a pending tool call. `{ remember: true }` ("Deny always") appends a `deny` rule and persists it before settling. `{ reason: { kind: 'organization-policy', modelMessage? } }` delivers a terminal policy explanation to the model and error result without persisting a remembered user rule. Control message on the existing turn. |
173
+ | `cancelTurn` | `() => Promise<void>` | Stop the in-flight turn (including one suspended awaiting a tool approval) and resolve only **after** the harness abort teardown AND its transcript-persistence flush complete. Fires an SDK-owned per-turn signal composed with any caller `abortSignal`, so it works with or without one. The turn's `eventStream` ends with exactly one `FinishEvent(finishReason: 'cancelled')` preceded by an `ErrorEvent` whose canonical `code` is `'cancelled'` (no harness-specific `'abort'`/`'aborted'`). Because the returned promise is a persistence barrier, a subsequent `getMessageHistory()` is deterministic with **no polling**. Idempotent / safe no-op with no turn in flight; owns no wall-clock timeout (wrap it if you need one). |
173
174
  | `getMessageHistory` | `() => Promise<Message[]>` | Retrieve all messages in chronological order. |
174
175
  | `clearHistory` | `() => Promise<void>` | Delete all messages. |
175
176
  | `getContextUsage` | `() => ContextUsage` | Snapshot of how much of the model's context window the most recent turn used. |
@@ -764,7 +765,7 @@ type ContextUsage = {
764
765
  usedFraction: number | undefined;
765
766
  };
766
767
 
767
- type FinishReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'error' | 'other';
768
+ type FinishReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'error' | 'max-steps' | 'cancelled' | 'other';
768
769
  ```
769
770
 
770
771
  **Tracking context-window utilization.** `ChatSession.getContextUsage()` always returns a populated `ContextUsage` —
@@ -87,7 +87,10 @@ export type ChatSessionParentBuses = {
87
87
  * cleared, but the underlying harness turn keeps running until it settles, so the harness rejects
88
88
  * the new turn until then. **To release a turn deterministically, abort it (via the turn's
89
89
  * `abortSignal`) rather than merely abandoning its stream** — abort settles the coordinator, so the
90
- * next `chat()` is admitted cleanly.
90
+ * next `chat()` is admitted cleanly. {@link ChatSession.cancelTurn} is the first-class verb for this:
91
+ * it fires an SDK-owned per-turn signal (composed with any caller `abortSignal`) and resolves only
92
+ * after teardown + transcript-persistence flush, so a subsequent `getMessageHistory()` is
93
+ * deterministic without polling.
91
94
  */
92
95
  export interface ChatSession {
93
96
  /** Returns the unique session/thread identifier. */
@@ -182,6 +185,35 @@ export interface ChatSession {
182
185
  * terminal error result. Policy reasons require `remember: false` and are never persisted.
183
186
  */
184
187
  declineToolCall(toolCallId: string, options?: DeclineToolCallOptions): Promise<void>;
188
+ /**
189
+ * Stop the turn currently in flight — including one suspended awaiting a
190
+ * tool approval — and resolve only after the harness has fully settled the
191
+ * abort teardown, INCLUDING flushing whatever transcript persistence that
192
+ * harness performs. That persistence barrier is the point of the returned
193
+ * promise: once it resolves, {@link getMessageHistory} deterministically
194
+ * reflects the settled transcript with NO polling.
195
+ *
196
+ * Cancellation fires an SDK-owned per-turn `AbortController` that composes
197
+ * with any caller-supplied `options.abortSignal` — so it works whether or
198
+ * not the caller passed a signal, and a caller aborting their own signal has
199
+ * the identical effect. The turn's `eventStream` ends with exactly one
200
+ * terminal `FinishEvent(finishReason: 'cancelled')`, preceded by an
201
+ * `ErrorEvent` whose canonical `code` is `'cancelled'` on every harness — no
202
+ * cancelled turn surfaces `finishReason: 'error'`, and callers never branch
203
+ * on harness-specific `'abort'` / `'aborted'`.
204
+ *
205
+ * Idempotent and a safe no-op: calling it with no turn in flight, or a
206
+ * second time, does nothing harmful. It owns no wall-clock timeout — a
207
+ * consumer that needs one wraps the call itself.
208
+ *
209
+ * The cancelled turn's terminal `ErrorEvent` + `FinishEvent('cancelled')`
210
+ * still flow on the turn's own `eventStream`. As with any turn, drain (or
211
+ * abandon via `break` / `.return()`) that stream so the session's
212
+ * one-turn-at-a-time guard clears before the next {@link chat} — a
213
+ * `subscribe()`-only consumer that never iterates the returned stream can
214
+ * otherwise see `SESSION_BUSY` on the next `chat()` until it does.
215
+ */
216
+ cancelTurn(): Promise<void>;
185
217
  /**
186
218
  * Retrieve message history for this session.
187
219
  *
@@ -371,6 +403,25 @@ export declare class DefaultChatSession implements ChatSession {
371
403
  * mutate this flag. See the interface-level "Failure handling" notes.
372
404
  */
373
405
  private turnActive;
406
+ /**
407
+ * SDK-owned abort controller for the turn started by the most recent
408
+ * {@link chat}. Its signal is composed (via `AbortSignal.any`) with any
409
+ * caller-supplied `options.abortSignal` and handed to `harness.stream()`, so
410
+ * {@link cancelTurn} can stop the turn whether or not the caller passed a
411
+ * signal, and a caller-fired signal reaches the harness the same way.
412
+ * `undefined` before the first `chat()`. Aborting a controller whose turn has
413
+ * already settled is a harmless no-op, which is what makes `cancelTurn`
414
+ * idempotent without extra bookkeeping.
415
+ */
416
+ private turnAbortController;
417
+ /**
418
+ * The current turn's settle barrier, taken from the harness's
419
+ * {@link HarnessStreamResult.settled}. {@link cancelTurn} awaits it so the
420
+ * returned promise resolves only after the harness abort teardown AND its
421
+ * transcript-persistence flush have completed. `undefined` when no turn has
422
+ * run, when the harness didn't supply one, or on a pre-stream failure.
423
+ */
424
+ private turnSettled;
374
425
  /**
375
426
  * @param harness - The agent harness managing thread and message lifecycle.
376
427
  * @param agentId - ID of the agent this session belongs to.
@@ -394,6 +445,18 @@ export declare class DefaultChatSession implements ChatSession {
394
445
  * before returning a stream result.
395
446
  */
396
447
  chat(message: string | MessagePart[], options?: ChatOptions): Promise<ChatStreamResult>;
448
+ /**
449
+ * @requirements
450
+ * - MUST fire `this.turnAbortController` so the SDK-owned per-turn signal (composed with any
451
+ * caller-supplied `options.abortSignal`) reaches the harness and tears the turn down.
452
+ * - MUST await `this.turnSettled` (the harness settle barrier) so it resolves only after the
453
+ * abort teardown AND its transcript-persistence flush have completed.
454
+ * - MUST be a safe no-op when no turn has ever run (`turnAbortController` undefined) and be
455
+ * idempotent on repeat calls / an already-settled turn (aborting a settled controller and
456
+ * awaiting an already-resolved barrier are both no-ops).
457
+ * - MUST NOT own a wall-clock timeout.
458
+ */
459
+ cancelTurn(): Promise<void>;
397
460
  /**
398
461
  * @requirements
399
462
  * - MUST delegate to `this.harness.submitToolResult()`, passing `this.agentId` and `this.threadId`.
@@ -6,6 +6,16 @@ import { backfillCreatedAt, EventBus, LogBus, RealClock, UUIDGenerator, } from '
6
6
  import { resolveToolDeclineModelMessage } from './harness/tool-decline.js';
7
7
  import { AgentSDKError, AgentSDKErrorType } from './errors.js';
8
8
  import { createTelemetryBus, } from './types/telemetry-events.js';
9
+ /**
10
+ * Composes the SDK-owned per-turn abort signal with the caller's (if any) into
11
+ * one signal handed to `harness.stream()`, so firing EITHER aborts the turn.
12
+ * With no caller signal, the SDK signal is passed through unchanged. Uses
13
+ * `AbortSignal.any`, which yields an already-aborted signal when either input is
14
+ * already aborted — preserving the harness's abort-at-entry handling.
15
+ */
16
+ function composeAbortSignals(own, caller) {
17
+ return caller === undefined ? own : AbortSignal.any([own, caller]);
18
+ }
9
19
  /**
10
20
  * Default implementation of {@link ChatSession} that delegates all operations
11
21
  * to an {@link AgentHarness}. The session holds its agent ID and thread ID
@@ -98,6 +108,25 @@ export class DefaultChatSession {
98
108
  * mutate this flag. See the interface-level "Failure handling" notes.
99
109
  */
100
110
  turnActive = false;
111
+ /**
112
+ * SDK-owned abort controller for the turn started by the most recent
113
+ * {@link chat}. Its signal is composed (via `AbortSignal.any`) with any
114
+ * caller-supplied `options.abortSignal` and handed to `harness.stream()`, so
115
+ * {@link cancelTurn} can stop the turn whether or not the caller passed a
116
+ * signal, and a caller-fired signal reaches the harness the same way.
117
+ * `undefined` before the first `chat()`. Aborting a controller whose turn has
118
+ * already settled is a harmless no-op, which is what makes `cancelTurn`
119
+ * idempotent without extra bookkeeping.
120
+ */
121
+ turnAbortController = undefined;
122
+ /**
123
+ * The current turn's settle barrier, taken from the harness's
124
+ * {@link HarnessStreamResult.settled}. {@link cancelTurn} awaits it so the
125
+ * returned promise resolves only after the harness abort teardown AND its
126
+ * transcript-persistence flush have completed. `undefined` when no turn has
127
+ * run, when the harness didn't supply one, or on a pre-stream failure.
128
+ */
129
+ turnSettled = undefined;
101
130
  /**
102
131
  * @param harness - The agent harness managing thread and message lifecycle.
103
132
  * @param agentId - ID of the agent this session belongs to.
@@ -144,9 +173,24 @@ export class DefaultChatSession {
144
173
  // cannot slip past the guard.
145
174
  this.assertNotBusy();
146
175
  this.turnActive = true;
176
+ // SDK-owned per-turn signal composed with the caller's (if any) so
177
+ // cancelTurn() can stop this turn regardless of whether the caller passed
178
+ // one. Set before the `await` so a cancelTurn() racing this call still
179
+ // aborts the right controller.
180
+ const turnAbort = new AbortController();
181
+ this.turnAbortController = turnAbort;
182
+ const abortSignal = composeAbortSignals(turnAbort.signal, options?.abortSignal);
147
183
  const startedAt = this.emitChatStreamStarted('chat');
184
+ const streamPromise = this.harness.stream(this.agentId, this.threadId, message, { ...options, abortSignal });
185
+ // Derive the settle barrier synchronously from the stream promise — before
186
+ // the `await` — so a `cancelTurn()` that races this `chat()` still awaits the
187
+ // real teardown + persistence flush rather than an `undefined` (an immediate
188
+ // resolve that would skip the barrier). On a pre-stream rejection there is no
189
+ // turn to settle, so the barrier resolves; the rejection itself surfaces on
190
+ // the awaited `streamPromise` below.
191
+ this.turnSettled = streamPromise.then((result) => result.settled, () => undefined);
148
192
  try {
149
- const result = await this.harness.stream(this.agentId, this.threadId, message, options);
193
+ const result = await streamPromise;
150
194
  return {
151
195
  textStream: result.textStream,
152
196
  eventStream: this.wrapEventStream(result.eventStream, startedAt),
@@ -160,6 +204,30 @@ export class DefaultChatSession {
160
204
  throw err;
161
205
  }
162
206
  }
207
+ /**
208
+ * @requirements
209
+ * - MUST fire `this.turnAbortController` so the SDK-owned per-turn signal (composed with any
210
+ * caller-supplied `options.abortSignal`) reaches the harness and tears the turn down.
211
+ * - MUST await `this.turnSettled` (the harness settle barrier) so it resolves only after the
212
+ * abort teardown AND its transcript-persistence flush have completed.
213
+ * - MUST be a safe no-op when no turn has ever run (`turnAbortController` undefined) and be
214
+ * idempotent on repeat calls / an already-settled turn (aborting a settled controller and
215
+ * awaiting an already-resolved barrier are both no-ops).
216
+ * - MUST NOT own a wall-clock timeout.
217
+ */
218
+ async cancelTurn() {
219
+ this.assertNotDisposed();
220
+ const controller = this.turnAbortController;
221
+ if (controller === undefined) {
222
+ // No turn has ever started on this session — nothing to cancel.
223
+ return;
224
+ }
225
+ controller.abort();
226
+ // Resolve only after the harness has settled teardown and flushed
227
+ // persistence. `settled` never rejects; absence (a harness that didn't
228
+ // supply one) degrades to abort-without-barrier.
229
+ await this.turnSettled;
230
+ }
163
231
  /**
164
232
  * @requirements
165
233
  * - MUST delegate to `this.harness.submitToolResult()`, passing `this.agentId` and `this.threadId`.
@@ -1,6 +1,6 @@
1
1
  import type { LogRecord, Unsubscribe } from '@salesforce/agentic-common';
2
2
  import type { McpServerInfo, McpAuthProviders } from '../mcp-config.js';
3
- import type { ChatStreamResult } from '../types/events.js';
3
+ import type { HarnessStreamResult } from '../types/events.js';
4
4
  import type { Message, MessagePart } from '../types/messages.js';
5
5
  import type { TelemetryEventCallback } from '../types/telemetry-events.js';
6
6
  import type { ToolDeclineReason, ToolResultInfo } from '../types/tools.js';
@@ -367,8 +367,10 @@ export interface AgentHarness {
367
367
  compactThread(agentId: string, threadId: string): Promise<string>;
368
368
  /**
369
369
  * Stream a response from the agent for the given message.
370
- * Returns a {@link ChatStreamResult} providing multiple ways to consume
371
- * the stream (text-only or full events).
370
+ * Returns a {@link HarnessStreamResult} a {@link ChatStreamResult} (text-only
371
+ * or full-event consumption) plus an optional internal `settled` promise the
372
+ * SDK uses as the {@link ChatSession.cancelTurn} persistence barrier. The
373
+ * `settled` field is off the consumer surface; see {@link HarnessStreamResult}.
372
374
  *
373
375
  * @param agentId - ID of the agent to invoke.
374
376
  * @param threadId - ID of the conversation thread.
@@ -377,7 +379,7 @@ export interface AgentHarness {
377
379
  * passing `tool-call` / `tool-result` parts is a programmer error and harnesses reject it.
378
380
  * @param options - Per-call streaming options.
379
381
  */
380
- stream(agentId: string, threadId: string, message: string | MessagePart[], options?: StreamOptions): Promise<ChatStreamResult>;
382
+ stream(agentId: string, threadId: string, message: string | MessagePart[], options?: StreamOptions): Promise<HarnessStreamResult>;
381
383
  /**
382
384
  * Feed the result of a **consumer-executed (client-side) tool** back into the
383
385
  * conversation. Implements the consumer-facing
@@ -42,6 +42,7 @@
42
42
  * is "harness-only" vs. "consumer-AND-harness", not "harness vs. consumer."
43
43
  */
44
44
  export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './index.js';
45
+ export type { HarnessStreamResult } from '../types/events.js';
45
46
  export type { AgentHooks } from '../types/redaction.js';
46
47
  export { SUPPORTED_PROTOCOL_VERSIONS } from './agent-harness.js';
47
48
  export { mcpServerConfigEqual } from '../mcp-config.js';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type { Message, MessagePart, MessageRole, ImagePart, FilePart } from './types/messages.js';
2
2
  export type { JsonValue, SessionContext } from './types/session-context.js';
3
- export type { ChatEvent, StartEvent, TextDeltaEvent, ReasoningDeltaEvent, ToolCallEvent, ToolCallDeltaEvent, ToolApprovalRequestEvent, ToolResultEvent, ToolProgressEvent, StepStartEvent, StepFinishEvent, ErrorEvent, FinishEvent, ChatStreamResult, } from './types/events.js';
3
+ export type { ChatEvent, StartEvent, TextDeltaEvent, ReasoningDeltaEvent, ToolCallEvent, ToolCallDeltaEvent, ToolApprovalRequestEvent, ToolResultEvent, ToolProgressEvent, StepStartEvent, StepFinishEvent, ErrorEvent, FinishEvent, ChatStreamResult, HarnessStreamResult, } from './types/events.js';
4
4
  export type { DeclineToolCallOptions, Decision, ToolDeclineReason, ToolDefinition, ToolCallInfo, ToolMatcher, ToolPolicyRule, ToolResultInfo, } from './types/tools.js';
5
5
  export { BUILT_IN_TOOL_POLICIES, SKILL_BRIDGE_SERVER_ID, definePolicy, matcherMatches, resolveToolApprovalPolicy, } from './policy-resolver.js';
6
6
  export type { ResolverResult, ResolverTiers, ToolInvocation } from './policy-resolver.js';
@@ -258,10 +258,14 @@ export type ErrorEvent = {
258
258
  /**
259
259
  * Stable, machine-readable classification of the error, when the harness
260
260
  * recognized it — consumers branch on this instead of string-matching
261
- * `error.message`. The cross-harness value in use today is
262
- * `'context-window-exceeded'` (the {@link AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED}
263
- * overflow signal, emitted uniformly by every harness). Other values are
264
- * harness-specific (e.g. `'network-error'`, `'abort'`, `'tool-approval-timeout'`).
261
+ * `error.message`. Two cross-harness values are emitted uniformly by every
262
+ * harness: `'context-window-exceeded'` (the
263
+ * {@link AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED} overflow signal) and
264
+ * `'cancelled'` (the canonical code paired with a terminal
265
+ * `FinishEvent(finishReason: 'cancelled')` when a turn is stopped by
266
+ * {@link ChatSession.cancelTurn} or a caller-fired `abortSignal` — callers
267
+ * no longer branch on harness-specific `'abort'` / `'aborted'`). Other values
268
+ * are harness-specific (e.g. `'network-error'`, `'tool-approval-timeout'`).
265
269
  * `undefined` when the harness could not classify the error.
266
270
  */
267
271
  code?: string;
@@ -291,3 +295,29 @@ export type ChatStreamResult = {
291
295
  */
292
296
  textStream: AsyncGenerator<string>;
293
297
  };
298
+ /**
299
+ * The result a harness's {@link AgentHarness.stream} returns to the SDK — a
300
+ * {@link ChatStreamResult} plus an optional internal `settled` promise. A
301
+ * harness-contract type (reachable from both `@salesforce/sfdx-agent-sdk` and
302
+ * `/harness`, like {@link AgentHarness}), NOT the consumer-facing result:
303
+ * `ChatSession.chat()` returns a plain {@link ChatStreamResult} that never
304
+ * carries `settled`. The SDK reads `settled` off the harness result and consumes
305
+ * it internally (the {@link ChatSession.cancelTurn} barrier); consumer code never
306
+ * sees the field.
307
+ */
308
+ export type HarnessStreamResult = ChatStreamResult & {
309
+ /**
310
+ * Resolves once the turn has fully settled — its single per-turn sink has
311
+ * ended (natural `finish` or an abort/teardown) AND any persistence flush
312
+ * that settling entails has completed (the harness's transcript save is on
313
+ * disk). This is the barrier {@link ChatSession.cancelTurn} awaits so a
314
+ * subsequent `getMessages()` is deterministic with no polling.
315
+ *
316
+ * Optional so a harness that hasn't wired it still satisfies the contract:
317
+ * the SDK's `cancelTurn()` still aborts the turn, it just can't await the
318
+ * persistence barrier. All production harnesses populate it. It never
319
+ * rejects — a failed teardown still resolves it (the turn is over either
320
+ * way).
321
+ */
322
+ settled?: Promise<void>;
323
+ };
@@ -1,5 +1,5 @@
1
1
  export type { Message, MessageRole, MessagePart, TextPart, ReasoningPart, ToolCallPart, ToolResultPart, } from './messages.js';
2
- export type { ChatEvent, StartEvent, TextDeltaEvent, ReasoningDeltaEvent, ToolCallEvent, ToolApprovalRequestEvent, ToolResultEvent, StepStartEvent, StepFinishEvent, ErrorEvent, FinishEvent, ChatStreamResult, } from './events.js';
2
+ export type { ChatEvent, StartEvent, TextDeltaEvent, ReasoningDeltaEvent, ToolCallEvent, ToolApprovalRequestEvent, ToolResultEvent, StepStartEvent, StepFinishEvent, ErrorEvent, FinishEvent, ChatStreamResult, HarnessStreamResult, } from './events.js';
3
3
  export type { ToolDefinition, ToolCallInfo, ToolResultInfo } from './tools.js';
4
4
  export type { UsageMetadata, FinishReason } from './usage.js';
5
5
  export type { ModelConnectivityInfo, ProviderHint } from './model-connectivity-info.js';
@@ -172,6 +172,10 @@ export type ContextUsage = {
172
172
  * - `max-steps` — The agentic loop was terminated because it reached the maximum step count.
173
173
  * The model wanted to continue (e.g., it was requesting more tool calls) but the loop was forcibly stopped.
174
174
  * This is distinct from `tool-calls` which in a step-finish context indicates normal mid-loop progress.
175
+ * - `cancelled` — The turn was stopped by an explicit cancellation — {@link ChatSession.cancelTurn} or the
176
+ * caller aborting the turn's `abortSignal`. Distinct from `error`: a cancel is an intentional stop, not a
177
+ * failure. The terminal `FinishEvent` for a cancelled turn always carries this reason (never `error`), and the
178
+ * accompanying `ErrorEvent.code` is the canonical `'cancelled'` on every harness.
175
179
  * - `other` — Provider-specific reason that doesn't map to the standard values.
176
180
  */
177
- export type FinishReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'error' | 'max-steps' | 'other';
181
+ export type FinishReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'error' | 'max-steps' | 'cancelled' | 'other';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.77.0",
3
+ "version": "0.79.0",
4
4
  "description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -48,9 +48,9 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@eslint/js": "^10.0.1",
51
- "@salesforce/sfdx-agent-harness-claude": "0.73.0",
52
- "@salesforce/sfdx-agent-harness-mastra": "0.76.0",
53
- "@salesforce/sfdx-agent-harness-openai": "0.42.0",
51
+ "@salesforce/sfdx-agent-harness-claude": "0.75.0",
52
+ "@salesforce/sfdx-agent-harness-mastra": "0.78.0",
53
+ "@salesforce/sfdx-agent-harness-openai": "0.44.0",
54
54
  "@types/node": "^22.20.1",
55
55
  "@vitest/coverage-istanbul": "^4.1.11",
56
56
  "@vitest/eslint-plugin": "^1.6.27",