Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ and runs the scanner:

```bash
set -a; source .env; set +a
uv run --extra db --extra deep-agent agent-permit runner --once --deep-agent auto
uv run --extra db --extra deep-agent agent-permit runner --once --deep-agent auto --agent-recursion-limit 20
```

Run the documentation site:
Expand Down
4 changes: 4 additions & 0 deletions dashboard/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Local Worker dev:
VITE_AGENT_PERMIT_API_URL=http://127.0.0.1:8787/api

# Production deploy uses:
# VITE_AGENT_PERMIT_API_URL=https://agent-permit-worker.hudson-228.workers.dev/api
2 changes: 2 additions & 0 deletions dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build:prod": "VITE_AGENT_PERMIT_API_URL=https://agent-permit-worker.hudson-228.workers.dev/api bun run build",
"deploy:prod": "bun run build:prod && bun x wrangler pages deploy dist --project-name agent-permit-dashboard --branch main",
"lint": "eslint .",
"preview": "vite preview",
"test:e2e": "playwright test",
Expand Down
11 changes: 9 additions & 2 deletions dashboard/src/components/proof-pack-viewer/FindingQueueTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function FindingQueueTable({
/>

<QueueOptionalPanels
activeJobs={activeJobs}
isQueueing={isQueueing}
jobEvents={jobEvents}
onCloseAddRepository={onCloseAddRepository}
Expand All @@ -113,6 +114,7 @@ export function FindingQueueTable({
}

function QueueOptionalPanels({
activeJobs,
isQueueing,
jobEvents,
onCloseAddRepository,
Expand All @@ -121,6 +123,7 @@ function QueueOptionalPanels({
recentJob,
showAddRepository,
}: {
activeJobs: ScanJob[]
isQueueing: boolean
jobEvents: RunEvent[]
onCloseAddRepository: () => void
Expand All @@ -141,8 +144,12 @@ function QueueOptionalPanels({
/>
) : null}

{recentJob || jobEvents.length > 0 ? (
<QueueProgressPanel events={jobEvents} job={recentJob} />
{recentJob || activeJobs.length > 0 || jobEvents.length > 0 ? (
<QueueProgressPanel
activeJobs={activeJobs}
events={jobEvents}
job={recentJob}
/>
) : null}
</>
)
Expand Down
35 changes: 27 additions & 8 deletions dashboard/src/components/proof-pack-viewer/QueueStatusPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Input } from "@/components/ui/input"
import type { ApiStatus, QueueScanInput, RunEvent, ScanJob } from "@/data/liveApi"

const RUNNER_COMMAND =
"set -a; source .env; set +a; uv run --extra db --extra deep-agent agent-permit runner --once --deep-agent auto"
"set -a; source .env; set +a; uv run --extra db --extra deep-agent agent-permit runner --once --deep-agent auto --agent-recursion-limit 20"

export function LiveStatusStrip({
apiStatus,
Expand Down Expand Up @@ -194,33 +194,36 @@ function RecentJobNotice({ recentJob }: { recentJob: ScanJob | null }) {

return (
<div className="mt-3 rounded-md border border-border bg-muted/30 px-3 py-2 text-sm text-muted-foreground">
Job queued for {recentJob.repositoryLabel}. Run the local runner command above
to start the scan.
{jobStatusMessage(recentJob)}
</div>
)
}

export function QueueProgressPanel({
activeJobs = [],
events,
job,
}: {
activeJobs?: ScanJob[]
events: RunEvent[]
job: ScanJob | null
}) {
const visibleJob = job ?? activeJobs[0] ?? null

return (
<div className="mb-5 rounded-lg border border-border bg-background p-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 className="text-sm font-semibold">Latest queued scan</h2>
<h2 className="text-sm font-semibold">Scan handoff</h2>
<p className="mt-1 text-sm text-muted-foreground">
{job
? `${job.repositoryLabel} is ${job.status}.`
{visibleJob
? jobStatusMessage(visibleJob)
: "Waiting for runner events."}
</p>
</div>
{job ? (
{visibleJob ? (
<span className="rounded-full border border-border px-2 py-1 font-mono text-xs text-muted-foreground">
{job.id}
{visibleJob.id}
</span>
) : null}
</div>
Expand Down Expand Up @@ -250,6 +253,22 @@ export function QueueProgressPanel({
)
}

function jobStatusMessage(job: ScanJob) {
if (job.status === "queued") {
return `${job.repositoryLabel} is queued. Start the local runner to clone and scan it.`
}
if (job.status === "running") {
return `${job.repositoryLabel} is running. The local runner is scanning and writing artifacts.`
}
if (job.status === "completed") {
return `${job.repositoryLabel} completed. Refreshing findings from the Worker API.`
}
if (job.status === "failed") {
return `${job.repositoryLabel} failed. ${job.error ?? "Review runner logs."}`
}
return `${job.repositoryLabel} status: ${job.status}.`
}

function liveStatusLabel(apiStatus: ApiStatus) {
const labels: Record<ApiStatus, string> = {
error: "Worker API unavailable",
Expand Down
3 changes: 3 additions & 0 deletions dashboard/tests/e2e/proof-pack-viewer.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ test("queue screen supports scan form, search, drilldown, back, and theme cycle"
await expect(page.getByTestId("runner-command")).toContainText(
"agent-permit runner --once",
)
await expect(page.getByTestId("runner-command")).toContainText(
"--agent-recursion-limit 20",
)
await expect(page.getByTestId("queue-scan-submit")).toBeDisabled()
await page
.getByTestId("queue-scan-path")
Expand Down
2 changes: 1 addition & 1 deletion docs-site/content/docs/cloudflare-neon-deployment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Process a dashboard-queued repository scan from the local repo root:

```bash
set -a; source .env; set +a
uv run --extra db --extra deep-agent agent-permit runner --once --deep-agent auto
uv run --extra db --extra deep-agent agent-permit runner --once --deep-agent auto --agent-recursion-limit 20
```

The Worker only creates the queue record. The dashboard should queue GitHub repository URLs first. The local runner claims the job from Neon, clones GitHub URLs into `.agent-permit/runner-worktrees`, runs the scanner, writes artifacts, and updates the shared database. Absolute local paths still work for advanced local scans.
Expand Down
101 changes: 101 additions & 0 deletions docs/post-deploy-live-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Post-Deploy Live Smoke

Date: 2026-07-03

Purpose: prove the deployed dashboard, Worker API, Neon database, local CLI runner, deterministic scanner, and Deep Agent handoff are wired together.

## Deployed surfaces

| Surface | URL |
| --- | --- |
| Worker API | `https://agent-permit-worker.hudson-228.workers.dev` |
| Dashboard | `https://agent-permit-dashboard.pages.dev` |
| Docs | `https://agent-permit-docs.hudson-228.workers.dev/docs` |

## Smoke path

1. Open the dashboard.
2. Click `Queue scan`.
3. Paste a GitHub repository URL.
4. Submit the job.
5. Run the local runner from the repo root:

```bash
set -a; source .env; set +a
uv run --extra db --extra deep-agent agent-permit runner --once --deep-agent auto --agent-recursion-limit 20
```

6. Refresh the dashboard and verify the finding count, queue status, and drilldown content changed.

## Expected proof

The Worker should show:

- one new job record
- one matching scan run
- repository source equal to the queued GitHub URL
- findings written by deterministic scanners
- Deep Agent usage attached when `OPENROUTER_API_KEY` is configured

The runner should show:

- `Status: runner_job_complete`
- `Deep Agent: completed (...)` when live model review succeeds
- local artifacts under `.agent-permit/runner-worktrees/<repo-job>/.agent-permit/runs/<job_id>/`

## Release gate

Run before a release tag:

```bash
uv run pytest -q
python3 tools/release_check.py
cd dashboard && bun run lint && bun run build && bun run test:e2e
cd ../docs-site && bun run build
cd ../worker && bun test
```

Do not tag a release if the live smoke job is failed, the dashboard still points to localhost, or proof pack generation reports missing required artifacts.

## Verified smoke on 2026-07-03

Target:

```text
https://github.com/github/github-mcp-server
```

Queued job:

```text
job_46a7dfa7-875b-4c64-bc05-554bc30a3ccd
```

Runner result:

```text
Status: runner_job_complete
Deep Agent: completed (openrouter:anthropic/claude-sonnet-4.6)
```

Worker snapshot after completion:

| Metric | Value |
| --- | ---: |
| repositories | 9 |
| runs | 9 |
| findings | 46 |
| queued jobs | 0 |
| latest run permit status | `needs_review` |
| latest run findings | 20 |
| latest run graph paths | 9 |
| latest run controls | 29 |
| files indexed | 456 |
| model calls | 4 |
| input tokens | 55,073 |
| output tokens | 2,554 |
| total tokens | 57,627 |
| cached tokens | 34,330 |
| cache hit ratio | 0.6234 |

The deployed path is proven for GitHub URL queueing, local clone execution, deterministic scanner output, Deep Agent report generation, model usage ingestion, and dashboard-readable Worker state.
32 changes: 32 additions & 0 deletions src/agent_permit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import time
from typing import Any, TextIO
from urllib.parse import urlparse

Expand Down Expand Up @@ -510,6 +512,15 @@ def build_parser() -> argparse.ArgumentParser:
f"{DEFAULT_DEEP_AGENT_RECURSION_LIMIT}"
),
)
runner_parser.add_argument(
"--clone-retention-days",
type=int,
default=int(os.getenv("AGENT_PERMIT_RUNNER_CLONE_RETENTION_DAYS", "7")),
help=(
"remove runner GitHub clone worktrees older than this many days; "
"set 0 to disable cleanup; default 7"
),
)
runner_parser.add_argument(
"--phoenix",
action="store_true",
Expand Down Expand Up @@ -785,6 +796,7 @@ def main(
deep_agent=args.deep_agent,
model=args.model,
agent_recursion_limit=args.agent_recursion_limit,
clone_retention_days=args.clone_retention_days,
enable_phoenix=args.phoenix,
enable_langsmith=args.langsmith,
stdout=stdout,
Expand Down Expand Up @@ -1679,6 +1691,20 @@ def _runner_clone_root() -> Path:
return root


def _cleanup_stale_runner_clones(retention_days: int) -> None:
if retention_days <= 0:
return
clone_root = _runner_clone_root()
if not clone_root.exists():
return
cutoff = time.time() - (retention_days * 24 * 60 * 60)
for child in clone_root.iterdir():
if not child.is_dir():
continue
if child.stat().st_mtime < cutoff:
shutil.rmtree(child)


def _github_clone_slug(source: str, *, job_id: str) -> str:
parsed = urlparse(source)
parts = parsed.path.strip("/").removesuffix(".git").split("/")
Expand All @@ -1698,6 +1724,7 @@ def run_runner(
deep_agent: str = "auto",
model: str | None = None,
agent_recursion_limit: int = DEFAULT_DEEP_AGENT_RECURSION_LIMIT,
clone_retention_days: int = 7,
enable_phoenix: bool = False,
enable_langsmith: bool = False,
stdout: TextIO,
Expand All @@ -1709,6 +1736,11 @@ def run_runner(
if deep_agent not in {"auto", "required", "off"}:
print("error: --deep-agent must be auto, required, or off", file=stderr)
return 2
try:
_cleanup_stale_runner_clones(clone_retention_days)
except OSError as exc:
print(f"error: failed to clean runner clone worktrees: {exc}", file=stderr)
return 1
try:
store = store_from_env()
claimed = store.claim_next_scan_job()
Expand Down
Loading
Loading