mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-11 22:53:00 +00:00
Split ~/.arc/logs/ into logs/ and runs/
Per-run data now lives in ~/.arc/runs/ while daily CLI log files stay in ~/.arc/logs/. Renames: logs_root → run_dir (RunConfig field, Handler trait param, all handlers), logs_dir → run_dir (CLI arg, local variables), default_logs_base → default_runs_base (path fn). Adds DB migration 002 to rename workflow_runs.logs_dir → run_dir. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
54ecae9586
commit
1dcad0529a
39 changed files with 541 additions and 539 deletions
|
|
@ -7,7 +7,7 @@ When an agent or prompt node finishes, Arc captures its response text and produc
|
|||
|
||||
## Response capture
|
||||
|
||||
After an agent or prompt node completes, Arc captures the full response text and writes it to the run logs at `{logs_root}/nodes/{node_id}/response.md`. It also writes the final outcome (status, context updates, routing directives) to `{logs_root}/nodes/{node_id}/status.json`.
|
||||
After an agent or prompt node completes, Arc captures the full response text and writes it to the run logs at `{run_dir}/nodes/{node_id}/response.md`. It also writes the final outcome (status, context updates, routing directives) to `{run_dir}/nodes/{node_id}/status.json`.
|
||||
|
||||
## Context updates
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ review -> approve [label="Approve"]
|
|||
|
||||
## Output logging
|
||||
|
||||
Arc writes several files per stage to `{logs_root}/nodes/{node_id}/`:
|
||||
Arc writes several files per stage to `{run_dir}/nodes/{node_id}/`:
|
||||
|
||||
| File | Contents |
|
||||
|---|---|
|
||||
|
|
@ -147,10 +147,10 @@ Values under 100KB remain in the context as-is.
|
|||
|
||||
### Artifact storage layout
|
||||
|
||||
Offloaded artifacts are written to the run's logs directory:
|
||||
Offloaded artifacts are written to the run's directory:
|
||||
|
||||
```
|
||||
~/.arc/logs/{run_id}/
|
||||
~/.arc/runs/{run_id}/
|
||||
artifacts/
|
||||
values/
|
||||
response.plan.json
|
||||
|
|
@ -206,7 +206,7 @@ For each pointer in the context updates:
|
|||
|
||||
```
|
||||
# Before sync (host path)
|
||||
file:///home/user/.arc/logs/01JK.../artifacts/values/response.plan.json
|
||||
file:///home/user/.arc/runs/01JK.../artifacts/values/response.plan.json
|
||||
|
||||
# After sync (sandbox path)
|
||||
file:///workspace/.arc/artifacts/response.plan.json
|
||||
|
|
@ -220,7 +220,7 @@ For local sandboxes, syncing is a no-op since the agent can already access the h
|
|||
|
||||
## Automatic asset capture
|
||||
|
||||
After each node executes a command, Arc automatically scans the sandbox for test artifacts — screenshots, videos, reports, and traces — and copies any new or changed files to the run's logs directory. This happens without any agent or workflow configuration.
|
||||
After each node executes a command, Arc automatically scans the sandbox for test artifacts — screenshots, videos, reports, and traces — and copies any new or changed files to the run's directory. This happens without any agent or workflow configuration.
|
||||
|
||||
### How asset capture works
|
||||
|
||||
|
|
@ -252,10 +252,10 @@ Tool caches and dependency directories (`node_modules`, `.cache/ms-playwright`,
|
|||
|
||||
### Asset storage layout
|
||||
|
||||
Collected assets are written to the run's logs directory, organized by node and retry attempt:
|
||||
Collected assets are written to the run's directory, organized by node and retry attempt:
|
||||
|
||||
```
|
||||
~/.arc/logs/{run_id}/
|
||||
~/.arc/runs/{run_id}/
|
||||
assets/
|
||||
{node_slug}/
|
||||
retry_1/
|
||||
|
|
@ -291,4 +291,4 @@ Outputs and artifacts appear in several observability surfaces:
|
|||
| `WorkflowRunCompleted` event | `artifact_count` -- total number of offloaded artifacts across the run |
|
||||
| [Retros](/execution/retros) | Per-stage `files_touched` and aggregate `files_touched` across all stages |
|
||||
| [Preambles](/execution/context#preamble-construction) | File list and artifact pointer references for completed stages |
|
||||
| Stage logs | `status.json` in each stage's logs directory contains the full outcome including `files_touched` |
|
||||
| Stage logs | `status.json` in each stage's run directory contains the full outcome including `files_touched` |
|
||||
|
|
|
|||
|
|
@ -292,4 +292,4 @@ Use prompt nodes for analysis, classification, and summarization tasks where too
|
|||
|
||||
## Prompt logging
|
||||
|
||||
Arc writes the assembled prompt to `{logs_root}/nodes/{node_id}/prompt.md` for every agent and prompt stage. This includes the preamble (if any) and the expanded prompt text. Use these files for debugging when an agent behaves unexpectedly.
|
||||
Arc writes the assembled prompt to `{run_dir}/nodes/{node_id}/prompt.md` for every agent and prompt stage. This includes the preamble (if any) and the expanded prompt text. Use these files for debugging when an agent behaves unexpectedly.
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ The `checkpoint.json` captures everything needed to resume a run:
|
|||
| `loop_failure_signatures` | Failure signature counts for loop detection |
|
||||
| `restart_failure_signatures` | Failure signature counts across loop-restart edges |
|
||||
|
||||
The checkpoint is also saved to `checkpoint.json` in the logs directory for quick local access.
|
||||
The checkpoint is also saved to `checkpoint.json` in the run directory for quick local access.
|
||||
|
||||
## Worktrees
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ Arc uses Git worktrees to isolate workflow runs from your working directory. Whe
|
|||
|
||||
1. Arc records the current HEAD as the **base SHA**
|
||||
2. Creates a new branch `arc/run/{run_id}` at that SHA
|
||||
3. Adds a worktree at `{logs_dir}/worktree` on that branch
|
||||
3. Adds a worktree at `{run_dir}/worktree` on that branch
|
||||
4. Changes into the worktree directory for the duration of the run
|
||||
|
||||
This means your original working directory stays untouched while the agent makes changes in the worktree. When the run completes, Arc removes the worktree and restores your original directory.
|
||||
|
|
@ -94,7 +94,7 @@ There are two ways to resume an interrupted run:
|
|||
|
||||
### From a checkpoint file
|
||||
|
||||
Resume from a `checkpoint.json` saved in the logs directory:
|
||||
Resume from a `checkpoint.json` saved in the run directory:
|
||||
|
||||
```bash
|
||||
arc run workflow.dot --resume path/to/logs/checkpoint.json
|
||||
|
|
@ -125,7 +125,7 @@ This reads the checkpoint, manifest, and graph DOT from the metadata branch (`re
|
|||
|
||||
Here's the full sequence that runs after every node completes:
|
||||
|
||||
1. **Save checkpoint to disk** — Write `checkpoint.json` to the logs directory
|
||||
1. **Save checkpoint to disk** — Write `checkpoint.json` to the run directory
|
||||
2. **Write metadata branch** — Serialize the checkpoint and any new artifacts to the metadata branch (shadow commit)
|
||||
3. **Commit to run branch** — Stage all file changes, commit with structured trailers linking to the shadow commit SHA
|
||||
4. **Update checkpoint** — Re-save `checkpoint.json` with the `git_commit_sha` field set
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Arc captures a structured event for every significant action during a workflow r
|
|||
|
||||
The event stream is the foundation of Arc's observability. Every workflow run emits a sequence of `WorkflowRunEvent` records that are:
|
||||
|
||||
- **Written to `progress.jsonl`** in the run's logs directory (one JSON object per line)
|
||||
- **Written to `progress.jsonl`** in the run's directory (one JSON object per line)
|
||||
- **Broadcast via SSE** to connected API clients in real time
|
||||
- **Logged via `tracing`** to the daily log file at `~/.arc/logs/`
|
||||
|
||||
|
|
@ -114,17 +114,17 @@ Arc writes two kinds of logs:
|
|||
|
||||
### Run logs (`progress.jsonl`)
|
||||
|
||||
Every run writes its event stream to `{logs_dir}/progress.jsonl`. This is the primary data source for post-run analysis — [retros](/execution/retros) read it, and you can query it directly with standard tools:
|
||||
Every run writes its event stream to `{run_dir}/progress.jsonl`. This is the primary data source for post-run analysis — [retros](/execution/retros) read it, and you can query it directly with standard tools:
|
||||
|
||||
```bash
|
||||
# Count tool calls in a run
|
||||
grep "ToolCallStarted" ~/.arc/logs/01JKXYZ.../progress.jsonl | wc -l
|
||||
grep "ToolCallStarted" ~/.arc/runs/01JKXYZ.../progress.jsonl | wc -l
|
||||
|
||||
# Find all failures
|
||||
grep -E "StageFailed|WorkflowRunFailed" ~/.arc/logs/01JKXYZ.../progress.jsonl | jq .
|
||||
grep -E "StageFailed|WorkflowRunFailed" ~/.arc/runs/01JKXYZ.../progress.jsonl | jq .
|
||||
|
||||
# See which edges were taken
|
||||
grep "EdgeSelected" ~/.arc/logs/01JKXYZ.../progress.jsonl | jq '{from: .from_node, to: .to_node}'
|
||||
grep "EdgeSelected" ~/.arc/runs/01JKXYZ.../progress.jsonl | jq '{from: .from_node, to: .to_node}'
|
||||
```
|
||||
|
||||
### Live snapshot (`live.json`)
|
||||
|
|
@ -132,7 +132,7 @@ grep "EdgeSelected" ~/.arc/logs/01JKXYZ.../progress.jsonl | jq '{from: .from_nod
|
|||
During execution, Arc also writes `live.json` — a pretty-printed copy of the most recent event. This is useful for quick status checks while a run is in progress:
|
||||
|
||||
```bash
|
||||
cat ~/.arc/logs/01JKXYZ.../live.json
|
||||
cat ~/.arc/runs/01JKXYZ.../live.json
|
||||
```
|
||||
|
||||
### Application logs
|
||||
|
|
@ -178,11 +178,11 @@ arc ps --label team=platform
|
|||
arc ps --json
|
||||
```
|
||||
|
||||
This scans `~/.arc/logs/` and displays each run's ID, workflow name, status, and start time. Use `--json` for machine-readable output.
|
||||
This scans `~/.arc/runs/` and displays each run's ID, workflow name, status, and start time. Use `--json` for machine-readable output.
|
||||
|
||||
### Run artifacts
|
||||
|
||||
Each run's logs directory contains a standard set of files:
|
||||
Each run's directory contains a standard set of files:
|
||||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ Open items capture follow-up work identified during the run:
|
|||
|
||||
Retro generation happens in two phases after a run completes:
|
||||
|
||||
1. **Derive** — Arc extracts stage durations from `progress.jsonl` and builds a retro from the checkpoint data. This is deterministic, fast, and produces the quantitative layer. The retro is saved immediately as `retro.json` in the run's logs directory.
|
||||
1. **Derive** — Arc extracts stage durations from `progress.jsonl` and builds a retro from the checkpoint data. This is deterministic, fast, and produces the quantitative layer. The retro is saved immediately as `retro.json` in the run's directory.
|
||||
|
||||
2. **Narrate** — An LLM agent session analyzes the run data. The agent has read access to `progress.jsonl`, `checkpoint.json`, and `manifest.json`. It uses grep and read tools to find interesting signals — failures, retries, errors, approach changes — then calls a `submit_retro` tool with its structured analysis. The narrative fields are merged into the existing retro and saved.
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ Both phases run automatically at the end of every CLI run. The API server derive
|
|||
|
||||
### CLI
|
||||
|
||||
Retros are saved to `{logs_dir}/retro.json` after every run. The path is printed at the end of the run output:
|
||||
Retros are saved to `{run_dir}/retro.json` after every run. The path is printed at the end of the run output:
|
||||
|
||||
```
|
||||
Retro: smooth — Successfully implemented the feature
|
||||
|
|
@ -129,4 +129,4 @@ Retros are also available via the REST API. See the [Retros API reference](/api-
|
|||
|
||||
## Storage
|
||||
|
||||
Retros are stored as `retro.json` in the run's logs directory alongside `checkpoint.json` and `progress.jsonl`. They are plain JSON files — easy to parse, query, or pipe into other tools.
|
||||
Retros are stored as `retro.json` in the run's directory alongside `checkpoint.json` and `progress.jsonl`. They are plain JSON files — easy to parse, query, or pipe into other tools.
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ arc run --run-branch arc/run/abc123
|
|||
| Argument / Flag | Description |
|
||||
|---|---|
|
||||
| `<WORKFLOW>` | Path to a `.dot` workflow file or `.toml` task config. Not required when using `--run-branch`. |
|
||||
| `--logs-dir <DIR>` | Log and artifact directory |
|
||||
| `--run-dir <DIR>` | Run output directory |
|
||||
| `--dry-run` | Execute with a simulated LLM backend |
|
||||
| `--preflight` | Validate run configuration without executing |
|
||||
| `--auto-approve` | Auto-approve all human gates |
|
||||
|
|
@ -71,7 +71,7 @@ arc run --run-branch arc/run/abc123
|
|||
|
||||
## `arc ps`
|
||||
|
||||
List workflow runs stored in `~/.arc/logs`.
|
||||
List workflow runs stored in `~/.arc/runs`.
|
||||
|
||||
```bash
|
||||
arc ps
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
---
|
||||
title: "Logs Directory"
|
||||
description: "Structure of Arc's local logs directory"
|
||||
title: "Logs & Runs Directories"
|
||||
description: "Structure of Arc's local logs and runs directories"
|
||||
---
|
||||
|
||||
Arc writes all local run data to `~/.arc/logs/`. This directory contains daily CLI log files and a subdirectory for each workflow run.
|
||||
Arc stores data in two directories under `~/.arc/`:
|
||||
|
||||
- **`~/.arc/runs/`** — Per-run data. Each workflow run gets its own subdirectory containing event streams, checkpoints, artifacts, and worktrees.
|
||||
- **`~/.arc/logs/`** — Daily CLI log files. One `.log` file per day with aggregated tracing output.
|
||||
|
||||
## Daily log files
|
||||
|
||||
|
|
@ -20,10 +23,10 @@ The log level defaults to `info`. Set `ARC_LOG=debug` or pass `--debug` for verb
|
|||
Each `arc run` invocation creates a timestamped directory:
|
||||
|
||||
```
|
||||
~/.arc/logs/20260307-01JQXYZ123ABC456DEF789/
|
||||
~/.arc/runs/20260307-01JQXYZ123ABC456DEF789/
|
||||
```
|
||||
|
||||
The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the location with `--logs-dir`.
|
||||
The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the location with `--run-dir`.
|
||||
|
||||
### Root-level files
|
||||
|
||||
|
|
@ -81,7 +84,7 @@ Manager nodes that run sub-workflows write a nested `child/` directory containin
|
|||
|
||||
## Browsing runs
|
||||
|
||||
Use `arc ps` to scan the logs directory and display a table of all runs with their status, workflow name, and timestamps. Pass `--json` for machine-readable output.
|
||||
Use `arc ps` to scan the runs directory and display a table of all runs with their status, workflow name, and timestamps. Pass `--json` for machine-readable output.
|
||||
|
||||
```bash
|
||||
arc ps
|
||||
|
|
@ -94,6 +97,8 @@ arc ps --filter workflow=my-workflow
|
|||
```
|
||||
~/.arc/logs/
|
||||
├── 2026-03-07.log # Daily CLI log
|
||||
|
||||
~/.arc/runs/
|
||||
├── 20260307-01JQXYZ123ABC456DEF789/ # One directory per run
|
||||
│ ├── manifest.json
|
||||
│ ├── graph.dot
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ struct ManagedRun {
|
|||
checkpoint: Option<Checkpoint>,
|
||||
cancel_tx: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
cancel_token: Option<Arc<AtomicBool>>,
|
||||
logs_root: Option<std::path::PathBuf>,
|
||||
run_dir: Option<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
/// Per-model usage totals.
|
||||
|
|
@ -491,7 +491,7 @@ async fn start_run(
|
|||
checkpoint: None,
|
||||
cancel_tx: None,
|
||||
cancel_token: None,
|
||||
logs_root: None,
|
||||
run_dir: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -591,10 +591,10 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
}
|
||||
}
|
||||
|
||||
let logs_root = std::env::temp_dir().join(format!("arc-{}", uuid::Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&logs_root).expect("failed to create logs directory");
|
||||
let run_dir = std::env::temp_dir().join(format!("arc-{}", uuid::Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&run_dir).expect("failed to create run directory");
|
||||
let config = RunConfig {
|
||||
logs_root,
|
||||
run_dir,
|
||||
cancel_token: Some(cancel_token),
|
||||
dry_run: state.dry_run,
|
||||
run_id: run_id.clone(),
|
||||
|
|
@ -626,7 +626,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
};
|
||||
|
||||
// Save final checkpoint
|
||||
let checkpoint = Checkpoint::load(&config.logs_root.join("checkpoint.json")).ok();
|
||||
let checkpoint = Checkpoint::load(&config.run_dir.join("checkpoint.json")).ok();
|
||||
|
||||
// Auto-derive retro and accumulate aggregate usage
|
||||
if let Some(ref cp) = checkpoint {
|
||||
|
|
@ -634,7 +634,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
Ok(_) => (false, None),
|
||||
Err(e) => (true, Some(e.to_string())),
|
||||
};
|
||||
let stage_durations = arc_workflows::retro::extract_stage_durations(&config.logs_root);
|
||||
let stage_durations = arc_workflows::retro::extract_stage_durations(&config.run_dir);
|
||||
let retro = arc_workflows::retro::derive_retro(
|
||||
&run_id,
|
||||
"workflow",
|
||||
|
|
@ -645,7 +645,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
0,
|
||||
&stage_durations,
|
||||
);
|
||||
let _ = retro.save(&config.logs_root);
|
||||
let _ = retro.save(&config.run_dir);
|
||||
|
||||
// Accumulate aggregate usage
|
||||
let mut agg = state
|
||||
|
|
@ -686,7 +686,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
}
|
||||
}
|
||||
managed_run.checkpoint = checkpoint;
|
||||
managed_run.logs_root = Some(config.logs_root.clone());
|
||||
managed_run.run_dir = Some(config.run_dir.clone());
|
||||
managed_run.event_tx = None;
|
||||
}
|
||||
drop(runs);
|
||||
|
|
@ -1388,19 +1388,19 @@ async fn get_retro(
|
|||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Response {
|
||||
let logs_root = {
|
||||
let run_dir = {
|
||||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
match runs.get(&id) {
|
||||
Some(managed_run) => managed_run.logs_root.clone(),
|
||||
Some(managed_run) => managed_run.run_dir.clone(),
|
||||
None => return ApiError::not_found("Run not found.").into_response(),
|
||||
}
|
||||
};
|
||||
|
||||
let Some(logs_root) = logs_root else {
|
||||
let Some(run_dir) = run_dir else {
|
||||
return (StatusCode::OK, Json(serde_json::json!(null))).into_response();
|
||||
};
|
||||
|
||||
match arc_workflows::retro::Retro::load(&logs_root) {
|
||||
match arc_workflows::retro::Retro::load(&run_dir) {
|
||||
Ok(retro) => (StatusCode::OK, Json(retro)).into_response(),
|
||||
Err(_) => (StatusCode::OK, Json(serde_json::json!(null))).into_response(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -692,22 +692,22 @@ fn doctor_live_flag_accepted() {
|
|||
#[test]
|
||||
fn dry_run_writes_jsonl_and_live_json() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let logs_dir = tmp.path().join("logs");
|
||||
let run_dir = tmp.path().join("run");
|
||||
|
||||
arc()
|
||||
.args([
|
||||
"run",
|
||||
"--dry-run",
|
||||
"--auto-approve",
|
||||
"--logs-dir",
|
||||
logs_dir.to_str().unwrap(),
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"../../../test/simple.dot",
|
||||
])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// progress.jsonl must exist and contain valid JSON lines
|
||||
let jsonl_path = logs_dir.join("progress.jsonl");
|
||||
let jsonl_path = run_dir.join("progress.jsonl");
|
||||
assert!(jsonl_path.exists(), "progress.jsonl should exist");
|
||||
let jsonl_content = std::fs::read_to_string(&jsonl_path).unwrap();
|
||||
let lines: Vec<&str> = jsonl_content.lines().collect();
|
||||
|
|
@ -738,7 +738,7 @@ fn dry_run_writes_jsonl_and_live_json() {
|
|||
assert!(!run_id.is_empty(), "run_id should be non-empty");
|
||||
|
||||
// live.json must exist and contain valid JSON matching the last JSONL line
|
||||
let live_path = logs_dir.join("live.json");
|
||||
let live_path = run_dir.join("live.json");
|
||||
assert!(live_path.exists(), "live.json should exist");
|
||||
let live_content: serde_json::Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(&live_path).unwrap()).unwrap();
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE workflow_runs RENAME COLUMN logs_dir TO run_dir;
|
||||
|
|
@ -73,7 +73,7 @@ mod tests {
|
|||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(row.0, 1);
|
||||
assert_eq!(row.0, 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -86,7 +86,7 @@ mod tests {
|
|||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(row.0, 1);
|
||||
assert_eq!(row.0, 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -98,7 +98,7 @@ mod tests {
|
|||
let now_str = now.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO workflow_runs (id, title, logs_dir, work_dir, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
"INSERT INTO workflow_runs (id, title, run_dir, work_dir, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
)
|
||||
.bind("run-1")
|
||||
.bind("My Run")
|
||||
|
|
@ -118,7 +118,7 @@ mod tests {
|
|||
|
||||
assert_eq!(run.id, "run-1");
|
||||
assert_eq!(run.title, "My Run");
|
||||
assert_eq!(run.logs_dir, "/tmp/logs");
|
||||
assert_eq!(run.run_dir, "/tmp/logs");
|
||||
assert_eq!(run.work_dir, "/tmp/work");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use sqlx::SqlitePool;
|
||||
use tracing::{debug, info};
|
||||
|
||||
const CURRENT_VERSION: i64 = 1;
|
||||
const CURRENT_VERSION: i64 = 2;
|
||||
|
||||
const MIGRATION_001: &str = include_str!("../migrations/001_create_workflow_runs.sql");
|
||||
const MIGRATION_002: &str = include_str!("../migrations/002_rename_logs_dir_to_run_dir.sql");
|
||||
|
||||
/// Apply all pending migrations to the database.
|
||||
///
|
||||
|
|
@ -26,6 +27,10 @@ pub async fn initialize_db(pool: &SqlitePool) -> Result<(), sqlx::Error> {
|
|||
sqlx::query(MIGRATION_001).execute(&mut *tx).await?;
|
||||
}
|
||||
|
||||
if from_version < 2 {
|
||||
sqlx::query(MIGRATION_002).execute(&mut *tx).await?;
|
||||
}
|
||||
|
||||
sqlx::query(&format!("PRAGMA user_version = {CURRENT_VERSION}"))
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
|
|||
pub struct WorkflowRun {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub logs_dir: String,
|
||||
pub run_dir: String,
|
||||
pub work_dir: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ registry.register("agent", Box::new(AgentHandler::new(None)));
|
|||
|
||||
let engine = PipelineEngine::new(registry, EventEmitter::new());
|
||||
let config = RunConfig {
|
||||
logs_root: "/tmp/pipeline-run".into(),
|
||||
run_dir: "/tmp/pipeline-run".into(),
|
||||
};
|
||||
|
||||
// engine.run(&graph, &config).await
|
||||
|
|
@ -107,7 +107,7 @@ impl Handler for MyHandler {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
// Custom logic here
|
||||
Ok(Outcome::success())
|
||||
|
|
|
|||
|
|
@ -304,11 +304,11 @@ pub async fn collect_assets(
|
|||
Ok(summary)
|
||||
}
|
||||
|
||||
/// Collect all asset paths from manifest files under `{logs_dir}/artifacts/assets/*/retry_*/manifest.json`.
|
||||
/// Collect all asset paths from manifest files under `{run_dir}/artifacts/assets/*/retry_*/manifest.json`.
|
||||
///
|
||||
/// Returns the full on-disk paths to the downloaded asset files.
|
||||
pub fn collect_asset_paths(logs_dir: &Path) -> Vec<String> {
|
||||
let assets_dir = logs_dir.join("artifacts/assets");
|
||||
pub fn collect_asset_paths(run_dir: &Path) -> Vec<String> {
|
||||
let assets_dir = run_dir.join("artifacts/assets");
|
||||
let Ok(nodes) = std::fs::read_dir(&assets_dir) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result};
|
|||
use clap::Args;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::cli::runs::{default_logs_base, find_run_by_prefix};
|
||||
use crate::cli::runs::{default_runs_base, find_run_by_prefix};
|
||||
use crate::sandbox_record::SandboxRecord;
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -172,7 +172,7 @@ async fn load_sandbox(
|
|||
|
||||
pub async fn cp_command(args: CpArgs) -> Result<()> {
|
||||
let direction = parse_direction(&args.src, &args.dst)?;
|
||||
let base = default_logs_base();
|
||||
let base = default_runs_base();
|
||||
|
||||
match direction {
|
||||
CopyDirection::Download {
|
||||
|
|
|
|||
|
|
@ -99,9 +99,9 @@ pub struct RunArgs {
|
|||
#[arg(required_unless_present = "run_branch")]
|
||||
pub workflow: Option<PathBuf>,
|
||||
|
||||
/// Log/artifact directory
|
||||
/// Run output directory
|
||||
#[arg(long)]
|
||||
pub logs_dir: Option<PathBuf>,
|
||||
pub run_dir: Option<PathBuf>,
|
||||
|
||||
/// Execute with simulated LLM backend
|
||||
#[arg(long)]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result};
|
|||
use clap::Args;
|
||||
use tracing::info;
|
||||
|
||||
use crate::cli::runs::{default_logs_base, find_run_by_prefix};
|
||||
use crate::cli::runs::{default_runs_base, find_run_by_prefix};
|
||||
use crate::conclusion::Conclusion;
|
||||
use crate::manifest::Manifest;
|
||||
use crate::outcome::StageStatus;
|
||||
|
|
@ -22,7 +22,7 @@ pub async fn pr_create_command(
|
|||
args: PrCreateArgs,
|
||||
github_app: Option<arc_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
let base = default_logs_base();
|
||||
let base = default_runs_base();
|
||||
pr_create_from(&base, args, github_app).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -641,16 +641,16 @@ impl ProgressUI {
|
|||
|
||||
// ── Logs dir (called externally) ────────────────────────────────────
|
||||
|
||||
pub fn show_logs_dir(&mut self, logs_dir: &Path) {
|
||||
let path_str = super::tilde_path(logs_dir);
|
||||
pub fn show_run_dir(&mut self, run_dir: &Path) {
|
||||
let path_str = super::tilde_path(run_dir);
|
||||
match &self.renderer {
|
||||
ProgressRenderer::Tty(tty) => {
|
||||
let bar = tty.multi.add(ProgressBar::new_spinner());
|
||||
bar.set_style(style_static_dim());
|
||||
bar.finish_with_message(format!("Logs: {path_str}"));
|
||||
bar.finish_with_message(format!("Run: {path_str}"));
|
||||
}
|
||||
ProgressRenderer::Plain => {
|
||||
eprintln!(" Logs: {path_str}");
|
||||
eprintln!(" Run: {path_str}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -392,21 +392,21 @@ pub async fn run_command(
|
|||
|
||||
// 3. Create logs directory
|
||||
let run_id = ulid::Ulid::new().to_string();
|
||||
let logs_dir = args.logs_dir.unwrap_or_else(|| {
|
||||
let run_dir = args.run_dir.unwrap_or_else(|| {
|
||||
let base = dirs::home_dir()
|
||||
.expect("could not determine home directory")
|
||||
.join(".arc")
|
||||
.join("logs");
|
||||
.join("runs");
|
||||
base.join(format!("{}-{}", Local::now().format("%Y%m%d"), run_id))
|
||||
});
|
||||
tokio::fs::create_dir_all(&logs_dir).await?;
|
||||
arc_util::run_log::activate(&logs_dir.join("cli.log"))
|
||||
tokio::fs::create_dir_all(&run_dir).await?;
|
||||
arc_util::run_log::activate(&run_dir.join("cli.log"))
|
||||
.context("Failed to activate per-run log")?;
|
||||
tokio::fs::write(logs_dir.join("graph.dot"), &source).await?;
|
||||
tokio::fs::write(logs_dir.join("run.pid"), std::process::id().to_string()).await?;
|
||||
tokio::fs::write(run_dir.join("graph.dot"), &source).await?;
|
||||
tokio::fs::write(run_dir.join("run.pid"), std::process::id().to_string()).await?;
|
||||
if workflow_path.extension().is_some_and(|ext| ext == "toml") {
|
||||
if let Ok(toml_contents) = tokio::fs::read(workflow_path).await {
|
||||
tokio::fs::write(logs_dir.join("run.toml"), toml_contents).await?;
|
||||
tokio::fs::write(run_dir.join("run.toml"), toml_contents).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -418,7 +418,7 @@ pub async fn run_command(
|
|||
ui.show_version();
|
||||
ui.show_run_id(&run_id);
|
||||
ui.show_time(&Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
|
||||
ui.show_logs_dir(&logs_dir);
|
||||
ui.show_run_dir(&run_dir);
|
||||
}
|
||||
|
||||
// 3. Build event emitter
|
||||
|
|
@ -455,8 +455,8 @@ pub async fn run_command(
|
|||
|
||||
// JSONL progress log + live.json snapshot
|
||||
{
|
||||
let jsonl_path = logs_dir.join("progress.jsonl");
|
||||
let live_path = logs_dir.join("live.json");
|
||||
let jsonl_path = run_dir.join("progress.jsonl");
|
||||
let live_path = run_dir.join("live.json");
|
||||
let run_id = Arc::new(Mutex::new(String::new()));
|
||||
let run_id_clone = Arc::clone(&run_id);
|
||||
emitter.on_event(move |event| {
|
||||
|
|
@ -575,7 +575,7 @@ pub async fn run_command(
|
|||
|
||||
let (worktree_work_dir, worktree_path, worktree_branch, worktree_base_sha) =
|
||||
if should_create_worktree {
|
||||
match setup_worktree(&original_cwd, &logs_dir, &run_id) {
|
||||
match setup_worktree(&original_cwd, &run_dir, &run_id) {
|
||||
Ok((wd, wt, branch, base)) => (Some(wd), Some(wt), Some(branch), Some(base)),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
|
|
@ -733,7 +733,7 @@ pub async fn run_command(
|
|||
}
|
||||
}
|
||||
};
|
||||
if let Err(e) = record.save(&logs_dir.join("sandbox.json")) {
|
||||
if let Err(e) = record.save(&run_dir.join("sandbox.json")) {
|
||||
tracing::warn!(error = %e, "Failed to save sandbox record");
|
||||
}
|
||||
}
|
||||
|
|
@ -957,7 +957,7 @@ pub async fn run_command(
|
|||
.unwrap_or_default();
|
||||
let pr_cfg = run_cfg.as_ref().and_then(|c| c.pull_request.as_ref());
|
||||
let config = RunConfig {
|
||||
logs_root: logs_dir.clone(),
|
||||
run_dir: run_dir.clone(),
|
||||
cancel_token: None,
|
||||
dry_run: dry_run_mode,
|
||||
run_id: run_id.clone(),
|
||||
|
|
@ -1016,7 +1016,7 @@ pub async fn run_command(
|
|||
failure_reason,
|
||||
final_git_commit_sha: last_git_sha.lock().unwrap().clone(),
|
||||
};
|
||||
let _ = conclusion.save(&logs_dir.join("conclusion.json"));
|
||||
let _ = conclusion.save(&run_dir.join("conclusion.json"));
|
||||
}
|
||||
|
||||
// Finish progress bars before printing summary
|
||||
|
|
@ -1035,7 +1035,7 @@ pub async fn run_command(
|
|||
&config.run_id,
|
||||
&graph.name,
|
||||
graph.goal(),
|
||||
&logs_dir,
|
||||
&run_dir,
|
||||
failed,
|
||||
failure_reason.as_deref(),
|
||||
run_duration_ms,
|
||||
|
|
@ -1058,7 +1058,7 @@ pub async fn run_command(
|
|||
outcome.status,
|
||||
StageStatus::Success | StageStatus::PartialSuccess
|
||||
) {
|
||||
let diff = tokio::fs::read_to_string(logs_dir.join("final.patch"))
|
||||
let diff = tokio::fs::read_to_string(run_dir.join("final.patch"))
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if let (
|
||||
|
|
@ -1119,7 +1119,7 @@ pub async fn run_command(
|
|||
&diff,
|
||||
&model,
|
||||
config.pull_request_draft,
|
||||
&logs_dir,
|
||||
&run_dir,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
|
@ -1130,7 +1130,7 @@ pub async fn run_command(
|
|||
draft: config.pull_request_draft,
|
||||
});
|
||||
pr_url = Some(record.html_url.clone());
|
||||
if let Err(e) = record.save(&logs_dir.join("pull_request.json")) {
|
||||
if let Err(e) = record.save(&run_dir.join("pull_request.json")) {
|
||||
tracing::warn!(error = %e, "Failed to save pull_request.json");
|
||||
}
|
||||
}
|
||||
|
|
@ -1214,7 +1214,7 @@ pub async fn run_command(
|
|||
"{}",
|
||||
styles
|
||||
.dim
|
||||
.apply_to(format!("Logs: {}", tilde_path(&logs_dir)))
|
||||
.apply_to(format!("Run: {}", tilde_path(&run_dir)))
|
||||
);
|
||||
|
||||
if let Some(failure) = outcome.failure_reason() {
|
||||
|
|
@ -1231,8 +1231,8 @@ pub async fn run_command(
|
|||
}
|
||||
}
|
||||
|
||||
print_final_output(&logs_dir, styles);
|
||||
print_assets(&logs_dir, styles);
|
||||
print_final_output(&run_dir, styles);
|
||||
print_assets(&run_dir, styles);
|
||||
|
||||
// 9. Cleanup sandbox (defuse the scopeguard so we await properly)
|
||||
scopeguard::ScopeGuard::into_inner(cleanup_guard);
|
||||
|
|
@ -1269,14 +1269,14 @@ pub async fn run_command(
|
|||
/// Returns (work_dir, worktree_path, branch_name, base_sha) on success.
|
||||
fn setup_worktree(
|
||||
original_cwd: &std::path::Path,
|
||||
logs_dir: &std::path::Path,
|
||||
run_dir: &std::path::Path,
|
||||
run_id: &str,
|
||||
) -> anyhow::Result<(PathBuf, PathBuf, String, String)> {
|
||||
let base_sha = crate::git::head_sha(original_cwd).map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
let branch_name = format!("arc/run/{run_id}");
|
||||
crate::git::create_branch(original_cwd, &branch_name).map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
|
||||
let worktree_path = logs_dir.join("worktree");
|
||||
let worktree_path = run_dir.join("worktree");
|
||||
crate::git::replace_worktree(original_cwd, &worktree_path, &branch_name)
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
|
||||
|
|
@ -1401,21 +1401,21 @@ async fn run_from_branch(
|
|||
}
|
||||
|
||||
// Set up logs directory
|
||||
let logs_dir = args.logs_dir.unwrap_or_else(|| {
|
||||
let run_dir = args.run_dir.unwrap_or_else(|| {
|
||||
let base = dirs::home_dir()
|
||||
.expect("could not determine home directory")
|
||||
.join(".arc")
|
||||
.join("logs");
|
||||
.join("runs");
|
||||
base.join(format!(
|
||||
"{}-{}",
|
||||
chrono::Local::now().format("%Y%m%d"),
|
||||
run_id
|
||||
))
|
||||
});
|
||||
tokio::fs::create_dir_all(&logs_dir).await?;
|
||||
arc_util::run_log::activate(&logs_dir.join("cli.log"))
|
||||
tokio::fs::create_dir_all(&run_dir).await?;
|
||||
arc_util::run_log::activate(&run_dir.join("cli.log"))
|
||||
.context("Failed to activate per-run log")?;
|
||||
tokio::fs::write(logs_dir.join("graph.dot"), &source).await?;
|
||||
tokio::fs::write(run_dir.join("graph.dot"), &source).await?;
|
||||
|
||||
let base_sha =
|
||||
crate::git::MetadataStore::read_manifest(&original_cwd, &run_id)?.and_then(|m| m.base_sha);
|
||||
|
|
@ -1432,7 +1432,7 @@ async fn run_from_branch(
|
|||
match sandbox_provider {
|
||||
SandboxProvider::Local | SandboxProvider::Docker => {
|
||||
// Re-attach worktree to the existing run branch
|
||||
let wt = logs_dir.join("worktree");
|
||||
let wt = run_dir.join("worktree");
|
||||
crate::git::replace_worktree(&original_cwd, &wt, run_branch).map_err(|e| {
|
||||
anyhow::anyhow!("failed to attach worktree to {run_branch}: {e}")
|
||||
})?;
|
||||
|
|
@ -1540,7 +1540,7 @@ async fn run_from_branch(
|
|||
|
||||
let meta_branch = Some(crate::git::MetadataStore::branch_name(&run_id));
|
||||
let config = RunConfig {
|
||||
logs_root: logs_dir.clone(),
|
||||
run_dir: run_dir.clone(),
|
||||
cancel_token: None,
|
||||
dry_run: dry_run_mode,
|
||||
run_id: run_id.clone(),
|
||||
|
|
@ -1594,7 +1594,7 @@ async fn run_from_branch(
|
|||
&config.run_id,
|
||||
&graph.name,
|
||||
graph.goal(),
|
||||
&logs_dir,
|
||||
&run_dir,
|
||||
failed,
|
||||
failure_reason.as_deref(),
|
||||
run_duration_ms,
|
||||
|
|
@ -1626,11 +1626,11 @@ async fn run_from_branch(
|
|||
"{}",
|
||||
styles
|
||||
.dim
|
||||
.apply_to(format!("Logs: {}", tilde_path(&logs_dir)))
|
||||
.apply_to(format!("Run: {}", tilde_path(&run_dir)))
|
||||
);
|
||||
|
||||
print_final_output(&logs_dir, styles);
|
||||
print_assets(&logs_dir, styles);
|
||||
print_final_output(&run_dir, styles);
|
||||
print_assets(&run_dir, styles);
|
||||
|
||||
arc_util::run_log::deactivate();
|
||||
match outcome.status {
|
||||
|
|
@ -1640,8 +1640,8 @@ async fn run_from_branch(
|
|||
}
|
||||
|
||||
/// Print the final stage output from the checkpoint, if available.
|
||||
fn print_final_output(logs_dir: &std::path::Path, styles: &Styles) {
|
||||
let Ok(checkpoint) = Checkpoint::load(&logs_dir.join("checkpoint.json")) else {
|
||||
fn print_final_output(run_dir: &std::path::Path, styles: &Styles) {
|
||||
let Ok(checkpoint) = Checkpoint::load(&run_dir.join("checkpoint.json")) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
@ -1661,8 +1661,8 @@ fn print_final_output(logs_dir: &std::path::Path, styles: &Styles) {
|
|||
}
|
||||
|
||||
/// Print collected asset paths, if any.
|
||||
fn print_assets(logs_dir: &std::path::Path, styles: &Styles) {
|
||||
let paths = crate::asset_snapshot::collect_asset_paths(logs_dir);
|
||||
fn print_assets(run_dir: &std::path::Path, styles: &Styles) {
|
||||
let paths = crate::asset_snapshot::collect_asset_paths(run_dir);
|
||||
if paths.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1934,7 +1934,7 @@ async fn generate_retro(
|
|||
run_id: &str,
|
||||
workflow_name: &str,
|
||||
goal: &str,
|
||||
logs_dir: &std::path::Path,
|
||||
run_dir: &std::path::Path,
|
||||
failed: bool,
|
||||
failure_reason: Option<&str>,
|
||||
run_duration_ms: u64,
|
||||
|
|
@ -1945,7 +1945,7 @@ async fn generate_retro(
|
|||
model: &str,
|
||||
styles: &'static Styles,
|
||||
) {
|
||||
let cp = match Checkpoint::load(&logs_dir.join("checkpoint.json")) {
|
||||
let cp = match Checkpoint::load(&run_dir.join("checkpoint.json")) {
|
||||
Ok(cp) => cp,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
|
|
@ -1956,7 +1956,7 @@ async fn generate_retro(
|
|||
}
|
||||
};
|
||||
|
||||
let stage_durations = crate::retro::extract_stage_durations(logs_dir);
|
||||
let stage_durations = crate::retro::extract_stage_durations(run_dir);
|
||||
let mut retro = crate::retro::derive_retro(
|
||||
run_id,
|
||||
workflow_name,
|
||||
|
|
@ -1968,7 +1968,7 @@ async fn generate_retro(
|
|||
&stage_durations,
|
||||
);
|
||||
|
||||
match retro.save(logs_dir) {
|
||||
match retro.save(run_dir) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
|
|
@ -1988,7 +1988,7 @@ async fn generate_retro(
|
|||
let narrative_result = if dry_run_mode {
|
||||
Ok(crate::retro_agent::dry_run_narrative())
|
||||
} else if let Some(client) = llm_client {
|
||||
crate::retro_agent::run_retro_agent(sandbox, logs_dir, client, provider_enum, model).await
|
||||
crate::retro_agent::run_retro_agent(sandbox, run_dir, client, provider_enum, model).await
|
||||
} else {
|
||||
Err(anyhow::anyhow!("No LLM client available"))
|
||||
};
|
||||
|
|
@ -1997,7 +1997,7 @@ async fn generate_retro(
|
|||
match narrative_result {
|
||||
Ok(narrative) => {
|
||||
retro.apply_narrative(narrative);
|
||||
match retro.save(logs_dir) {
|
||||
match retro.save(run_dir) {
|
||||
Ok(()) => {
|
||||
// Line 1: smoothness + outcome with right-aligned duration
|
||||
let smoothness_str = retro
|
||||
|
|
@ -2046,7 +2046,7 @@ async fn generate_retro(
|
|||
}
|
||||
|
||||
// Line 3: file path
|
||||
let retro_path = format!("{}/retro.json", super::tilde_path(logs_dir));
|
||||
let retro_path = format!("{}/retro.json", super::tilde_path(run_dir));
|
||||
eprintln!(
|
||||
" {} {}",
|
||||
styles.dim.apply_to("Retro saved to"),
|
||||
|
|
|
|||
|
|
@ -239,6 +239,10 @@ fn default_data_dir() -> PathBuf {
|
|||
.join(".arc")
|
||||
}
|
||||
|
||||
pub(crate) fn default_runs_base() -> PathBuf {
|
||||
default_data_dir().join("runs")
|
||||
}
|
||||
|
||||
pub(crate) fn default_logs_base() -> PathBuf {
|
||||
default_data_dir().join("logs")
|
||||
}
|
||||
|
|
@ -272,7 +276,7 @@ pub fn find_run_by_prefix(base: &Path, prefix: &str) -> Result<PathBuf> {
|
|||
}
|
||||
|
||||
pub fn list_command(args: &RunsListArgs) -> Result<()> {
|
||||
let base = default_logs_base();
|
||||
let base = default_runs_base();
|
||||
let runs = scan_runs(&base)?;
|
||||
let label_filters = parse_label_filters(&args.filter.label);
|
||||
let filtered = filter_runs(
|
||||
|
|
@ -362,13 +366,14 @@ fn format_size(bytes: u64) -> String {
|
|||
|
||||
pub fn df_command(args: &DfArgs) -> Result<()> {
|
||||
let data_dir = default_data_dir();
|
||||
let logs_base = data_dir.join("logs");
|
||||
df_from(args, &data_dir, &logs_base)
|
||||
let runs_base = default_runs_base();
|
||||
let logs_base = default_logs_base();
|
||||
df_from(args, &data_dir, &runs_base, &logs_base)
|
||||
}
|
||||
|
||||
pub fn df_from(args: &DfArgs, data_dir: &Path, logs_base: &Path) -> Result<()> {
|
||||
pub fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -> Result<()> {
|
||||
// --- Runs ---
|
||||
let runs = scan_runs(logs_base)?;
|
||||
let runs = scan_runs(runs_base)?;
|
||||
let mut active_count = 0u64;
|
||||
let mut total_run_size = 0u64;
|
||||
let mut reclaimable_run_size = 0u64;
|
||||
|
|
@ -535,7 +540,7 @@ pub fn df_from(args: &DfArgs, data_dir: &Path, logs_base: &Path) -> Result<()> {
|
|||
}
|
||||
|
||||
pub fn prune_command(args: &RunsPruneArgs) -> Result<()> {
|
||||
let base = default_logs_base();
|
||||
let base = default_runs_base();
|
||||
prune_from(args, &base)
|
||||
}
|
||||
|
||||
|
|
@ -1043,12 +1048,14 @@ mod tests {
|
|||
fn df_reports_run_sizes() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let data_dir = tmp.path();
|
||||
let runs_base = data_dir.join("runs");
|
||||
fs::create_dir(&runs_base).unwrap();
|
||||
let logs_base = data_dir.join("logs");
|
||||
fs::create_dir(&logs_base).unwrap();
|
||||
|
||||
// Running run
|
||||
make_run_dir(
|
||||
&logs_base,
|
||||
&runs_base,
|
||||
"20260308-RUNNING",
|
||||
Some(serde_json::json!({
|
||||
"run_id": "running-1",
|
||||
|
|
@ -1063,14 +1070,14 @@ mod tests {
|
|||
);
|
||||
// Add a file to give it size
|
||||
fs::write(
|
||||
logs_base.join("20260308-RUNNING").join("data.bin"),
|
||||
runs_base.join("20260308-RUNNING").join("data.bin"),
|
||||
vec![0u8; 100],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Completed run
|
||||
make_run_dir(
|
||||
&logs_base,
|
||||
&runs_base,
|
||||
"20260307-DONE",
|
||||
Some(serde_json::json!({
|
||||
"run_id": "done-1",
|
||||
|
|
@ -1088,20 +1095,22 @@ mod tests {
|
|||
false,
|
||||
);
|
||||
fs::write(
|
||||
logs_base.join("20260307-DONE").join("data.bin"),
|
||||
runs_base.join("20260307-DONE").join("data.bin"),
|
||||
vec![0u8; 200],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let args = DfArgs { verbose: false };
|
||||
// Should not panic
|
||||
df_from(&args, data_dir, &logs_base).unwrap();
|
||||
df_from(&args, data_dir, &runs_base, &logs_base).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn df_reports_log_files() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let data_dir = tmp.path();
|
||||
let runs_base = data_dir.join("runs");
|
||||
fs::create_dir(&runs_base).unwrap();
|
||||
let logs_base = data_dir.join("logs");
|
||||
fs::create_dir(&logs_base).unwrap();
|
||||
|
||||
|
|
@ -1109,13 +1118,15 @@ mod tests {
|
|||
fs::write(logs_base.join("serve-2026-03-08.log"), vec![0u8; 300]).unwrap();
|
||||
|
||||
let args = DfArgs { verbose: false };
|
||||
df_from(&args, data_dir, &logs_base).unwrap();
|
||||
df_from(&args, data_dir, &runs_base, &logs_base).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn df_reports_database_files() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let data_dir = tmp.path();
|
||||
let runs_base = data_dir.join("runs");
|
||||
fs::create_dir(&runs_base).unwrap();
|
||||
let logs_base = data_dir.join("logs");
|
||||
fs::create_dir(&logs_base).unwrap();
|
||||
|
||||
|
|
@ -1124,7 +1135,7 @@ mod tests {
|
|||
fs::write(data_dir.join("arc.db-shm"), vec![0u8; 32]).unwrap();
|
||||
|
||||
let args = DfArgs { verbose: false };
|
||||
df_from(&args, data_dir, &logs_base).unwrap();
|
||||
df_from(&args, data_dir, &runs_base, &logs_base).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -283,11 +283,7 @@ pub fn resolve_thread_id(
|
|||
// --- Run directory helpers (spec 5.6) ---
|
||||
|
||||
/// Write manifest.json at the start of a workflow run. Returns the manifest.
|
||||
fn write_manifest(
|
||||
logs_root: &Path,
|
||||
graph: &Graph,
|
||||
config: &RunConfig,
|
||||
) -> crate::manifest::Manifest {
|
||||
fn write_manifest(run_dir: &Path, graph: &Graph, config: &RunConfig) -> crate::manifest::Manifest {
|
||||
let workflow_name = if graph.name.is_empty() {
|
||||
"unnamed".to_string()
|
||||
} else {
|
||||
|
|
@ -305,20 +301,20 @@ fn write_manifest(
|
|||
labels: config.labels.clone(),
|
||||
base_branch: config.base_branch.clone(),
|
||||
};
|
||||
let _ = std::fs::create_dir_all(logs_root);
|
||||
let _ = manifest.save(&logs_root.join("manifest.json"));
|
||||
let _ = std::fs::create_dir_all(run_dir);
|
||||
let _ = manifest.save(&run_dir.join("manifest.json"));
|
||||
manifest
|
||||
}
|
||||
|
||||
/// Return the directory for a node's logs.
|
||||
///
|
||||
/// First visit (`visit <= 1`): `{logs_root}/nodes/{node_id}`
|
||||
/// Subsequent visits: `{logs_root}/nodes/{node_id}-visit_{visit}`
|
||||
pub fn node_dir(logs_root: &Path, node_id: &str, visit: usize) -> PathBuf {
|
||||
/// First visit (`visit <= 1`): `{run_dir}/nodes/{node_id}`
|
||||
/// Subsequent visits: `{run_dir}/nodes/{node_id}-visit_{visit}`
|
||||
pub fn node_dir(run_dir: &Path, node_id: &str, visit: usize) -> PathBuf {
|
||||
if visit <= 1 {
|
||||
logs_root.join("nodes").join(node_id)
|
||||
run_dir.join("nodes").join(node_id)
|
||||
} else {
|
||||
logs_root
|
||||
run_dir
|
||||
.join("nodes")
|
||||
.join(format!("{node_id}-visit_{visit}"))
|
||||
}
|
||||
|
|
@ -329,9 +325,9 @@ pub fn visit_from_context(context: &Context) -> usize {
|
|||
context.node_visit_count()
|
||||
}
|
||||
|
||||
/// Write status.json for a completed node into {`logs_root}/nodes/{node_id}/status.json`.
|
||||
fn write_node_status(logs_root: &Path, node_id: &str, visit: usize, outcome: &Outcome) {
|
||||
let node_dir = node_dir(logs_root, node_id, visit);
|
||||
/// Write status.json for a completed node into {`run_dir}/nodes/{node_id}/status.json`.
|
||||
fn write_node_status(run_dir: &Path, node_id: &str, visit: usize, outcome: &Outcome) {
|
||||
let node_dir = node_dir(run_dir, node_id, visit);
|
||||
let _ = std::fs::create_dir_all(&node_dir);
|
||||
let status = serde_json::json!({
|
||||
"status": outcome.status.to_string(),
|
||||
|
|
@ -811,7 +807,7 @@ pub async fn git_replace_worktree_remote(sandbox: &dyn Sandbox, path: &str, bran
|
|||
|
||||
/// Configuration for a workflow run.
|
||||
pub struct RunConfig {
|
||||
pub logs_root: PathBuf,
|
||||
pub run_dir: PathBuf,
|
||||
pub cancel_token: Option<Arc<AtomicBool>>,
|
||||
pub dry_run: bool,
|
||||
/// Unique identifier for this workflow run.
|
||||
|
|
@ -965,7 +961,7 @@ impl WorkflowRunEngine {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
policy: &RetryPolicy,
|
||||
stage_index: usize,
|
||||
visit: usize,
|
||||
|
|
@ -985,7 +981,7 @@ impl WorkflowRunEngine {
|
|||
|
||||
// Gap #11: Panic safety -- catch panics from handler execution
|
||||
let result = {
|
||||
let future = handler.execute(node, context, graph, logs_root, &self.services);
|
||||
let future = handler.execute(node, context, graph, run_dir, &self.services);
|
||||
let panic_safe = AssertUnwindSafe(future).catch_unwind();
|
||||
// Gap #2: Timeout enforcement -- wrap with tokio::time::timeout
|
||||
let timed_result = if let Some(duration) = node_timeout {
|
||||
|
|
@ -1009,7 +1005,7 @@ impl WorkflowRunEngine {
|
|||
} else {
|
||||
"handler panicked".to_string()
|
||||
};
|
||||
let panic_dir = node_dir(logs_root, &node.id, visit);
|
||||
let panic_dir = node_dir(run_dir, &node.id, visit);
|
||||
let _ = std::fs::create_dir_all(&panic_dir);
|
||||
let _ = std::fs::write(panic_dir.join("panic.txt"), &msg);
|
||||
Err(ArcError::handler(msg))
|
||||
|
|
@ -1024,7 +1020,7 @@ impl WorkflowRunEngine {
|
|||
} else {
|
||||
format!("{}-visit_{visit}", node.id)
|
||||
};
|
||||
let assets_dir = logs_root
|
||||
let assets_dir = run_dir
|
||||
.join("artifacts")
|
||||
.join("assets")
|
||||
.join(&node_slug)
|
||||
|
|
@ -1205,7 +1201,7 @@ impl WorkflowRunEngine {
|
|||
) -> Result<(Outcome, Context)> {
|
||||
let run_start = Instant::now();
|
||||
let run_id = config.run_id.clone();
|
||||
let artifact_store = ArtifactStore::new(Some(config.logs_root.clone()));
|
||||
let artifact_store = ArtifactStore::new(Some(config.run_dir.clone()));
|
||||
|
||||
// Populate git_state for handlers (parallel, fan_in) when checkpointing is active
|
||||
let git_state = match (&config.git_checkpoint, &config.base_sha) {
|
||||
|
|
@ -1253,7 +1249,7 @@ impl WorkflowRunEngine {
|
|||
}
|
||||
|
||||
// Write manifest.json (spec 5.6)
|
||||
let manifest = write_manifest(&config.logs_root, graph, config);
|
||||
let manifest = write_manifest(&config.run_dir, graph, config);
|
||||
|
||||
// Initialize metadata branch for git-native checkpoint storage (best-effort)
|
||||
if config.meta_branch.is_some() {
|
||||
|
|
@ -1267,7 +1263,7 @@ impl WorkflowRunEngine {
|
|||
let store = crate::git::MetadataStore::new(repo_path, &config.git_author);
|
||||
let manifest_bytes = serde_json::to_vec_pretty(&manifest).unwrap_or_default();
|
||||
let dot_source =
|
||||
std::fs::read(config.logs_root.join("graph.dot")).unwrap_or_default();
|
||||
std::fs::read(config.run_dir.join("graph.dot")).unwrap_or_default();
|
||||
if let Err(e) = store.init_run(&config.run_id, &manifest_bytes, &dot_source) {
|
||||
tracing::warn!(run_id = %config.run_id, error = %e, "Metadata branch init failed");
|
||||
}
|
||||
|
|
@ -1615,7 +1611,7 @@ impl WorkflowRunEngine {
|
|||
let (mut outcome, attempts_used) = if let Some((ref token, _)) = stall_token {
|
||||
tokio::select! {
|
||||
result = self.execute_with_retry(
|
||||
node, &context, graph, &config.logs_root, &retry_policy, stage_index, visit, &config.asset_globs,
|
||||
node, &context, graph, &config.run_dir, &retry_policy, stage_index, visit, &config.asset_globs,
|
||||
) => result?,
|
||||
() = token.cancelled() => {
|
||||
let idle_secs = graph.stall_timeout().map_or(0, |d| d.as_secs());
|
||||
|
|
@ -1634,7 +1630,7 @@ impl WorkflowRunEngine {
|
|||
node,
|
||||
&context,
|
||||
graph,
|
||||
&config.logs_root,
|
||||
&config.run_dir,
|
||||
&retry_policy,
|
||||
stage_index,
|
||||
visit,
|
||||
|
|
@ -1751,7 +1747,7 @@ impl WorkflowRunEngine {
|
|||
}
|
||||
|
||||
// Write per-node status.json (spec 5.6)
|
||||
write_node_status(&config.logs_root, &node.id, visit, &outcome);
|
||||
write_node_status(&config.run_dir, &node.id, visit, &outcome);
|
||||
|
||||
// Offload large context values to artifact store before recording
|
||||
if let Err(e) = offload_large_values(&mut outcome.context_updates, &artifact_store) {
|
||||
|
|
@ -1869,7 +1865,7 @@ impl WorkflowRunEngine {
|
|||
loop_state.restart_failure_signatures.clone(),
|
||||
loop_state.node_visits.clone(),
|
||||
);
|
||||
let checkpoint_path = config.logs_root.join("checkpoint.json");
|
||||
let checkpoint_path = config.run_dir.join("checkpoint.json");
|
||||
if let Err(e) = checkpoint.save(&checkpoint_path) {
|
||||
context.append_log(format!("checkpoint save failed: {e}"));
|
||||
} else {
|
||||
|
|
@ -2010,7 +2006,7 @@ impl WorkflowRunEngine {
|
|||
.unwrap_or(&sha);
|
||||
let diff_base = prev.to_string();
|
||||
let diff_dest =
|
||||
node_dir(&config.logs_root, &node.id, visit).join("diff.patch");
|
||||
node_dir(&config.run_dir, &node.id, visit).join("diff.patch");
|
||||
|
||||
let diff_result = match mode {
|
||||
GitCheckpointMode::Host(work_dir) => {
|
||||
|
|
@ -2182,7 +2178,7 @@ impl WorkflowRunEngine {
|
|||
};
|
||||
if let Some(patch) = patch {
|
||||
if !patch.is_empty() {
|
||||
let _ = std::fs::write(config.logs_root.join("final.patch"), patch);
|
||||
let _ = std::fs::write(config.run_dir.join("final.patch"), patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2223,7 +2219,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &crate::handler::EngineServices,
|
||||
) -> std::result::Result<Outcome, ArcError> {
|
||||
Ok(Outcome::fail_classify("always fails"))
|
||||
|
|
@ -2242,7 +2238,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &crate::handler::EngineServices,
|
||||
) -> std::result::Result<Outcome, ArcError> {
|
||||
tokio::time::sleep(Duration::from_millis(self.sleep_ms)).await;
|
||||
|
|
@ -2844,7 +2840,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -2872,7 +2868,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -2908,7 +2904,7 @@ mod tests {
|
|||
|
||||
let engine = WorkflowRunEngine::new(make_registry(), Arc::new(emitter), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -2940,7 +2936,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -2968,7 +2964,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3009,7 +3005,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3074,7 +3070,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3166,7 +3162,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3201,7 +3197,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "labels-run".into(),
|
||||
|
|
@ -3231,7 +3227,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "no-labels-run".into(),
|
||||
|
|
@ -3261,7 +3257,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3295,7 +3291,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3457,7 +3453,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3502,7 +3498,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3565,7 +3561,7 @@ mod tests {
|
|||
registry.register("always_fail", Box::new(AlwaysFailHandler));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3631,7 +3627,7 @@ mod tests {
|
|||
registry.register("always_fail", Box::new(AlwaysFailHandler));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3701,7 +3697,7 @@ mod tests {
|
|||
registry.register("slow", Box::new(SlowHandler { sleep_ms: 500 }));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3760,7 +3756,7 @@ mod tests {
|
|||
registry.register("slow", Box::new(SlowHandler { sleep_ms: 10 }));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3820,7 +3816,7 @@ mod tests {
|
|||
registry.register("slow", Box::new(SlowHandler { sleep_ms: 500 }));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3855,7 +3851,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3886,7 +3882,7 @@ mod tests {
|
|||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let cancel_token = Arc::new(AtomicBool::new(true));
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: Some(cancel_token),
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3916,7 +3912,7 @@ mod tests {
|
|||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let cancel_token = Arc::new(AtomicBool::new(false));
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: Some(cancel_token),
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -3959,7 +3955,7 @@ mod tests {
|
|||
registry.register("slow", Box::new(SlowHandler { sleep_ms: 200 }));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: Some(cancel_token),
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4039,7 +4035,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4072,7 +4068,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: true,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4107,7 +4103,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: true,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4147,7 +4143,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4185,7 +4181,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: true,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4220,7 +4216,7 @@ mod tests {
|
|||
let engine =
|
||||
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4284,7 +4280,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &crate::handler::EngineServices,
|
||||
) -> std::result::Result<Outcome, ArcError> {
|
||||
panic!("test panic message");
|
||||
|
|
@ -4316,7 +4312,7 @@ mod tests {
|
|||
registry.register("panicker", Box::new(PanickingHandler));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4481,7 +4477,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &crate::handler::EngineServices,
|
||||
) -> std::result::Result<Outcome, ArcError> {
|
||||
Ok(Outcome::fail_classify("connection refused"))
|
||||
|
|
@ -4509,7 +4505,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &crate::handler::EngineServices,
|
||||
) -> std::result::Result<Outcome, ArcError> {
|
||||
let n = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||
|
|
@ -4527,7 +4523,7 @@ mod tests {
|
|||
registry.register("always_fail", Box::new(AlwaysFailHandler));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4565,7 +4561,7 @@ mod tests {
|
|||
registry.register("always_fail", Box::new(TransientFailHandler));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4610,7 +4606,7 @@ mod tests {
|
|||
);
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4695,7 +4691,7 @@ mod tests {
|
|||
registry.register("always_fail", Box::new(AlwaysFailHandler));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4737,7 +4733,7 @@ mod tests {
|
|||
node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
services: &crate::handler::EngineServices,
|
||||
) -> std::result::Result<Outcome, ArcError> {
|
||||
let start = Instant::now();
|
||||
|
|
@ -4791,7 +4787,7 @@ mod tests {
|
|||
registry.register("slow", Box::new(SlowHandler { sleep_ms: 60_000 }));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4864,7 +4860,7 @@ mod tests {
|
|||
);
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4924,7 +4920,7 @@ mod tests {
|
|||
registry.register("slow", Box::new(SlowHandler { sleep_ms: 50 }));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -4985,7 +4981,7 @@ mod tests {
|
|||
registry.register("always_fail", Box::new(AlwaysFailHandler));
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -5054,7 +5050,7 @@ mod tests {
|
|||
.trim()
|
||||
.to_string();
|
||||
|
||||
let logs_dir = tempfile::tempdir().unwrap();
|
||||
let run_tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
// Build start -> work -> exit graph so work node produces a git checkpoint
|
||||
let mut g = simple_graph();
|
||||
|
|
@ -5073,7 +5069,7 @@ mod tests {
|
|||
|
||||
let engine = WorkflowRunEngine::new(make_registry(), Arc::new(emitter), local_env());
|
||||
let config = RunConfig {
|
||||
logs_root: logs_dir.path().to_path_buf(),
|
||||
run_dir: run_tmp.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "git-cp-test".into(),
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ impl Handler for AgentHandler {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
// 1. Build prompt (prepend fidelity preamble if present)
|
||||
|
|
@ -220,7 +220,7 @@ impl Handler for AgentHandler {
|
|||
|
||||
// 2. Write prompt to logs
|
||||
let visit = crate::engine::visit_from_context(context);
|
||||
let stage_dir = crate::engine::node_dir(logs_root, &node.id, visit);
|
||||
let stage_dir = crate::engine::node_dir(run_dir, &node.id, visit);
|
||||
tokio::fs::create_dir_all(&stage_dir).await?;
|
||||
tokio::fs::write(stage_dir.join("prompt.md"), &prompt).await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ impl Handler for CommandHandler {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
_graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let script = node
|
||||
|
|
@ -59,7 +59,7 @@ impl Handler for CommandHandler {
|
|||
}
|
||||
|
||||
let visit = crate::engine::visit_from_context(context);
|
||||
let stage_dir = crate::engine::node_dir(logs_root, &node.id, visit);
|
||||
let stage_dir = crate::engine::node_dir(run_dir, &node.id, visit);
|
||||
tokio::fs::create_dir_all(&stage_dir).await?;
|
||||
|
||||
let invocation = serde_json::json!({
|
||||
|
|
@ -177,10 +177,10 @@ mod tests {
|
|||
let node = Node::new("script_node");
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
@ -197,10 +197,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -219,10 +219,10 @@ mod tests {
|
|||
.insert("script".to_string(), AttrValue::String("false".to_string()));
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
@ -242,10 +242,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let err = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap_err();
|
||||
let msg = err.to_string();
|
||||
|
|
@ -265,14 +265,14 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let invocation_path = logs_root
|
||||
let invocation_path = run_dir
|
||||
.path()
|
||||
.join("nodes")
|
||||
.join("script_node")
|
||||
|
|
@ -298,14 +298,14 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let invocation_path = logs_root
|
||||
let invocation_path = run_dir
|
||||
.path()
|
||||
.join("nodes")
|
||||
.join("script_node")
|
||||
|
|
@ -327,14 +327,14 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let stage_dir = logs_root.path().join("nodes").join("script_node");
|
||||
let stage_dir = run_dir.path().join("nodes").join("script_node");
|
||||
let stdout = std::fs::read_to_string(stage_dir.join("stdout.log")).unwrap();
|
||||
assert_eq!(stdout.trim(), "hello");
|
||||
let stderr = std::fs::read_to_string(stage_dir.join("stderr.log")).unwrap();
|
||||
|
|
@ -351,14 +351,14 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let stage_dir = logs_root.path().join("nodes").join("script_node");
|
||||
let stage_dir = run_dir.path().join("nodes").join("script_node");
|
||||
let stderr = std::fs::read_to_string(stage_dir.join("stderr.log")).unwrap();
|
||||
assert_eq!(stderr.trim(), "oops");
|
||||
}
|
||||
|
|
@ -373,14 +373,14 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let timing_path = logs_root
|
||||
let timing_path = run_dir
|
||||
.path()
|
||||
.join("nodes")
|
||||
.join("script_node")
|
||||
|
|
@ -400,14 +400,14 @@ mod tests {
|
|||
.insert("script".to_string(), AttrValue::String("false".to_string()));
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let timing_path = logs_root
|
||||
let timing_path = run_dir
|
||||
.path()
|
||||
.join("nodes")
|
||||
.join("script_node")
|
||||
|
|
@ -432,14 +432,14 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let _err = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
let timing_path = logs_root
|
||||
let timing_path = run_dir
|
||||
.path()
|
||||
.join("nodes")
|
||||
.join("script_node")
|
||||
|
|
@ -465,10 +465,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -493,10 +493,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
@ -516,10 +516,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
@ -539,10 +539,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -560,10 +560,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -699,14 +699,14 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(
|
||||
&node,
|
||||
&context,
|
||||
&graph,
|
||||
logs_root.path(),
|
||||
run_dir.path(),
|
||||
&make_spy_services(spy.clone()),
|
||||
)
|
||||
.await
|
||||
|
|
@ -748,14 +748,14 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(
|
||||
&node,
|
||||
&context,
|
||||
&graph,
|
||||
logs_root.path(),
|
||||
run_dir.path(),
|
||||
&make_spy_services(spy.clone()),
|
||||
)
|
||||
.await
|
||||
|
|
@ -785,7 +785,7 @@ mod tests {
|
|||
.insert("script".to_string(), AttrValue::String("true".to_string()));
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let mut services = make_spy_services(spy.clone());
|
||||
services
|
||||
|
|
@ -793,7 +793,7 @@ mod tests {
|
|||
.insert("MY_VAR".to_string(), "my_value".to_string());
|
||||
|
||||
handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &services)
|
||||
.execute(&node, &context, &graph, run_dir.path(), &services)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -814,10 +814,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -838,10 +838,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
@ -885,10 +885,10 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = tempfile::tempdir().unwrap();
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root.path(), &make_services())
|
||||
.execute(&node, &context, &graph, run_dir.path(), &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ impl Handler for ConditionalHandler {
|
|||
node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let mut outcome = Outcome::success();
|
||||
|
|
@ -55,9 +55,9 @@ mod tests {
|
|||
let node = Node::new("gate");
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Handler for ExitHandler {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
Ok(Outcome::success())
|
||||
|
|
@ -52,9 +52,9 @@ mod tests {
|
|||
let node = Node::new("exit");
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ impl Handler for FanInHandler {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
_graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let results = context.get(keys::PARALLEL_RESULTS);
|
||||
|
|
@ -51,7 +51,7 @@ impl Handler for FanInHandler {
|
|||
prompt_text,
|
||||
&results,
|
||||
context,
|
||||
logs_root,
|
||||
run_dir,
|
||||
&node.id,
|
||||
&services.emitter,
|
||||
&services.sandbox,
|
||||
|
|
@ -196,7 +196,7 @@ async fn llm_evaluate(
|
|||
prompt: &str,
|
||||
results: &serde_json::Value,
|
||||
context: &Context,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
node_id: &str,
|
||||
emitter: &Arc<EventEmitter>,
|
||||
sandbox: &Arc<dyn Sandbox>,
|
||||
|
|
@ -211,7 +211,7 @@ async fn llm_evaluate(
|
|||
|
||||
// Write prompt to logs
|
||||
let visit = crate::engine::visit_from_context(context);
|
||||
let stage_dir = crate::engine::node_dir(logs_root, node_id, visit);
|
||||
let stage_dir = crate::engine::node_dir(run_dir, node_id, visit);
|
||||
tokio::fs::create_dir_all(&stage_dir).await?;
|
||||
tokio::fs::write(stage_dir.join("prompt.md"), &full_prompt).await?;
|
||||
|
||||
|
|
@ -318,10 +318,10 @@ mod tests {
|
|||
let node = Node::new("fan_in");
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
@ -340,10 +340,10 @@ mod tests {
|
|||
]),
|
||||
);
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -367,10 +367,10 @@ mod tests {
|
|||
]),
|
||||
);
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
|
|
@ -404,10 +404,10 @@ mod tests {
|
|||
]),
|
||||
);
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -501,10 +501,10 @@ mod tests {
|
|||
]),
|
||||
);
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
@ -528,10 +528,10 @@ mod tests {
|
|||
]),
|
||||
);
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ impl Handler for HumanHandler {
|
|||
node: &Node,
|
||||
_context: &Context,
|
||||
graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
// 1. Derive choices from outgoing edges
|
||||
|
|
@ -353,10 +353,10 @@ mod tests {
|
|||
let graph = build_graph_with_human_gate();
|
||||
let node = graph.nodes.get("gate").unwrap();
|
||||
let context = Context::new();
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(node, &context, &graph, logs_root, &make_services())
|
||||
.execute(node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);
|
||||
|
|
@ -377,10 +377,10 @@ mod tests {
|
|||
graph.nodes.insert("gate".to_string(), gate);
|
||||
let node = graph.nodes.get("gate").unwrap();
|
||||
let context = Context::new();
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(node, &context, &graph, logs_root, &make_services())
|
||||
.execute(node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, crate::outcome::StageStatus::Fail);
|
||||
|
|
@ -409,10 +409,10 @@ mod tests {
|
|||
|
||||
let node = graph.nodes.get("gate").unwrap();
|
||||
let context = Context::new();
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = handler
|
||||
.execute(node, &context, &graph, logs_root, &make_services())
|
||||
.execute(node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ impl Handler for SubWorkflowHandler {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
_graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let poll_interval = node
|
||||
|
|
@ -127,7 +127,7 @@ impl Handler for SubWorkflowHandler {
|
|||
|
||||
// Build child RunConfig
|
||||
let visit = context.node_visit_count() as u64;
|
||||
let child_logs = logs_root.join(format!("nodes/{}_{visit}/child", node.id));
|
||||
let child_logs = run_dir.join(format!("nodes/{}_{visit}/child", node.id));
|
||||
let _ = std::fs::create_dir_all(&child_logs);
|
||||
|
||||
let parent_run_id = context.run_id();
|
||||
|
|
@ -136,7 +136,7 @@ impl Handler for SubWorkflowHandler {
|
|||
|
||||
let git_state = services.git_state();
|
||||
let child_config = RunConfig {
|
||||
logs_root: child_logs,
|
||||
run_dir: child_logs,
|
||||
cancel_token: Some(cancel_token),
|
||||
dry_run: false,
|
||||
run_id: format!("{parent_run_id}_child_{}", node.id),
|
||||
|
|
@ -375,7 +375,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let target = context.get_string("review.target", "");
|
||||
|
|
@ -491,7 +491,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
|
|
@ -552,7 +552,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
|
|
@ -712,7 +712,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let target = context.get_string("review.target", "");
|
||||
|
|
@ -798,7 +798,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let parent_preamble = context.get_string(keys::INTERNAL_PARENT_PREAMBLE, "");
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ pub trait Handler: Send + Sync {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError>;
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
Ok(Outcome::success())
|
||||
|
|
@ -251,7 +251,7 @@ mod tests {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
Ok(Outcome::success())
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ impl Handler for ParallelHandler {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let parallel_start = Instant::now();
|
||||
|
|
@ -323,7 +323,7 @@ impl Handler for ParallelHandler {
|
|||
|
||||
match &gs.mode {
|
||||
GitCheckpointMode::Host(work_dir) => {
|
||||
let wt_path = logs_root
|
||||
let wt_path = run_dir
|
||||
.join("parallel")
|
||||
.join(&node.id)
|
||||
.join(branch_key)
|
||||
|
|
@ -352,7 +352,7 @@ impl Handler for ParallelHandler {
|
|||
}
|
||||
GitCheckpointMode::Remote(_) => {
|
||||
let wt_path_str = format!(
|
||||
"{}/.arc/logs/{}/parallel/{}/{}",
|
||||
"{}/.arc/runs/{}/parallel/{}/{}",
|
||||
services.sandbox.working_directory(),
|
||||
gs.run_id,
|
||||
node.id,
|
||||
|
|
@ -422,7 +422,7 @@ impl Handler for ParallelHandler {
|
|||
let hook_runner = services.hook_runner.clone();
|
||||
let env = services.env.clone();
|
||||
let graph = graph.clone();
|
||||
let logs_root = logs_root.to_path_buf();
|
||||
let run_dir = run_dir.to_path_buf();
|
||||
let sem = Arc::clone(&semaphore);
|
||||
let has_git = git_state.is_some();
|
||||
let run_id = git_state.as_ref().map(|gs| gs.run_id.clone());
|
||||
|
|
@ -476,7 +476,7 @@ impl Handler for ParallelHandler {
|
|||
target_node,
|
||||
&setup.branch_context,
|
||||
&graph,
|
||||
&logs_root,
|
||||
&run_dir,
|
||||
&branch_services,
|
||||
)
|
||||
.await?;
|
||||
|
|
@ -676,7 +676,7 @@ impl Handler for ParallelHandler {
|
|||
context.set(keys::PARALLEL_BRANCH_COUNT, serde_json::json!(total));
|
||||
|
||||
let visit = crate::engine::visit_from_context(context);
|
||||
let node_dir = crate::engine::node_dir(logs_root, &node.id, visit);
|
||||
let node_dir = crate::engine::node_dir(run_dir, &node.id, visit);
|
||||
let _ = tokio::fs::create_dir_all(&node_dir).await;
|
||||
if let Ok(json) = serde_json::to_string_pretty(&results_json) {
|
||||
let _ = tokio::fs::write(node_dir.join("parallel_results.json"), json).await;
|
||||
|
|
@ -812,10 +812,10 @@ mod tests {
|
|||
let node = Node::new("par");
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
|
||||
let outcome = ParallelHandler
|
||||
.execute(&node, &context, &graph, logs_root, &services)
|
||||
.execute(&node, &context, &graph, run_dir, &services)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Fail);
|
||||
|
|
@ -889,9 +889,9 @@ mod tests {
|
|||
.insert("branch_a".to_string(), Node::new("branch_a"));
|
||||
graph.edges.push(Edge::new("par", "branch_a"));
|
||||
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
let outcome = ParallelHandler
|
||||
.execute(&node, &context, &graph, logs_root, &services)
|
||||
.execute(&node, &context, &graph, run_dir, &services)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -922,9 +922,9 @@ mod tests {
|
|||
graph.edges.push(Edge::new("par", "branch_b"));
|
||||
graph.edges.push(Edge::new("par", "branch_c"));
|
||||
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
let outcome = ParallelHandler
|
||||
.execute(&node, &context, &graph, logs_root, &services)
|
||||
.execute(&node, &context, &graph, run_dir, &services)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Handler for PromptHandler {
|
|||
node: &Node,
|
||||
context: &Context,
|
||||
graph: &Graph,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
// 1. Build prompt (prepend fidelity preamble if present)
|
||||
|
|
@ -76,7 +76,7 @@ impl Handler for PromptHandler {
|
|||
|
||||
// 2. Write prompt to logs
|
||||
let visit = crate::engine::visit_from_context(context);
|
||||
let stage_dir = crate::engine::node_dir(logs_root, &node.id, visit);
|
||||
let stage_dir = crate::engine::node_dir(run_dir, &node.id, visit);
|
||||
tokio::fs::create_dir_all(&stage_dir).await?;
|
||||
tokio::fs::write(stage_dir.join("prompt.md"), &prompt).await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Handler for StartHandler {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
Ok(Outcome::success())
|
||||
|
|
@ -51,9 +51,9 @@ mod tests {
|
|||
let node = Node::new("start");
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Handler for WaitHandler {
|
|||
node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let duration = node
|
||||
|
|
@ -70,9 +70,9 @@ mod tests {
|
|||
);
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
let outcome = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);
|
||||
|
|
@ -84,9 +84,9 @@ mod tests {
|
|||
let node = Node::new("wait_no_dur");
|
||||
let context = Context::new();
|
||||
let graph = Graph::new("test");
|
||||
let logs_root = Path::new("/tmp/test");
|
||||
let run_dir = Path::new("/tmp/test");
|
||||
let result = handler
|
||||
.execute(&node, &context, &graph, logs_root, &make_services())
|
||||
.execute(&node, &context, &graph, run_dir, &make_services())
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,9 +189,9 @@ fn parse_dot_summary(dot: &str) -> (String, usize, usize) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read the DOT graph source from `logs_dir/graph.dot`.
|
||||
fn read_dot_source(logs_dir: &Path) -> Option<String> {
|
||||
let path = logs_dir.join("graph.dot");
|
||||
/// Read the DOT graph source from `run_dir/graph.dot`.
|
||||
fn read_dot_source(run_dir: &Path) -> Option<String> {
|
||||
let path = run_dir.join("graph.dot");
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(content) => {
|
||||
debug!(path = %path.display(), "Read DOT graph for PR body");
|
||||
|
|
@ -201,11 +201,11 @@ fn read_dot_source(logs_dir: &Path) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read plan text from the first `nodes/plan*/response.md` found in logs_dir.
|
||||
/// Read plan text from the first `nodes/plan*/response.md` found in run_dir.
|
||||
///
|
||||
/// Entries are sorted alphabetically so `plan` is preferred over `planning`.
|
||||
fn read_plan_text(logs_dir: &Path) -> Option<String> {
|
||||
let nodes_dir = logs_dir.join("nodes");
|
||||
fn read_plan_text(run_dir: &Path) -> Option<String> {
|
||||
let nodes_dir = run_dir.join("nodes");
|
||||
let mut entries: Vec<_> = std::fs::read_dir(&nodes_dir).ok()?.flatten().collect();
|
||||
entries.sort_by_key(|e| e.file_name());
|
||||
for entry in entries {
|
||||
|
|
@ -264,13 +264,13 @@ pub async fn build_pr_body(
|
|||
diff: &str,
|
||||
goal: &str,
|
||||
model: &str,
|
||||
logs_dir: &Path,
|
||||
run_dir: &Path,
|
||||
) -> Result<String, String> {
|
||||
debug!("Building PR body");
|
||||
|
||||
let plan_text = read_plan_text(logs_dir);
|
||||
let retro = Retro::load(logs_dir).ok();
|
||||
let dot_source = read_dot_source(logs_dir);
|
||||
let plan_text = read_plan_text(run_dir);
|
||||
let retro = Retro::load(run_dir).ok();
|
||||
let dot_source = read_dot_source(run_dir);
|
||||
|
||||
// Build LLM prompt
|
||||
let system = if plan_text.is_some() {
|
||||
|
|
@ -342,7 +342,7 @@ pub async fn maybe_open_pull_request(
|
|||
diff: &str,
|
||||
model: &str,
|
||||
draft: bool,
|
||||
logs_dir: &Path,
|
||||
run_dir: &Path,
|
||||
) -> Result<Option<PullRequestRecord>, String> {
|
||||
if diff.is_empty() {
|
||||
debug!("Empty diff, skipping pull request creation");
|
||||
|
|
@ -352,7 +352,7 @@ pub async fn maybe_open_pull_request(
|
|||
let https_url = ssh_url_to_https(origin_url);
|
||||
let (owner, repo) = github_app::parse_github_owner_repo(&https_url)?;
|
||||
|
||||
let body = build_pr_body(diff, goal, model, logs_dir).await?;
|
||||
let body = build_pr_body(diff, goal, model, run_dir).await?;
|
||||
let body = truncate_pr_body(&body);
|
||||
|
||||
let title = pr_title_from_goal(goal);
|
||||
|
|
|
|||
|
|
@ -168,21 +168,21 @@ impl Retro {
|
|||
};
|
||||
}
|
||||
|
||||
/// Save the retro as JSON to `logs_root/retro.json`.
|
||||
pub fn save(&self, logs_root: &Path) -> Result<()> {
|
||||
crate::save_json(self, &logs_root.join("retro.json"), "retro")
|
||||
/// Save the retro as JSON to `run_dir/retro.json`.
|
||||
pub fn save(&self, run_dir: &Path) -> Result<()> {
|
||||
crate::save_json(self, &run_dir.join("retro.json"), "retro")
|
||||
}
|
||||
|
||||
/// Load a retro from `logs_root/retro.json`.
|
||||
pub fn load(logs_root: &Path) -> Result<Self> {
|
||||
crate::load_json(&logs_root.join("retro.json"), "retro")
|
||||
/// Load a retro from `run_dir/retro.json`.
|
||||
pub fn load(run_dir: &Path) -> Result<Self> {
|
||||
crate::load_json(&run_dir.join("retro.json"), "retro")
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract stage durations from `progress.jsonl` by reading `StageCompleted` events.
|
||||
pub fn extract_stage_durations(logs_root: &Path) -> HashMap<String, u64> {
|
||||
pub fn extract_stage_durations(run_dir: &Path) -> HashMap<String, u64> {
|
||||
let mut durations = HashMap::new();
|
||||
let jsonl_path = logs_root.join("progress.jsonl");
|
||||
let jsonl_path = run_dir.join("progress.jsonl");
|
||||
let Ok(data) = std::fs::read_to_string(&jsonl_path) else {
|
||||
return durations;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ const SUBMIT_RETRO_SCHEMA: &str = r#"{
|
|||
/// files via tool access, then calls `submit_retro` with its analysis.
|
||||
pub async fn run_retro_agent(
|
||||
sandbox: &Arc<dyn Sandbox>,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
llm_client: &Client,
|
||||
provider: Provider,
|
||||
model: &str,
|
||||
|
|
@ -122,7 +122,7 @@ pub async fn run_retro_agent(
|
|||
// Upload data files into sandbox (needed for Daytona; no-op effect for local
|
||||
// since the agent can also read from the original paths via tools).
|
||||
let retro_data_dir = "/tmp/retro_data";
|
||||
upload_data_files(sandbox, logs_root, retro_data_dir).await?;
|
||||
upload_data_files(sandbox, run_dir, retro_data_dir).await?;
|
||||
|
||||
// Build provider profile with the submit_retro tool
|
||||
let captured: Arc<Mutex<Option<RetroNarrative>>> = Arc::new(Mutex::new(None));
|
||||
|
|
@ -165,7 +165,7 @@ pub async fn run_retro_agent(
|
|||
let mut session = Session::new(llm_client.clone(), profile, Arc::clone(sandbox), config);
|
||||
|
||||
// Set up event writer before initialize (which emits SessionStarted)
|
||||
let retro_dir = logs_root.join("retro");
|
||||
let retro_dir = run_dir.join("retro");
|
||||
std::fs::create_dir_all(&retro_dir)?;
|
||||
let rx = session.subscribe();
|
||||
let event_writer_handle = spawn_retro_event_writer(rx, retro_dir.join("retro_session.jsonl"));
|
||||
|
|
@ -316,7 +316,7 @@ fn build_profile(provider: Provider, model: &str) -> Box<dyn ProviderProfile> {
|
|||
|
||||
async fn upload_data_files(
|
||||
sandbox: &Arc<dyn Sandbox>,
|
||||
logs_root: &Path,
|
||||
run_dir: &Path,
|
||||
target_dir: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
// Create target directory
|
||||
|
|
@ -327,7 +327,7 @@ async fn upload_data_files(
|
|||
|
||||
let files = ["progress.jsonl", "checkpoint.json", "manifest.json"];
|
||||
for filename in &files {
|
||||
let source = logs_root.join(filename);
|
||||
let source = run_dir.join(filename);
|
||||
if source.exists() {
|
||||
let content = std::fs::read_to_string(&source)?;
|
||||
sandbox
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ impl Handler for LargeOutputHandler {
|
|||
node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
_services: &arc_workflows::handler::EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let mut outcome = Outcome::success();
|
||||
|
|
@ -389,7 +389,7 @@ async fn daytona_pipeline_artifact_offload_and_sync() {
|
|||
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "test-run".into(),
|
||||
|
|
@ -464,7 +464,7 @@ impl Handler for FileWriterHandler {
|
|||
node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
services: &arc_workflows::handler::EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let content = format!("output from {}", node.id);
|
||||
|
|
@ -587,7 +587,7 @@ async fn daytona_git_checkpoint_remote_emits_events() {
|
|||
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env.clone());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id,
|
||||
|
|
@ -659,10 +659,7 @@ async fn daytona_git_checkpoint_remote_emits_events() {
|
|||
|
||||
// Assert final.patch exists and contains changes from the run
|
||||
let final_patch = dir.path().join("final.patch");
|
||||
assert!(
|
||||
final_patch.exists(),
|
||||
"final.patch should exist in logs_root"
|
||||
);
|
||||
assert!(final_patch.exists(), "final.patch should exist in run_dir");
|
||||
let patch_content = std::fs::read_to_string(&final_patch).unwrap();
|
||||
assert!(!patch_content.is_empty(), "final.patch should not be empty");
|
||||
|
||||
|
|
@ -758,7 +755,7 @@ async fn daytona_parallel_git_branching_e2e() {
|
|||
graph.edges.push(Edge::new("branch_b", "fan_in"));
|
||||
graph.edges.push(Edge::new("fan_in", "exit"));
|
||||
|
||||
let logs_dir = tempfile::tempdir().unwrap();
|
||||
let run_tmp = tempfile::tempdir().unwrap();
|
||||
let mut emitter = EventEmitter::new();
|
||||
let events = Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
{
|
||||
|
|
@ -777,11 +774,11 @@ async fn daytona_parallel_git_branching_e2e() {
|
|||
let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), Arc::clone(&env));
|
||||
|
||||
let config = RunConfig {
|
||||
logs_root: logs_dir.path().to_path_buf(),
|
||||
run_dir: run_tmp.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: run_id.clone(),
|
||||
git_checkpoint: Some(GitCheckpointMode::Remote(logs_dir.path().to_path_buf())),
|
||||
git_checkpoint: Some(GitCheckpointMode::Remote(run_tmp.path().to_path_buf())),
|
||||
base_sha: Some(base_sha),
|
||||
run_branch: Some(branch_name),
|
||||
meta_branch: None,
|
||||
|
|
@ -808,7 +805,7 @@ async fn daytona_parallel_git_branching_e2e() {
|
|||
|
||||
// Verify parallel.results has head_sha for each branch
|
||||
let checkpoint =
|
||||
Checkpoint::load(&logs_dir.path().join("checkpoint.json")).expect("checkpoint should load");
|
||||
Checkpoint::load(&run_tmp.path().join("checkpoint.json")).expect("checkpoint should load");
|
||||
let parallel_results = checkpoint
|
||||
.context_values
|
||||
.get("parallel.results")
|
||||
|
|
@ -1155,7 +1152,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
|
|||
let meta_branch = MetadataStore::branch_name(&run_id);
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: run_id.clone(),
|
||||
|
|
@ -1210,10 +1207,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
|
|||
|
||||
// Assert final.patch exists
|
||||
let final_patch = dir.path().join("final.patch");
|
||||
assert!(
|
||||
final_patch.exists(),
|
||||
"final.patch should exist in logs_root"
|
||||
);
|
||||
assert!(final_patch.exists(), "final.patch should exist in run_dir");
|
||||
|
||||
env.cleanup().await.unwrap();
|
||||
}
|
||||
|
|
@ -1232,7 +1226,7 @@ impl Handler for AssetCreatorHandler {
|
|||
_node: &Node,
|
||||
_context: &Context,
|
||||
_graph: &Graph,
|
||||
_logs_root: &Path,
|
||||
_run_dir: &Path,
|
||||
services: &arc_workflows::handler::EngineServices,
|
||||
) -> Result<Outcome, ArcError> {
|
||||
let script = concat!(
|
||||
|
|
@ -1299,7 +1293,7 @@ async fn daytona_asset_collection() {
|
|||
graph.edges.push(Edge::new("create_assets", "exit"));
|
||||
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: "asset-test-daytona".into(),
|
||||
|
|
@ -1555,7 +1549,7 @@ async fn daytona_git_push_run_branch_to_origin() {
|
|||
|
||||
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
|
||||
let config = RunConfig {
|
||||
logs_root: dir.path().to_path_buf(),
|
||||
run_dir: dir.path().to_path_buf(),
|
||||
cancel_token: None,
|
||||
dry_run: false,
|
||||
run_id: run_id.clone(),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue