@salesforce/sfdx-agent-sdk 0.52.0 → 0.53.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.53.0] - 2026-08-24
7
+
8
+ ### Features
9
+ - **agent-sdk**: persist per-agent consumer metadata for governed PATCH @W-23972709@ ([#760](https://github.com/forcedotcom/agentic-dx/pull/760))
10
+
6
11
  ## [0.52.0] - 2026-08-24
7
12
 
8
13
  ### Features
package/README.md CHANGED
@@ -102,18 +102,18 @@ 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 }) => 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>`. |
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
+ | `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. |
117
117
 
118
118
  #### `RestoreFailure`
119
119
 
@@ -136,24 +136,26 @@ A configured AI agent. Factory for chat sessions. The optional `H` type paramete
136
136
  — harness-specific features are reached through `manager.extensions`, not `agent.extensions`. The default `AgentHarness`
137
137
  keeps unparameterized call sites working.
138
138
 
139
- | Method | Signature | Description |
140
- | -------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
141
- | `getId` | `() => string` | Agent identifier. |
142
- | `getProjectRoot` | `() => string` | Absolute project path. |
143
- | `getOrgConnection` | `() => OrgConnection \| undefined` | The Salesforce `OrgConnection` resolved for this agent (or `undefined` if the connectivity resolver omitted it — non-Salesforce hosts on the BYOK / api-key path return `undefined`). |
144
- | `getAgentConfig` | `() => AgentConfig` | Current configuration (shallow copy). |
145
- | `getMcpServerInfo` | `() => McpServerInfo[]` | MCP server status and discovered tools. |
146
- | `reconnectMcpServer` | `(serverName: string) => Promise<void>` | Recover one MCP server without recycling the agent. Semantics vary by harness — observe via `getMcpServerInfo()` and discovery telemetry. |
147
- | `updateAgentConfig` | `(config?: AgentConfig, options?: { abortSignal?: AbortSignal; forceResolve?: boolean }) => Promise<void>` | Merge new config into the live agent. Pass `forceResolve: true` to re-run the connectivity resolver even when the partial config doesn't include `orgAlias` or `modelId` — for consumer-side state changes (BYOK toggle, feature-id flip, rate-limit gate) the resolver reads but the SDK can't observe directly. |
148
- | `createChatSession` | `() => Promise<ChatSession>` | Open a new conversation thread. |
149
- | `getChatSession` | `(sessionId: string) => ChatSession` | Retrieve a session. Throws `AgentSDKError` (`CHAT_SESSION_NOT_FOUND`). |
150
- | `getChatSessionIds` | `() => string[]` | List active session IDs. |
151
- | `destroyChatSession` | `(sessionId: string) => Promise<void>` | Destroy a session and its history. |
152
- | `cloneChatSession` | `(sourceSessionId: string) => Promise<ChatSession>` | Clone a session with its message history. |
153
- | `compactChatSession` | `(sessionId: string) => Promise<ChatSession>` | Compact a session into a summarized new session. |
154
- | `destroy` | `() => Promise<void>` | Destroy the agent and all its sessions. |
155
- | `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry scoped to this agent (and its sessions). |
156
- | `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to logs scoped to this agent (and its sessions). |
139
+ | Method | Signature | Description |
140
+ | --------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
141
+ | `getId` | `() => string` | Agent identifier. |
142
+ | `getProjectRoot` | `() => string` | Absolute project path. |
143
+ | `getOrgConnection` | `() => OrgConnection \| undefined` | The Salesforce `OrgConnection` resolved for this agent (or `undefined` if the connectivity resolver omitted it — non-Salesforce hosts on the BYOK / api-key path return `undefined`). |
144
+ | `getAgentConfig` | `() => AgentConfig` | Current configuration (shallow copy). |
145
+ | `getConsumerMetadata` | `() => JsonValue \| undefined` | Deep copy of the agent's SDK-owned opaque consumer metadata, or `undefined` if unset. Persisted beside the config; never forwarded to the harness and never on the wire. Set it via `updateAgentState`. |
146
+ | `getMcpServerInfo` | `() => McpServerInfo[]` | MCP server status and discovered tools. |
147
+ | `reconnectMcpServer` | `(serverName: string) => Promise<void>` | Recover one MCP server without recycling the agent. Semantics vary by harness observe via `getMcpServerInfo()` and discovery telemetry. |
148
+ | `updateAgentConfig` | `(config?: AgentConfig, options?: { abortSignal?: AbortSignal; forceResolve?: boolean }) => Promise<void>` | Merge new config into the live agent. Pass `forceResolve: true` to re-run the connectivity resolver even when the partial config doesn't include `orgAlias` or `modelId` — for consumer-side state changes (BYOK toggle, feature-id flip, rate-limit gate) the resolver reads but the SDK can't observe directly. A thin wrapper over `updateAgentState({ config }, options)`. |
149
+ | `updateAgentState` | `(update: AgentStateUpdate, options?: { abortSignal?: AbortSignal; forceResolve?: boolean }) => Promise<void>` | Atomically apply a config partial and/or a consumer-metadata mutation. When both are present they commit in one persistence write, so a failed update leaves config and metadata at their previous values together. A metadata-only update never calls the harness. See `AgentStateUpdate` / `ConsumerMetadataMutation` below. |
150
+ | `createChatSession` | `() => Promise<ChatSession>` | Open a new conversation thread. |
151
+ | `getChatSession` | `(sessionId: string) => ChatSession` | Retrieve a session. Throws `AgentSDKError` (`CHAT_SESSION_NOT_FOUND`). |
152
+ | `getChatSessionIds` | `() => string[]` | List active session IDs. |
153
+ | `destroyChatSession` | `(sessionId: string) => Promise<void>` | Destroy a session and its history. |
154
+ | `cloneChatSession` | `(sourceSessionId: string) => Promise<ChatSession>` | Clone a session with its message history. |
155
+ | `compactChatSession` | `(sessionId: string) => Promise<ChatSession>` | Compact a session into a summarized new session. |
156
+ | `destroy` | `() => Promise<void>` | Destroy the agent and all its sessions. |
157
+ | `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry scoped to this agent (and its sessions). |
158
+ | `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to logs scoped to this agent (and its sessions). |
157
159
 
