Commit graph

153 commits

Author SHA1 Message Date
Bryan Helmkamp
501ff3df40 Push metadata branch to origin after checkpoint in Remote (Daytona) mode
After each checkpoint commit, the metadata branch (checkpoint.json, manifest,
graph DOT, artifacts) is now pushed from the host process to the GitHub remote
using a GitHub App installation token. The local custom ref (refs/arc/{run_id})
is mapped to refs/heads/arc/meta/{run_id} on the remote since GitHub rejects
branch names starting with "refs/".

Changes:
- Move ssh_url_to_https to github_app.rs as pub fn for reuse
- Add push_ref() to git.rs for pushing a ref to an explicit URL
- Add github_app field to RunConfig to thread credentials into the engine
- Add git_push_meta_host() async wrapper in engine.rs
- Call git_push_meta_host after each remote checkpoint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:08:49 -05:00
Bryan Helmkamp
95d95e81b1 Rename arc models CLI subcommand to arc model
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:47:07 -05:00
Bryan Helmkamp
9b842c49d5 Simplify git add pathspec building by hoisting common arg
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:29:10 -05:00
Bryan Helmkamp
314ee9be99 Add checkpoint exclude globs to skip bulky artifacts from git checkpoint commits
Introduces a [checkpoint] config table with exclude_globs in both run.toml
(per-run) and server.toml (defaults). Globs are merged (union + dedup) when
both are present. Non-empty excludes use git pathspec :(glob,exclude) syntax
to prevent staging matching files during checkpoint commits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:25:27 -05:00
Bryan Helmkamp
c1630b5a21 Restructure ModelInfo into nested shape with new fields
Replaces flat ModelInfo fields with nested sub-structs (ModelLimits,
ModelFeatures, ModelCosts) and adds family, training, and
cache_input_cost_per_mtok fields to enrich the model catalog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:44:25 -05:00
Bryan Helmkamp
4a95fac48f Remove unnecessary Vec allocation in debug log
HashMap::keys() already implements Debug, no need to collect into a Vec.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:38:01 -05:00
Bryan Helmkamp
4cefad86eb Fix sub-workflow context diff leaking child internals into parent
Filter engine-internal keys (internal.*, graph.*, thread.*, current*)
from the context diff returned by SubWorkflowHandler, preventing child
run state from overwriting parent values. Pass the parent's preamble
into the child context so child workflows have awareness of what the
parent already accomplished.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:31:50 -05:00
Bryan Helmkamp
ce80cb6101 Extract outcome and failure_reason from LLM routing directives
The extract_status_fields function recognized "outcome" as a field for
detecting status JSON objects but never read its value — LLM responses
like {"outcome": "fail", "failure_reason": "tests failed"} were silently
ignored and the outcome was always Success.

Now extract_status_fields reads the outcome field to set the node status
and failure_reason to populate the failure detail. Also adds a fallback:
if no routing directives are found in the response text, the handler
reads status.json from the sandbox CWD (written by agents that prefer
file output over inline JSON). Response text always takes priority.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 09:38:16 -05:00
Bryan Helmkamp
5df7e178ac Add SandboxProvider::Exe for exe.dev VM sandboxes
New `arc-exe` crate that runs agent tool operations inside ephemeral
exe.dev VMs via SSH. Uses two SSH connections: a management plane
(`ssh exe.dev`) for VM lifecycle and a data plane (`ssh vmname.exe.xyz`)
for command execution and file I/O.

Includes SshRunner trait with MockSshRunner for unit tests and
OpensshRunner for real SSH, with raw_mode for the exe.dev management
plane's custom command handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 09:18:00 -05:00
Bryan Helmkamp
98066669b3 Fix turn/tool-call counts always showing 0 in non-TTY mode
The active_stages map was only populated inside a TTY renderer guard,
so Plain mode never tracked counts. Move counters to a separate
stage_counts map that is always populated regardless of renderer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 03:33:19 -05:00
Bryan Helmkamp
b2f8a55b8f Fix push credentials for public repos in Daytona sandbox
resolve_clone_credentials was short-circuiting for public repos,
returning no token. This broke git push from the sandbox since push
requires authentication regardless of repo visibility. Always generate
an installation access token.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 03:20:53 -05:00
Bryan Helmkamp
4d9b0828c1 Fix ensure_cli for Daytona: rootless Node.js install, better errors
- Replace apt-get/NodeSource install (requires root) with direct Node.js
  binary download to ~/.local (works as non-root daytona user)
