@salesforce/sfdx-agent-sdk 0.55.0 → 0.56.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,11 @@
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.56.0] - 2026-08-27
7
+
8
+ ### Features
9
+ - **agent-sdk**: recoverAgent re-installs a restore-failed agent with thread rehydration @W-24002660@ ([#769](https://github.com/forcedotcom/agentic-dx/pull/769))
10
+
6
11
  ## [0.55.0] - 2026-08-25
7
12
 
8
13
  ### Features
package/README.md CHANGED
@@ -102,18 +102,19 @@ through a typed `manager.extensions` slot. `createAgentManager` infers `H` from
102
102
  explicitly. The `createAgent` config parameter narrows automatically when the harness brands itself with
103
103
  `WithAgentConfig` — see "Harness Extensibility" below.
104
104
 
105
- | Property / Method | Signature | Description |
106
- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
107
- | `extensions` | `H['extensions']` | Harness-specific extensions namespace (read-only). Re-exposes the harness's `extensions` slot typed off `H`. Per-agent accessors take the agent id as their first argument. The SDK never reads or interprets this — see "Harness Extensibility" below. |
108
- | `createAgent` | `(projectRoot: string, config?: ConfigOf<H> & { agentId?: string }, options?: { abortSignal?: AbortSignal; consumerMetadata?: JsonValue }) => Promise<Agent<H>>` | Create and register a new agent and persist its identity triple. `projectRoot` must be an existing directory. If `agentId` is omitted a UUID is generated. The config type is inferred from the harness — see `ConfigOf<H>`. `options.consumerMetadata` seeds the agent's opaque consumer metadata in the same persistence write as the config (create and initial metadata land together); it is never forwarded to the harness. |
109
- | `getAgent` | `(agentId: string) => Agent<H>` | Retrieve a live agent by ID. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) for unknown ids and for ids that are only present in `getRestoreFailures()`. |
110
- | `getAgentIds` | `() => string[]` | List all live agent IDs (successful + successfully restored). Failed-restore agents are not included query `getRestoreFailures()` separately. |
111
- | `destroyAgent` | `(agentId: string) => Promise<void>` | Destroy an agent, remove its identity record from disk, and clear any matching `getRestoreFailures()` entry. Failed-restore-only ids are accepted (no harness call made). |
112
- | `shutdown` | `() => Promise<void>` | Destroy all live agents and shut down the harness. Identity files survive (that's the whole point) restart `createAgentManager` over the same root to bring them back. |
113
- | `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry across all managed agents. |
114
- | `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to structured logs across all managed agents. Bridge this into your host logger to observe restore-failure events + soft-skip warnings. |
115
- | `onWireCommunication` | `(callback: WireCommunicationEventCallback) => Unsubscribe` | Subscribe to wire-level communication events from the harness. Opt-in diagnostic channel that surfaces outbound LLM requests, responses, and harness-specific monitoring metadata. Subscriber-gated end-to-end harnesses pay no cost when nobody listens. See "Wire-Communication Events" below for the event-shape catalog and the harness-asymmetric coverage (Mastra emits per-call request/response pairs; Claude emits a per-stream pointer to a debug log file). |
116
- | `getRestoreFailures` | `() => RestoreFailure[]` | Snapshot of agents the SDK could not restore on this boot. Each entry carries the persisted `{ agentId, projectRoot, config }` plus the underlying error. |
105
+ | Property / Method | Signature | Description |
106
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
107
+ | `extensions` | `H['extensions']` | Harness-specific extensions namespace (read-only). Re-exposes the harness's `extensions` slot typed off `H`. Per-agent accessors take the agent id as their first argument. The SDK never reads or interprets this — see "Harness Extensibility" below. |
108
+ | `createAgent` | `(projectRoot: string, config?: ConfigOf<H> & { agentId?: string }, options?: { abortSignal?: AbortSignal; consumerMetadata?: JsonValue }) => Promise<Agent<H>>` | Create and register a new agent and persist its identity triple. `projectRoot` must be an existing directory. If `agentId` is omitted a UUID is generated. The config type is inferred from the harness — see `ConfigOf<H>`. `options.consumerMetadata` seeds the agent's opaque consumer metadata in the same persistence write as the config (create and initial metadata land together); it is never forwarded to the harness. |
109
+ | `recoverAgent` | `(agentId: string, options?: { abortSignal?: AbortSignal }) => Promise<Agent<H>>` | Re-install an already-persisted agent whose boot-time restore failed, under the **same** id, resolving connectivity fresh from disk and **rehydrating its chat threads** so a follow-up turn resumes prior context. Idempotent for a live id (no-op returning the existing agent). Success clears the `getRestoreFailures()` entry; failure leaves it intact and re-armable with the persisted files untouched. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) for an id that is neither live nor a restore-failure it never fabricates an agent. Unlike `createAgent`, this re-establishes an existing identity rather than provisioning a new one. |
110
+ | `getAgent` | `(agentId: string) => Agent<H>` | Retrieve a live agent by ID. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) for unknown ids and for ids that are only present in `getRestoreFailures()`. |
111
+ | `getAgentIds` | `() => string[]` | List all live agent IDs (successful + successfully restored). Failed-restore agents are not included query `getRestoreFailures()` separately. |
112
+ | `destroyAgent` | `(agentId: string) => Promise<void>` | Destroy an agent, remove its identity record from disk, and clear any matching `getRestoreFailures()` entry. Failed-restore-only ids are accepted (no harness call made). |
113
+ | `shutdown` | `() => Promise<void>` | Destroy all live agents and shut down the harness. Identity files survive (that's the whole point) — restart `createAgentManager` over the same root to bring them back. |
114
+ | `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry across all managed agents. |
115
+ | `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to structured logs across all managed agents. Bridge this into your host logger to observe restore-failure events + soft-skip warnings. |
116
+ | `onWireCommunication` | `(callback: WireCommunicationEventCallback) => Unsubscribe` | Subscribe to wire-level communication events from the harness. Opt-in diagnostic channel that surfaces outbound LLM requests, responses, and harness-specific monitoring metadata. Subscriber-gated end-to-end harnesses pay no cost when nobody listens. See "Wire-Communication Events" below for the event-shape catalog and the harness-asymmetric coverage (Mastra emits per-call request/response pairs; Claude emits a per-stream pointer to a debug log file). |
117
+ | `getRestoreFailures` | `() => RestoreFailure[]` | Snapshot of agents the SDK could not restore on this boot. Each entry carries the persisted `{ agentId, projectRoot, config }` plus the underlying error. |
117
118
 
118
119
  #### `RestoreFailure`
119
120
 
@@ -759,7 +760,7 @@ from `AgentSDKErrorType`:
759
760
 
760
761
  | Type | Thrown By |
761
762
  | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
762
- | `AGENT_NOT_FOUND` | `AgentManager.getAgent()`, `AgentManager.destroyAgent()` |
763
+ | `AGENT_NOT_FOUND` | `AgentManager.getAgent()`, `AgentManager.destroyAgent()`, `AgentManager.recoverAgent()` (id neither live nor a restore-failure) |
763
764
  | `CHAT_SESSION_NOT_FOUND` | `Agent.getChatSession()`, `Agent.destroyChatSession()`, `Agent.cloneChatSession()`, `Agent.compactChatSession()` |
764
765
  | `COMPACTION_FAILED` | `Agent.compactChatSession()` when the harness's underlying summarization call rejects. The original error is attached as `cause`; the source session is left intact. |
765
766
  | `DISPOSED` | `Agent` and `ChatSession` methods called after the owner has been destroyed |
@@ -56,8 +56,13 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
56
56
  * and initial metadata land together. It is never forwarded to the harness.
57
57
  *
58
58
  * @throws If `projectRoot` does not exist or is not a directory.
59
- * @throws If `config.agentId` is provided and an agent with that ID is
60
- * already registered (live or in restore-failure state).
59
+ * @throws If `config.agentId` is provided and a **live** agent with that id
60
+ * already exists. An id that is only present in {@link getRestoreFailures}
61
+ * is **not** rejected: `createAgent` re-installs it cleanly as a brand-new
62
+ * identity, overwriting the persisted record and clearing the failure entry
63
+ * — but it does **not** rehydrate the agent's prior chat threads. To recover
64
+ * a restore-failed id *with* its conversation history, use
65
+ * {@link recoverAgent} instead.
61
66
  */
62
67
  createAgent(projectRoot: string, config?: ConfigOf<H> & {
63
68
  agentId?: string;
@@ -65,6 +70,46 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
65
70
  abortSignal?: AbortSignal;
66
71
  consumerMetadata?: JsonValue;
67
72
  }): Promise<Agent>;
73
+ /**
74
+ * Re-installs an already-persisted agent whose boot-time restore failed,
75
+ * returning a live {@link Agent}.
76
+ *
77
+ * Where {@link createAgent} provisions a **brand-new** identity, this
78
+ * re-establishes an **existing** one: it re-runs the same restore/construct
79
+ * path boot uses — resolving connectivity fresh from disk and **rehydrating
80
+ * the agent's chat threads** — so a follow-up turn resumes prior conversation
81
+ * context. Use it to recover an id surfaced by {@link getRestoreFailures}
82
+ * (e.g. one wedged after an org session token expired) under the **same** id,
83
+ * without minting a new agent id and breaking the caller's project→agent
84
+ * binding.
85
+ *
86
+ * - **Idempotent for a live id.** If the id is already live, this is a no-op
87
+ * that returns the existing {@link Agent} — no second construction.
88
+ * - **Success clears the restore-failure entry.** The id then appears in
89
+ * {@link getAgentIds} and is gone from {@link getRestoreFailures}.
90
+ * - **Failure is non-destructive.** If the re-install fails (e.g. auth is
91
+ * still stale), the {@link getRestoreFailures} entry stays intact and
92
+ * re-armable and the persisted identity/thread files are untouched — call
93
+ * again once fresh auth lands.
94
+ *
95
+ * Recovery re-installs from the agent's durable on-disk record and never
96
+ * writes one; if that record is gone, recovery has no basis and throws
97
+ * (see below) rather than construct an unpersisted agent. Concurrent
98
+ * same-id calls (recover racing recover, or recover racing
99
+ * {@link destroyAgent} / {@link createAgent}) are the caller's
100
+ * responsibility to serialize: state never corrupts and a destroy always
101
+ * wins, but a losing concurrent recover may reject rather than return the
102
+ * live agent.
103
+ *
104
+ * @throws `AGENT_NOT_FOUND` if the id is unknown to the SDK — neither live
105
+ * nor present in {@link getRestoreFailures} — or if its durable identity
106
+ * record is missing (e.g. a concurrent {@link destroyAgent} removed it).
107
+ * The method never fabricates an agent for an unknown id (the caller
108
+ * relies on this to gate recovery).
109
+ */
110
+ recoverAgent(agentId: string, options?: {
111
+ abortSignal?: AbortSignal;
112
+ }): Promise<Agent>;
68
113
  /**
69
114
  * Returns the live {@link Agent} for the given id.
70
115
  *
@@ -126,7 +171,8 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
126
171
  * Returns a snapshot of the boot-time restore failures the SDK has not
127
172
  * yet been told to forget. Each entry is cleared on a successful
128
173
  * {@link destroyAgent} for the same id; recreating an agent with the
129
- * same id via {@link createAgent} also clears the entry.
174
+ * same id via {@link createAgent}, or recovering it via
175
+ * {@link recoverAgent}, also clears the entry.
130
176
  */
131
177
  getRestoreFailures(): RestoreFailure[];
132
178
  }
