@sweny-ai/core 0.1.133 → 0.1.135

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/dist/cli/e2e.js CHANGED
@@ -10,7 +10,7 @@ import * as p from "@clack/prompts";
10
10
  import chalk from "chalk";
11
11
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
12
12
  import { execute } from "../executor.js";
13
- import { ClaudeClient } from "../claude.js";
13
+ import { createHarness } from "../harness/index.js";
14
14
  import { createSkillMap } from "../skills/index.js";
15
15
  import { configuredSkills } from "../skills/custom-loader.js";
16
16
  import { consoleLogger } from "../types.js";
@@ -906,7 +906,7 @@ export async function runE2eRun(options) {
906
906
  }
907
907
  // Build skills + Claude client
908
908
  const skills = createSkillMap(configuredSkills(process.env, cwd));
909
- const claude = new ClaudeClient({
909
+ const claude = createHarness("claude-code", {
910
910
  maxTurns: 80,
911
911
  cwd,
912
912
  logger: consoleLogger,
package/dist/cli/main.js CHANGED
@@ -9,7 +9,7 @@ import chalk from "chalk";
9
9
  import { execute } from "../executor.js";
10
10
  import { triageWorkflow, implementWorkflow, seedContentWorkflow } from "../workflows/index.js";
11
11
  import { consoleLogger } from "../types.js";
12
- import { ClaudeClient } from "../claude.js";
12
+ import { createHarness } from "../harness/index.js";
13
13
  import { builtinSkills, createSkillMap, validateWorkflowSkills } from "../skills/index.js";
14
14
  import { formatMissingSkillLines, skillEnvWarnings } from "./skill-env.js";
15
15
  import { configuredSkills, configuredSkillsWithDiagnostics } from "../skills/custom-loader.js";
@@ -203,7 +203,7 @@ triageCmd.action(async (options) => {
203
203
  const skills = createSkillMap(triageSkillDiscovery.skills);
204
204
  const mcpAutoConfig = buildMcpAutoConfig(config);
205
205
  const mcpServers = buildAutoMcpServers(mcpAutoConfig);
206
- const claude = new ClaudeClient({
206
+ const claude = createHarness("claude-code", {
207
207
  maxTurns: config.maxInvestigateTurns || 50,
208
208
  cwd: process.cwd(),
209
209
  logger: consoleLogger,
@@ -380,7 +380,7 @@ triageCmd.action(async (options) => {
380
380
  try {
381
381
  const { results, trace } = await execute(triageWorkflow, workflowInput, {
382
382
  skills,
383
- claude,
383
+ harness: claude,
384
384
  observer,
385
385
  logger: consoleLogger,
386
386
  cwd: process.cwd(),
@@ -513,7 +513,7 @@ implementCmd.action(async (issueId, options) => {
513
513
  const skills = createSkillMap(implementSkillDiscovery.skills);
514
514
  const mcpAutoConfig = buildMcpAutoConfig(config);
515
515
  const mcpServers = buildAutoMcpServers(mcpAutoConfig);
516
- const claude = new ClaudeClient({
516
+ const claude = createHarness("claude-code", {
517
517
  maxTurns: config.maxImplementTurns || 40,
518
518
  cwd: process.cwd(),
519
519
  logger: consoleLogger,
@@ -584,7 +584,7 @@ implementCmd.action(async (issueId, options) => {
584
584
  try {
585
585
  const { results } = await execute(implementWorkflow, workflowInput, {
586
586
  skills,
587
- claude,
587
+ harness: claude,
588
588
  observer,
589
589
  logger: consoleLogger,
590
590
  cwd: process.cwd(),
@@ -757,7 +757,7 @@ export async function workflowRunAction(file, options) {
757
757
  });
758
758
  // Raw [info]/[debug] lines are verbose-only; warnings are rendered (#383).
759
759
  const runLogger = createRunLogger({ verbose: Boolean(options.verbose), tty: isTTY });
760
- const claude = new ClaudeClient({
760
+ const claude = createHarness("claude-code", {
761
761
  maxTurns: config.maxInvestigateTurns || 50,
762
762
  cwd: process.cwd(),
763
763
  logger: runLogger,
@@ -926,7 +926,7 @@ export async function workflowRunAction(file, options) {
926
926
  try {
927
927
  const { results, trace } = await runWithWallClockBudget((signal) => execute(workflow, workflowInput, {
928
928
  skills,
929
- claude,
929
+ harness: claude,
930
930
  observer,
931
931
  logger: runLogger,
932
932
  cwd: process.cwd(),
@@ -1101,7 +1101,7 @@ workflowCmd
1101
1101
  console.warn("\x1B[33m ⚠ `sweny workflow create` is deprecated. Use `sweny new` instead.\x1B[0m\n");
1102
1102
  }
1103
1103
  const skills = configuredSkills();
1104
- const claude = new ClaudeClient({
1104
+ const claude = createHarness("claude-code", {
1105
1105
  maxTurns: 3,
1106
1106
  cwd: process.cwd(),
1107
1107
  logger: consoleLogger,
@@ -1168,7 +1168,7 @@ workflowCmd
1168
1168
  return;
1169
1169
  }
1170
1170
  const skills = configuredSkills();
1171
- const claude = new ClaudeClient({
1171
+ const claude = createHarness("claude-code", {
1172
1172
  maxTurns: 3,
1173
1173
  cwd: process.cwd(),
1174
1174
  logger: consoleLogger,
package/dist/cli/new.js CHANGED
@@ -14,7 +14,7 @@ import chalk from "chalk";
14
14
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
15
15
  import { WORKFLOW_TEMPLATES } from "./templates.js";
16
16
  import { buildWorkflow, refineWorkflow } from "../workflow-builder.js";
17
- import { ClaudeClient } from "../claude.js";
17
+ import { createHarness } from "../harness/index.js";
18
18
  import { consoleLogger } from "../types.js";
19
19
  import { configuredSkills } from "../skills/custom-loader.js";
20
20
  import { builtinSkills } from "../skills/index.js";
@@ -541,7 +541,7 @@ export async function runNew(options) {
541
541
  const { installMarketplaceWorkflow } = await import("./marketplace.js");
542
542
  const allSkills = configuredSkills(process.env, cwd);
543
543
  const hasAgent = !!process.env.ANTHROPIC_API_KEY;
544
- const claude = hasAgent ? new ClaudeClient({ maxTurns: 3, cwd, logger: consoleLogger }) : null;
544
+ const claude = hasAgent ? createHarness("claude-code", { maxTurns: 3, cwd, logger: consoleLogger }) : null;
545
545
  if (!options.skipIntro)
546
546
  p.intro(`Installing ${options.marketplaceId} from marketplace`);
547
547
  // Pre-compute source-control from git-remote (same method as the wizard path).
@@ -833,7 +833,7 @@ async function runCustomWorkflowBuilder(skills) {
833
833
  });
834
834
  if (p.isCancel(description))
835
835
  return null;
836
- const claude = new ClaudeClient({
836
+ const claude = createHarness("claude-code", {
837
837
  maxTurns: 3,
838
838
  cwd: process.cwd(),
839
839
  logger: consoleLogger,
@@ -48,6 +48,11 @@ export interface RunRecord {
48
48
  to: string;
49
49
  }>;
50
50
  totals: RunTotals;
51
+ /** Harness that ran the nodes (id + version). Absent for runs with no harness-tagged result. */
52
+ harness?: {
53
+ id: string;
54
+ version: string;
55
+ };
51
56
  }
52
57
  /** sha256 (hex) of the workflow as canonical JSON: keys sorted, undefined dropped. */
53
58
  export declare function hashWorkflow(workflow: Workflow): string;
@@ -91,6 +91,7 @@ export function buildRunRecord(i) {
91
91
  retries: retries.get(id) ?? 0,
92
92
  };
93
93
  });
94
+ const harness = [...i.results.values()].find((r) => r.harness)?.harness;
94
95
  return {
95
96
  schema_version: RUN_HISTORY_SCHEMA_VERSION,
96
97
  run_id: i.runId,
@@ -109,6 +110,7 @@ export function buildRunRecord(i) {
109
110
  tokens: summary.tokens ?? null,
110
111
  cost_usd: summary.costUsd ?? null,
111
112
  },
113
+ ...(harness ? { harness: { id: harness.id, version: harness.version } } : {}),
112
114
  };
113
115
  }
114
116
  // ── Write, prune, read ──────────────────────────────────────────
@@ -8,7 +8,10 @@ import { evaluateFunctionRule } from "./function.js";
8
8
  import { evaluateJudge } from "./judge.js";
9
9
  /** Reasoning cap, matching the spec's ~500-character ceiling. */
10
10
  const MAX_REASONING_LENGTH = 500;
11
- /** Default judge model when neither evaluator nor node nor workflow specify one. */
11
+ /**
12
+ * Judge model when neither evaluator, node, workflow nor the harness names one.
13
+ * Harnesses declare their own via `defaultJudgeModel` (Claude Code: this value).
14
+ */
12
15
  const DEFAULT_JUDGE_MODEL = "claude-haiku-4-5";
13
16
  function capReasoning(reasoning) {
14
17
  if (!reasoning)
@@ -17,8 +20,8 @@ function capReasoning(reasoning) {
17
20
  return reasoning;
18
21
  return reasoning.slice(0, MAX_REASONING_LENGTH - 1) + "…";
19
22
  }
20
- function resolveJudgeModel(evaluator, node, workflow) {
21
- return evaluator.model ?? node?.judge_model ?? workflow?.judge_model ?? DEFAULT_JUDGE_MODEL;
23
+ function resolveJudgeModel(evaluator, node, workflow, harnessDefault) {
24
+ return evaluator.model ?? node?.judge_model ?? workflow?.judge_model ?? harnessDefault ?? DEFAULT_JUDGE_MODEL;
22
25
  }
23
26
  /**
24
27
  * Run every declared evaluator against the node's result and return one
@@ -56,7 +59,7 @@ export async function evaluateAll(evaluators, result, opts = {}) {
56
59
  if (!opts.claude) {
57
60
  throw new Error(`evaluator '${e.name}' is kind: judge but no Claude client was provided to evaluateAll`);
58
61
  }
59
- const model = resolveJudgeModel(e, opts.node, opts.workflow);
62
+ const model = resolveJudgeModel(e, opts.node, opts.workflow, opts.claude.defaultJudgeModel);
60
63
  const verdict = await evaluateJudge(e, result, opts.claude, { model });
61
64
  out.push({
62
65
  name: e.name,
@@ -13,13 +13,22 @@
13
13
  * This replaces ~8k lines of engine + recipe step code.
14
14
  */
15
15
  import type { Workflow, Skill, Tool, Claude, Observer, Logger, ExecutionResult } from "./types.js";
16
+ import type { AgentHarness } from "./harness/types.js";
16
17
  export interface ExecuteOptions {
17
18
  /** Registered skills (id → Skill) */
18
19
  skills: Map<string, Skill>;
19
20
  /** Config values — env vars + explicit overrides */
20
21
  config?: Record<string, string>;
21
- /** Claude client */
22
- claude: Claude;
22
+ /**
23
+ * The agent harness that runs nodes (`createHarness("claude-code")`).
24
+ * Required unless the deprecated `claude` option is given.
25
+ */
26
+ harness?: AgentHarness;
27
+ /**
28
+ * Claude client.
29
+ * @deprecated Pass `harness`. Still accepted (see `claudeCompat()`); when both are set, `harness` wins.
30
+ */
31
+ claude?: Claude;
23
32
  /** Event observer for streaming/logging */
24
33
  observer?: Observer;
25
34
  /** Logger */
package/dist/executor.js CHANGED
@@ -22,6 +22,7 @@ import { buildToolAliases } from "./skills/index.js";
22
22
  import { validateWorkflow } from "./schema.js";
23
23
  import { resolveAgentAccess } from "./agent-env.js";
24
24
  import { fenceUntrusted } from "./untrusted.js";
25
+ import { asClaude } from "./harness/compat.js";
25
26
  /**
26
27
  * Default workflow-level step budget. Counts total node executions across the
27
28
  * whole run (loop iterations included). Generous enough for legitimate deep
@@ -47,7 +48,12 @@ function throwIfAborted(signal) {
47
48
  * - `trace`: full ordered execution trace including loops and routing decisions
48
49
  */
49
50
  export async function execute(workflow, input, options) {
50
- const { claude, observer, signal, timeoutMs } = options;
51
+ const { observer, signal, timeoutMs } = options;
52
+ // `harness` is the seam; a legacy `claude` object is used as-is (what `asClaude(claudeCompat(claude))` yields).
53
+ const claude = options.harness ? asClaude(options.harness) : options.claude;
54
+ if (!claude) {
55
+ throw new Error("execute() needs options.harness (or the deprecated options.claude)");
56
+ }
51
57
  // If the caller already aborted before we started, fail fast.
52
58
  throwIfAborted(signal);
53
59
  const dryRun = isDryRunInput(input);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Declared capabilities per adapter. No runtime imports, so the executor can use
3
+ * them without pulling in an agent SDK.
4
+ */
5
+ import type { HarnessCapabilities } from "./types.js";
6
+ /** Claude Code enforces every sweny opinion natively, so `policyGate` never degrades or refuses it. */
7
+ export declare const CLAUDE_CODE_CAPABILITIES: HarnessCapabilities;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Declared capabilities per adapter. No runtime imports, so the executor can use
3
+ * them without pulling in an agent SDK.
4
+ */
5
+ /** Claude Code enforces every sweny opinion natively, so `policyGate` never degrades or refuses it. */
6
+ export const CLAUDE_CODE_CAPABILITIES = {
7
+ structuredOutput: "native",
8
+ toolTrace: "full",
9
+ builtinDeny: "by-name",
10
+ mcp: { inject: true, exclusive: "native" },
11
+ sandbox: { fs: true, network: true },
12
+ readOnly: "native",
13
+ turnLimit: "native",
14
+ usage: { tokens: true, costUsd: true, live: false },
15
+ cancel: "signal",
16
+ resume: false,
17
+ };
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Claude Client - Headless Claude Code backend
3
+ *
4
+ * Uses the @anthropic-ai/claude-agent-sdk to run headless Claude Code
5
+ * as the LLM backend. Tools are exposed via an in-process MCP server
6
+ * that Claude Code calls during execution.
7
+ *
8
+ * This is the ONLY supported LLM backend. The whole point of sweny
9
+ * is to use headless Claude Code - never the raw Anthropic API.
10
+ */
11
+ import type { Claude, ToolContext, Logger, McpServerConfig } from "../types.js";
12
+ import { type SandboxMode } from "../agent-env.js";
13
+ import type { AgentHarness, HarnessCapabilities, HarnessCompleteRequest, HarnessInfo, HarnessRunRequest, HarnessRunResult } from "./types.js";
14
+ import { CLAUDE_CODE_CAPABILITIES } from "./capabilities.js";
15
+ import { buildEvaluatePrompt } from "./prompts.js";
16
+ export { buildEvaluatePrompt, CLAUDE_CODE_CAPABILITIES };
17
+ /**
18
+ * Built-in tools disallowed for the route-evaluation (`evaluate`) and
19
+ * reflection/judge (`ask`) calls.
20
+ *
21
+ * These are pure classification calls over prior-node data that can be
22
+ * attacker-influenceable (a prior node summarizes an untrusted issue body,
23
+ * PR diff, log line, etc.). They must never be able to mutate the workspace
24
+ * or shell out, regardless of any node's own `disallowed_tools` policy.
25
+ * `maxTurns: 1` already bounds them; this removes the powerful built-ins
26
+ * from the model's context entirely as structural defense-in-depth.
27
+ */
28
+ export declare const CLASSIFICATION_DISALLOWED_TOOLS: readonly ["Bash", "Write", "Edit", "MultiEdit", "NotebookEdit", "WebFetch", "WebSearch"];
29
+ /**
30
+ * Built-in tools disallowed for a node run with `readOnly: true` (dry run,
31
+ * #380): everything that can edit the workspace, shell out (and so reach
32
+ * `gh`, `curl`, `git push`), spawn a subagent that could, or fetch an
33
+ * arbitrary URL (a GET can still trigger a webhook or exfiltrate data).
34
+ * Read, Grep, Glob and WebSearch stay available for analysis.
35
+ */
36
+ export declare const READ_ONLY_DISALLOWED_TOOLS: readonly ["Bash", "Write", "Edit", "MultiEdit", "NotebookEdit", "Task", "Agent", "WebFetch"];
37
+ /** How sweny resolves which credentials reach the Claude Code subprocess. */
38
+ export type SwenyAuthMode = "auto" | "api-key" | "oauth";
39
+ export interface ResolveAuthEnvOpts {
40
+ /** Explicit override; when absent, read from `env.SWENY_AUTH`. Test seam. */
41
+ mode?: SwenyAuthMode;
42
+ /** Logger for the debug line + invalid-value warning. */
43
+ logger?: Pick<Logger, "debug" | "warn">;
44
+ }
45
+ /**
46
+ * Decide which auth credentials survive into the Claude Code subprocess env.
47
+ *
48
+ * sweny does not select the winning credential (the spawned agent and any
49
+ * on-disk `~/.claude/.credentials.json` do that). This function only controls
50
+ * what is *present* in the env we hand the agent.
51
+ *
52
+ * Modes (`SWENY_AUTH`, default `auto`):
53
+ * - `auto` - today's protective behavior: when an OAuth token is present,
54
+ * strip `ANTHROPIC_API_KEY` so a stray `.env` key cannot
55
+ * silently bill a metered API account. `ANTHROPIC_AUTH_TOKEN`
56
+ * (a bearer token, not a console key) is never touched.
57
+ * - `api-key` - user explicitly authenticates a gateway with a key/bearer:
58
+ * preserve both even when an OAuth token is also present.
59
+ * - `oauth` - user explicitly wants subscription/OAuth: strip both
60
+ * `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` so a leftover
61
+ * credential cannot win at the agent. Fails closed.
62
+ *
63
+ * Base-URL presence is deliberately NOT a signal here: pass-through proxies
64
+ * (corporate egress, observability) set `ANTHROPIC_BASE_URL` while still
65
+ * billing the real key, so inferring "use the key" from a base URL would
66
+ * re-introduce a surprise-billing path. Intent must be explicit.
67
+ *
68
+ * An unrecognized `SWENY_AUTH` value falls back to `auto` with a warning
69
+ * (never throws - an env typo must not crash a CI run).
70
+ *
71
+ * Pure: returns a copy, never mutates the input.
72
+ */
73
+ export declare function resolveAuthEnv(env: Record<string, string>, opts?: ResolveAuthEnvOpts): Record<string, string>;
74
+ export interface ClaudeCodeHarnessOptions {
75
+ /** Model override */
76
+ model?: string;
77
+ /** Max turns for tool use loop (default: 20) */
78
+ maxTurns?: number;
79
+ /** Working directory for Claude Code (default: process.cwd()) */
80
+ cwd?: string;
81
+ /** Logger */
82
+ logger?: Logger;
83
+ /** Default tool context for standalone usage (not via executor) */
84
+ defaultContext?: ToolContext;
85
+ /** Catalog defaults, overridden by per-run skill servers and explicit mcpServers. */
86
+ defaultMcpServers?: Record<string, McpServerConfig>;
87
+ /** Explicit external MCP servers, overriding defaults and per-run skill servers. */
88
+ mcpServers?: Record<string, McpServerConfig>;
89
+ /**
90
+ * Extra env var names passed to the agent subprocess on top of the
91
+ * allowlist (see agent-env.ts). `"*"` inherits the full environment.
92
+ * Default: `SWENY_ENV_PASSTHROUGH` (comma-separated).
93
+ */
94
+ envPassthrough?: string[];
95
+ /**
96
+ * Scope the agent env to the allowlist. Default: `SWENY_ENV_SCOPE`, else on
97
+ * when CI is truthy and off locally (full env).
98
+ */
99
+ envScope?: boolean;
100
+ /**
101
+ * SDK sandbox for agent commands: `auto` (sandbox when the host supports
102
+ * it, else warn and run unsandboxed), `strict` (sandbox or fail), `off`.
103
+ * Default: `SWENY_SANDBOX`, else `auto` when CI is truthy, `off` otherwise.
104
+ */
105
+ sandbox?: SandboxMode;
106
+ /** Extra hosts sandboxed commands may reach. Default: `SWENY_SANDBOX_ALLOWED_DOMAINS`. */
107
+ sandboxAllowedDomains?: string[];
108
+ /** Sandbox preflight probe (test seam). Returns a reason when the sandbox cannot run. */
109
+ sandboxProbe?: () => string | undefined;
110
+ }
111
+ /**
112
+ * Wire an optional timeout + caller signal onto a single AbortController.
113
+ *
114
+ * Returns undefined when neither a timeout nor a signal is supplied so the
115
+ * default code path (no abortController, no timer) is byte-for-byte unchanged.
116
+ *
117
+ * `reason()` reports whether the abort came from the timeout timer or an
118
+ * external signal, so callers can log a distinct timeout message.
119
+ */
120
+ export declare function makeAbort(timeoutMs?: number, signal?: AbortSignal): {
121
+ controller: AbortController;
122
+ clear: () => void;
123
+ reason: () => "timeout" | "signal" | undefined;
124
+ } | undefined;
125
+ export declare class ClaudeCodeHarness implements Claude, AgentHarness {
126
+ readonly id: "claude-code";
127
+ readonly capabilities: HarnessCapabilities;
128
+ readonly defaultJudgeModel = "claude-haiku-4-5";
129
+ private model;
130
+ private maxTurns;
131
+ private cwd;
132
+ readonly logger: Logger;
133
+ private defaultContext;
134
+ private mcpServers;
135
+ private defaultMcpServers;
136
+ private envPassthrough;
137
+ private sandboxMode;
138
+ private sandboxAllowedDomains;
139
+ private sandboxProbe;
140
+ private sandboxWarned;
141
+ private envScope;
142
+ constructor(opts?: ClaudeCodeHarnessOptions);
143
+ /**
144
+ * Build env for the Claude Code subprocess (#360). Applies auth precedence
145
+ * via {@link resolveAuthEnv} on the full process env (it reads
146
+ * `SWENY_AUTH`). When env scoping is on (default in CI, see
147
+ * {@link resolveEnvScope}) the result is narrowed to the allowlist plus
148
+ * `extraVars` (the node's declared skill env vars) and the operator
149
+ * passthrough list, and the withheld names (never values) are reported once
150
+ * per process ({@link reportWithheldEnv}). When off, the full env passes through as before.
151
+ */
152
+ private buildEnv;
153
+ /** Which harness (and SDK version) produced a result. */
154
+ info(): HarnessInfo;
155
+ preflight(): Promise<{
156
+ ok: true;
157
+ version: string;
158
+ }>;
159
+ /**
160
+ * Run a node. The query itself is unchanged by the harness seam; this only
161
+ * runs {@link policyGate} (always `degraded: []` for Claude Code) and tags
162
+ * the result with the harness id and version.
163
+ */
164
+ run(req: HarnessRunRequest): Promise<HarnessRunResult>;
165
+ private runQuery;
166
+ /**
167
+ * Single completion: no tools, no MCP, one turn. Shared by core `ask()`
168
+ * (retry reflection, judge scoring) and `evaluate()` (route choice); the
169
+ * query options are the ones `ask` and `evaluate` used before the seam.
170
+ * Returns null when the query failed (SDK error, timeout, non-success
171
+ * subtype); callers fail closed.
172
+ */
173
+ complete(req: HarnessCompleteRequest): Promise<string | null>;
174
+ /** Route evaluation. Delegates to core {@link coreEvaluate} over {@link complete}. */
175
+ evaluate(opts: {
176
+ question: string;
177
+ context: Record<string, unknown>;
178
+ choices: {
179
+ id: string;
180
+ description: string;
181
+ }[];
182
+ /** Abort the query after this many ms. Default: no timeout (back-compat). */
183
+ timeoutMs?: number;
184
+ /** Caller-supplied abort signal. Aborting it interrupts the query. */
185
+ signal?: AbortSignal;
186
+ }): Promise<string | null>;
187
+ /** Free-text completion. Delegates to core {@link coreAsk} over {@link complete}. */
188
+ ask(opts: {
189
+ instruction: string;
190
+ context: Record<string, unknown>;
191
+ model?: string;
192
+ /** Abort the query after this many ms. Default: no timeout (back-compat). */
193
+ timeoutMs?: number;
194
+ /** Caller-supplied abort signal. Aborting it interrupts the query. */
195
+ signal?: AbortSignal;
196
+ }): Promise<string>;
197
+ }
198
+ /** @deprecated Use {@link ClaudeCodeHarnessOptions}. */
199
+ export type ClaudeClientOptions = ClaudeCodeHarnessOptions;
200
+ /** @deprecated Use {@link ClaudeCodeHarness} (or `createHarness("claude-code")`). */
201
+ export declare const ClaudeClient: typeof ClaudeCodeHarness;
202
+ /** @deprecated Use {@link ClaudeCodeHarness}. */
203
+ export type ClaudeClient = ClaudeCodeHarness;
204
+ /**
205
+ * Best-effort recovery of typed output from a tool_result's `content`.
206
+ *
207
+ * The MCP protocol sends tool results as string content. Our in-process
208
+ * wrapper JSON-stringifies structured output before returning; external
209
+ * MCP servers generally do the same for JSON payloads. If the string
210
+ * looks like a JSON object or array and parses, return the parsed value
211
+ * so verify's output-path walks work against typed data.
212
+ *
213
+ * Raw strings are preserved verbatim. JSON-primitive strings (e.g. the
214
+ * literal four characters `"42"`) are intentionally NOT parsed - we
215
+ * cannot distinguish a tool that returned the number 42 (wrapper sends
216
+ * `"42"`) from a tool that returned the string "42" (wrapper also sends
217
+ * `"42"`). Preserving the string is safer than guessing.
218
+ */
219
+ export declare function parseToolResultContent(content: unknown): unknown;
220
+ /**
221
+ * Produce a short, single-line description of a tool-error payload suitable
222
+ * for the CI log stream. The full parsed value stays on the ToolCall for
223
+ * verify and downstream tooling - this is only for inline observability.
224
+ *
225
+ * Collapses newlines, trims whitespace, and caps to 300 chars so a huge
226
+ * API response body doesn't flood the log.
227
+ */
228
+ export declare function summarizeToolError(parsed: unknown): string;