158
160
  ### `ChatSession`
159
161
 
@@ -280,6 +282,25 @@ telemetry for a service of your own.
280
282
  | `toolPolicies?` | `ToolPolicyRule[]` | Ordered per-tool approval rules resolved by `resolveToolApprovalPolicy` (cross-tier deny-wins / within-tier last-wins). Author directly or via `definePolicy(...)`. See "Tool Approval Policy" below. |
281
283
  | `defaultToolDecision?` | `Decision` | Fallback decision when no rule matches. Defaults to `'allow'` (no policy ⇒ no gating). Set to `'require-approval'` for a fail-closed posture (recommended for catalogs with un-annotated MCP servers). |
282
284
 
285
+ #### `AgentStateUpdate` and `ConsumerMetadataMutation`
286
+
287
+ Inputs to `Agent.updateAgentState`. Consumer metadata is SDK-owned opaque JSON persisted beside the agent config — it
288
+ never sits on `AgentConfig`, never reaches the harness, and never appears on the REST wire.
289
+
290
+ ```typescript
291
+ type ConsumerMetadataMutation = { action: 'replace'; value: JsonValue } | { action: 'clear' };
292
+
293
+ type AgentStateUpdate = {
294
+ config?: AgentConfig;
295
+ consumerMetadata?: ConsumerMetadataMutation;
296
+ };
297
+ ```
298
+
299
+ `replace` stores a deep copy of `value`; `clear` removes the metadata; omitting `consumerMetadata` leaves it unchanged.
300
+ When `update.config` is present, `updateAgentState` applies it to the harness exactly as `updateAgentConfig` does and
301
+ persists config plus metadata in one write; when it is absent, the update is metadata-only and never calls the harness.
302
+ `getConsumerMetadata()` reads the current value back as a deep copy.
303
+
283
304
  #### `StreamOptions`
284
305
 
285
306
  | Field | Type | Description |
