@salesforce/sfdx-agent-harness-claude 0.28.0 → 0.30.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 +20 -0
- package/README.md +16 -50
- package/dist/claude-approval-coordinator.d.ts +4 -4
- package/dist/claude-approval-coordinator.js +3 -3
- package/dist/claude-harness-factory.d.ts +5 -20
- package/dist/claude-harness-factory.js +1 -7
- package/dist/claude-harness-factory.js.map +1 -1
- package/dist/claude-harness.d.ts +13 -50
- package/dist/claude-harness.js +69 -98
- package/dist/claude-harness.js.map +1 -1
- package/dist/claude-query-env-builder.d.ts +12 -2
- package/dist/claude-query-env-builder.js +52 -3
- package/dist/claude-query-env-builder.js.map +1 -1
- package/dist/claude-query-options-builder.d.ts +2 -2
- package/dist/claude-query-options-builder.js +33 -7
- package/dist/claude-query-options-builder.js.map +1 -1
- package/dist/test/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
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.30.0] - 2026-07-15
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- **agent-sdk,harness-claude,harness-mastra**: per-model gateway config — geo-aware ids, thinking mode, param exclusion @W-23447370@ ([#672](https://github.com/forcedotcom/agentic-dx/pull/672))
|
|
10
|
+
|
|
11
|
+
## [0.29.0] - 2026-07-14
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
- **agent-sdk,harness-mastra,harness-claude**: cross-harness conformance test suite @W-23372341@ ([#663](https://github.com/forcedotcom/agentic-dx/pull/663))
|
|
15
|
+
- **sdk,harness-mastra,harness-claude**: delete deprecated tool-approval surfaces @W-23090034 ([#637](https://github.com/forcedotcom/agentic-dx/pull/637))
|
|
16
|
+
|
|
17
|
+
### Fixes
|
|
18
|
+
- **harness-conformance**: static version instead of -dev.N so release check passes @W-23446682@ ([#670](https://github.com/forcedotcom/agentic-dx/pull/670))
|
|
19
|
+
|
|
20
|
+
### Chores
|
|
21
|
+
- **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))
|
|
22
|
+
- **deps-dev**: bump the eslint group across 1 directory with 2 updates ([#659](https://github.com/forcedotcom/agentic-dx/pull/659))
|
|
23
|
+
- **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))
|
|
24
|
+
- **deps-dev**: bump the vitest group across 1 directory with 3 updates ([#655](https://github.com/forcedotcom/agentic-dx/pull/655))
|
|
25
|
+
|
|
6
26
|
## [0.28.0] - 2026-07-08
|
|
7
27
|
|
|
8
28
|
### 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,
|
|
33
|
-
|
|
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`,
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
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.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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`
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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`
|
|
31
|
-
* consumer-tool wire prefix `mcp__sfdx-agent-sdk-consumer-tools__.*`
|
|
32
|
-
* the `
|
|
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.
|
|
9
|
-
* `
|
|
10
|
-
* harness
|
|
11
|
-
*
|
|
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
|
|
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;
|
|
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"}
|
package/dist/claude-harness.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
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
|
|
381
|
-
* `StreamOptions.batchApprovals
|
|
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>;
|
package/dist/claude-harness.js
CHANGED
|
@@ -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,
|
|
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}`;
|
|
@@ -893,7 +864,11 @@ export class ClaudeHarness {
|
|
|
893
864
|
* `canUseTool` broker — the task is text-in / text-out.
|
|
894
865
|
*/
|
|
895
866
|
async summarize(agent, prompt) {
|
|
896
|
-
const { env, nativeModelId } = await this.envBuilder.build(agent.modelConnectivityInfo
|
|
867
|
+
const { env, nativeModelId } = await this.envBuilder.build(agent.modelConnectivityInfo,
|
|
868
|
+
// Drive the same per-model thinking shape as a normal turn — the summary
|
|
869
|
+
// call hits the same gateway backend and would 400 on Opus 4.7/4.8 without
|
|
870
|
+
// the adaptive-capability env injection (see ClaudeQueryEnvBuilder).
|
|
871
|
+
agent.model.thinkingMode);
|
|
897
872
|
const summaryConfig = {
|
|
898
873
|
orgJwt: agent.config.orgJwt,
|
|
899
874
|
// The role declaration + summarization imperative + the
|
|
@@ -908,7 +883,16 @@ export class ClaudeHarness {
|
|
|
908
883
|
'Preserve numeric values, dates, and proper names verbatim. ' +
|
|
909
884
|
'Do not invent details that are not present in the transcript.',
|
|
910
885
|
};
|
|
911
|
-
const options = this.optionsBuilder.build(agent.projectRoot, summaryConfig, { maxSteps: 1 }, env, undefined,
|
|
886
|
+
const options = this.optionsBuilder.build(agent.projectRoot, summaryConfig, { maxSteps: 1 }, env, undefined, // sessionOptions
|
|
887
|
+
nativeModelId, undefined, // brokerPreToolUseHook
|
|
888
|
+
undefined, // mcpServers
|
|
889
|
+
undefined, // rulesAppend
|
|
890
|
+
undefined, // postToolUseRedactionHook
|
|
891
|
+
undefined, // skillDirectories
|
|
892
|
+
// Same per-model thinking shape as a normal turn — the summary call
|
|
893
|
+
// hits the same gateway backend and would 400 on Opus 4.7/4.8 with
|
|
894
|
+
// the CLI's default `"enabled"` shape otherwise.
|
|
895
|
+
agent.model.thinkingMode);
|
|
912
896
|
// Override post-build: ephemeral session, no persistence, no
|
|
913
897
|
// sessionStore (rejected together with persistSession:false).
|
|
914
898
|
options.persistSession = false;
|
|
@@ -1048,31 +1032,19 @@ export class ClaudeHarness {
|
|
|
1048
1032
|
};
|
|
1049
1033
|
}
|
|
1050
1034
|
/**
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1035
|
+
* Decide whether the approval gate engages this turn and, if so, build the
|
|
1036
|
+
* per-tool policy resolver the coordinator's `preToolUseHook` consults.
|
|
1037
|
+
* Mirrors `MastraHarness.buildPolicyGate` so both harnesses share the same
|
|
1038
|
+
* opt-in semantics.
|
|
1055
1039
|
*
|
|
1056
|
-
* Gating is **opt-in**. It engages when the consumer set
|
|
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`.
|
|
1040
|
+
* Gating is **opt-in**. It engages when the consumer set either:
|
|
1041
|
+
* - `config.toolPolicies` (non-empty), or
|
|
1042
|
+
* - `config.defaultToolDecision`.
|
|
1062
1043
|
*
|
|
1063
|
-
* When
|
|
1044
|
+
* When neither is set, returns `{ lookupPolicy: undefined }` — the caller
|
|
1064
1045
|
* omits the `PreToolUse` hook and tools run free (the SDK's "no policy ⇒ no
|
|
1065
1046
|
* gating" back-compat invariant), with no per-tool hook round-trip.
|
|
1066
1047
|
*
|
|
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
1048
|
* **Live config read (#638).** `lookupPolicy` reads `agentState.config` on
|
|
1077
1049
|
* every call rather than closing over a snapshot taken at stream start. A
|
|
1078
1050
|
* mid-turn `updateAgentConfig` — notably the `source: 'remember'` rule an
|
|
@@ -1083,49 +1055,26 @@ export class ClaudeHarness {
|
|
|
1083
1055
|
* auto-resolve subsequent matching tool calls within the same turn. Mirrors
|
|
1084
1056
|
* `MastraHarness.buildPolicyGate`.
|
|
1085
1057
|
*
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
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.
|
|
1058
|
+
* `gatingActive` stays **frozen at stream start** — the turn either
|
|
1059
|
+
* installed the `PreToolUse` hook or it didn't; gating cannot begin
|
|
1060
|
+
* mid-turn. The closure is built (and returned) iff gating engaged now.
|
|
1099
1061
|
*
|
|
1100
|
-
* `batchApprovals` is the serial-vs-batch emission axis
|
|
1101
|
-
* `StreamOptions.batchApprovals
|
|
1062
|
+
* `batchApprovals` is the serial-vs-batch emission axis, read straight off
|
|
1063
|
+
* `StreamOptions.batchApprovals`.
|
|
1102
1064
|
*/
|
|
1103
|
-
buildPolicyGate(agentState, streamOptions
|
|
1065
|
+
buildPolicyGate(agentState, streamOptions) {
|
|
1104
1066
|
const config = agentState.config;
|
|
1105
|
-
const
|
|
1106
|
-
const legacyGating = deprecatedMode !== undefined;
|
|
1107
|
-
const batchApprovals = streamOptions?.batchApprovals === true || deprecatedMode === 'batch';
|
|
1108
|
-
const gatingActive = hasPolicy || legacyGating || batchApprovals;
|
|
1067
|
+
const gatingActive = (config.toolPolicies?.length ?? 0) > 0 || config.defaultToolDecision !== undefined;
|
|
1109
1068
|
if (!gatingActive) {
|
|
1110
1069
|
return { batchApprovals: false };
|
|
1111
1070
|
}
|
|
1112
|
-
|
|
1113
|
-
//
|
|
1114
|
-
//
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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 };
|
|
1071
|
+
const lookupPolicy = (invocation) =>
|
|
1072
|
+
// Read the config live so a mid-turn `updateAgentConfig` (e.g. a
|
|
1073
|
+
// remembered rule) is honored for the rest of this turn (#638).
|
|
1074
|
+
resolveToolApprovalPolicy(invocation, agentState.config, {
|
|
1075
|
+
harness: CLAUDE_BUILT_IN_TOOL_POLICIES,
|
|
1076
|
+
}).decision;
|
|
1077
|
+
return { lookupPolicy, batchApprovals: streamOptions?.batchApprovals === true };
|
|
1129
1078
|
}
|
|
1130
1079
|
async stream(agentId, threadId, message, streamOptions) {
|
|
1131
1080
|
const agentState = this.agents.get(agentId);
|
|
@@ -1140,7 +1089,11 @@ export class ClaudeHarness {
|
|
|
1140
1089
|
// synchronously here so failures reject the `stream()` promise (pre-stream contract) rather
|
|
1141
1090
|
// than surfacing mid-iteration.
|
|
1142
1091
|
const prompt = typeof message === 'string' ? message : this.buildMultimodalPrompt(agentState, message);
|
|
1143
|
-
const { env, nativeModelId } = await this.envBuilder.build(agentState.modelConnectivityInfo
|
|
1092
|
+
const { env, nativeModelId } = await this.envBuilder.build(agentState.modelConnectivityInfo,
|
|
1093
|
+
// When 'adaptive', injects the capability-override env pair so the CLI drives
|
|
1094
|
+
// adaptive extended-thinking on our gateway-alias id instead of 400ing on the
|
|
1095
|
+
// legacy 'enabled' shape (see ClaudeQueryEnvBuilder).
|
|
1096
|
+
agentState.model.thinkingMode);
|
|
1144
1097
|
// Pass the underlying claudeSessionId to the Claude SDK, NOT the
|
|
1145
1098
|
// user-facing threadId. They equal each other for legacy threads
|
|
1146
1099
|
// but diverge after `clearMessages()` (see issue #511 / ThreadState
|
|
@@ -1163,13 +1116,11 @@ export class ClaudeHarness {
|
|
|
1163
1116
|
const lookup = (toolName) => lookupMcpToolMetadata(agentState.mcpServers, toolName);
|
|
1164
1117
|
const adapter = new ClaudeEventAdapter(this.buses.getLogBus(), lookup);
|
|
1165
1118
|
const controller = new AbortController();
|
|
1166
|
-
//
|
|
1167
|
-
//
|
|
1168
|
-
// `
|
|
1169
|
-
// `
|
|
1170
|
-
|
|
1171
|
-
const deprecatedMode = resolveToolApprovalMode(streamOptions?.requireToolApproval);
|
|
1172
|
-
const { lookupPolicy, batchApprovals } = this.buildPolicyGate(agentState, streamOptions, deprecatedMode);
|
|
1119
|
+
// Resolve whether the approval gate engages this turn and, if so, build
|
|
1120
|
+
// the per-tool policy resolver the coordinator consults. Gating is
|
|
1121
|
+
// opt-in via `buildPolicyGate` — active only when the consumer set
|
|
1122
|
+
// `toolPolicies` or `defaultToolDecision`.
|
|
1123
|
+
const { lookupPolicy, batchApprovals } = this.buildPolicyGate(agentState, streamOptions);
|
|
1173
1124
|
const gatingActive = lookupPolicy !== undefined;
|
|
1174
1125
|
const consumerTools = config.tools ?? [];
|
|
1175
1126
|
// Always allocate a coordinator per turn (issue #591). The
|
|
@@ -1240,7 +1191,12 @@ export class ClaudeHarness {
|
|
|
1240
1191
|
// Grant the subprocess read access to the configured skills'
|
|
1241
1192
|
// directories so the model can open a skill's sibling files after
|
|
1242
1193
|
// `load_skill` surfaces the skill location (W-23231661 / #626).
|
|
1243
|
-
skillDirectories(agentState.skillMap)
|
|
1194
|
+
skillDirectories(agentState.skillMap),
|
|
1195
|
+
// The model's declared thinking shape. Overrides the bundled CLI's
|
|
1196
|
+
// per-model default, which the Bedrock backend rejects for Opus
|
|
1197
|
+
// 4.7/4.8 (`"enabled"` unsupported) and for Claude 4.5 (`"adaptive"`
|
|
1198
|
+
// unsupported). See SDK `Model.thinkingMode`.
|
|
1199
|
+
agentState.model.thinkingMode);
|
|
1244
1200
|
options.abortController = controller;
|
|
1245
1201
|
// Mirror subprocess writes into the harness-owned store. With this
|
|
1246
1202
|
// set, the host-side `getSessionMessages` / `deleteSession` calls
|
|
@@ -1410,6 +1366,13 @@ export class ClaudeHarness {
|
|
|
1410
1366
|
* onto the same `initial` sink the consumer is iterating from `chat()`.
|
|
1411
1367
|
*/
|
|
1412
1368
|
async submitToolResult(agentId, threadId, toolResult) {
|
|
1369
|
+
// Unknown agent → AGENT_NOT_FOUND first, so an unregistered agent is
|
|
1370
|
+
// distinguishable from a registered agent with no in-flight run (#464
|
|
1371
|
+
// cross-harness contract; matches Mastra's two-tier distinction and the
|
|
1372
|
+
// shared conformance table in `@salesforce/harness-conformance`).
|
|
1373
|
+
if (!this.agents.has(agentId)) {
|
|
1374
|
+
throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
|
|
1375
|
+
}
|
|
1413
1376
|
const coordinator = this.coordinators.get(this.coordinatorKey(agentId, threadId));
|
|
1414
1377
|
if (!coordinator) {
|
|
1415
1378
|
throw new Error(`ClaudeHarness.submitToolResult(): no active consumer-tool stream for thread "${threadId}" on agent "${agentId}"`);
|
|
@@ -1420,6 +1383,10 @@ export class ClaudeHarness {
|
|
|
1420
1383
|
coordinator.submitToolResult(toolResult.toolCallId, toolResult.result, toolResult.isError);
|
|
1421
1384
|
}
|
|
1422
1385
|
async approveToolCall(agentId, threadId, toolCallId) {
|
|
1386
|
+
// Unknown agent → AGENT_NOT_FOUND first (see submitToolResult above).
|
|
1387
|
+
if (!this.agents.has(agentId)) {
|
|
1388
|
+
throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
|
|
1389
|
+
}
|
|
1423
1390
|
const coordinator = this.coordinators.get(this.coordinatorKey(agentId, threadId));
|
|
1424
1391
|
if (!coordinator) {
|
|
1425
1392
|
throw new Error(`ClaudeHarness.approveToolCall(): no active approval session for thread "${threadId}" on agent "${agentId}"`);
|
|
@@ -1427,6 +1394,10 @@ export class ClaudeHarness {
|
|
|
1427
1394
|
coordinator.approve(toolCallId);
|
|
1428
1395
|
}
|
|
1429
1396
|
async declineToolCall(agentId, threadId, toolCallId) {
|
|
1397
|
+
// Unknown agent → AGENT_NOT_FOUND first (see submitToolResult above).
|
|
1398
|
+
if (!this.agents.has(agentId)) {
|
|
1399
|
+
throw new AgentSDKError(`No Agent found with id: "${agentId}"`, AgentSDKErrorType.AGENT_NOT_FOUND);
|
|
1400
|
+
}
|
|
1430
1401
|
const coordinator = this.coordinators.get(this.coordinatorKey(agentId, threadId));
|
|
1431
1402
|
if (!coordinator) {
|
|
1432
1403
|
throw new Error(`ClaudeHarness.declineToolCall(): no active approval session for thread "${threadId}" on agent "${agentId}"`);
|