- Run node install + npm install in single shell so PATH persists
- Add ~/.local/bin to PATH in env file and version check
- Fall back to stdout for error details when stderr is empty (Daytona
  always returns empty stderr)
- Add e2e assertion that cli_stdout.log is written during poll
- Verified on Daytona with haiku: ensure_cli installs in 2s, full
  workflow succeeds

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 02:01:47 -05:00
Bryan Helmkamp
50aaf22f8c Auto-install agent CLIs in sandboxes when missing
AgentCliBackend now detects missing CLIs at runtime and installs them
on-demand (including Node.js via NodeSource if needed), removing the
need for custom Dockerfiles that pre-install CLI tools.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 01:44:14 -05:00
Bryan Helmkamp
bb48b0d33b Centralize model defaults in catalog, upgrade OpenAI default to gpt-5.4
Remove hardcoded default_model_for_provider() from arc-workflows and
default_model() from arc-agent, delegating both to the catalog via
arc_llm::catalog::default_model_for_provider(). Add claude-sonnet-4-6
to catalog and move "sonnet"/"claude-sonnet" aliases to it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 00:59:28 -05:00
Bryan Helmkamp
378c3eb3e3 Stream CLI stdout/stderr to stage logs during poll
Sync cli_stdout.log and cli_stderr.log to stage_dir each poll iteration
so there is visibility into what the CLI agent is doing before it finishes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 00:58:40 -05:00
Bryan Helmkamp
552e7d7636 Use setsid to detach CLI process, touch emitter during poll
Daytona's POST /process/execute blocks until all descendant processes
exit. The backgrounded claude process kept the API hanging, causing a
60-second HTTP timeout. Using setsid creates a new session so the child
is fully detached — the API now returns in ~200ms.

Also touch the event emitter during the poll loop to prevent the stall
watchdog from killing the stage while waiting for claude to finish.

Falls back gracefully on macOS where setsid isn't available (not needed
since the local exec implementation doesn't wait for grandchildren).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 23:36:00 -05:00
Bryan Helmkamp
e532842a3b CLI backend: background+poll to avoid HTTP proxy timeouts on Daytona
Daytona's POST /process/execute is synchronous and blocks until the
command finishes. Long-running CLI agent sessions (claude, codex, gemini)
cause HTTP proxy timeouts. Replace the single blocking exec_command with
a background launch + poll pattern:

- Generate UUID-based temp file paths to avoid collisions between
  concurrent CLI nodes
- Disable sandbox auto-stop before launching (new Sandbox trait method)
- Launch command in background, capture PID
- Poll every 5s for exit code file
- Read stdout/stderr from temp files after completion
- Cleanup temp files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 19:52:13 -05:00
Bryan Helmkamp
4e0f990484 Push run branch to origin after each Daytona checkpoint
When a workflow runs on a Daytona sandbox, commits on the run branch
are lost when the sandbox is deleted. This pushes the run branch to
origin after each checkpoint so the full commit history is preserved.