@@ -176,6 +222,9 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
176
222
  abortSignal?: AbortSignal;
177
223
  consumerMetadata?: JsonValue;
178
224
  }): Promise<Agent>;
225
+ recoverAgent(agentId: string, options?: {
226
+ abortSignal?: AbortSignal;
227
+ }): Promise<Agent>;
179
228
  /**
180
229
  * Shared install path for {@link createAgent} and the boot-time restore
181
230
  * loop. Resolves connectivity, calls `harness.createAgent`, constructs
@@ -161,6 +161,61 @@ export class DefaultAgentManager {
161
161
  this.restoreFailures = this.restoreFailures.filter((f) => f.agentId !== agentId);
162
162
  return agent;
163
163
  }
164
+ async recoverAgent(agentId, options) {
165
+ this.assertNotDisposed();
166
+ // Idempotent: an already-live id is a no-op — return the existing handle rather than
167
+ // constructing a second agent over the same id. (The wedge this method exists to fix
168
+ // only ever leaves a restore-failure placeholder, never a live duplicate.)
169
+ const live = this.agents.get(agentId);
170
+ if (live) {
171
+ return live;
172
+ }
173
+ // Recovery applies only to an id the SDK recorded as a boot-restore failure. An id that
174
+ // is neither live nor a restore-failure is unknown — surface AGENT_NOT_FOUND rather than
175
+ // fabricating an agent. The service's governance-bypass gate depends on this: an
176
+ // SDK-unknown `error` placeholder must not be silently healed.
177
+ const failure = this.restoreFailures.find((f) => f.agentId === agentId);
178
+ if (!failure) {
179
+ throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
180
+ }
181
+ // Recovery re-establishes an EXISTING identity, so it re-installs from the durable on-disk
182
+ // record — and, unlike `createAgent`, never writes one. Read it fresh (this also carries
183
+ // `consumerMetadata`, which `RestoreFailure` does not). If the file is gone — a concurrent
184
+ // `destroyAgent` removed it during this call, or it was deleted out of band — recovery has
185
+ // no durable basis: throw AGENT_NOT_FOUND rather than construct a live-but-unpersisted
186
+ // "ghost" that would evaporate on the next boot (`list()` finds no file), silently undoing
187
+ // the very identity-continuity guarantee this method exists to provide.
188
+ const record = await this.identityStore.read(agentId);
189
+ if (!record) {
190
+ throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
191
+ }
192
+ // Re-run the shared install path with thread rehydration: connectivity resolves fresh
193
+ // from `~/.sfdx` (a stale-then-fresh token round-trips) and persisted threads re-attach
194
+ // via `restoreSessions`. A failure here throws WITHOUT reaching the clear below, so the
195
+ // restore-failure entry stays re-armable; `installAgent`'s rollback touches only
196
+ // in-memory/harness state, never the identity store, so the persisted files survive.
197
+ const agent = await this.installAgent(agentId, resolve(record.projectRoot), record.config, {
198
+ ...(options?.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}),
199
+ rehydrateThreads: true,
200
+ consumerMetadata: record.consumerMetadata,
201
+ });
202
+ // TOCTOU guard for the install-window race: a concurrent `destroyAgent` (which, for a
203
+ // restore-failure-only id, splices the failure entry and deletes the file) — or another
204
+ // verb that consumed this id — may have run during `installAgent`'s awaits. If our exact
205
+ // failure entry is gone, that op won: undo the install we just did rather than leave a
206
+ // live agent with no persisted record. `.includes(failure)` matches by object identity,
207
+ // which survives the `.filter(...)` reassignments and `destroyAgent`'s `.splice(...)`.
208
+ // (Concurrent same-id `recoverAgent` calls are the caller's responsibility to serialize;
209
+ // the loser may see the harness's "already registered" error, but state never corrupts.)
210
+ if (!this.restoreFailures.includes(failure)) {
211
+ await this.rollbackInstall(agentId, agent);
212
+ throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
213
+ }
214
+ // Success clears the now-stale restore-failure entry (mirrors createAgent's clear at the
215
+ // top of this class). The id now answers `getAgent` / `getAgentIds`.
216
+ this.restoreFailures = this.restoreFailures.filter((f) => f.agentId !== agentId);
217
+ return agent;
218
+ }
164
219
  /**
165
220
  * Shared install path for {@link createAgent} and the boot-time restore
166
221
  * loop. Resolves connectivity, calls `harness.createAgent`, constructs
@@ -39,5 +39,23 @@ export declare class AgentIdentityStore {
39
39
  private writeImmediate;
40
40
  remove(agentId: string): Promise<void>;
41
41
  list(): Promise<AgentIdentityRecord[]>;
42
+ /**
43
+ * Reads a single persisted record by agent id, applying the same
44
+ * parse/validate/harness-match gate as {@link list}. Returns `undefined` if
45
+ * the file is absent, unreadable, corrupt, missing required fields, or was
46
+ * written by a different harness (each soft skip warns via `LogBus`, matching
47
+ * `list()`).
48
+ *
49
+ * Used by `AgentManager.recoverAgent` to reconstruct a restore-failed agent
50
+ * from durable on-disk state — including `consumerMetadata`, which
51
+ * `RestoreFailure` does not carry.
52
+ */
53
+ read(agentId: string): Promise<AgentIdentityRecord | undefined>;
54
+ /**
55
+ * Shared parse + field-validation + harness-match gate for {@link list} and
56
+ * {@link read}. Returns the validated record, or `undefined` (with a
57
+ * `LogBus.warn`) for a corrupt / incomplete / foreign-harness file.
58
+ */
59
+ private parseAndValidate;
42
60
  private dir;
