diff --git a/run.json b/run.json index 996f07b3f..c7b27c53a 100644 --- a/run.json +++ b/run.json @@ -521,7 +521,7 @@ "kind": "running" }, "status_updated_at": "2026-05-23T14:10:36.810792Z", - "last_event_at": "2026-05-23T14:10:38.675121Z", + "last_event_at": "2026-05-23T14:10:46.732424Z", "pending_control": null, "checkpoints": [ { @@ -562,36 +562,100 @@ "diff": {} }, { - "seq": 0, + "seq": 27, "checkpoint": { - "timestamp": "2026-05-23T14:10:41.007038Z", + "timestamp": "2026-05-23T14:10:46.730887Z", "current_node": "toolchain", "completed_nodes": [ "start", "toolchain" ], "node_retries": {}, + "context_values": { + "graph.rankdir": "LR", + "internal.run_id": "01KSAJRA1SB258HTA5E7VRNH1T", + "internal.node_visit_count": 1, + "internal.retry_count.start": 0, + "thread.start.current_node": "toolchain", + "internal.thread_id": "start", + "command.output": "blob://sha256/fc14b2ba2d770e5cd3169df7a29525c962adfc4cfa3097b9098c63ebd61a748c", + "failure_class": "", + "graph.model_stylesheet": "\n * { model: claude-opus-4-7; }\n ", + "failure_signature": "", + "current_node": "toolchain", + "graph.goal": "# Slack Run Lifecycle Notifications\n\n## Summary\n\nAdd Slack notifications for `run.started`, `run.completed`, and `run.failed` using the existing `[run.notifications]` namespace as the per-run/per-workflow source of truth. Keep Slack interviews as separate behavior sharing the same Slack connection, and do not move notification settings to server config.\n\n## Key Changes\n\n- Treat configured routes like this as active runtime subscriptions:\n\n ```toml\n [run.notifications.deploys]\n enabled = true\n provider = \"slack\"\n events = [\"run.started\", \"run.completed\", \"run.failed\"]\n\n [run.notifications.deploys.slack]\n channel = \"#deploys\"\n ```\n\n- Require `[run.notifications..slack].channel` for lifecycle Slack notifications; skip and log a warning if missing or unresolved.\n- Start `SlackService` when Slack credentials are present, even if `server.integrations.slack.default_channel` is absent. Keep `default_channel` only for the existing interview path.\n- Keep interview messages and lifecycle notifications independent:\n - interviews keep `posted_messages` and `thread_registry` behavior\n - lifecycle notifications are fire-and-forget and never accept answers or update messages\n\n## Implementation\n\n- In `lib/crates/fabro-server/src/server.rs`, extend `SlackService::handle_event`:\n - Existing interview event handling remains unchanged.\n - Add a lifecycle path for `RunStarted`, `RunCompleted`, and `RunFailed`.\n - For lifecycle events, load the cached run projection, inspect `projection.spec.settings.run.notifications`, select enabled Slack routes whose `events` contains the current event name, resolve each route channel, and post once per route.\n- In `lib/crates/fabro-slack/src/blocks.rs`, add lifecycle-specific Block Kit builders separate from interview builders.\n - Include run ID, Fabro run link when available, workflow label, result when applicable, duration when applicable, and PR info when available.\n - Use existing Slack escaping/truncation patterns for all untrusted text.\n- Derive fields as follows:\n - workflow: workflow name, then workflow slug, then graph name, then `run.started` event name\n - result: completed status/reason or failed reason/message\n - duration: `RunTiming.wall_time_ms` from completed/failed events, formatted compactly\n - PR: latest prior `pull_request.created` event for number/title/link; if unavailable, fall back to projection `PullRequestLink` with number/link only\n- Update public docs for Slack and run configuration to document `[run.notifications]` and remove or qualify the \"interviews only\" limitation.\n\n## Test Plan\n\n- Add `fabro-slack` unit tests for lifecycle block rendering, escaping, truncation, run links, duration formatting, failed/completed variants, and optional PR fields.\n- Add server tests with a fake Slack endpoint/client path proving:\n - `run.started` posts for a matching enabled route\n - `run.completed` includes result and duration\n - `run.failed` includes failure result/message and duration\n - non-matching events and disabled routes do not post\n - missing lifecycle route channel is skipped and does not affect other routes\n - prior `pull_request.created` adds PR number/title/link\n - interview Slack messages still use interview state and are not mixed with lifecycle notification state\n\n## Assumptions\n\n- Lifecycle Slack notifications are opt-in only through `[run.notifications]`.\n- `provider = \"slack\"` is required; other providers remain parsed but unsupported.\n- Route-level Slack channel is required for lifecycle notifications.\n- Slack post failures are logged and never fail, block, or alter the run.\n- No GitHub API call is made during notification delivery; PR title comes only from existing run events.\n", + "internal.fidelity": "compact", + "internal.work_dir": "/home/daytona/workspace/fabro", + "outcome": "succeeded", + "internal.retry_count.toolchain": 0 + }, + "node_outcomes": { + "toolchain": { + "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/fc14b2ba2d770e5cd3169df7a29525c962adfc4cfa3097b9098c63ebd61a748c" + }, + "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "usage": null + }, + "start": { + "status": "succeeded", + "usage": null + } + }, + "next_node_id": "preflight_compile", + "git_commit_sha": "ff3a0514cec9479e183de314ab8ef7f5a0c492e3", + "node_visits": { + "toolchain": 1, + "start": 1 + } + }, + "diff": { + "summary": { + "files_changed": 0, + "additions": 0, + "deletions": 0 + } + } + }, + { + "seq": 0, + "checkpoint": { + "timestamp": "2026-05-23T14:12:45.495809Z", + "current_node": "preflight_compile", + "completed_nodes": [ + "start", + "toolchain", + "preflight_compile" + ], + "node_retries": {}, "context_values": { "failure_signature": "", "internal.run_id": "01KSAJRA1SB258HTA5E7VRNH1T", - "command.output": "blob://sha256/fc14b2ba2d770e5cd3169df7a29525c962adfc4cfa3097b9098c63ebd61a748c", + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "internal.retry_count.preflight_compile": 0, "graph.goal": "# Slack Run Lifecycle Notifications\n\n## Summary\n\nAdd Slack notifications for `run.started`, `run.completed`, and `run.failed` using the existing `[run.notifications]` namespace as the per-run/per-workflow source of truth. Keep Slack interviews as separate behavior sharing the same Slack connection, and do not move notification settings to server config.\n\n## Key Changes\n\n- Treat configured routes like this as active runtime subscriptions:\n\n ```toml\n [run.notifications.deploys]\n enabled = true\n provider = \"slack\"\n events = [\"run.started\", \"run.completed\", \"run.failed\"]\n\n [run.notifications.deploys.slack]\n channel = \"#deploys\"\n ```\n\n- Require `[run.notifications..slack].channel` for lifecycle Slack notifications; skip and log a warning if missing or unresolved.\n- Start `SlackService` when Slack credentials are present, even if `server.integrations.slack.default_channel` is absent. Keep `default_channel` only for the existing interview path.\n- Keep interview messages and lifecycle notifications independent:\n - interviews keep `posted_messages` and `thread_registry` behavior\n - lifecycle notifications are fire-and-forget and never accept answers or update messages\n\n## Implementation\n\n- In `lib/crates/fabro-server/src/server.rs`, extend `SlackService::handle_event`:\n - Existing interview event handling remains unchanged.\n - Add a lifecycle path for `RunStarted`, `RunCompleted`, and `RunFailed`.\n - For lifecycle events, load the cached run projection, inspect `projection.spec.settings.run.notifications`, select enabled Slack routes whose `events` contains the current event name, resolve each route channel, and post once per route.\n- In `lib/crates/fabro-slack/src/blocks.rs`, add lifecycle-specific Block Kit builders separate from interview builders.\n - Include run ID, Fabro run link when available, workflow label, result when applicable, duration when applicable, and PR info when available.\n - Use existing Slack escaping/truncation patterns for all untrusted text.\n- Derive fields as follows:\n - workflow: workflow name, then workflow slug, then graph name, then `run.started` event name\n - result: completed status/reason or failed reason/message\n - duration: `RunTiming.wall_time_ms` from completed/failed events, formatted compactly\n - PR: latest prior `pull_request.created` event for number/title/link; if unavailable, fall back to projection `PullRequestLink` with number/link only\n- Update public docs for Slack and run configuration to document `[run.notifications]` and remove or qualify the \"interviews only\" limitation.\n\n## Test Plan\n\n- Add `fabro-slack` unit tests for lifecycle block rendering, escaping, truncation, run links, duration formatting, failed/completed variants, and optional PR fields.\n- Add server tests with a fake Slack endpoint/client path proving:\n - `run.started` posts for a matching enabled route\n - `run.completed` includes result and duration\n - `run.failed` includes failure result/message and duration\n - non-matching events and disabled routes do not post\n - missing lifecycle route channel is skipped and does not affect other routes\n - prior `pull_request.created` adds PR number/title/link\n - interview Slack messages still use interview state and are not mixed with lifecycle notification state\n\n## Assumptions\n\n- Lifecycle Slack notifications are opt-in only through `[run.notifications]`.\n- `provider = \"slack\"` is required; other providers remain parsed but unsupported.\n- Route-level Slack channel is required for lifecycle notifications.\n- Slack post failures are logged and never fail, block, or alter the run.\n- No GitHub API call is made during notification delivery; PR title comes only from existing run events.\n", - "internal.thread_id": "start", + "internal.thread_id": "toolchain", "thread.start.current_node": "toolchain", "graph.rankdir": "LR", "graph.model_stylesheet": "\n * { model: claude-opus-4-7; }\n ", "internal.fidelity": "compact", - "current_node": "toolchain", + "current_node": "preflight_compile", "internal.node_visit_count": 1, "internal.retry_count.start": 0, "outcome": "succeeded", + "thread.toolchain.current_node": "preflight_compile", "internal.work_dir": "/home/daytona/workspace/fabro", "failure_class": "", "internal.retry_count.toolchain": 0 }, "node_outcomes": { - "start": { + "preflight_compile": { "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126" + }, + "notes": "Script completed: cargo check -q --workspace 2>&1", "usage": null }, "toolchain": { @@ -601,12 +665,17 @@ }, "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", "usage": null + }, + "start": { + "status": "succeeded", + "usage": null } }, - "next_node_id": "preflight_compile", + "next_node_id": "preflight_lint", "node_visits": { "start": 1, - "toolchain": 1 + "toolchain": 1, + "preflight_compile": 1 } }, "diff": {} @@ -671,7 +740,12 @@ "first_event_seq": 20, "prompt": null, "response": null, - "completion": null, + "completion": { + "outcome": "succeeded", + "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "failure_reason": null, + "timestamp": "2026-05-23T14:10:41.006578Z" + }, "provider_used": null, "diff": null, "script_invocation": { @@ -679,10 +753,53 @@ "command": "exec 2>&1\ncommand -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", "language": "shell" }, + "script_timing": { + "output": "blob://sha256/fc14b2ba2d770e5cd3169df7a29525c962adfc4cfa3097b9098c63ebd61a748c", + "exit_code": 0, + "duration_ms": 2322, + "termination": "exited", + "output_bytes": 36, + "live_streaming": true + }, + "parallel_results": null, + "output": null, + "output_bytes": 36, + "live_streaming": true, + "termination": "exited", + "started_at": "2026-05-23T14:10:38.674938Z", + "handler": "command", + "timing": { + "wall_time_ms": 2331, + "inference_time_ms": 0, + "tool_time_ms": 0, + "active_time_ms": 0 + }, + "usage": { + "input_tokens": 0, + "output_tokens": 0, + "total_tokens": 0, + "reasoning_tokens": 0, + "cache_read_tokens": 0, + "cache_write_tokens": 0 + }, + "state": "succeeded" + }, + "preflight_compile@1": { + "first_event_seq": 30, + "prompt": null, + "response": null, + "completion": null, + "provider_used": null, + "diff": null, + "script_invocation": { + "script": "cargo check -q --workspace 2>&1", + "command": "exec 2>&1\ncargo check -q --workspace 2>&1", + "language": "shell" + }, "script_timing": null, "parallel_results": null, "output": null, - "started_at": "2026-05-23T14:10:38.674938Z", + "started_at": "2026-05-23T14:10:46.732086Z", "handler": "command", "usage": { "input_tokens": 0, diff --git a/stages/002-toolchain@1/output.log b/stages/002-toolchain@1/output.log new file mode 100644 index 000000000..4e86d161d --- /dev/null +++ b/stages/002-toolchain@1/output.log @@ -0,0 +1 @@ +blob://sha256/fc14b2ba2d770e5cd3169df7a29525c962adfc4cfa3097b9098c63ebd61a748c \ No newline at end of file diff --git a/stages/002-toolchain@1/script_timing.json b/stages/002-toolchain@1/script_timing.json new file mode 100644 index 000000000..0681db693 --- /dev/null +++ b/stages/002-toolchain@1/script_timing.json @@ -0,0 +1,8 @@ +{ + "output": "blob://sha256/fc14b2ba2d770e5cd3169df7a29525c962adfc4cfa3097b9098c63ebd61a748c", + "exit_code": 0, + "duration_ms": 2322, + "termination": "exited", + "output_bytes": 36, + "live_streaming": true +} \ No newline at end of file diff --git a/stages/002-toolchain@1/status.json b/stages/002-toolchain@1/status.json new file mode 100644 index 000000000..d60699229 --- /dev/null +++ b/stages/002-toolchain@1/status.json @@ -0,0 +1,6 @@ +{ + "outcome": "succeeded", + "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "failure_reason": null, + "timestamp": "2026-05-23T14:10:41.006578Z" +} \ No newline at end of file diff --git a/stages/003-preflight_compile@1/script_invocation.json b/stages/003-preflight_compile@1/script_invocation.json new file mode 100644 index 000000000..d3abb832f --- /dev/null +++ b/stages/003-preflight_compile@1/script_invocation.json @@ -0,0 +1,5 @@ +{ + "script": "cargo check -q --workspace 2>&1", + "command": "exec 2>&1\ncargo check -q --workspace 2>&1", + "language": "shell" +} \ No newline at end of file