- Upgrade GitHub App token permission from contents:read to contents:write
- Add refresh_push_credentials to Sandbox trait (default no-op)
- Store origin URL on DaytonaSandbox and configure push credentials after clone
- Implement refresh_push_credentials on DaytonaSandbox to rotate expiring tokens
- Add git_push_remote helper, called after each remote GitCheckpoint
- Add e2e test verifying the branch appears on GitHub after push

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 19:38:53 -05:00
Bryan Helmkamp
bec07c2218 CLI backend: forward API keys via env file, add --verbose for claude, log stdout/stderr on failure
- Write provider API key to /tmp/arc_cli_env.sh and source it before
  running the CLI tool (Daytona exec API doesn't support env vars)
- Add --verbose flag to claude command (required with -p + stream-json)
- Log cli_stdout.log and cli_stderr.log on failure for debugging
- Fall back to stdout in error message when stderr is empty

Known limitation: CLI backend on Daytona times out for long-running
claude sessions due to Daytona proxy execute API timeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 17:45:38 -05:00
Bryan Helmkamp
de2458c518 Forward provider API keys to sandbox, wire up Daytona env vars, improve CLI error logging
- Collect provider API key env vars and pass them to sandbox exec_command
  so CLI tools can authenticate in the sandbox environment
- Wire up previously-ignored env_vars parameter in DaytonaSandbox, explicitly
  set all ExecuteCommandOptions fields (catches new fields at compile time)
- Write stdout/stderr logs on CLI failure, fall back to stdout in error detail

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 17:01:09 -05:00
Bryan Helmkamp
adc70b6cef Filter preamble noise: exclude meta nodes, blank context values, empty sections
- Skip start/exit nodes from all preamble modes (compact, summary high/medium/low)
- Exclude blank-string context values (e.g. failure_class: "") from context sections
- Emit section headers lazily so they don't appear when all content is filtered
- Exclude meta nodes from summary:high pipeline progress counts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 16:38:24 -05:00
Bryan Helmkamp
a039d54817 Skip writing empty diff.patch and final.patch files
Add !patch.is_empty() guards so per-node diff.patch and top-level
final.patch are only written when there are actual code changes.
Eliminates noise from empty patch files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 16:30:44 -05:00
Bryan Helmkamp
181aecc301 Improve CLI backend error message, parallel test runner, fix backend-demo
- CLI backend: show last 500 chars of stderr (not first), and include
  the command itself when stderr is empty (e.g. exit code 127)
- Test runner: add PARALLEL env var for concurrent execution
- backend-demo.dot: use API backend since claude CLI isn't in sandbox

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 16:17:45 -05:00
Bryan Helmkamp
942f717eb4 Fix GitHub App repo visibility check: treat 401/403 as private
The is_repo_public function called GET /repos/{owner}/{repo} with
the App JWT, but GitHub returns 401 for App JWTs on the repos
endpoint (they need an installation token). Previously this 401
was treated as an auth error, failing sandbox init.

Now 401 and 403 are treated like 404: assume private and proceed
to create an installation access token, which has the right perms.

Also add preflight phase to the DOT test runner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 15:44:00 -05:00
Bryan Helmkamp
72c0c8e257 Fix 10 OpenAPI review items for HITL, run outputs, and verifications
Addresses agreed items from the openapi-hitl-and-run-outputs review:
rename retrieveRunDiff operationId, add 409s to steer/preview, bound
expires_in_secs, add selected_option_keys for multi-select end-to-end,
document skip/na semantics, add slug and require type on
RunVerificationControl, require file on CodeLocation, and remove the
checkpoint "all" sentinel in favor of omitting the parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 15:13:06 -05:00
Bryan Helmkamp
c61aafcde6 Return structured JSON for run config and server config
Replace raw TOML / UI-oriented SettingGroup responses with structured
JSON that mirrors the Rust config types (WorkflowRunConfig, ServerConfig).

- Add Serialize derives to all config types in arc-workflows and arc-api
- Add manual Serialize impl for DaytonaNetwork enum
- Update OpenAPI spec: /runs/{id}/configuration returns RunConfiguration,
  /settings returns ServerConfiguration, WorkflowDetail.config is now
  RunConfiguration object instead of TOML string
- Remove SettingGroup/SettingField/SettingFieldType schemas
- Update demo handlers to construct and serialize real config structs
- Regenerate TypeScript client
- Simplify settings page to JSON display, update run-configuration and
  workflow-definition pages to show JSON

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 14:04:57 -05:00
Bryan Helmkamp
8aa7abf7f4 Deduplicate utilities, remove TOCTOU checks, and strengthen types
- Extract millis_u64 and save_json/load_json to shared crate root, replacing
  identical copies in engine, parallel, human, conclusion, manifest,
  checkpoint, and retro modules
- Remove exists() pre-checks before Manifest::load and Conclusion::load
  in CLI runs scanner (TOCTOU anti-pattern)
- Merge duplicate cancel_run match arms for Queued/Starting/Running
- Change Conclusion.status from String to StageStatus enum
- Extract RunFilterArgs shared struct from RunsListArgs/RunsPruneArgs
- Replace hand-rolled formatDuration with formatDurationMs wrapper over
  existing formatDurationSecs
- Extract duplicated ToolRow/ToolBlock components to shared tool-use.tsx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:59:22 -05:00
Bryan Helmkamp
83a358c727 Add DEBUG tracing for fidelity resolution, degradation, and session reuse
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:19:27 -05:00
Bryan Helmkamp
158af9d69b Add Fidelity enum to replace string literals for context fidelity modes
Introduces a strongly typed Fidelity enum (Full, Truncate, Compact,
SummaryLow, SummaryMedium, SummaryHigh) with Display/FromStr for
string roundtripping and a degraded() method for checkpoint resume.

Updates all consumers: resolve_fidelity, build_preamble, PreambleTransform,
FidelityValidRule, and Context::fidelity() accessor now use the enum.
Invalid fidelity strings in DOT attributes are rejected at parse boundaries
rather than silently falling through to a default at usage sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:11:54 -05:00
Bryan Helmkamp
b4159243ac Add context key constants and typed accessors to eliminate string literal typo risk
Introduces context/keys.rs with 24 static constants, 4 prefix constants, and
4 helper functions for dynamic keys. Adds 6 typed accessor methods on Context
(run_id, fidelity, preamble, thread_id, node_visit_count, current_node_id).
Replaces all bare string literals across 13 files with constants/accessors.

Fixes bug in manager_loop.rs where "internal.node_visit" was read instead of
"internal.node_visit_count".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:55:31 -05:00
Bryan Helmkamp
af6e091ef3 Rename final.json to conclusion.json and RunFinal to Conclusion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:48:07 -05:00
Bryan Helmkamp
deda9bd512 Add strong Rust types for manifest.json and final.json
Replace ad-hoc serde_json::Value construction/parsing with typed Manifest
and RunFinal structs, matching the pattern used by Checkpoint and Retro.
This gives compile-time guarantees for field access and eliminates
stringly-typed indexing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:46:05 -05:00
Bryan Helmkamp
cbc1444489 Add GitHub App IAT integration tests and improve is_repo_public error handling
Add three end-to-end tests for the GitHub App Installation Access Token clone
flow (private repo clone, public repo optimization, not-installed error). Also
return an error from is_repo_public on non-404 HTTP failures instead of
attempting to parse the response as JSON.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 06:46:52 -05:00
Bryan Helmkamp
72b6e86f3d Clean up GitHub App credentials: share reqwest client, accept raw PEM, avoid clone
- Share a single reqwest::Client across GitHub API calls in resolve_clone_credentials
  to reuse the TLS connection pool
- Accept raw PEM (not just base64-encoded) in build_github_app_credentials, matching
  the existing decode_pem_env/decode_pem_value convention
- Move github_app into DaytonaSandbox::new() instead of cloning

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 22:45:19 -05:00
Bryan Helmkamp
cf50625824 Switch Daytona git cloning from gh CLI to GitHub App Installation Access Tokens
Replace `gh auth token` with GitHub App IATs scoped to `contents: read` for
Daytona sandbox git cloning. Public repos are auto-detected and cloned without
credentials. Private repos get short-lived, repo-scoped tokens. Clear error
messages for each failure mode (app not installed, suspended, no repo access,
auth failure). Falls back gracefully when no GitHub App is configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 22:31:40 -05:00
Bryan Helmkamp
1b2a63f6af Implement per-node max_visits override for loop detection
Per-node `max_visits` now overrides the graph-level `max_node_visits`
(and the dry-run default of 10) for individual nodes, giving tighter
control over specific loops like fix-and-verify cycles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 22:05:11 -05:00
Bryan Helmkamp
69a203c2a8 Add $$ escape mechanism for variable expansion
Allows literal $ signs in prompts and DOT files by writing $$. Also
refactors VariableExpansionTransform to use expand_vars instead of
string replace, fixing a substring-matching bug with $goal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:44:50 -05:00
Bryan Helmkamp
24f0fa7ca0 Add Daytona network access control and fill in execution docs
- Add DaytonaNetwork enum (block/allow_all/allow_list) with custom serde
  Deserialize for TOML string-or-table syntax
- Wire network config through run_config defaults merging and base_params
- Document network access in sandboxing, environments, and run-configuration
- Fill in execution docs: checkpoints, environments, failures, interviews,
  run configuration, observability, retros
- Rename compounding.mdx → retros.mdx, insights.mdx → observability.mdx
- Use DaytonaConfig::default() in tests to reduce boilerplate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 18:31:19 -05:00
Bryan Helmkamp
735b4648a0 Rename script.output/script.stderr context keys to command.output/command.stderr
Aligns context key names with the handler name (CommandHandler), which was
previously inconsistent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 18:09:42 -05:00
Bryan Helmkamp
dc348d553e Extract parse_ast() and use Write sink in parse_command
- Extract shared strip+parse+trailing-check into parser::parse_ast()
  so both parser::parse() and parse_command reuse it
- Accept impl Write in parse_command so tests verify actual JSON output
  instead of re-parsing independently

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:19:34 -05:00
Bryan Helmkamp
57dd00a1a5 Add arc parse FILE.dot subcommand to print raw AST as JSON
Parses a DOT file and outputs its AST as pretty-printed JSON, useful for
debugging and tooling. Adds Serialize/Deserialize to all AST types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:16:04 -05:00
Bryan Helmkamp
65e3a0bcee Fix pre-existing clippy warnings in arc-workflows
- Replace useless format!() with .to_string() in parse_decision
- Derive Default for HookDecision instead of manual impl
- Use contains_key() instead of get().is_none() in semantic parser

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:11:44 -05:00
Bryan Helmkamp
3a7e7deb5a Use structured output for prompt hooks via generate_object()
Replace freeform LLM text generation with schema-constrained
generate_object() for prompt hooks, eliminating the need for
JSON formatting instructions in the system prompt and the
code-fence stripping workaround.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 09:55:16 -05:00
Bryan Helmkamp
8a1d1a3ff4 Extend condition expression language with ||, !, numeric comparisons, contains, and matches
Replace flat Vec<Clause> parser with AST-based recursive descent parser
supporting full operator precedence (&& binds tighter than ||, ! is prefix).
New operators: >, <, >=, <= (numeric), contains (substring/array membership),
matches (regex, validated at parse time). Simplify ConditionSyntaxRule to
delegate entirely to parse_condition(). Public API unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 08:20:07 -05:00
Bryan Helmkamp
2d3ae34059 Extract is_llm_handler_type() to deduplicate 4 match-arm sites
The pattern Some("agent") | Some("agent_loop") | Some("prompt") |
Some("one_shot") was duplicated across preamble.rs (3x) and
validation/rules.rs (1x). Centralizes into a single function in
graph/types.rs. Also fixes stale doc comment on default_registry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 07:52:13 -05:00
Bryan Helmkamp
059a50e1b3 Rename handler types: agent_loop → agent, one_shot → prompt
Keep legacy aliases (agent_loop, one_shot) in the handler registry
and validation rules for backwards compatibility. Add codergen_mode
attribute support in the DOT parser, translating legacy values to
the new type names.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 07:44:33 -05:00
Bryan Helmkamp
4bb072884c Rename handler types and split one_shot into its own handler
- Rename handler type strings: codergen → agent_loop, wait.human → human,
  script → command, wait.timer → wait
- Rename handler modules/structs to match: AgentHandler, HumanHandler,
  CommandHandler, WaitHandler
- Split one_shot into PromptHandler (handler/prompt.rs) with shape=tab mapping
- Remove CodergenMode enum and codergen_mode attribute — one_shot is now its
  own handler type, not a mode flag on the agent loop handler
- Update all demo DOT files: codergen_mode="one_shot" → shape=tab
- Update spec, README, validation rules, preamble, and hook tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 07:22:17 -05:00
Bryan Helmkamp
4f4f1f98a3 Simplify hook implementation: deduplicate LLM setup, fix async I/O, cache HTTP clients
- Extract shared prompt/agent hook setup (model resolution, system prompt,
  user message, timeout wrapper) into reusable helpers
- Fix blocking I/O: std::process::Command → tokio::process::Command for
  host-mode hook execution
- Cache reqwest::Client per TLS mode via OnceLock instead of rebuilding
  per HTTP hook call
- Return Cow from resolved_hook_type() to avoid cloning HookType on
  every call
- Rename command_executor → executor in HookRunner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 02:49:06 -05:00
Bryan Helmkamp
c7e7b30906 Reuse core tool registry in agent hooks and add e2e tests
- Change HookExecutor trait to take Arc<dyn Sandbox> so agent hooks can
  share the sandbox with the ToolRegistry via ToolContext
- Replace hand-rolled 2-tool dispatch with register_core_tools() giving
  agent hooks the full tool set (read_file, write_file, shell, grep, glob)
- Add strip_code_fences() to handle LLMs wrapping JSON in markdown
- Set max_tokens(1024) on prompt hooks to avoid exceeding model limits
- Add e2e tests: TOML parsing, prompt proceed/block, agent proceed,
  agent with tool use (reads a file via read_file tool)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 02:33:10 -05:00
Bryan Helmkamp
fe42189dc9 Add prompt and agent hook types for LLM-based hook evaluation
Prompt hooks make a single-turn LLM call returning {"ok": true/false}.
Agent hooks run a multi-turn LLM tool loop with sandbox access (exec_command, read_file).
Both fail-open on errors/timeouts. Prompt hooks default to 30s timeout,
agent hooks to 60s with max 50 tool rounds. Default model is "haiku".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 02:19:09 -05:00