43
61
  }
@@ -99,10 +99,9 @@ export class AgentIdentityStore {
99
99
  if (!entry.endsWith('.json'))
100
100
  continue;
101
101
  const filePath = join(this.dir(), entry);
102
- let parsed;
102
+ let raw;
103
103
  try {
104
- const raw = await readFile(filePath, 'utf8');
105
- parsed = JSON.parse(raw);
104
+ raw = await readFile(filePath, 'utf8');
106
105
  }
107
106
  catch (err) {
108
107
  this.logBus.warn('skipping unreadable persisted agent identity file', {
@@ -111,32 +110,81 @@ export class AgentIdentityStore {
111
110
  });
112
111
  continue;
113
112
  }
114
- if (!parsed.agentId || !parsed.projectRoot || !parsed.config || !parsed.harnessId) {
115
- // `harnessId` is in the missing-fields gate (not the harness-mismatch gate below)
116
- // so a record without it produces a "missing required fields" warn rather than
117
- // a confusing "different harness" warn with `recordHarnessId: undefined`.
118
- this.logBus.warn('skipping persisted agent identity file with missing required fields', {
119
- filePath,
120
- });
121
- continue;
122
- }
123
- if (parsed.harnessId !== this.harnessId) {
124
- this.logBus.warn('skipping persisted agent identity file from a different harness', {
125
- filePath,
126
- agentId: parsed.agentId,
127
- recordHarnessId: parsed.harnessId,
128
- currentHarnessId: this.harnessId,
129
- });
130
- continue;
131
- }
132
- records.push({
113
+ const record = this.parseAndValidate(filePath, raw);
114
+ if (record)
115
+ records.push(record);
116
+ }
117
+ return records;
118
+ }
119
+ /**
120
+ * Reads a single persisted record by agent id, applying the same
121
+ * parse/validate/harness-match gate as {@link list}. Returns `undefined` if
122
+ * the file is absent, unreadable, corrupt, missing required fields, or was
123
+ * written by a different harness (each soft skip warns via `LogBus`, matching
124
+ * `list()`).
125
+ *
126
+ * Used by `AgentManager.recoverAgent` to reconstruct a restore-failed agent
127
+ * from durable on-disk state — including `consumerMetadata`, which
128
+ * `RestoreFailure` does not carry.
129
+ */
130
+ async read(agentId) {
131
+ const filePath = join(this.dir(), `${agentId}.json`);
132
+ let raw;
133
+ try {
134
+ raw = await readFile(filePath, 'utf8');
135
+ }
136
+ catch (err) {
137
+ if (err.code === 'ENOENT')
138
+ return undefined;
139
+ this.logBus.warn('skipping unreadable persisted agent identity file', {
140
+ filePath,
141
+ error: getErrorMessage(err),
142
+ });
143
+ return undefined;
144
+ }
145
+ return this.parseAndValidate(filePath, raw);
146
+ }
147
+ /**
148
+ * Shared parse + field-validation + harness-match gate for {@link list} and
149
+ * {@link read}. Returns the validated record, or `undefined` (with a
150
+ * `LogBus.warn`) for a corrupt / incomplete / foreign-harness file.
151
+ */
152
+ parseAndValidate(filePath, raw) {
153
+ let parsed;
154
+ try {
155
+ parsed = JSON.parse(raw);
156
+ }
157
+ catch (err) {
158
+ this.logBus.warn('skipping unreadable persisted agent identity file', {
159
+ filePath,
160
+ error: getErrorMessage(err),
161
+ });
162
+ return undefined;
163
+ }
164
+ if (!parsed.agentId || !parsed.projectRoot || !parsed.config || !parsed.harnessId) {
165
+ // `harnessId` is in the missing-fields gate (not the harness-mismatch gate below)
166
+ // so a record without it produces a "missing required fields" warn rather than
167
+ // a confusing "different harness" warn with `recordHarnessId: undefined`.
168
+ this.logBus.warn('skipping persisted agent identity file with missing required fields', {
169
+ filePath,
170
+ });
171
+ return undefined;
172
+ }
173
+ if (parsed.harnessId !== this.harnessId) {
174
+ this.logBus.warn('skipping persisted agent identity file from a different harness', {
175
+ filePath,
133
176
  agentId: parsed.agentId,
134
- projectRoot: parsed.projectRoot,
135
- config: parsed.config,
136
- ...(parsed.consumerMetadata !== undefined ? { consumerMetadata: parsed.consumerMetadata } : {}),
177
+ recordHarnessId: parsed.harnessId,
178
+ currentHarnessId: this.harnessId,
137
179
  });
180
+ return undefined;
138
181
  }
139
- return records;
182
+ return {
183
+ agentId: parsed.agentId,
184
+ projectRoot: parsed.projectRoot,
185
+ config: parsed.config,
186
+ ...(parsed.consumerMetadata !== undefined ? { consumerMetadata: parsed.consumerMetadata } : {}),
187
+ };
140
188
  }
141
189
  dir() {
142
190
  return join(this.storageRootFolder, AGENTS_SUBDIR);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.55.0",
3
+ "version": "0.56.0",
4
4
  "description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -47,9 +47,9 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@eslint/js": "^10.0.1",
50
- "@salesforce/sfdx-agent-harness-claude": "0.51.0",
51
- "@salesforce/sfdx-agent-harness-mastra": "0.54.0",
52
- "@salesforce/sfdx-agent-harness-openai": "0.20.0",
50
+ "@salesforce/sfdx-agent-harness-claude": "0.52.0",
51
+ "@salesforce/sfdx-agent-harness-mastra": "0.55.0",
52
+ "@salesforce/sfdx-agent-harness-openai": "0.21.0",
53
53
  "@types/node": "^22.20.1",
54
54
  "@vitest/coverage-istanbul": "^4.1.10",
55
55
  "@vitest/eslint-plugin": "^1.6.27",