feat: support local-script targets in opfor hunt#192
Conversation
Extends `opfor hunt` to accept `type: "local-script"` targets (previously HTTP-only), matching the local-script support already in `opfor run`. Adds a TargetClient that shells out to a user script per turn instead of making an HTTP request, threading hunt's per-thread threadId through as the script's sessionId so thread forks get proper session isolation for free. Also fixes two related bugs: - local-script error responses weren't prefixed with "ERROR:", so isTargetError() never flagged them as target errors - --ui fell back to the setup wizard whenever --endpoint was absent, even when --target-config supplied a valid local-script target
WalkthroughAdds local-script targets to autonomous target configuration and client creation. The hunt CLI maps and validates these targets, while local script invocation gains a longer timeout and prefixed error messages. ChangesLocal-script target support
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant HuntCLI
participant TargetClientFactory
participant LocalScriptTargetClient
participant LocalScriptInvoker
HuntCLI->>TargetClientFactory: Create client from local-script TargetConfig
TargetClientFactory->>LocalScriptTargetClient: Route local-script target
LocalScriptTargetClient->>LocalScriptInvoker: Invoke script with prompt and thread session
LocalScriptInvoker-->>LocalScriptTargetClient: Return response or error
LocalScriptTargetClient-->>HuntCLI: Return mapped target result
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
core/src/autonomous/target/localScript.ts (1)
12-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the error message actionable.
Per coding guidelines, error messages should tell the user how to fix the problem, not just that something failed. The current message says what's missing but not how to set it.
As per coding guidelines: "Write actionable error messages that tell the user how to fix the problem, not just that something failed."
♻️ Proposed fix
- throw new Error("local-script target is missing `scriptPath`."); + throw new Error( + "local-script target is missing `scriptPath`. Set `scriptPath` on the target in --target-config (e.g., { \"type\": \"local-script\", \"scriptPath\": \"./adapter.js\" })." + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/autonomous/target/localScript.ts` around lines 12 - 15, Update the missing-scriptPath error in createLocalScriptTargetClient to include actionable guidance explaining that the caller must set config.scriptPath to the local script path, while preserving the existing validation behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@core/src/autonomous/target/localScript.ts`:
- Around line 12-15: Update the missing-scriptPath error in
createLocalScriptTargetClient to include actionable guidance explaining that the
caller must set config.scriptPath to the local script path, while preserving the
existing validation behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5fd84311-d2a9-4246-ac9d-1fef8639d89c
📒 Files selected for processing (5)
core/src/autonomous/lib/types.tscore/src/autonomous/target/http.tscore/src/autonomous/target/localScript.tscore/src/lib/localScriptTarget.tsrunners/cli/src/commands/hunt.ts
Add hunt.md coverage for the local-script target support added in this PR — --target-config usage, session/threadId wiring, and the 240s per-turn timeout.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/hunt.md`:
- Around line 29-40: Update the `--endpoint` option description in the options
table to state that the flag is required only when no endpoint is provided by
`--target-config`, while preserving the exception for local-script targets
configured there.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| | Option | Description | | ||
| | ---------------------------- | ----------------------------------------------------------------------------------------------------------- | | ||
| | `--endpoint <url>` | Target HTTP endpoint (required unless a local-script target is given via `--target-config`) | | ||
| | `--objective <text>` | Attack objective | | ||
| | `--objective-file <path>` | Read objective from file | | ||
| | `--target-key-env <var>` | Env var with target API key | | ||
| | `--target-key <key>` | Target API key directly | | ||
| | `--name <name>` | Display name for target | | ||
| | `--target-model <id>` | Model value in requests | | ||
| | `--stateless` / `--stateful` | History handling mode | | ||
| | `--session-field <name>` | Body field for the session id (client-owned, stateful) | | ||
| | `--target-config <path>` | JSON file with a run-style `target` block; enables server-owned & header sessions, and local-script targets | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
--endpoint description omits the --target-config HTTP case.
The description says --endpoint is "required unless a local-script target is given via --target-config", but for HTTP targets the endpoint can also be supplied inside the --target-config file, making the --endpoint flag optional in that case too (see endpoint: opts.endpoint ?? baseTarget.endpoint in hunt.ts). The current wording could mislead users into thinking --endpoint is always required for HTTP targets.
📝 Suggested wording fix
-| `--endpoint <url>` | Target HTTP endpoint (required unless a local-script target is given via `--target-config`) |
+| `--endpoint <url>` | Target HTTP endpoint (required for HTTP targets unless provided via `--target-config`; not used for local-script targets) |📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| | Option | Description | | |
| | ---------------------------- | ----------------------------------------------------------------------------------------------------------- | | |
| | `--endpoint <url>` | Target HTTP endpoint (required unless a local-script target is given via `--target-config`) | | |
| | `--objective <text>` | Attack objective | | |
| | `--objective-file <path>` | Read objective from file | | |
| | `--target-key-env <var>` | Env var with target API key | | |
| | `--target-key <key>` | Target API key directly | | |
| | `--name <name>` | Display name for target | | |
| | `--target-model <id>` | Model value in requests | | |
| | `--stateless` / `--stateful` | History handling mode | | |
| | `--session-field <name>` | Body field for the session id (client-owned, stateful) | | |
| | `--target-config <path>` | JSON file with a run-style `target` block; enables server-owned & header sessions, and local-script targets | | |
| | Option | Description | | |
| | ---------------------------- | ----------------------------------------------------------------------------------------------------------- | | |
| | `--endpoint <url>` | Target HTTP endpoint (required for HTTP targets unless provided via `--target-config`; not used for local-script targets) | | |
| | `--objective <text>` | Attack objective | | |
| | `--objective-file <path>` | Read objective from file | | |
| | `--target-key-env <var>` | Env var with target API key | | |
| | `--target-key <key>` | Target API key directly | | |
| | `--name <name>` | Display name for target | | |
| | `--target-model <id>` | Model value in requests | | |
| | `--stateless` / `--stateful` | History handling mode | | |
| | `--session-field <name>` | Body field for the session id (client-owned, stateful) | | |
| | `--target-config <path>` | JSON file with a run-style `target` block; enables server-owned & header sessions, and local-script targets | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/hunt.md` around lines 29 - 40, Update the `--endpoint` option
description in the options table to state that the flag is required only when no
endpoint is provided by `--target-config`, while preserving the exception for
local-script targets configured there.
Problem
opfor hunt(the autonomous commander/operator/scout red-teaming mode) only supported HTTP targets. If a target's chat API couldn't be modeled as a simple request/response HTTP call — e.g. async/polling APIs, session ids embedded in a URL path segment rather than a body/header field (whichsession.sendhas no way to express), or auth flows needing custom logic — there was no way to pointopfor huntat it.opfor runalready supportedtype: "local-script"targets for exactly this case;opfor huntexplicitly threw"local-script targets are not supported by opfor hunt (HTTP only).".Separately,
--uihad a bug: it fell back to launching the setup wizard whenever--endpointwas omitted, even when a valid target was already supplied via--target-config(which is how local-script targets are configured, since they have noendpoint).Solution
TargetKind("http" | "local-script") to the autonomous runner'sTargetConfig, plus ascriptPathfield.createLocalScriptTargetClient(), aTargetClientimplementation that shells out to a user script per turn (via the sameinvokeLocalTargetScriptstdin/stdout contractopfor runalready uses) instead of making an HTTP request. Hunt's per-threadthreadIdis passed through as the script'ssessionId, so thread forks each get isolated sessions automatically — no extra wiring needed.createTargetClient()now branches onconfig.typeto pick the HTTP or local-script client.throwinmapAgentTargetToAutonomous()and added proper mapping, validation (checksscriptPathinstead ofendpoint), and name-fallback (script basename instead of URL host) for local-script targets.--ui's fallback condition to also check!opts.targetConfig, so it only launches the setup wizard when no target was specified at all."ERROR:", which meantisTargetError()never actually flagged them as errors (it checksresponse.startsWith("ERROR:")) — errors from a script were silently treated as normal target replies.Changes
core/src/autonomous/lib/types.ts—TargetKindtype,type/scriptPathfields onTargetConfigcore/src/autonomous/target/http.ts— branch tocreateLocalScriptTargetClient()increateTargetClient()core/src/autonomous/target/localScript.ts(new) — local-scriptTargetClientimplementationcore/src/lib/localScriptTarget.ts— timeout 30s → 240s; error responses now prefixedERROR:runners/cli/src/commands/hunt.ts— local-script mapping/validation/naming inmapAgentTargetToAutonomous();--uifallback condition fixIssue
N/A
Summary by CodeRabbit
New Features
opfor hunt, configurable via--target-config(including session, prompt, model, and response settings).--uiis used, the CLI now enters setup mode only if no target is already configured; otherwise it opens the live dashboard for the configured target.Bug Fixes
ERROR:prefix for clarity.Documentation
huntguidance, adding a Local-script targets section and example configurations.