@@ -403,14 +424,14 @@ const manager = await createAgentManager(storageRoot, harnessFactory, {
403
424
  The harness forwards the provider into the underlying HTTP transport (`@modelcontextprotocol/sdk`'s
404
425
  `StreamableHTTPClientTransport` on Claude / OpenAI, `@mastra/mcp`'s `HttpServerDefinition` on Mastra). The transport
405
426
  attaches existing tokens and silently refreshes them when possible. If user interaction is required, it invokes the
406
- provider's `redirectToAuthorization` callback and surfaces `McpServerErrorDetail.category === 'oauth-required'`;
407
- ADX does not open a browser or accept the authorization callback code. The static `headers` seam cannot provide this
427
+ provider's `redirectToAuthorization` callback and surfaces `McpServerErrorDetail.category === 'oauth-required'`; ADX
428
+ does not open a browser or accept the authorization callback code. The static `headers` seam cannot provide this
408
429
  recovery because headers are snapshotted at publish time.
409
430
 
410
431
  - **`McpOAuthClientProvider`** (exported) is a harness-neutral interface that structurally mirrors the required core of
411
432
  the MCP SDK's `OAuthClientProvider`, so a real provider assigns to it with no cast and the SDK stays free of a direct
412
- `@modelcontextprotocol/sdk` dependency. **`McpAuthProviders`** (exported) is the `Readonly<Record<serverName,
413
- McpOAuthClientProvider>>` the resolver returns.
433
+ `@modelcontextprotocol/sdk` dependency. **`McpAuthProviders`** (exported) is the
434
+ `Readonly<Record<serverName, McpOAuthClientProvider>>` the resolver returns.
414
435
  - **One provider instance per server**, and return the **same** instance across calls — the upstream contract forbids
415
436
  tokens / codes / verifiers crossing sessions, and a harness cycles a server when its provider reference changes (so
416
437
  returning a fresh instance each update reconnects that server every time).
@@ -422,8 +443,8 @@ recovery because headers are snapshotted at publish time.
422
443
  (no Salesforce JWT injected, `HTTPS_PROXY` preserved) while the provider drives auth.
423
444
 
424
445
  > **Interactive authorization stays with the consumer.** AFV's provider owns the Sign in UI, browser, VS Code callback,
425
- > and code exchange. After it saves the new tokens, call `Agent.reconnectMcpServer` to connect. ADX owns runtime provider
426
- > forwarding and silent refresh only; the transport's `finishAuth(code)` seam is not part of the public API.
446
+ > and code exchange. After it saves the new tokens, call `Agent.reconnectMcpServer` to connect. ADX owns runtime
447
+ > provider forwarding and silent refresh only; the transport's `finishAuth(code)` seam is not part of the public API.
427
448
 
428
449
  **Tool-exposure policy** (which tools bypass the active runtime's tool-search deferral) is configured per-agent on the
429
450
  harness extension surface, not per-server here. See `MastraAgentConfig.toolSearch.alwaysActive` and
@@ -450,8 +471,8 @@ only `MCPRemoteServerConfig` carries it.
450
471
 
451
472
  #### `McpServerErrorDetail`
452
473
 
453
- Structured projection of an MCP server failure. Route interactive authorization on `category === 'oauth-required'`;
454
- an ordinary missing or invalid bearer remains `http-401`. The same detail is attached to `mcp-server-discovery-failed`
474
+ Structured projection of an MCP server failure. Route interactive authorization on `category === 'oauth-required'`; an
475
+ ordinary missing or invalid bearer remains `http-401`. The same detail is attached to `mcp-server-discovery-failed`
455
476
  telemetry so subscribers can route on it without pattern-matching `error.message`.
456
477
 
457
478
  | Field | Type | Description |
@@ -460,8 +481,8 @@ telemetry so subscribers can route on it without pattern-matching `error.message
460
481
  | `code?` | `number` | JSON-RPC error code from the underlying `McpError`, when the failure originated as a JSON-RPC error. Undefined for transport-level failures and for harnesses whose underlying SDK does not surface the code (e.g. Claude). |
461
482
  | `retriable` | `boolean` | Whether the SDK considers the failure transient (worth `Agent.reconnectMcpServer`) versus fatal. |
462
483
 
463
- `McpServerErrorCategory` values: `'connect-timeout'`, `'oauth-required'`, `'http-401'`, `'http-403'`, `'http-4xx'`, `'http-5xx'`,
464
- `'transport-eof'`, `'protocol-error'`, `'config-error'`, `'aborted'`, `'unknown'`.
484
+ `McpServerErrorCategory` values: `'connect-timeout'`, `'oauth-required'`, `'http-401'`, `'http-403'`, `'http-4xx'`,
485
+ `'http-5xx'`, `'transport-eof'`, `'protocol-error'`, `'config-error'`, `'aborted'`, `'unknown'`.
465
486
 
466
487
  #### `McpToolInfo`
467
488
 
@@ -2,6 +2,7 @@ import { type Clock, LogBus, type LogRecord, type Unsubscribe, type UniqueIDGene
2
2
  import { type AgentHarness, type ConfigOf } from './harness/agent-harness.js';
3
3
  import type { HarnessFactory } from './harness/harness-factory.js';
4
4
  import { type AgentConfig, type McpAuthProviderResolver } from './harness/harness-config.js';
5
+ import type { JsonValue } from './types/session-context.js';
5
6
  import { type Agent, type AgentRuntimeResolvers } from './agent.js';
6
7
  import type { HooksForAgent } from './types/redaction.js';
7
8
  import { type TelemetryEventCallback } from './types/telemetry-events.js';
@@ -50,6 +51,10 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
50
51
  * without an explicit generic at the call site. Extra fields thread
51
52
  * through opaquely to the harness, which narrows what it cares about.
52
53
  *
54
+ * `options.consumerMetadata` seeds the agent's SDK-owned opaque consumer
55
+ * metadata, persisted in the same identity write as the config so create
56
+ * and initial metadata land together. It is never forwarded to the harness.
57
+ *
53
58
  * @throws If `projectRoot` does not exist or is not a directory.
54
59
  * @throws If `config.agentId` is provided and an agent with that ID is
55
60
  * already registered (live or in restore-failure state).
@@ -58,6 +63,7 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
58
63
  agentId?: string;
59
64
  }, options?: {
60
65
  abortSignal?: AbortSignal;
66
+ consumerMetadata?: JsonValue;
61
67
  }): Promise<Agent>;
62
68
  /**
63
69
  * Returns the live {@link Agent} for the given id.
@@ -168,6 +174,7 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
168
174
  agentId?: string;
169
175
  }, options?: {
170
176
  abortSignal?: AbortSignal;
177
+ consumerMetadata?: JsonValue;
171
178
  }): Promise<Agent>;
172
179
  /**
173
180
  * Shared install path for {@link createAgent} and the boot-time restore
@@ -89,6 +89,7 @@ export class DefaultAgentManager {
89
89
  try {
90
90
  await this.installAgent(record.agentId, record.projectRoot, record.config, {
91
91
  rehydrateThreads: true,
92
+ consumerMetadata: record.consumerMetadata,
92
93
  });
93
94
  }
94
95
  catch (err) {
@@ -131,19 +132,26 @@ export class DefaultAgentManager {
131
132
  const resolvedProjectRoot = resolve(projectRoot);
132
133
  const { agentId: providedAgentId, ...agentConfig } = config;
133
134
  const agentId = providedAgentId ?? this.agentIdGenerator.getUniqueId();
135
+ const consumerMetadata = options?.consumerMetadata;
134
136
  if (this.agents.has(agentId)) {
135
137
  throw new Error(`Agent with id "${agentId}" already exists`);
136
138
  }
137
139
  // installAgent validates projectRoot existence — same path as the restore loop.
138
140
  const agent = await this.installAgent(agentId, resolvedProjectRoot, agentConfig, {
139
141
  abortSignal: options?.abortSignal,
142
+ consumerMetadata,
140
143
  });
141
144
  // If the disk write fails (disk full, permissions, fs error), the in-memory install
142
145
  // is now stale — the harness has the agent, the manager has it in `agents`, but no
143
146
  // record on disk means a subsequent restart loses it. Roll back the install so the
144
147
  // failure is observable and the id stays reusable, then rethrow.
145
148
  try {
146
- await this.identityStore.write(agentId, resolvedProjectRoot, agentConfig);
149
+ if (consumerMetadata === undefined) {
150
+ await this.identityStore.write(agentId, resolvedProjectRoot, agentConfig);
151
+ }
152
+ else {
153
+ await this.identityStore.write(agentId, resolvedProjectRoot, agentConfig, consumerMetadata);
154
+ }
147
155
  }
148
156
  catch (err) {
149
157
  await this.rollbackInstall(agentId, agent);
@@ -186,7 +194,11 @@ export class DefaultAgentManager {
186
194
  }
187
195
  const hooks = this.resolvers.hooksForAgent?.(agentId, config) ?? {};
188
196
  const mcpAuthProviders = normalizeMcpAuthProviders(config.mcpServers, this.resolvers.mcpAuthProviderResolver?.(agentId, config));
189
- await this.harness.createAgent(agentId, projectRoot, runtime.modelConnectivityInfo, toHarnessConfig(config, runtime.orgJwt), { ...(options.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}), hooks, mcpAuthProviders });
197
+ await this.harness.createAgent(agentId, projectRoot, runtime.modelConnectivityInfo, toHarnessConfig(config, runtime.orgJwt), {
198
+ ...(options.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}),
199
+ hooks,
200
+ mcpAuthProviders,
201
+ });
190
202
  const agentSlice = this.router.registerAgent(agentId);
191
203
  // Forward-compat: register the agent against the wire-communication router
192
204
  // too. Today every WireCommunicationEvent lands on the unrouted slice (no
@@ -194,7 +206,7 @@ export class DefaultAgentManager {
194
206
  // symmetrically with the telemetry router so the wiring is in place if/when
195
207
  // the event shape grows an agentId field.
196
208
  this.wireRouter.registerAgent(agentId);
197
- const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.modelConnectivityInfo, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.harnessSupportedProviderHints, this.resolvers, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
209
+ const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, options.consumerMetadata, runtime.modelConnectivityInfo, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.harnessSupportedProviderHints, this.resolvers, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
198
210
  this.agents.set(agentId, agent);
199
211
  const agentCreatedAt = this.clock.now();
200
212
  const modelName = runtime.modelConnectivityInfo.model.name;
package/dist/agent.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { type Clock, type JSONWebToken, LogBus, type LogRecord, type OrgConnection, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
2
2
  import type { AgentHarness } from './harness/agent-harness.js';
3
- import { type AgentConfig, type McpAuthProviderResolver } from './harness/harness-config.js';
3
+ import { type AgentConfig, type AgentStateUpdate, type McpAuthProviderResolver } from './harness/harness-config.js';
4
4
  import { type ChatSession } from './chat-session.js';
5
+ import type { JsonValue } from './types/session-context.js';
5
6
  import type { McpServerInfo } from './mcp-config.js';
6
7
  import type { AgentConnectivityResolver } from './agent-connectivity-resolver.js';
7
8
  import type { ModelConnectivityInfo, ProviderHint } from './types/model-connectivity-info.js';
@@ -40,6 +41,13 @@ export interface Agent {
40
41
  getOrgConnection(): OrgConnection | undefined;
41
42
  /** Returns the current agent configuration. */
42
43
  getAgentConfig(): AgentConfig;
44
+ /**
45
+ * Returns a deep copy of the agent's persisted consumer metadata, or
46
+ * `undefined` if none has been set. This is SDK-owned opaque JSON: it is
47
+ * never placed on {@link AgentConfig}, never forwarded to the harness, and
48
+ * never appears on the REST wire. Set it via {@link updateAgentState}.
49
+ */
50
+ getConsumerMetadata(): JsonValue | undefined;
43
51
  /**
44
52
  * Returns runtime information about MCP servers attached to this agent,
45
53
  * including connection status and discovered tool names. This is a synchronous
@@ -97,6 +105,21 @@ export interface Agent {
97
105
  abortSignal?: AbortSignal;
98
106
  forceResolve?: boolean;
99
107
  }): Promise<void>;
108
+ /**
109
+ * Atomically update the agent's configuration and/or its persisted consumer
110
+ * metadata. `update.config` is merged and applied to the harness exactly as
111
+ * {@link updateAgentConfig} does; `update.consumerMetadata` mutates the
112
+ * SDK-owned opaque metadata. When both are present they commit in one
113
+ * persistence write, so a failed update leaves config and metadata at their
114
+ * previous values together. A metadata-only update never calls the harness.
115
+ *
116
+ * @param update - The config partial and/or consumer-metadata mutation to apply.
117
+ * @param options - Same execution options as {@link updateAgentConfig}.
118
+ */
119
+ updateAgentState(update: AgentStateUpdate, options?: {
120
+ abortSignal?: AbortSignal;
121
+ forceResolve?: boolean;
122
+ }): Promise<void>;
100
123
  /**
101
124
  * Create a new chat session (conversation thread) for this agent.
102
125
  * @returns A new {@link ChatSession} with a unique thread ID.
@@ -162,6 +185,7 @@ export declare class DefaultAgent implements Agent {
162
185
  private readonly agentId;
163
186
  private readonly projectRoot;
164
187
  private config;
188
+ private consumerMetadata;
165
189
  private modelConnectivityInfo;
166
190
  private orgConnection;
167
191
  private orgJwt;
@@ -184,6 +208,8 @@ export declare class DefaultAgent implements Agent {
184
208
  * @param agentId - Unique identifier for this agent.
185
209
  * @param projectRoot - Project folder this agent is allowed to operate within.
186
210
  * @param config - Initial agent configuration (instructions, model, tools, etc.).
211
+ * @param consumerMetadata - Initial SDK-owned opaque consumer metadata (or `undefined`). Persisted beside
212
+ * `config`; never forwarded to the harness. Mutated via `updateAgentState`.
187
213
  * @param modelConnectivityInfo - Connectivity bag (model, baseUrl, nativeModelId,
188
214
  * providerHint, getHeaders) the harness uses to talk to the LLM. Replaced
189
215
  * on every `updateAgentConfig` re-resolve.
@@ -201,7 +227,7 @@ export declare class DefaultAgent implements Agent {
201
227
  * @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
202
228
  * @param parent - Manager's bus pair; this agent forwards its events upward into them.
203
229
  */
204
- constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, modelConnectivityInfo: ModelConnectivityInfo, orgConnection: OrgConnection | undefined, orgJwt: JSONWebToken | undefined, agentConnectivityResolver: AgentConnectivityResolver, harnessSupportedProviderHints: readonly ProviderHint[], resolvers: AgentRuntimeResolvers, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
230
+ constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, consumerMetadata: JsonValue | undefined, modelConnectivityInfo: ModelConnectivityInfo, orgConnection: OrgConnection | undefined, orgJwt: JSONWebToken | undefined, agentConnectivityResolver: AgentConnectivityResolver, harnessSupportedProviderHints: readonly ProviderHint[], resolvers: AgentRuntimeResolvers, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
205
231
  /**
206
232
  * @requirements
207
233
  * - MUST return the agent's ID.
@@ -215,6 +241,12 @@ export declare class DefaultAgent implements Agent {
215
241
  * - MUST return a shallow copy of the internal `config` object to prevent external mutation of the agent's state.
216
242
  */
217
243
  getAgentConfig(): AgentConfig;
244
+ /**
245
+ * @requirements
246
+ * - MUST return a deep copy so a caller cannot mutate the agent's persisted consumer metadata in place.
247
+ * - MUST return `undefined` when no metadata has been set.
248
+ */
249
+ getConsumerMetadata(): JsonValue | undefined;
218
250
  getMcpServerInfo(): McpServerInfo[];
219
251
  reconnectMcpServer(serverName: string): Promise<void>;
220
252
  /**
@@ -237,6 +269,27 @@ export declare class DefaultAgent implements Agent {
237
269
  abortSignal?: AbortSignal;
238
270
  forceResolve?: boolean;
239
271
  }): Promise<void>;
272
+ /**
273
+ * @requirements
274
+ * - MUST merge `update.config` (when present) with the internal `config` object; `agentId` stays unchanged.
275
+ * - MUST apply the merged config to the harness via `this.harness.updateAgent(...)` ONLY when `update.config`
276
+ * is present. A metadata-only update MUST NOT call the harness at all (no server cycling, no resolver run).
277
+ * - MUST compute the next consumer metadata from `update.consumerMetadata`: `replace` stores a deep copy of the
278
+ * value; `clear` removes it; an absent mutation leaves it unchanged.
279
+ * - MUST persist config + metadata together in ONE `this.identityStore.write(...)` after any `harness.updateAgent`
280
+ * succeeds and before the in-memory swaps, so a write failure rolls back through the same catch path.
281
+ * - MUST preserve BOTH the previous config AND the previous metadata if `updateAgent` or persistence fails.
282
+ */
283
+ updateAgentState(update: AgentStateUpdate, options?: {
284
+ abortSignal?: AbortSignal;
285
+ forceResolve?: boolean;
286
+ }): Promise<void>;
287
+ /**
288
+ * Writes the identity record, calling `identityStore.write` with a 4th argument only when consumer metadata is
289
+ * present. Omitting the argument (rather than passing `undefined`) keeps a config-only write byte-identical to the
290
+ * pre-metadata call shape and clears any previously-persisted metadata.
291
+ */
292
+ private writeIdentity;
240
293
  /**
241
294
  * @requirements
242
295
  * - MUST delegate to `this.harness.createThread(this.config.agentId)` to generate a new thread ID.
package/dist/agent.js CHANGED
@@ -3,11 +3,22 @@
3
3
  * See LICENSE.txt for license terms.
4
4
  */
5
5
  import { LogBus, RealClock, UUIDGenerator, } from '@salesforce/agentic-common';
6
- import { toHarnessConfig } from './harness/harness-config.js';
6
+ import { toHarnessConfig, } from './harness/harness-config.js';
7
7
  import { normalizeMcpAuthProviders } from './mcp-auth.js';
8
8
  import { DefaultChatSession } from './chat-session.js';
9
9
  import { AgentSDKError, AgentSDKErrorType } from './errors.js';
10
10
  import { createTelemetryBus } from './types/telemetry-events.js';
11
+ /**
12
+ * Resolves the next consumer-metadata value from the current value and an optional mutation. `replace` deep-clones the
13
+ * incoming value so the stored copy is isolated from later caller mutation; `clear` yields `undefined`; an absent
14
+ * mutation leaves the current value untouched.
15
+ */
16
+ function applyConsumerMetadataMutation(current, mutation) {
17
+ if (mutation === undefined) {
18
+ return current;
19
+ }
20
+ return mutation.action === 'replace' ? structuredClone(mutation.value) : undefined;
21
+ }
11
22
  /**
12
23
  * Default implementation of {@link Agent} that delegates
13
24
  * agent and thread operations to an {@link AgentHarness}.
@@ -17,6 +28,7 @@ export class DefaultAgent {
17
28
  agentId;
18
29
  projectRoot;
19
30
  config;
31
+ consumerMetadata;
20
32
  modelConnectivityInfo;
21
33
  orgConnection;
22
34
  orgJwt;
@@ -41,6 +53,8 @@ export class DefaultAgent {
41
53
  * @param agentId - Unique identifier for this agent.
42
54
  * @param projectRoot - Project folder this agent is allowed to operate within.
43
55
  * @param config - Initial agent configuration (instructions, model, tools, etc.).
56
+ * @param consumerMetadata - Initial SDK-owned opaque consumer metadata (or `undefined`). Persisted beside
57
+ * `config`; never forwarded to the harness. Mutated via `updateAgentState`.
44
58
  * @param modelConnectivityInfo - Connectivity bag (model, baseUrl, nativeModelId,
45
59
  * providerHint, getHeaders) the harness uses to talk to the LLM. Replaced
46
60
  * on every `updateAgentConfig` re-resolve.
@@ -58,11 +72,12 @@ export class DefaultAgent {
58
72
  * @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
59
73
  * @param parent - Manager's bus pair; this agent forwards its events upward into them.
60
74
  */
61
- constructor(harness, agentId, projectRoot, config, modelConnectivityInfo, orgConnection, orgJwt, agentConnectivityResolver, harnessSupportedProviderHints, resolvers, identityStore, router, inbound, parent, clock = new RealClock(), idGenerator = new UUIDGenerator()) {
75
+ constructor(harness, agentId, projectRoot, config, consumerMetadata, modelConnectivityInfo, orgConnection, orgJwt, agentConnectivityResolver, harnessSupportedProviderHints, resolvers, identityStore, router, inbound, parent, clock = new RealClock(), idGenerator = new UUIDGenerator()) {
62
76
  this.harness = harness;
63
77
  this.agentId = agentId;
64
78
  this.projectRoot = projectRoot;
65
79
  this.config = config;
80
+ this.consumerMetadata = consumerMetadata;
66
81
  this.modelConnectivityInfo = modelConnectivityInfo;
67
82
  this.orgConnection = orgConnection;
68
83
  this.orgJwt = orgJwt;
@@ -103,6 +118,15 @@ export class DefaultAgent {
103
118
  this.assertNotDisposed();
104
119
  return { ...this.config };
105
120
  }
121
+ /**
122
+ * @requirements
123
+ * - MUST return a deep copy so a caller cannot mutate the agent's persisted consumer metadata in place.
124
+ * - MUST return `undefined` when no metadata has been set.
125
+ */
126
+ getConsumerMetadata() {
127
+ this.assertNotDisposed();
128
+ return this.consumerMetadata === undefined ? undefined : structuredClone(this.consumerMetadata);
129
+ }
106
130
  getMcpServerInfo() {
107
131
  this.assertNotDisposed();
108
132
  return this.harness.getMcpServerInfo(this.agentId);
@@ -128,11 +152,41 @@ export class DefaultAgent {
128
152
  * against its current (possibly partially-updated) state and reverts only the actual deltas.
129
153
  */
130
154
  async updateAgentConfig(config = {}, options) {
155
+ return this.updateAgentState({ config }, options);
156
+ }
157
+ /**
158
+ * @requirements
159
+ * - MUST merge `update.config` (when present) with the internal `config` object; `agentId` stays unchanged.
160
+ * - MUST apply the merged config to the harness via `this.harness.updateAgent(...)` ONLY when `update.config`
161
+ * is present. A metadata-only update MUST NOT call the harness at all (no server cycling, no resolver run).
162
+ * - MUST compute the next consumer metadata from `update.consumerMetadata`: `replace` stores a deep copy of the
163
+ * value; `clear` removes it; an absent mutation leaves it unchanged.
164
+ * - MUST persist config + metadata together in ONE `this.identityStore.write(...)` after any `harness.updateAgent`
165
+ * succeeds and before the in-memory swaps, so a write failure rolls back through the same catch path.
166
+ * - MUST preserve BOTH the previous config AND the previous metadata if `updateAgent` or persistence fails.
167
+ */
168
+ async updateAgentState(update, options) {
131
169
  this.assertNotDisposed();
170
+ const { config, consumerMetadata: metadataMutation } = update;
132
171
  const previousConfig = { ...this.config };
172
+ const previousConsumerMetadata = this.consumerMetadata;
133
173
  const previousModelConnectivityInfo = this.modelConnectivityInfo;
134
174
  const previousOrgJwt = this.orgJwt;
135
- const nextConfig = { ...this.config, ...config };
175
+ const nextConfig = config === undefined ? this.config : { ...this.config, ...config };
176
+ const nextConsumerMetadata = applyConsumerMetadataMutation(this.consumerMetadata, metadataMutation);
177
+ // A metadata-only update never touches the harness, the resolver, or the connectivity bag — it just
178
+ // rewrites the persisted record and swaps the in-memory metadata.
179
+ if (config === undefined) {
180
+ try {
181
+ await this.writeIdentity(nextConfig, nextConsumerMetadata);
182
+ this.consumerMetadata = nextConsumerMetadata;
183
+ }
184
+ catch (error) {
185
+ this.consumerMetadata = previousConsumerMetadata;
186
+ throw error;
187
+ }
188
+ return;
189
+ }
136
190
  const orgAliasRequested = Object.prototype.hasOwnProperty.call(config, 'orgAlias');
137
191
  const modelIdRequested = Object.prototype.hasOwnProperty.call(config, 'modelId');
138
192
  let nextModelConnectivityInfo = previousModelConnectivityInfo;
@@ -169,8 +223,9 @@ export class DefaultAgent {
169
223
  // Persist before the in-memory swaps so a write failure flows through the same
170
224
  // catch block as an updateAgent failure: the rollback re-runs updateAgent against
171
225
  // previousConfig and disk state remains the pre-update record.
172
- await this.identityStore.write(this.agentId, this.projectRoot, nextConfig);
226
+ await this.writeIdentity(nextConfig, nextConsumerMetadata);
173
227
  this.config = nextConfig;
228
+ this.consumerMetadata = nextConsumerMetadata;
174
229
  this.modelConnectivityInfo = nextModelConnectivityInfo;
175
230
  this.orgConnection = nextConnection;
176
231
  this.orgJwt = nextOrgJwt;
@@ -191,6 +246,19 @@ export class DefaultAgent {
191
246
  throw error;
192
247
  }
193
248
  }
249
+ /**
250
+ * Writes the identity record, calling `identityStore.write` with a 4th argument only when consumer metadata is
251
+ * present. Omitting the argument (rather than passing `undefined`) keeps a config-only write byte-identical to the
252
+ * pre-metadata call shape and clears any previously-persisted metadata.
253
+ */
254
+ async writeIdentity(config, consumerMetadata) {
255
+ if (consumerMetadata === undefined) {
256
+ await this.identityStore.write(this.agentId, this.projectRoot, config);
257
+ }
258
+ else {
259
+ await this.identityStore.write(this.agentId, this.projectRoot, config, consumerMetadata);
260
+ }
261
+ }
194
262
  /**
195
263
  * @requirements
196
264
  * - MUST delegate to `this.harness.createThread(this.config.agentId)` to generate a new thread ID.
@@ -2,6 +2,7 @@ import type { Decision, ToolDefinition, ToolPolicyRule } from '../types/tools.js
2
2
  import type { MCPConfiguration, McpAuthProviders } from '../mcp-config.js';
3
3
  import type { JSONWebToken } from '@salesforce/agentic-common';
4
4
  import type { Model, ModelName } from '../models/index.js';
5
+ import type { JsonValue } from '../types/session-context.js';
5
6
  /**
6
7
  * Configuration for an agent's behavior and capabilities.
7
8
  * This excludes identity; `agentId` is handled separately.
@@ -140,6 +141,42 @@ export type HarnessAgentConfig = Omit<AgentConfig, 'orgAlias'> & {
140
141
  * `test/harness/harness-config.test.ts` that asserts unknown fields survive.
141
142
  */
142
143
  export declare function toHarnessConfig(config: AgentConfig, orgJwt?: JSONWebToken): HarnessAgentConfig;
144
+ /**
145
+ * A mutation applied to an agent's persisted consumer metadata by
146
+ * {@link AgentStateUpdate}. Two explicit actions rather than a bare value so
147
+ * "set it to this" is distinguishable from "delete it": `undefined` reads as
148
+ * "field omitted / leave unchanged" and `null` is a legal JSON value a
149
+ * consumer may legitimately want to store, so neither can double as a clear
150
+ * signal.
151
+ *
152
+ * - `replace` — set the metadata to `value` (stored as an opaque deep copy).
153
+ * - `clear` — remove the metadata entirely (a subsequent
154
+ * {@link Agent.getConsumerMetadata} returns `undefined`).
155
+ */
156
+ export type ConsumerMetadataMutation = {
157
+ action: 'replace';
158
+ value: JsonValue;
159
+ } | {
160
+ action: 'clear';
161
+ };
162
+ /**
163
+ * A single atomic update to an agent's persisted state, applied by
164
+ * {@link Agent.updateAgentState}. Either or both members may be present:
165
+ *
166
+ * - `config` — a partial {@link AgentConfig} merged into the live config and
167
+ * applied to the harness (identical semantics to {@link Agent.updateAgentConfig}).
168
+ * - `consumerMetadata` — a {@link ConsumerMetadataMutation} on the agent's
169
+ * opaque consumer metadata. This metadata is SDK-owned persistence only: it
170
+ * is never placed on {@link AgentConfig}, never passed through
171
+ * {@link toHarnessConfig}, and never forwarded to the harness.
172
+ *
173
+ * When both are present the SDK commits them in one persistence write, so a
174
+ * failed update leaves config and metadata at their previous values together.
175
+ */
176
+ export type AgentStateUpdate = {
177
+ config?: AgentConfig;
178
+ consumerMetadata?: ConsumerMetadataMutation;
179
+ };
143
180
  /**
144
181
  * Per-call options controlling streaming behavior.
145
182
  */
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export { BUILT_IN_TOOL_POLICIES, SKILL_BRIDGE_SERVER_ID, definePolicy, matcherMa
6
6
  export type { ResolverResult, ResolverTiers, ToolInvocation } from './policy-resolver.js';
7
7
  export type { ContextUsage, FinishReason, UsageMetadata } from './types/usage.js';
8
8
  export type { AgentHooks, HooksForAgent, ToolResultRedactor, ToolResultRedactionInput, ToolResultRedactionResult, } from './types/redaction.js';
9
- export type { AgentConfig, HarnessAgentConfig, McpAuthProviderResolver, StreamOptions, } from './harness/harness-config.js';
9
+ export type { AgentConfig, AgentStateUpdate, ConsumerMetadataMutation, HarnessAgentConfig, McpAuthProviderResolver, StreamOptions, } from './harness/harness-config.js';
10
10
  export { DEFAULT_MAX_STEPS } from './harness/harness-config.js';
11
11
  export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpOAuthClientProvider, McpAuthProviders, McpServerInfo, McpServerErrorCategory, McpServerErrorDetail, McpToolInfo, McpToolAnnotations, } from './mcp-config.js';
12
12
  export { McpServerStatus, mcpServerConfigEqual } from './mcp-config.js';
@@ -1,9 +1,11 @@
1
1
  import { type LogBus } from '@salesforce/agentic-common';
2
2
  import type { AgentConfig } from '../harness/harness-config.js';
3
+ import type { JsonValue } from '../types/session-context.js';
3
4
  export type AgentIdentityRecord = {
4
5
  agentId: string;
5
6
  projectRoot: string;
6
7
  config: AgentConfig;
8
+ consumerMetadata?: JsonValue;
7
9
  };
8
10
  /**
9
11
  * SDK-owned persistence for the agent-identity triple
@@ -33,7 +35,7 @@ export declare class AgentIdentityStore {
33
35
  */
34
36
  private readonly inflightWrites;
35
37
  constructor(storageRootFolder: string, harnessId: string, logBus: LogBus);
36
- write(agentId: string, projectRoot: string, config: AgentConfig): Promise<void>;
38
+ write(agentId: string, projectRoot: string, config: AgentConfig, consumerMetadata?: JsonValue): Promise<void>;
37
39
  private writeImmediate;
38
40
  remove(agentId: string): Promise<void>;
39
41
  list(): Promise<AgentIdentityRecord[]>;
@@ -39,11 +39,13 @@ export class AgentIdentityStore {
39
39
  this.harnessId = harnessId;
40
40
  this.logBus = logBus;
41
41
  }
42
- async write(agentId, projectRoot, config) {
42
+ async write(agentId, projectRoot, config, consumerMetadata) {
43
43
  const previous = this.inflightWrites.get(agentId) ?? Promise.resolve();
44
44
  // `.catch(() => undefined)` so a previous failure doesn't poison the next caller's await.
45
45
  // Each caller still observes its own write's success or failure via the returned promise.
46
- const next = previous.catch(() => undefined).then(() => this.writeImmediate(agentId, projectRoot, config));
46
+ const next = previous
47
+ .catch(() => undefined)
48
+ .then(() => this.writeImmediate(agentId, projectRoot, config, consumerMetadata));
47
49
  this.inflightWrites.set(agentId, next);
48
50
  try {
49
51
  await next;
@@ -56,7 +58,7 @@ export class AgentIdentityStore {
56
58
  }
57
59
  }
58
60
  }
59
- async writeImmediate(agentId, projectRoot, config) {
61
+ async writeImmediate(agentId, projectRoot, config, consumerMetadata) {
60
62
  const dir = this.dir();
61
63
  await mkdir(dir, { recursive: true });
62
64
  const payload = {
@@ -65,6 +67,7 @@ export class AgentIdentityStore {
65
67
  agentId,
66
68
  projectRoot,
67
69
  config,
70
+ ...(consumerMetadata !== undefined ? { consumerMetadata } : {}),
68
71
  };
69
72
  const target = join(dir, `${agentId}.json`);
70
73
  const tmp = `${target}.tmp`;
@@ -130,6 +133,7 @@ export class AgentIdentityStore {
130
133
  agentId: parsed.agentId,
131
134
  projectRoot: parsed.projectRoot,
132
135
  config: parsed.config,
136
+ ...(parsed.consumerMetadata !== undefined ? { consumerMetadata: parsed.consumerMetadata } : {}),
133
137
  });
134
138
  }
135
139
  return records;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.52.0",
3
+ "version": "0.53.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.48.0",
51
- "@salesforce/sfdx-agent-harness-mastra": "0.51.0",
52
- "@salesforce/sfdx-agent-harness-openai": "0.17.0",
50
+ "@salesforce/sfdx-agent-harness-claude": "0.49.0",
51
+ "@salesforce/sfdx-agent-harness-mastra": "0.52.0",
52
+ "@salesforce/sfdx-agent-harness-openai": "0.18.0",
53
53
  "@types/node": "^22.20.1",
54
54
  "@vitest/coverage-istanbul": "^4.1.10",
55
55
  "@vitest/eslint-plugin": "^1.6.26",