@salesforce/sfdx-agent-harness-claude 0.28.0 → 0.29.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,21 @@
3
3
  All notable changes to `@salesforce/sfdx-agent-harness-claude` are documented in this file.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.29.0] - 2026-07-14
7
+
8
+ ### Features
9
+ - **agent-sdk,harness-mastra,harness-claude**: cross-harness conformance test suite @W-23372341@ ([#663](https://github.com/forcedotcom/agentic-dx/pull/663))
10
+ - **sdk,harness-mastra,harness-claude**: delete deprecated tool-approval surfaces @W-23090034 ([#637](https://github.com/forcedotcom/agentic-dx/pull/637))
11
+
12
+ ### Fixes
13
+ - **harness-conformance**: static version instead of -dev.N so release check passes @W-23446682@ ([#670](https://github.com/forcedotcom/agentic-dx/pull/670))
14
+
15
+ ### Chores
16
+ - **deps-dev**: bump typescript-eslint from 8.62.1 to 8.63.0 in the dev-dependencies group across 1 directory ([#665](https://github.com/forcedotcom/agentic-dx/pull/665))
17
+ - **deps-dev**: bump the eslint group across 1 directory with 2 updates ([#659](https://github.com/forcedotcom/agentic-dx/pull/659))
18
+ - **deps-dev**: bump @vitest/eslint-plugin from 1.6.22 to 1.6.23 in the vitest group ([#660](https://github.com/forcedotcom/agentic-dx/pull/660))
19
+ - **deps-dev**: bump the vitest group across 1 directory with 3 updates ([#655](https://github.com/forcedotcom/agentic-dx/pull/655))
20
+
6
21
  ## [0.28.0] - 2026-07-08
7
22
 
8
23
  ### Fixes
package/README.md CHANGED
@@ -29,8 +29,8 @@ const manager = await createAgentManager('/path/to/storage', harnessFactory, {
29
29
 
30
30
  Connectivity (gateway URL, JWT, headers, native model id) is resolved by an `AgentConnectivityResolver` on the SDK and
31
31
  handed to the harness as a `ModelConnectivityInfo` bag. The harness factory itself owns harness-only concerns
32
- (permission mode, tool-approval timeout, bypass list, query defaults). For BYOK / direct-Anthropic / LLMG-Express
33
- deployments use `ApiKeyConnectivityResolver`; for non-Salesforce hosts implement your own resolver. See the
32
+ (permission mode, tool-approval timeout, query defaults). For BYOK / direct-Anthropic / LLMG-Express deployments use
33
+ `ApiKeyConnectivityResolver`; for non-Salesforce hosts implement your own resolver. See the
34
34
  [`@salesforce/sfdx-agent-sdk` README](../sfdx-agent-sdk/README.md) for the full resolver surface.
35
35
 
36
36
  See the [`@salesforce/sfdx-agent-sdk` README](../sfdx-agent-sdk/README.md) for the full consumer-facing API. For
@@ -41,7 +41,7 @@ internal architecture, see [ARCHITECTURE.md](ARCHITECTURE.md).
41
41
  | Export | Kind | Description |
42
42
  | ------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
43
43
  | `ClaudeHarnessFactory` | class | `HarnessFactory` that produces a Claude-backed `AgentHarness` bound to a storage root. |
44
- | `ClaudeHarnessFactoryConfig` | interface | Configuration for `ClaudeHarnessFactory` (`permissionMode`, optional `queryDefaults`, `toolApprovalTimeoutMs`, deprecated `bypassApprovalTools`, `subprocessEnv`). |
44
+ | `ClaudeHarnessFactoryConfig` | interface | Configuration for `ClaudeHarnessFactory` (`permissionMode`, optional `queryDefaults`, `toolApprovalTimeoutMs`, `subprocessEnv`). |
45
45
  | `CLAUDE_BUILT_IN_TOOL_POLICIES` | const | Frozen `ReadonlyArray<ToolPolicyRule>` the harness feeds the SDK's `resolveToolApprovalPolicy` as the `tiers.harness` slice — `require-approval` rules for Claude's mutating built-ins `Bash` / `Edit` / `MultiEdit` / `Write` / `NotebookEdit`. |
46
46
  | `CLAUDE_BUILT_IN_TOOL_NAMES` | const | The five built-in tool names `CLAUDE_BUILT_IN_TOOL_POLICIES` covers. |
47
47
  | `ClaudeHarnessPermissionMode` | type | Narrowed permission mode: `'bypassPermissions' \| 'acceptEdits' \| 'plan'`. |
@@ -172,19 +172,14 @@ decision site: for each non-consumer tool it resolves a decision and acts on it:
172
172
  - **`require-approval`** — a `tool-approval-request` is emitted and the SDK hook parks until the consumer settles via
173
173
  `chatSession.approveToolCall(toolCallId)` / `declineToolCall(toolCallId)`.
174
174
 
175
- Gating is **opt-in**: it engages only when the agent's config sets `toolPolicies` or `defaultToolDecision`, or the
176
- `chat()` call sets the deprecated `requireToolApproval` or `batchApprovals`. With none configured, tools run without
177
- gating (no `PreToolUse` hook is installed), matching the SDK's "no policy ⇒ no gating" default. The harness adds
178
- `CLAUDE_BUILT_IN_TOOL_POLICIES` (`require-approval` rules for the mutating built-ins `Bash` / `Edit` / `MultiEdit` /
179
- `Write` / `NotebookEdit`) as the `tiers.harness` slice and the SDK's `skill_bridge` meta-tool rules as `allow` defaults,
180
- so capability-discovery never prompts and read-only built-ins (`Read` / `Glob` / `Grep` / `Task`) run free by absence.
181
- Consumer-executed tools (`AgentConfig.tools`) always bypass the gate — their execution is the consumer's responsibility
182
- via `submitToolResult`. Set `StreamOptions.batchApprovals: true` to surface parallel `require-approval` requests on one
183
- stream for a batch-approval UI.
184
-
185
- > `StreamOptions.requireToolApproval` is **deprecated** — superseded by per-tool `AgentConfig.toolPolicies`. It still
186
- > works for one release: a truthy value engages all-or-nothing gating (every non-consumer tool requires approval), and
187
- > `'batch'` maps onto `batchApprovals`.
175
+ Gating is **opt-in**: it engages only when the agent's config sets `toolPolicies` or `defaultToolDecision`. With none
176
+ configured, tools run without gating (no `PreToolUse` hook is installed), matching the SDK's "no policy ⇒ no gating"
177
+ default. The harness adds `CLAUDE_BUILT_IN_TOOL_POLICIES` (`require-approval` rules for the mutating built-ins `Bash` /
178
+ `Edit` / `MultiEdit` / `Write` / `NotebookEdit`) as the `tiers.harness` slice and the SDK's `skill_bridge` meta-tool
179
+ rules as `allow` defaults, so capability-discovery never prompts and read-only built-ins (`Read` / `Glob` / `Grep` /
180
+ `Task`) run free by absence. Consumer-executed tools (`AgentConfig.tools`) always bypass the gate — their execution is
181
+ the consumer's responsibility via `submitToolResult`. Set `StreamOptions.batchApprovals: true` to surface parallel
182
+ `require-approval` requests on one stream for a batch-approval UI.
188
183
 
189
184
  Unlike Mastra (which suspends every tool and re-stamps a declined native result), Claude's `deny` is the SDK's native
190
185
  `permissionDecision: 'deny'` — the Claude Agent SDK synthesizes the `tool_result(isError=true)` itself, so no
@@ -206,7 +201,7 @@ patterns. Single-tool approve / decline, parallel batches (`batchApprovals: true
206
201
 
207
202
  #### Abort signal
208
203
 
209
- When `StreamOptions.abortSignal` fires during an active turn with `requireToolApproval: true`:
204
+ When `StreamOptions.abortSignal` fires during an active turn with approval gating on:
210
205
 
211
206
  - All pending permission Promises reject with an `AbortError`.
212
207
  - The underlying Claude subprocess is killed (existing `AbortController` wiring).
@@ -391,45 +386,16 @@ const agent = await manager.createAgent(projectRoot, {
391
386
  // `tool-approval-request`, regardless of any policy configured.
392
387
  ```
393
388
 
394
- This is the recommended pattern when the consumer wants identical behavior across Mastra and Claude. For Claude-only
395
- consumers who prefer to drive the built-in directly, see "Bypassing the approval gate for specific tools" below.
396
-
397
- ### Bypassing the approval gate for specific tools (deprecated)
398
-
399
- > **Deprecated.** `ClaudeHarnessFactoryConfig.bypassApprovalTools` is superseded by per-tool `AgentConfig.toolPolicies`
400
- > (e.g. `definePolicy({ AskUserQuestion: 'allow' })`). It still works for one release — the harness runtime-merges the
401
- > list into the policy resolver as synthetic `'allow'` rules (the `tiers.factory` slice) and logs a once-per-process
402
- > warning pointing at `toolPolicies`. It is removed in a later phase. Prefer `toolPolicies` for new code.
403
-
404
- When gating is active, every built-in / MCP tool call reaches the `PreToolUse` hook, which resolves it against the
405
- policy resolver. (Consumer-declared tools from `AgentConfig.tools[i]` always bypass — see "Tool-approval policy" above.)
406
- `bypassApprovalTools` opts specific tool names to `'allow'`:
407
-
408
- ```ts
409
- const harnessFactory = new ClaudeHarnessFactory({
410
- permissionMode: 'bypassPermissions',
411
- // DEPRECATED — equivalent to:
412
- // toolPolicies: definePolicy({ AskUserQuestion: 'allow' })
413
- // Calls to these tools resolve to 'allow'; they still emit `tool-call` /
414
- // `tool-result` ChatEvents so the consumer can drive UX off the `tool-call`.
415
- bypassApprovalTools: ['AskUserQuestion'],
416
- });
417
- ```
418
-
419
- The names become synthetic `{ type: 'builtin', name, decision: 'allow' }` rules in the resolver's `tiers.factory` slice.
420
- They sit below the SDK built-ins and the harness built-ins but above consumer `toolPolicies`, so a consumer `deny` of
421
- the same tool still wins (cross-tier deny-wins). Bare-name form only — consumer-declared tools already bypass via the
422
- always-on consumer-tool prefix, so the old dual-form (`mcp__sfdx-agent-sdk-consumer-tools__<name>`) expansion is no
423
- longer needed.
389
+ This is the recommended pattern when the consumer wants identical behavior across Mastra and Claude. To auto-approve a
390
+ specific built-in the consumer drives directly (e.g. `AskUserQuestion`), add an `'allow'` rule for it to
391
+ `AgentConfig.toolPolicies` (e.g. `definePolicy({ AskUserQuestion: 'allow' })`) — it resolves to `'allow'` and still
392
+ emits `tool-call` / `tool-result` ChatEvents so the consumer can drive UX off the `tool-call`.
424
393
 
425
394
  > **Note:** `permissionMode: 'plan'` denies mutating built-ins (`Edit`, `Write`, `Bash`, …) at the SDK's permission
426
395
  > cache rather than running them natively. Resolving such a tool to `'allow'` does not "unlock" it under plan mode — the
427
396
  > SDK still denies it. Plan mode applies to the whole subprocess; the policy resolver only chooses whether the harness's
428
397
  > coordinator gates the call.
429
398
 
430
- When `bypassApprovalTools` is undefined or empty and no `toolPolicies` / `defaultToolDecision` is set, gating is off and
431
- no `PreToolUse` hook is installed — no behavior change for existing consumers. (Consumer-declared tools always bypass.)
432
-
433
399
  ### Tool-result redaction
434
400
 
435
401
  Tool-result redaction is configured at the manager layer, not the factory. Pass a `hooksForAgent` callback to
@@ -37,10 +37,10 @@ export interface ClaudeApprovalCoordinatorOptions {
37
37
  * `tool_result(is_error=true)` natively, so no re-stamp is needed);
38
38
  * `'require-approval'` ⇒ emit `tool-approval-request` and park. The harness
39
39
  * supplies this (closing over the SDK's `resolveToolApprovalPolicy` +
40
- * `CLAUDE_BUILT_IN_TOOL_POLICIES` + the deprecated `bypassApprovalTools`
41
- * factory tier) only when its opt-in gating predicate engaged; the hook is
42
- * wired iff this is present, so a missing resolver here can only be reached
43
- * defensively (it falls back to `'require-approval'`).
40
+ * `CLAUDE_BUILT_IN_TOOL_POLICIES`) only when its opt-in gating predicate
41
+ * engaged; the hook is wired iff this is present, so a missing resolver
42
+ * here can only be reached defensively (it falls back to
43
+ * `'require-approval'`).
44
44
  */
45
45
  lookupPolicy?: (invocation: ToolInvocation) => Decision;
46
46
  /**
@@ -27,9 +27,9 @@ import { GenSink } from '@salesforce/sfdx-agent-sdk/harness';
27
27
  * `chat()`. There are no per-tool sinks and no `lastGenSinkStale` flag.
28
28
  *
29
29
  * Consumer-executed tools bypass the approval gate by construction
30
- * (issue #529 step 2b): `claude-query-options-builder.ts` adds the
31
- * consumer-tool wire prefix `mcp__sfdx-agent-sdk-consumer-tools__.*` to
32
- * the `bypassApprovalTools` matcher unconditionally, so the SDK skips
30
+ * (issue #529 step 2b): `claude-query-options-builder.ts` anchors the
31
+ * consumer-tool wire prefix `mcp__sfdx-agent-sdk-consumer-tools__.*` into
32
+ * the `PreToolUse` matcher's negative lookahead, so the SDK skips
33
33
  * `PreToolUse` for consumer tools entirely. This coordinator therefore
34
34
  * never sees a `PreToolUse` invocation for a consumer tool — the parker
35
35
  * path handles their lifecycle alone.
@@ -5,36 +5,21 @@ import { type ClaudeAgentHarness } from './claude-harness.js';
5
5
  * Subset of the Claude SDK's `PermissionMode` enum that the harness supports.
6
6
  *
7
7
  * Excludes `'default'`, `'dontAsk'`, and `'auto'` because those require an
8
- * interactive prompt source that the harness does not provide. Use
9
- * `StreamOptions.requireToolApproval` for human-in-the-loop gating; the
10
- * harness overrides `permissionMode` to `'default'` per-turn when approval is
11
- * requested.
8
+ * interactive prompt source that the harness does not provide. Configure
9
+ * `AgentConfig.toolPolicies` / `defaultToolDecision` for human-in-the-loop
10
+ * gating; the harness wires the coordinator's `PreToolUse` hook per-turn when
11
+ * gating is active.
12
12
  */
13
13
  export type ClaudeHarnessPermissionMode = 'bypassPermissions' | 'acceptEdits' | 'plan';
14
14
  export interface ClaudeHarnessFactoryConfig {
15
15
  permissionMode: ClaudeHarnessPermissionMode;
16
16
  queryDefaults?: ClaudeQueryDefaults;
17
17
  /**
18
- * How long to wait for a `requireToolApproval` consumer to call
18
+ * How long to wait for a consumer to call
19
19
  * `approveToolCall`/`declineToolCall` before auto-denying with interrupt.
20
20
  * Defaults to `600_000` (10 minutes), matching the Mastra MCP timeout.
21
21
  */
22
22
  toolApprovalTimeoutMs?: number;
23
- /**
24
- * @deprecated Use `AgentConfig.toolPolicies` (e.g.
25
- * `definePolicy({ <tool>: 'allow' })`) instead. Runtime-merged into the
26
- * tool-approval policy resolver as synthetic `'allow'` rules during the
27
- * deprecation window; removed in Phase 4. Removing a name takes effect on
28
- * the next stream (not persisted). A once-per-process `LogBus.warn` fires
29
- * when a non-empty list is configured.
30
- *
31
- * Tool names that bypass the approval gate. Useful for built-ins like
32
- * `AskUserQuestion` where the consumer drives the UX off the `tool-call`
33
- * event directly and doesn't want a `tool-approval-request` for it. The
34
- * subprocess runs these tools natively (per the factory-pinned
35
- * `permissionMode`); they still emit `tool-call` / `tool-result` ChatEvents.
36
- */
37
- bypassApprovalTools?: string[];
38
23
  /**
39
24
  * Static operational environment variables to set on every Claude
40
25
  * subprocess spawn. Layered above the inherited host env (so these win
@@ -37,13 +37,7 @@ export class ClaudeHarnessFactory {
37
37
  // call site inside `ClaudeHarness` (createAgent, reconnect, updateAgent)
38
38
  // routes MCP HTTP transports through the same proxy-aware fetch.
39
39
  const mcpClientFactory = (config, orgJwt) => buildMcpClients(config, orgJwt, innerFetch);
40
- return new ClaudeHarness(envBuilder, optionsBuilder, this.config.toolApprovalTimeoutMs ?? DEFAULT_TOOL_APPROVAL_TIMEOUT_MS, storageRootFolder,
41
- // `bypassApprovalTools` is now consumed by the harness (folded into
42
- // the policy resolver as synthetic `tiers.factory` allow rules)
43
- // rather than baked into a constructor-time options-builder regex —
44
- // the matcher is per-stream now and the resolver is the decision
45
- // site. Deprecated; removed in Phase 4.
46
- { mcpClientFactory, bypassApprovalTools: this.config.bypassApprovalTools });
40
+ return new ClaudeHarness(envBuilder, optionsBuilder, this.config.toolApprovalTimeoutMs ?? DEFAULT_TOOL_APPROVAL_TIMEOUT_MS, storageRootFolder, { mcpClientFactory });
47
41
  }
48
42
  }
49
43
  //# sourceMappingURL=claude-harness-factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-harness-factory.js","sourceRoot":"","sources":["../src/claude-harness-factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAG3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,aAAa,EAA2B,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAa1D,MAAM,gCAAgC,GAAG,OAAO,CAAC;AAiDjD,MAAM,OAAO,oBAAoB;IAYA;IAXpB,SAAS,GAAG,QAAQ,CAAC;IACrB,eAAe,GAAG,CAAC,CAAC;IAC7B;;;;;;OAMG;IACM,sBAAsB,GAA4B,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAE9F,YAA6B,MAAkC;QAAlC,WAAM,GAAN,MAAM,CAA4B;IAAG,CAAC;IAEnE,KAAK,CAAC,MAAM,CAAC,iBAAyB;QAClC,MAAM,UAAU,GAAG,qBAAqB,CAAC,sBAAsB,EAAE,CAAC,CAAC;QACnE,MAAM,UAAU,GAAG,IAAI,qBAAqB,CAAC,iBAAiB,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QACtG,MAAM,kBAAkB,GAAG;YACvB,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc;YAC1C,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,KAAK,mBAAmB;gBAClD,CAAC,CAAC,EAAE,+BAA+B,EAAE,IAAa,EAAE;gBACpD,CAAC,CAAC,EAAE,CAAC;SACZ,CAAC;QACF,MAAM,cAAc,GAAG,IAAI,yBAAyB,CAAC,EAAE,GAAG,kBAAkB,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC;QAC9G,iEAAiE;QACjE,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,gBAAgB,GAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAChE,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;QAChD,OAAO,IAAI,aAAa,CACpB,UAAU,EACV,cAAc,EACd,IAAI,CAAC,MAAM,CAAC,qBAAqB,IAAI,gCAAgC,EACrE,iBAAiB;QACjB,oEAAoE;QACpE,gEAAgE;QAChE,oEAAoE;QACpE,iEAAiE;QACjE,wCAAwC;QACxC,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,mBAAmB,EAAE,CAC7E,CAAC;IACN,CAAC;CACJ"}
1
+ {"version":3,"file":"claude-harness-factory.js","sourceRoot":"","sources":["../src/claude-harness-factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAG3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,aAAa,EAA2B,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAa1D,MAAM,gCAAgC,GAAG,OAAO,CAAC;AAkCjD,MAAM,OAAO,oBAAoB;IAYA;IAXpB,SAAS,GAAG,QAAQ,CAAC;IACrB,eAAe,GAAG,CAAC,CAAC;IAC7B;;;;;;OAMG;IACM,sBAAsB,GAA4B,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAE9F,YAA6B,MAAkC;QAAlC,WAAM,GAAN,MAAM,CAA4B;IAAG,CAAC;IAEnE,KAAK,CAAC,MAAM,CAAC,iBAAyB;QAClC,MAAM,UAAU,GAAG,qBAAqB,CAAC,sBAAsB,EAAE,CAAC,CAAC;QACnE,MAAM,UAAU,GAAG,IAAI,qBAAqB,CAAC,iBAAiB,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QACtG,MAAM,kBAAkB,GAAG;YACvB,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc;YAC1C,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,KAAK,mBAAmB;gBAClD,CAAC,CAAC,EAAE,+BAA+B,EAAE,IAAa,EAAE;gBACpD,CAAC,CAAC,EAAE,CAAC;SACZ,CAAC;QACF,MAAM,cAAc,GAAG,IAAI,yBAAyB,CAAC,EAAE,GAAG,kBAAkB,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC;QAC9G,iEAAiE;QACjE,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,gBAAgB,GAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAChE,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;QAChD,OAAO,IAAI,aAAa,CACpB,UAAU,EACV,cAAc,EACd,IAAI,CAAC,MAAM,CAAC,qBAAqB,IAAI,gCAAgC,EACrE,iBAAiB,EACjB,EAAE,gBAAgB,EAAE,CACvB,CAAC;IACN,CAAC;CACJ"}
@@ -47,14 +47,6 @@ export interface ClaudeHarnessOptions {
47
47
  sessionStore?: DiskBackedSessionStore;
48
48
  mcpClientFactory?: typeof buildMcpClients;
49
49
  threadContextStore?: ThreadContextStore;
50
- /**
51
- * Deprecated `ClaudeHarnessFactoryConfig.bypassApprovalTools` names. The
52
- * factory threads them here so the harness can fold them into the policy
53
- * resolver as synthetic `'allow'` rules (the `tiers.factory` slice) during
54
- * the deprecation window — runtime-merged, not persisted, removed in Phase
55
- * 4. The deprecation `LogBus.warn` (once per process) fires from here.
56
- */
57
- bypassApprovalTools?: string[];
58
50
  }
59
51
  export declare class ClaudeHarness implements ClaudeAgentHarness {
60
52
  private readonly envBuilder;
@@ -94,13 +86,6 @@ export declare class ClaudeHarness implements ClaudeAgentHarness {
94
86
  private readonly mcpClientFactory;
95
87
  private readonly storageRootFolder;
96
88
  private shuttingDown;
97
- /**
98
- * Synthetic `'allow'` rules translated from the deprecated factory
99
- * `bypassApprovalTools` list — the `tiers.factory` slice passed to
100
- * `resolveToolApprovalPolicy`. Empty when no bypass list was configured;
101
- * removed entirely in Phase 4. See {@link ClaudeHarnessOptions.bypassApprovalTools}.
102
- */
103
- private readonly bypassApprovalToolsFactoryTier;
104
89
  constructor(envBuilder: ClaudeQueryEnvBuilder, optionsBuilder: ClaudeQueryOptionsBuilder, toolApprovalTimeoutMs: number, storageRootFolder: string, opts?: ClaudeHarnessOptions);
105
90
  private coordinatorKey;
106
91
  /**
@@ -328,31 +313,19 @@ export declare class ClaudeHarness implements ClaudeAgentHarness {
328
313
  */
329
314
  private buildPostToolUseRedactionHook;
330
315
  /**
331
- * Phase 2b: decide whether the approval gate engages this turn and, if so,
332
- * build the per-tool policy resolver the coordinator's `preToolUseHook`
333
- * consults. Near-verbatim port of `MastraHarness.buildPolicyGate` so both
334
- * harnesses share the same opt-in semantics and deprecation bridge.
316
+ * Decide whether the approval gate engages this turn and, if so, build the
317
+ * per-tool policy resolver the coordinator's `preToolUseHook` consults.
318
+ * Mirrors `MastraHarness.buildPolicyGate` so both harnesses share the same
319
+ * opt-in semantics.
335
320
  *
336
- * Gating is **opt-in**. It engages when the consumer set any of:
337
- * - `config.toolPolicies` (non-empty),
338
- * - `config.defaultToolDecision`,
339
- * - the deprecated `StreamOptions.requireToolApproval` (any non-`false`
340
- * value — `deprecatedMode` is `'serial'` / `'batch'` / `undefined`), or
341
- * - `StreamOptions.batchApprovals: true`.
321
+ * Gating is **opt-in**. It engages when the consumer set either:
322
+ * - `config.toolPolicies` (non-empty), or
323
+ * - `config.defaultToolDecision`.
342
324
  *
343
- * When none are set, returns `{ lookupPolicy: undefined }` — the caller
325
+ * When neither is set, returns `{ lookupPolicy: undefined }` — the caller
344
326
  * omits the `PreToolUse` hook and tools run free (the SDK's "no policy ⇒ no
345
327
  * gating" back-compat invariant), with no per-tool hook round-trip.
346
328
  *
347
- * **Deprecation bridge.** When the gate engages *only* because the
348
- * deprecated `requireToolApproval` was truthy (no `toolPolicies`, no
349
- * `defaultToolDecision`), the effective default must be `'require-approval'`
350
- * — legacy all-or-none — not the resolver's `'allow'` default. Built-in
351
- * allow-rules (the skill_bridge meta-tools) and any consumer `toolPolicies`
352
- * still win via within-tier last-wins / cross-tier deny-wins, so the
353
- * capability-discovery meta-tools stay auto-approved even under legacy
354
- * gating.
355
- *
356
329
  * **Live config read (#638).** `lookupPolicy` reads `agentState.config` on
357
330
  * every call rather than closing over a snapshot taken at stream start. A
358
331
  * mid-turn `updateAgentConfig` — notably the `source: 'remember'` rule an
@@ -363,22 +336,12 @@ export declare class ClaudeHarness implements ClaudeAgentHarness {
363
336
  * auto-resolve subsequent matching tool calls within the same turn. Mirrors
364
337
  * `MastraHarness.buildPolicyGate`.
365
338
  *
366
- * Two decisions stay **frozen at stream start** and MUST NOT be recomputed
367
- * live:
368
- * - `gatingActive` — the turn either installed the `PreToolUse` hook or it
369
- * didn't; gating cannot begin mid-turn. The closure is built (and
370
- * returned) iff gating engaged now.
371
- * - `applyLegacyBridge` (`!hasPolicy && legacyGating`) — whether the
372
- * deprecation bridge injects `defaultToolDecision: 'require-approval'`.
373
- * If this were recomputed live, the FIRST remembered rule would flip
374
- * `hasPolicy` false→true, turn the bridge OFF mid-turn, and silently drop
375
- * the effective default back to the resolver's `'allow'` — auto-approving
376
- * every remaining legacy-gated tool. Freezing the boolean and applying it
377
- * to the live config preserves legacy all-or-none semantics while still
378
- * honoring any rule the turn appends.
339
+ * `gatingActive` stays **frozen at stream start** — the turn either
340
+ * installed the `PreToolUse` hook or it didn't; gating cannot begin
341
+ * mid-turn. The closure is built (and returned) iff gating engaged now.
379
342
  *
380
- * `batchApprovals` is the serial-vs-batch emission axis: the explicit
381
- * `StreamOptions.batchApprovals` OR the legacy `requireToolApproval: 'batch'`.
343
+ * `batchApprovals` is the serial-vs-batch emission axis, read straight off
344
+ * `StreamOptions.batchApprovals`.
382
345
  */
383
346
  private buildPolicyGate;
384
347
  stream(agentId: string, threadId: string, message: string | MessagePart[], streamOptions?: StreamOptions): Promise<ChatStreamResult>;
@@ -5,7 +5,7 @@
5
5
  import { appendFile, mkdir, realpath } from 'node:fs/promises';
6
6
  import { join } from 'node:path';
7
7
  import { RealClock, UUIDGenerator, backfillCreatedAt, buildSummaryPrompt, getErrorMessage, isAbortError, } from '@salesforce/agentic-common';
8
- import { AgentSDKError, AgentSDKErrorType, McpServerStatus, resolveToolApprovalMode, resolveToolApprovalPolicy, } from '@salesforce/sfdx-agent-sdk';
8
+ import { AgentSDKError, AgentSDKErrorType, McpServerStatus, resolveToolApprovalPolicy, } from '@salesforce/sfdx-agent-sdk';
9
9
  import { HarnessBusOwner, lowerStreamInput, matchesAlwaysActive, mcpServerConfigEqual, splitToolResultsIntoToolMessages, validateAlwaysActiveEntry, } from '@salesforce/sfdx-agent-sdk/harness';
10
10
  import { validateMultimodalFiles } from '@salesforce/sfdx-agent-sdk';
11
11
  import { deleteSession, forkSession, getSessionMessages, listSessions, query, } from '@anthropic-ai/claude-agent-sdk';
@@ -86,13 +86,6 @@ export class ClaudeHarness {
86
86
  mcpClientFactory;
87
87
  storageRootFolder;
88
88
  shuttingDown = false;
89
- /**
90
- * Synthetic `'allow'` rules translated from the deprecated factory
91
- * `bypassApprovalTools` list — the `tiers.factory` slice passed to
92
- * `resolveToolApprovalPolicy`. Empty when no bypass list was configured;
93
- * removed entirely in Phase 4. See {@link ClaudeHarnessOptions.bypassApprovalTools}.
94
- */
95
- bypassApprovalToolsFactoryTier;
96
89
  constructor(envBuilder, optionsBuilder, toolApprovalTimeoutMs, storageRootFolder, opts = {}) {
97
90
  this.envBuilder = envBuilder;
98
91
  this.optionsBuilder = optionsBuilder;
@@ -109,28 +102,6 @@ export class ClaudeHarness {
109
102
  this.threadContextStore =
110
103
  opts.threadContextStore ?? new ThreadContextStore(storageRootFolder, this.buses.getLogBus());
111
104
  this.mcpClientFactory = opts.mcpClientFactory ?? buildMcpClients;
112
- // Translate the deprecated `bypassApprovalTools` factory list into
113
- // synthetic `'allow'` rules (the resolver's `tiers.factory` slice).
114
- // Bare-name `builtin` matchers only: the always-on consumer-tool prefix
115
- // bypass in the options builder's matcher already covers the
116
- // consumer-tool-by-name case the old dual-form expansion served, so the
117
- // bridge-prefixed form is unnecessary under the resolver. `source` is
118
- // `'built-in'` (not `'agent-config'`) because these are harness-injected
119
- // — "which tier fired" telemetry should attribute the bypass to the
120
- // harness, not the consumer's AgentConfig.
121
- const bypassNames = opts.bypassApprovalTools ?? [];
122
- this.bypassApprovalToolsFactoryTier = Object.freeze(bypassNames.map((name) => ({
123
- matcher: { type: 'builtin', name },
124
- decision: 'allow',
125
- source: 'built-in',
126
- })));
127
- if (bypassNames.length > 0) {
128
- this.buses
129
- .getLogBus()
130
- ?.warn('ClaudeHarnessFactoryConfig.bypassApprovalTools is deprecated; it is runtime-merged into the ' +
131
- 'tool-approval policy resolver as synthetic allow rules and will be removed. Migrate to ' +
132
- 'AgentConfig.toolPolicies (e.g. definePolicy({ <tool>: "allow" })).', { bypassApprovalTools: bypassNames });
133
- }
134
105
  }
135
106
  coordinatorKey(agentId, threadId) {
136
107
  return `${agentId}::${threadId}`;
@@ -1048,31 +1019,19 @@ export class ClaudeHarness {
1048
1019
  };
1049
1020
  }
1050
1021
  /**
1051
- * Phase 2b: decide whether the approval gate engages this turn and, if so,
1052
- * build the per-tool policy resolver the coordinator's `preToolUseHook`
1053
- * consults. Near-verbatim port of `MastraHarness.buildPolicyGate` so both
1054
- * harnesses share the same opt-in semantics and deprecation bridge.
1022
+ * Decide whether the approval gate engages this turn and, if so, build the
1023
+ * per-tool policy resolver the coordinator's `preToolUseHook` consults.
1024
+ * Mirrors `MastraHarness.buildPolicyGate` so both harnesses share the same
1025
+ * opt-in semantics.
1055
1026
  *
1056
- * Gating is **opt-in**. It engages when the consumer set any of:
1057
- * - `config.toolPolicies` (non-empty),
1058
- * - `config.defaultToolDecision`,
1059
- * - the deprecated `StreamOptions.requireToolApproval` (any non-`false`
1060
- * value — `deprecatedMode` is `'serial'` / `'batch'` / `undefined`), or
1061
- * - `StreamOptions.batchApprovals: true`.
1027
+ * Gating is **opt-in**. It engages when the consumer set either:
1028
+ * - `config.toolPolicies` (non-empty), or
1029
+ * - `config.defaultToolDecision`.
1062
1030
  *
1063
- * When none are set, returns `{ lookupPolicy: undefined }` — the caller
1031
+ * When neither is set, returns `{ lookupPolicy: undefined }` — the caller
1064
1032
  * omits the `PreToolUse` hook and tools run free (the SDK's "no policy ⇒ no
1065
1033
  * gating" back-compat invariant), with no per-tool hook round-trip.
1066
1034
  *
1067
- * **Deprecation bridge.** When the gate engages *only* because the
1068
- * deprecated `requireToolApproval` was truthy (no `toolPolicies`, no
1069
- * `defaultToolDecision`), the effective default must be `'require-approval'`
1070
- * — legacy all-or-none — not the resolver's `'allow'` default. Built-in
1071
- * allow-rules (the skill_bridge meta-tools) and any consumer `toolPolicies`
1072
- * still win via within-tier last-wins / cross-tier deny-wins, so the
1073
- * capability-discovery meta-tools stay auto-approved even under legacy
1074
- * gating.
1075
- *
1076
1035
  * **Live config read (#638).** `lookupPolicy` reads `agentState.config` on
1077
1036
  * every call rather than closing over a snapshot taken at stream start. A
1078
1037
  * mid-turn `updateAgentConfig` — notably the `source: 'remember'` rule an
@@ -1083,49 +1042,26 @@ export class ClaudeHarness {
1083
1042
  * auto-resolve subsequent matching tool calls within the same turn. Mirrors
1084
1043
  * `MastraHarness.buildPolicyGate`.
1085
1044
  *
1086
- * Two decisions stay **frozen at stream start** and MUST NOT be recomputed
1087
- * live:
1088
- * - `gatingActive` — the turn either installed the `PreToolUse` hook or it
1089
- * didn't; gating cannot begin mid-turn. The closure is built (and
1090
- * returned) iff gating engaged now.
1091
- * - `applyLegacyBridge` (`!hasPolicy && legacyGating`) — whether the
1092
- * deprecation bridge injects `defaultToolDecision: 'require-approval'`.
1093
- * If this were recomputed live, the FIRST remembered rule would flip
1094
- * `hasPolicy` false→true, turn the bridge OFF mid-turn, and silently drop
1095
- * the effective default back to the resolver's `'allow'` — auto-approving
1096
- * every remaining legacy-gated tool. Freezing the boolean and applying it
1097
- * to the live config preserves legacy all-or-none semantics while still
1098
- * honoring any rule the turn appends.
1045
+ * `gatingActive` stays **frozen at stream start** — the turn either
1046
+ * installed the `PreToolUse` hook or it didn't; gating cannot begin
1047
+ * mid-turn. The closure is built (and returned) iff gating engaged now.
1099
1048
  *
1100
- * `batchApprovals` is the serial-vs-batch emission axis: the explicit
1101
- * `StreamOptions.batchApprovals` OR the legacy `requireToolApproval: 'batch'`.
1049
+ * `batchApprovals` is the serial-vs-batch emission axis, read straight off
1050
+ * `StreamOptions.batchApprovals`.
1102
1051
  */
1103
- buildPolicyGate(agentState, streamOptions, deprecatedMode) {
1052
+ buildPolicyGate(agentState, streamOptions) {
1104
1053
  const config = agentState.config;
1105
- const hasPolicy = (config.toolPolicies?.length ?? 0) > 0 || config.defaultToolDecision !== undefined;
1106
- const legacyGating = deprecatedMode !== undefined;
1107
- const batchApprovals = streamOptions?.batchApprovals === true || deprecatedMode === 'batch';
1108
- const gatingActive = hasPolicy || legacyGating || batchApprovals;
1054
+ const gatingActive = (config.toolPolicies?.length ?? 0) > 0 || config.defaultToolDecision !== undefined;
1109
1055
  if (!gatingActive) {
1110
1056
  return { batchApprovals: false };
1111
1057
  }
1112
- // Freeze the deprecation-bridge decision at stream start (see JSDoc) —
1113
- // whether to inject `defaultToolDecision: 'require-approval'` must not
1114
- // change when a mid-turn remember rule flips `hasPolicy`.
1115
- const applyLegacyBridge = !hasPolicy && legacyGating;
1116
- const lookupPolicy = (invocation) => {
1117
- // Read the config live so a mid-turn `updateAgentConfig` (e.g. a
1118
- // remembered rule) is honored for the rest of this turn.
1119
- const liveConfig = agentState.config;
1120
- const resolverConfig = applyLegacyBridge
1121
- ? { ...liveConfig, defaultToolDecision: 'require-approval' }
1122
- : liveConfig;
1123
- return resolveToolApprovalPolicy(invocation, resolverConfig, {
1124
- harness: CLAUDE_BUILT_IN_TOOL_POLICIES,
1125
- factory: this.bypassApprovalToolsFactoryTier,
1126
- }).decision;
1127
- };
1128
- return { lookupPolicy, batchApprovals };
1058
+ const lookupPolicy = (invocation) =>
1059
+ // Read the config live so a mid-turn `updateAgentConfig` (e.g. a
1060
+ // remembered rule) is honored for the rest of this turn (#638).
1061
+ resolveToolApprovalPolicy(invocation, agentState.config, {
1062
+ harness: CLAUDE_BUILT_IN_TOOL_POLICIES,
1063
+ }).decision;
1064
+ return { lookupPolicy, batchApprovals: streamOptions?.batchApprovals === true };
1129
1065
  }
1130
1066
  async stream(agentId, threadId, message, streamOptions) {
1131
1067
  const agentState = this.agents.get(agentId);
@@ -1163,13 +1099,11 @@ export class ClaudeHarness {
1163
1099
  const lookup = (toolName) => lookupMcpToolMetadata(agentState.mcpServers, toolName);
1164
1100
  const adapter = new ClaudeEventAdapter(this.buses.getLogBus(), lookup);
1165
1101
  const controller = new AbortController();
1166
- // Phase 2b: resolve whether the approval gate engages this turn and, if
1167
- // so, build the per-tool policy resolver the coordinator consults.
1168
- // `resolveToolApprovalMode` still validates the deprecated
1169
- // `requireToolApproval` union (throws on garbage) and yields the
1170
- // serial/batch axis; gating itself is opt-in via `buildPolicyGate`.
1171
- const deprecatedMode = resolveToolApprovalMode(streamOptions?.requireToolApproval);
1172
- const { lookupPolicy, batchApprovals } = this.buildPolicyGate(agentState, streamOptions, deprecatedMode);
1102
+ // Resolve whether the approval gate engages this turn and, if so, build
1103
+ // the per-tool policy resolver the coordinator consults. Gating is
1104
+ // opt-in via `buildPolicyGate` — active only when the consumer set
1105
+ // `toolPolicies` or `defaultToolDecision`.
1106
+ const { lookupPolicy, batchApprovals } = this.buildPolicyGate(agentState, streamOptions);
1173
1107
  const gatingActive = lookupPolicy !== undefined;
1174
1108
  const consumerTools = config.tools ?? [];
1175
1109
  // Always allocate a coordinator per turn (issue #591). The
@@ -1410,6 +1344,13 @@ export class ClaudeHarness {
1410
1344
  * onto the same `initial` sink the consumer is iterating from `chat()`.
1411
1345
  */
1412
1346
  async submitToolResult(agentId, threadId, toolResult) {
1347
+ // Unknown agent → AGENT_NOT_FOUND first, so an unregistered agent is
1348
+ // distinguishable from a registered agent with no in-flight run (#464
1349
+ // cross-harness contract; matches Mastra's two-tier distinction and the
1350
+ // shared conformance table in `@salesforce/harness-conformance`).
1351
+ if (!this.agents.has(agentId)) {
1352
+ throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
1353
+ }
1413
1354
  const coordinator = this.coordinators.get(this.coordinatorKey(agentId, threadId));
1414
1355
  if (!coordinator) {
1415
1356
  throw new Error(`ClaudeHarness.submitToolResult(): no active consumer-tool stream for thread "${threadId}" on agent "${agentId}"`);
@@ -1420,6 +1361,10 @@ export class ClaudeHarness {
1420
1361
  coordinator.submitToolResult(toolResult.toolCallId, toolResult.result, toolResult.isError);
1421
1362
  }
1422
1363
  async approveToolCall(agentId, threadId, toolCallId) {
1364
+ // Unknown agent → AGENT_NOT_FOUND first (see submitToolResult above).
1365
+ if (!this.agents.has(agentId)) {
1366
+ throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
1367
+ }
1423
1368
  const coordinator = this.coordinators.get(this.coordinatorKey(agentId, threadId));
1424
1369
  if (!coordinator) {
1425
1370
  throw new Error(`ClaudeHarness.approveToolCall(): no active approval session for thread "${threadId}" on agent "${agentId}"`);
@@ -1427,6 +1372,10 @@ export class ClaudeHarness {
1427
1372
  coordinator.approve(toolCallId);
1428
1373
  }
1429
1374
  async declineToolCall(agentId, threadId, toolCallId) {
1375
+ // Unknown agent → AGENT_NOT_FOUND first (see submitToolResult above).
1376
+ if (!this.agents.has(agentId)) {
1377
+ throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
1378
+ }
1430
1379
  const coordinator = this.coordinators.get(this.coordinatorKey(agentId, threadId));
1431
1380
  if (!coordinator) {
1432
1381
  throw new Error(`ClaudeHarness.declineToolCall(): no active approval session for thread "${threadId}" on agent "${agentId}"`);