diff --git a/dashboard/src/main.tsx b/dashboard/src/main.tsx index 4b17799..7d2f546 100644 --- a/dashboard/src/main.tsx +++ b/dashboard/src/main.tsx @@ -32,9 +32,12 @@ type RuntimeUsageBucket = { bucket: string; seconds: number; minutes: number; + runs?: number; jobs: number; work_seconds: number; review_seconds: number; + work_runs: number; + review_runs: number; work_jobs: number; review_jobs: number; }; @@ -3942,6 +3945,7 @@ function RuntimeUsageChart({ usage, loading, totalJobs }: { usage: RuntimeUsage label: runtimeBucketLabel(row.bucket, grouping), })); const totalSeconds = rows.reduce((total, row) => total + row.seconds, 0); + const totalRuns = rows.reduce((total, row) => total + (row.runs ?? row.jobs), 0); if (loading && data.length === 0) return ; if (data.length === 0) return 0 ? "No jobs have recorded runtime yet." : "No job history available."} />; return ( @@ -3949,7 +3953,7 @@ function RuntimeUsageChart({ usage, loading, totalJobs }: { usage: RuntimeUsage
- {formatRuntimeUsageSeconds(totalSeconds)} consumed across {rows.reduce((total, row) => total + row.jobs, 0)} job{rows.reduce((total, row) => total + row.jobs, 0) === 1 ? "" : "s"} + {formatRuntimeUsageSeconds(totalSeconds)} consumed across {totalRuns} run{totalRuns === 1 ? "" : "s"}
{(["day", "month"] as const).map((value) => ( diff --git a/docs/operations.md b/docs/operations.md index eafc14c..31451f2 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -479,6 +479,30 @@ before it is returned to the authenticated dashboard. The process activity panel uses persisted process samples for a compact CPU history line chart when monitor samples exist, and falls back to the live executor snapshot otherwise. +### Run history and runtime accounting + +Every successful queue claim creates a `job_runs` row with its own attempt, +worker, OpenClaw session id, and start time. Every transition out of `running` +closes that run with a finish time and a `done`, `blocked`, `requeued`, or +`cancelled` result. The `started_at` and `finished_at` columns on `jobs` remain a +compatibility summary of the current or latest attempt; runtime metrics use +completed `job_runs` as their source of truth. + +`GET /api/metrics/summary` assigns a completed run to the dashboard-local day +that contains the largest share of its elapsed runtime. A tie is assigned to the +local start day, and monthly totals are derived from that selected day. Runtime +is measured in UTC elapsed seconds, so DST changes do not add or lose execution +time. The response retains the legacy `jobs` count fields as aliases for run +counts and also returns explicit `runs`, `work_runs`, and `review_runs` fields. + +Schema initialization backfills at most one recoverable interval per legacy +job. That row has result `historical` and `is_estimated=1`; earlier attempts are +not reconstructed from the old aggregate fields. Run rows have the same +retention lifecycle as their parent job and are removed by `ON DELETE CASCADE` +when that job is deleted. They are not independently age-pruned because doing +so would silently rewrite historical usage totals; deployments should apply any +future job-retention policy to `jobs`, which also bounds run storage. + When publishing the dashboard through nginx, disable buffering for the proxied dashboard location so SSE events flush immediately. Also intercept upstream restart errors so browser users see a short auto-refreshing maintenance page diff --git a/src/github_agent_bridge/dashboard_data.py b/src/github_agent_bridge/dashboard_data.py index 33517aa..3008057 100644 --- a/src/github_agent_bridge/dashboard_data.py +++ b/src/github_agent_bridge/dashboard_data.py @@ -4,7 +4,7 @@ import os import sqlite3 from collections import Counter -from datetime import UTC, datetime +from datetime import UTC, datetime, time, timedelta from pathlib import Path from typing import Any from zoneinfo import ZoneInfo, ZoneInfoNotFoundError @@ -47,7 +47,8 @@ def parse_utc(value: str | None) -> datetime | None: if not value: return None try: - return datetime.fromisoformat(value.replace("Z", "+00:00")) + parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) + return parsed.replace(tzinfo=UTC) if parsed.tzinfo is None else parsed.astimezone(UTC) except ValueError: return None @@ -396,6 +397,14 @@ def get_job_detail(db: str | Path, job_id: int) -> dict[str, Any] | None: (job_id,), ).fetchall() ] if table_exists(con, "job_progress") else [] + job["runs"] = [ + dict(run) + for run in con.execute( + """SELECT id, attempt, started_at, finished_at, result, worker_id, session_id, is_estimated + FROM job_runs WHERE job_id=? ORDER BY attempt""", + (job_id,), + ).fetchall() + ] if table_exists(con, "job_runs") else [] job["coalesced_notifications"] = [ { "id": row["id"], @@ -704,13 +713,19 @@ def metrics_summary(db: str | Path, *, timezone_name: str = "UTC") -> dict[str, if not table_exists(con, "jobs"): return {"db_exists": True, "schema_ok": False, "status_counts": {}, "runtime_seconds": {}} rows = con.execute("SELECT status, repo, action, work_intent, created_at, started_at, finished_at FROM jobs").fetchall() + run_rows = con.execute( + """SELECT job_runs.started_at, job_runs.finished_at, jobs.work_intent + FROM job_runs + JOIN jobs ON jobs.id=job_runs.job_id + WHERE job_runs.finished_at IS NOT NULL""" + ).fetchall() if table_exists(con, "job_runs") else rows status_counts = Counter(row["status"] for row in rows) by_repo = Counter(row["repo"] or "unknown" for row in rows) by_action = Counter(row["action"] for row in rows) by_intent = Counter(row["work_intent"] for row in rows) by_created_day = Counter(day for day in (created_day(row["created_at"], timezone) for row in rows) if day) runtimes = sorted( - seconds for seconds in (completed_duration_seconds(row["started_at"], row["finished_at"]) for row in rows) if seconds is not None + seconds for seconds in (completed_duration_seconds(row["started_at"], row["finished_at"]) for row in run_rows) if seconds is not None ) waits = sorted( seconds for seconds in (duration_seconds(row["created_at"], row["started_at"]) for row in rows if row["started_at"]) if seconds is not None @@ -723,7 +738,7 @@ def metrics_summary(db: str | Path, *, timezone_name: str = "UTC") -> dict[str, "by_action": dict(by_action), "by_intent": dict(by_intent), "by_created_day": dict(sorted(by_created_day.items())), - "runtime_usage": runtime_usage(rows, timezone), + "runtime_usage": runtime_usage(run_rows, timezone), "runtime_seconds": percentiles(runtimes), "queue_wait_seconds": percentiles(waits), } @@ -754,10 +769,10 @@ def runtime_usage(rows: list[sqlite3.Row], timezone: ZoneInfo) -> dict[str, list seconds = completed_duration_seconds(row["started_at"], row["finished_at"]) if started is None or finished is None or seconds is None: continue - bucket_at = finished - local = bucket_at.astimezone(timezone) - day = local.date().isoformat() - month = f"{local.year:04d}-{local.month:02d}" + day = majority_runtime_day(started, finished, timezone) + if day is None: + continue + month = day[:7] mode = runtime_usage_mode(row["work_intent"]) add_runtime_bucket(daily, day, seconds, mode) add_runtime_bucket(monthly, month, seconds, mode) @@ -767,15 +782,40 @@ def runtime_usage(rows: list[sqlite3.Row], timezone: ZoneInfo) -> dict[str, list } +def majority_runtime_day(started: datetime, finished: datetime, timezone: ZoneInfo) -> str | None: + """Return the local day containing most elapsed runtime, preferring the start day on ties.""" + if finished < started: + return None + start_day = started.astimezone(timezone).date() + end_day = finished.astimezone(timezone).date() + current_day = start_day + best_day = start_day + best_seconds = -1.0 + while current_day <= end_day: + day_start = datetime.combine(current_day, time.min, tzinfo=timezone).astimezone(UTC) + next_day = current_day + timedelta(days=1) + day_end = datetime.combine(next_day, time.min, tzinfo=timezone).astimezone(UTC) + overlap_start = max(started, day_start) + overlap_end = min(finished, day_end) + overlap_seconds = max(0.0, (overlap_end - overlap_start).total_seconds()) + if overlap_seconds > best_seconds: + best_day = current_day + best_seconds = overlap_seconds + current_day = next_day + return best_day.isoformat() + + def runtime_usage_mode(work_intent: str | None) -> str: return "work" if work_intent == "work_allowed" else "review" def add_runtime_bucket(buckets: dict[str, dict[str, int]], bucket: str, seconds: int, mode: str) -> None: - current = buckets.setdefault(bucket, {"seconds": 0, "jobs": 0, "work_seconds": 0, "review_seconds": 0, "work_jobs": 0, "review_jobs": 0}) + current = buckets.setdefault(bucket, {"seconds": 0, "runs": 0, "jobs": 0, "work_seconds": 0, "review_seconds": 0, "work_runs": 0, "review_runs": 0, "work_jobs": 0, "review_jobs": 0}) current["seconds"] += seconds + current["runs"] += 1 current["jobs"] += 1 current[f"{mode}_seconds"] += seconds + current[f"{mode}_runs"] += 1 current[f"{mode}_jobs"] += 1 @@ -785,9 +825,12 @@ def runtime_bucket_rows(buckets: dict[str, dict[str, int]]) -> list[dict[str, An "bucket": bucket, "seconds": values["seconds"], "minutes": round(values["seconds"] / 60, 2), + "runs": values["runs"], "jobs": values["jobs"], "work_seconds": values["work_seconds"], "review_seconds": values["review_seconds"], + "work_runs": values["work_runs"], + "review_runs": values["review_runs"], "work_jobs": values["work_jobs"], "review_jobs": values["review_jobs"], } diff --git a/src/github_agent_bridge/dashboard_static/assets/index-B5yiYDzv.js b/src/github_agent_bridge/dashboard_static/assets/index-QvZxB06B.js similarity index 94% rename from src/github_agent_bridge/dashboard_static/assets/index-B5yiYDzv.js rename to src/github_agent_bridge/dashboard_static/assets/index-QvZxB06B.js index 4cbc096..0bf747c 100644 --- a/src/github_agent_bridge/dashboard_static/assets/index-B5yiYDzv.js +++ b/src/github_agent_bridge/dashboard_static/assets/index-QvZxB06B.js @@ -184,4 +184,4 @@ var ea=e=>{throw TypeError(e)};var $r=(e,t,n)=>t.has(e)||ea("Cannot "+n);var g=( } } } -}`;return i.jsxs(fe,{title:"Connect an agent",children:[s?i.jsx(Ne,{tone:"warning",text:"Set GITHUB_AGENT_BRIDGE_DASHBOARD_PUBLIC_URL, or forward X-Forwarded-* headers from the proxy, before connecting remote agents."}):null,i.jsxs("div",{className:"grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]",children:[i.jsxs("div",{className:"grid min-w-0 gap-3",children:[i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"flex flex-wrap items-center gap-2 text-sm font-semibold",children:[i.jsx(Gt,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"Public dashboard URL",i.jsx("span",{className:"rounded-sm border border-border bg-slate-50 px-1.5 py-0.5 font-mono text-[11px] font-normal text-muted",children:r})]}),i.jsx("p",{className:"mt-1 text-xs text-muted",children:"Share this page URL with bridge admins only after it resolves to the external dashboard origin."}),i.jsx("pre",{className:"mt-3 overflow-auto rounded-md bg-slate-950 px-3 py-2 font-mono text-xs text-slate-100",children:a})]}),i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm font-semibold",children:[i.jsx(Gt,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"HTTP MCP endpoint"]}),i.jsx("p",{className:"mt-1 text-xs text-muted",children:"Remote agents connect directly with a bearer token; no local `gab` binary is required on the agent host."}),i.jsx("pre",{className:"mt-3 overflow-auto rounded-md bg-slate-950 px-3 py-2 font-mono text-xs text-slate-100",children:o})]})]}),i.jsxs("div",{className:"grid min-w-0 gap-3",children:[i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm font-semibold",children:[i.jsx(Tr,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"Agent config"]}),i.jsx("p",{className:"mt-1 text-xs text-muted",children:"Create a token below, then store the one-time secret as `GITHUB_AGENT_BRIDGE_MCP_TOKEN` for the agent."}),i.jsx("pre",{className:"mt-3 max-h-64 overflow-auto rounded-md bg-slate-950 px-3 py-2 font-mono text-xs leading-relaxed text-slate-100",children:u})]}),i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm font-semibold",children:[i.jsx(Hi,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"Agent prompt"]}),i.jsx("p",{className:"mt-1 text-xs text-muted",children:"Ask the agent to use the read-only bridge knowledge server before acting on repository work."}),i.jsx("pre",{className:"mt-3 max-h-40 overflow-auto rounded-md bg-slate-950 px-3 py-2 font-mono text-xs leading-relaxed text-slate-100",children:"Use the github-agent-bridge MCP server for repository knowledge. Query list_repositories and list_knowledge before making repository decisions."})]})]})]})]})}function zl({icon:e,title:t,subtitle:n,action:r}){return i.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsxs("h2",{className:"flex items-center gap-2 text-base font-semibold",children:[e,t]}),i.jsx("p",{className:"text-xs text-muted",children:n})]}),r]})}function ox({tokens:e,loading:t,now:n,ownerOptions:r,canManageOwners:s,revokingId:a,updatingOwnerId:o,onUpdateOwner:l,onRevoke:u}){return t&&e.length===0?i.jsx(Z,{text:"Loading MCP tokens..."}):e.length===0?i.jsx(Z,{text:"No active MCP tokens."}):i.jsxs(i.Fragment,{children:[i.jsx("div",{className:"grid gap-2 md:hidden",children:e.map(c=>i.jsxs("article",{className:"grid min-w-0 gap-3 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsx("h3",{className:"break-words text-sm font-semibold [overflow-wrap:anywhere]",children:c.name}),i.jsx("div",{className:"mt-1 break-all font-mono text-xs text-muted",children:c.id}),s?i.jsx(so,{token:c,ownerOptions:r,disabled:o===c.id,onUpdateOwner:l}):c.user_login?i.jsxs("div",{className:"mt-1 text-xs text-muted",children:["Owner: @",c.user_login]}):null]}),i.jsxs("div",{className:"grid grid-cols-2 gap-2 text-xs",children:[i.jsx(J,{label:"Created",value:i.jsx(Ie,{value:c.created_at,relative:!0,now:n})}),i.jsx(J,{label:"Last used",value:c.last_used_at?i.jsx(Ie,{value:c.last_used_at,relative:!0,now:n}):"never"})]}),i.jsxs("button",{className:"inline-flex h-8 items-center justify-center gap-2 rounded-md border border-red-200 px-3 text-sm font-semibold text-red-700 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-60",type:"button",disabled:a===c.id,onClick:()=>u(c.id),children:[i.jsx(ki,{className:"h-4 w-4","aria-hidden":!0}),a===c.id?"Revoking...":"Revoke"]})]},c.id))}),i.jsx("div",{className:"hidden overflow-auto rounded-md border border-border md:block",children:i.jsxs("table",{className:"min-w-full border-collapse text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"border-b border-border bg-slate-50 text-left text-xs text-muted",children:[i.jsx("th",{className:"px-3 py-2 font-semibold",children:"Name"}),i.jsx("th",{className:"px-3 py-2 font-semibold",children:"Owner"}),i.jsx("th",{className:"px-3 py-2 font-semibold",children:"Created"}),i.jsx("th",{className:"px-3 py-2 font-semibold",children:"Last used"}),i.jsx("th",{className:"px-3 py-2 font-semibold",children:"ID"}),i.jsx("th",{className:"px-3 py-2 text-right font-semibold",children:"Actions"})]})}),i.jsx("tbody",{children:e.map(c=>i.jsxs("tr",{className:"border-b border-border last:border-b-0",children:[i.jsx("td",{className:"px-3 py-3 font-semibold",children:c.name}),i.jsx("td",{className:"px-3 py-3 text-xs text-muted",children:s?i.jsx(so,{token:c,ownerOptions:r,disabled:o===c.id,onUpdateOwner:l}):c.user_login?`@${c.user_login}`:"unassigned"}),i.jsx("td",{className:"px-3 py-3 font-mono text-xs",children:i.jsx(Ie,{value:c.created_at,relative:!0,now:n})}),i.jsx("td",{className:"px-3 py-3 font-mono text-xs",children:c.last_used_at?i.jsx(Ie,{value:c.last_used_at,relative:!0,now:n}):"never"}),i.jsx("td",{className:"px-3 py-3 font-mono text-xs text-muted",children:c.id}),i.jsx("td",{className:"px-3 py-3 text-right",children:i.jsxs("button",{className:"inline-flex h-8 items-center gap-2 rounded-md border border-red-200 px-3 text-sm font-semibold text-red-700 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-60",type:"button",disabled:a===c.id,onClick:()=>u(c.id),children:[i.jsx(ki,{className:"h-4 w-4","aria-hidden":!0}),a===c.id?"Revoking...":"Revoke"]})})]},c.id))})]})})]})}function so({token:e,ownerOptions:t,disabled:n,onUpdateOwner:r}){const s=e.user_login??"";return i.jsx(ql,{ariaLabel:`Owner for ${e.name}`,value:s,ownerOptions:t,disabled:n||t.length===0,emptyLabel:"Select owner",size:"compact",onChange:a=>{!a||a===e.user_login||r(e.id,a)}})}function ql({value:e,ownerOptions:t,disabled:n,onChange:r,ariaLabel:s,selectedSuffix:a,emptyLabel:o,size:l="default"}){const u=t.find(d=>d.login===e),c=u?`@${u.login}${(a==null?void 0:a(u))??""}`:o;return i.jsxs("div",{className:V("relative flex min-w-0 items-center gap-2 rounded-md border border-border bg-panel text-foreground focus-within:border-primary focus-within:ring-1 focus-within:ring-primary",n?"opacity-60":"",l==="compact"?"h-8 w-44 max-w-full px-2":"h-9 w-full px-2.5"),children:[i.jsx("span",{className:V("pointer-events-none flex shrink-0 items-center justify-center",l==="compact"?"h-5 w-5":"h-6 w-6"),children:u!=null&&u.avatar_url?i.jsx("img",{className:"h-full w-full rounded-full bg-slate-100",src:Ze(u.avatar_url),alt:"",referrerPolicy:"no-referrer"}):i.jsx(Wn,{className:V("text-muted",l==="compact"?"h-4 w-4":"h-5 w-5"),"aria-hidden":!0})}),i.jsx("span",{className:V("pointer-events-none min-w-0 flex-1 truncate font-mono",u?"text-foreground":"text-muted",l==="compact"?"text-xs":"text-sm"),children:c}),i.jsxs("select",{className:"absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed","aria-label":s,value:e,disabled:n,onChange:d=>r(d.target.value),children:[t.length===0||!e?i.jsx("option",{value:"",children:o}):null,t.map(d=>i.jsxs("option",{value:d.login,children:["@",d.login,(a==null?void 0:a(d))??""]},d.login))]}),i.jsx(yn,{className:V("pointer-events-none shrink-0 text-muted",l==="compact"?"h-3.5 w-3.5":"h-4 w-4"),"aria-hidden":!0})]})}function Xn({urls:e,compact:t=!1}){const n=t?e.slice(0,2):e,r=e.length-n.length;return i.jsxs("div",{className:"flex min-w-0 max-w-full flex-wrap gap-1.5",children:[n.map(s=>i.jsxs("a",{className:V("inline-flex max-w-full items-center gap-1 rounded-md border border-border font-semibold text-primary hover:bg-white hover:underline",t?"h-7 px-2 text-xs":"min-h-7 px-2 py-1 text-xs"),href:Ze(s),"aria-label":s,rel:"noreferrer",target:"_blank",children:[i.jsx(Gt,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":!0}),i.jsx("span",{className:"truncate",children:t?lx(s):s})]},s)),r>0?i.jsxs("span",{className:"inline-flex h-7 items-center rounded-md border border-border px-2 font-mono text-xs text-muted",children:["+",r]}):null]})}function lx(e){try{const t=new URL(e);return t.pathname.replace(/^\//,"")+t.hash}catch{return e}}function Bl({scope:e,type:t,confidence:n,status:r,timestamp:s,now:a}){return i.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-2 text-xs text-muted",children:[i.jsx("span",{className:"truncate font-mono font-semibold text-foreground",children:Dl(e)}),i.jsx("span",{className:"rounded-sm border border-border px-1.5 py-0.5 font-mono",children:t}),i.jsxs("span",{className:"rounded-sm border border-border px-1.5 py-0.5 font-mono",children:[Math.round(n*100),"%"]}),i.jsx("span",{className:"rounded-sm border border-border px-1.5 py-0.5 font-mono",children:r}),i.jsx("span",{className:"font-mono",children:i.jsx(Ie,{value:s,relative:!0,now:a})})]})}function ux({user:e,loading:t}){const n=e!=null&&e.login?`@${e.login}`:t?"Loading profile...":"GitHub OAuth",r=e!=null&&e.is_admin?"admin":"read-only",s=e!=null&&e.avatar_url?i.jsx("img",{className:"h-10 w-10 rounded-full border border-slate-700 bg-slate-800",src:e.avatar_url,alt:e.login?`${e.login} avatar`:"",referrerPolicy:"no-referrer"}):i.jsx("span",{className:"inline-flex h-10 w-10 items-center justify-center rounded-full border border-slate-700 bg-slate-900",children:i.jsx(Wn,{className:"h-5 w-5","aria-hidden":!0})}),a=e!=null&&e.html_url?i.jsx("a",{className:"truncate font-semibold text-white hover:underline",href:Ze(e.html_url),rel:"noreferrer",target:"_blank",children:n}):i.jsx("div",{className:"truncate font-semibold text-white",children:n});return i.jsxs("div",{className:"flex max-w-full shrink-0 items-center gap-3 text-sm text-slate-300","aria-label":e!=null&&e.login?`Signed in as ${e.login}`:"Dashboard account",children:[i.jsx(Ko,{className:"hidden h-4 w-4 shrink-0 sm:block","aria-hidden":!0}),i.jsxs("div",{className:"hidden min-w-0 text-right sm:block",children:[a,i.jsxs("div",{className:"text-xs text-slate-400",children:["Signed in · ",r]})]}),s]})}function cx({config:e,loading:t,onEnable:n,onDisable:r}){const[s,a]=T.useState(!1),[o,l]=T.useState(""),u=!!(e!=null&&e.status.enabled),c=typeof navigator<"u"&&typeof window<"u"&&Rr(),d=t||s||!(e!=null&&e.configured)||!c,h=c?e!=null&&e.configured?u?"Disable notifications":"Enable notifications":"Notifications not configured":"Notifications unavailable";return i.jsx("div",{className:"relative",children:i.jsx("button",{className:V("inline-flex h-9 w-9 items-center justify-center rounded-md border text-sm font-semibold",u?"border-emerald-400 bg-emerald-50 text-emerald-700":"border-slate-700 bg-slate-900 text-slate-200",d&&"cursor-not-allowed opacity-60"),type:"button","aria-label":h,title:o||h,disabled:d,onClick:async()=>{a(!0),l("");try{u?await r():await n()}catch(m){l(m instanceof Error?m.message:String(m))}finally{a(!1)}},children:i.jsx(Uo,{className:V("h-4 w-4",s&&"animate-live-pulse"),"aria-hidden":!0})})})}function dx({notification:e,onDismiss:t,onNavigate:n}){if(!e)return null;const r=e.title||"GitHub Agent Bridge",s=e.body||e.summary||"Bridge job finished",a=e.url||e.job_url||(e.job_id?Sn(e.job_id):"/"),o=a.startsWith("/");return i.jsx("aside",{className:"fixed right-3 top-20 z-50 w-[min(calc(100vw-1.5rem),28rem)] rounded-md border border-emerald-300 bg-white shadow-xl shadow-slate-950/15","aria-live":"assertive","aria-label":"Bridge notification",children:i.jsxs("div",{className:"flex items-start gap-3 p-3",children:[i.jsx("div",{className:"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-emerald-50 text-emerald-700",children:i.jsx(Uo,{className:"h-4 w-4","aria-hidden":!0})}),i.jsxs("div",{className:"min-w-0 flex-1",children:[i.jsxs("div",{className:"flex items-start justify-between gap-2",children:[i.jsx("h2",{className:"min-w-0 break-words text-sm font-semibold text-foreground",children:r}),i.jsx("button",{className:"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted hover:bg-slate-100 hover:text-foreground",type:"button","aria-label":"Dismiss notification",onClick:t,children:i.jsx(Jt,{className:"h-4 w-4","aria-hidden":!0})})]}),i.jsx("p",{className:"mt-1 min-w-0 break-words text-sm text-muted",children:s}),e.detail?i.jsx("p",{className:"mt-1 line-clamp-2 min-w-0 break-words text-xs text-muted",children:e.detail}):null,i.jsxs("button",{className:"mt-3 inline-flex h-8 items-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-semibold text-foreground hover:bg-slate-50",type:"button",onClick:()=>{t(),o?n(a):window.open(Ze(a),"_blank","noopener,noreferrer")},children:[i.jsx(Gt,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Open"]})]})]})})}function hx({count:e,limit:t,loading:n,onRefresh:r}){return i.jsxs("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-lg border border-border bg-white px-3 py-3 shadow-sm md:px-4",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsx("h2",{className:"text-base font-semibold",children:"Jobs"}),i.jsx("p",{className:"text-xs text-muted",children:n?"Refreshing latest jobs...":`Showing ${e} of the latest ${t} requested jobs`})]}),i.jsx(lt,{onClick:r,compactOnMobile:!0})]})}function fe({title:e,action:t,children:n,className:r,flushHeader:s=!1}){return i.jsxs("section",{className:V("min-w-0 rounded-lg border border-border bg-panel p-4 shadow-sm",r),children:[i.jsxs("div",{className:V("flex flex-wrap items-center justify-between gap-3",!s&&"mb-4"),children:[i.jsx("h2",{className:"text-sm font-semibold",children:e}),t]}),n]})}function _e({title:e,value:t,icon:n}){return i.jsxs("div",{className:"rounded-lg border border-border bg-panel p-3 shadow-sm md:p-4",children:[i.jsxs("div",{className:"flex items-center justify-between text-muted",children:[i.jsx("span",{className:"text-sm font-medium",children:e}),n]}),i.jsx("strong",{className:"mt-3 block text-2xl leading-none md:mt-4 md:text-3xl",children:t})]})}function px({filters:e,actorOptions:t,onChange:n}){const[r,s]=T.useState(e);T.useEffect(()=>s(e),[e]);const a=Df(e),o=io(e)||io(r),l=()=>{s(Mi),n(Mi)};return i.jsxs("details",{className:"my-3 rounded-md border border-border bg-slate-50/70",children:[i.jsxs("summary",{className:"grid cursor-pointer list-none grid-cols-[minmax(0,1fr)_auto] items-center gap-3 px-3 py-2 text-sm font-semibold marker:hidden",children:[i.jsxs("span",{className:"flex min-w-0 flex-wrap items-center gap-2",children:[i.jsxs("span",{className:"inline-flex shrink-0 items-center gap-2",children:[i.jsx(tc,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"Filters"]}),a.length>0?i.jsx("span",{className:"flex min-w-0 flex-wrap items-center gap-1.5","aria-label":"Applied filters",children:a.map(u=>i.jsxs("span",{className:"inline-flex max-w-full items-center gap-1 rounded-full border border-blue-200 bg-blue-50 px-2 py-0.5 text-xs font-semibold text-blue-700",children:[i.jsx("span",{className:"text-blue-700/70",children:u.label}),i.jsx("span",{className:"max-w-40 truncate font-mono",children:u.value.trim()})]},u.key))}):null]}),i.jsxs("span",{className:"inline-flex shrink-0 items-center gap-2",children:[o?i.jsxs("button",{className:"inline-flex h-8 items-center justify-center gap-1.5 rounded-md border border-border bg-white px-2.5 text-xs font-semibold text-foreground hover:bg-slate-50",type:"button",onClick:u=>{u.preventDefault(),u.stopPropagation(),l()},children:[i.jsx(Gn,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Clear filters"]}):null,i.jsx(yn,{className:"h-4 w-4 text-muted","aria-hidden":!0})]})]}),i.jsxs("form",{className:"grid gap-3 border-t border-border bg-white p-3 md:grid-cols-3 xl:grid-cols-9",onSubmit:u=>{u.preventDefault(),n(r)},children:[i.jsx(Ye,{label:"Status",children:i.jsxs("select",{className:"control",value:r.status,onChange:u=>s({...r,status:u.target.value}),children:[i.jsx("option",{value:"",children:"All"}),i.jsx("option",{value:"pending",children:"pending"}),i.jsx("option",{value:"running",children:"running"}),i.jsx("option",{value:"blocked",children:"blocked"}),i.jsx("option",{value:"done",children:"done"}),i.jsx("option",{value:"denied",children:"denied"}),i.jsx("option",{value:"waiting_approval",children:"waiting_approval"})]})}),i.jsx(Ye,{label:"Repository",children:i.jsx("input",{className:"control",value:r.repo,placeholder:"owner/repo",onChange:u=>s({...r,repo:u.target.value})})}),i.jsx(Ye,{label:"Thread",children:i.jsx("input",{className:"control",value:r.thread,inputMode:"numeric",placeholder:"issue or PR",onChange:u=>s({...r,thread:u.target.value})})}),i.jsx(Ye,{label:"Action",children:i.jsx("input",{className:"control",value:r.action,placeholder:"reply_comment",onChange:u=>s({...r,action:u.target.value})})}),i.jsx(Ye,{label:"Actor",className:"xl:col-span-2",children:i.jsx(mx,{value:r.actor,options:t,onChange:u=>s({...r,actor:u})})}),i.jsx(Ye,{label:"Intent",children:i.jsxs("select",{className:"control",value:r.intent,onChange:u=>s({...r,intent:u.target.value}),children:[i.jsx("option",{value:"",children:"All"}),i.jsx("option",{value:"review_only",children:"review_only"}),i.jsx("option",{value:"work_allowed",children:"work_allowed"})]})}),i.jsxs("div",{className:"grid grid-cols-2 gap-2 self-end xl:col-span-2",children:[i.jsxs("button",{className:"inline-flex h-9 items-center justify-center gap-2 rounded-md border border-border px-3 text-sm font-semibold text-foreground hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-white",type:"button",disabled:!o,onClick:l,children:[i.jsx(Gn,{className:"h-4 w-4","aria-hidden":!0}),"Clear"]}),i.jsxs("button",{className:"inline-flex h-9 items-center justify-center gap-2 rounded-md bg-primary px-3 text-sm font-semibold text-white",type:"submit",children:[i.jsx(sc,{className:"h-4 w-4","aria-hidden":!0}),"Apply"]})]})]})]})}function mx({value:e,options:t,onChange:n}){const[r,s]=T.useState(!1),a=e.trim().replace(/^@/,"").toLowerCase(),o=t.filter(u=>!a||u.login.toLowerCase().includes(a)).slice(0,8),l=t.find(u=>u.login.toLowerCase()===a);return i.jsxs("div",{className:"relative min-w-0",children:[i.jsxs("div",{className:"control flex items-center gap-2 px-2",children:[l?i.jsx("img",{className:"h-5 w-5 shrink-0 rounded-full bg-slate-100",src:Ze(l.avatar_url??""),alt:`${l.login} avatar`,referrerPolicy:"no-referrer"}):i.jsx(Wn,{className:"h-4 w-4 shrink-0 text-muted","aria-hidden":!0}),i.jsx("input",{className:"min-w-0 flex-1 bg-transparent font-mono text-sm outline-none",value:e,placeholder:"@login",onChange:u=>{n(u.target.value),s(!0)},onFocus:()=>s(!0),onBlur:()=>window.setTimeout(()=>s(!1),100)}),e?i.jsx("button",{className:"rounded-sm p-1 text-muted hover:bg-slate-100",type:"button","aria-label":"Clear actor filter",onClick:()=>n(""),children:i.jsx(Jt,{className:"h-3.5 w-3.5","aria-hidden":!0})}):null]}),r&&o.length>0?i.jsx("div",{className:"absolute left-0 right-0 z-20 mt-1 max-h-72 overflow-auto rounded-md border border-border bg-white p-1 shadow-lg",children:o.map(u=>i.jsxs("button",{className:"flex w-full items-center gap-2 rounded px-2 py-1.5 text-left hover:bg-slate-50",type:"button",onMouseDown:c=>c.preventDefault(),onClick:()=>{n(u.login),s(!1)},children:[u.avatar_url?i.jsx("img",{className:"h-6 w-6 shrink-0 rounded-full bg-slate-100",src:Ze(u.avatar_url),alt:`${u.login} avatar`,referrerPolicy:"no-referrer"}):i.jsx(Wn,{className:"h-5 w-5 shrink-0 text-muted","aria-hidden":!0}),i.jsxs("span",{className:"min-w-0 flex-1 truncate font-mono text-xs text-foreground",children:["@",u.login]}),i.jsx("span",{className:"shrink-0 rounded-full bg-slate-100 px-1.5 py-0.5 text-[10px] font-semibold text-muted",children:u.job_count})]},u.login))}):null]})}function Ye({label:e,children:t,className:n}){return i.jsxs("label",{className:V("grid min-w-0 gap-1 text-xs font-semibold text-muted",n),children:[e,t]})}function fx({jobs:e,loading:t,loadingMore:n=!1,hasMore:r=!1,onLoadMore:s,onViewJob:a,now:o,user:l,onRetry:u,onDismiss:c,onCancel:d}){const[h,m]=T.useState(null),[p,y]=T.useState(null),[w,j]=T.useState(null),b=T.useRef(!1),S=T.useRef(n),_=!!(l!=null&&l.is_admin&&u),N=!!(l!=null&&l.is_admin&&c);T.useEffect(()=>{S.current&&!n&&(b.current=!1),S.current=n},[n]);const q=T.useCallback(()=>{b.current||(b.current=!0,s==null||s())},[s]),k=T.useCallback(async v=>{if(u){m(v);try{await u(v)}finally{m(null)}}},[u]),B=T.useCallback(async v=>{if(c){y(v);try{await c(v)}finally{y(null)}}},[c]),M=T.useCallback(async(v,F)=>{if(d){j(v);try{await d(v,F)}finally{j(null)}}},[d]);return t&&e.length===0?i.jsx(Z,{text:"Loading jobs..."}):e.length===0?i.jsx(Z,{text:"No jobs match the current filters."}):i.jsxs(i.Fragment,{children:[i.jsxs("div",{className:"grid gap-2 md:hidden",children:[e.map(v=>i.jsx(bx,{job:v,onViewJob:a,now:o,canRetry:_&&zn(v.status),retrying:h===v.id,onRetry:k,canDismiss:N&&zn(v.status),dismissing:p===v.id,onDismiss:B,canCancel:!!(d&&Oi(v,l)),cancelling:w===v.id,onCancel:M},v.id)),i.jsx(gx,{hasMore:r,loading:n,onLoadMore:q})]}),i.jsx(Di,{noun:"jobs",hasMore:r,loading:n,onLoadMore:q,className:"hidden md:block",children:i.jsxs("table",{className:"min-w-[1080px] table-fixed border-collapse text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"sticky top-0 z-10 border-b border-border bg-panel text-left text-xs text-muted",children:[i.jsx("th",{className:"w-[42%] px-3 py-2 font-semibold",children:"Job"}),i.jsx("th",{className:"w-32 px-3 py-2 font-semibold",children:"Status"}),i.jsx("th",{className:"w-40 px-3 py-2 font-semibold",children:"Action"}),i.jsx("th",{className:"w-36 px-3 py-2 font-semibold",children:"Actor"}),i.jsx("th",{className:"w-36 px-3 py-2 font-semibold",children:"Timing"}),i.jsx("th",{className:"w-28 px-3 py-2 font-semibold",children:"Updated"}),i.jsx("th",{className:"w-24 px-3 py-2 text-right font-semibold",children:"Actions"})]})}),i.jsx("tbody",{children:e.map(v=>i.jsx(xx,{job:v,now:o,onViewJob:a,canRetry:_&&zn(v.status),retrying:h===v.id,onRetry:k,canDismiss:N&&zn(v.status),dismissing:p===v.id,onDismiss:B,canCancel:!!(d&&Oi(v,l)),cancelling:w===v.id,onCancel:M},v.id))})]})})]})}function xx({job:e,now:t,onViewJob:n,canRetry:r,retrying:s,onRetry:a,canDismiss:o,dismissing:l,onDismiss:u,canCancel:c,cancelling:d,onCancel:h}){return i.jsxs("tr",{className:"cursor-pointer border-b border-border align-top hover:bg-slate-50",onClick:()=>n(e.id),children:[i.jsx("td",{className:"min-w-0 px-3 py-3",children:i.jsxs("div",{className:"flex min-w-0 items-start gap-2",children:[i.jsxs("span",{className:"mt-0.5 shrink-0 font-mono text-xs font-semibold text-muted",children:["#",e.id]}),i.jsxs("div",{className:"min-w-0 space-y-1",children:[i.jsx("div",{className:"line-clamp-2 text-sm font-semibold leading-snug text-foreground [overflow-wrap:anywhere]",title:e.subject,children:e.subject}),i.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted",children:[i.jsx("span",{className:"min-w-0 max-w-full truncate font-mono",children:e.repo??e.work_key}),i.jsxs("span",{className:"shrink-0",children:["thread ",e.thread??"n/a"]})]})]})]})}),i.jsx("td",{className:"px-3 py-3",children:i.jsx(us,{status:e.status})}),i.jsxs("td",{className:"px-3 py-3",children:[i.jsx("div",{className:"min-w-0 truncate text-sm text-foreground",title:e.action,children:e.action}),i.jsx("div",{className:"mt-1",children:i.jsx(ls,{intent:e.intent,actionMode:e.action_mode,display:"intent",compact:!0})})]}),i.jsx("td",{className:"px-3 py-3",children:i.jsx(Cn,{actor:e.trigger_actor,avatarUrl:e.trigger_actor_avatar_url})}),i.jsxs("td",{className:"px-3 py-3 text-xs text-muted",children:[i.jsxs("div",{className:"flex items-center justify-between gap-2",children:[i.jsx("span",{children:"wait"}),i.jsx("span",{className:"font-mono text-foreground",children:Qe(os(e,t))})]}),i.jsxs("div",{className:"mt-1 flex items-center justify-between gap-2",children:[i.jsx("span",{children:"run"}),i.jsx("span",{className:"font-mono text-foreground",children:Qe(as(e,t))})]}),i.jsxs("div",{className:"mt-1 flex items-center justify-between gap-2",children:[i.jsx("span",{children:"tries"}),i.jsx("span",{className:"font-mono text-foreground",children:e.attempts})]})]}),i.jsx("td",{className:"px-3 py-3 font-mono text-xs",children:i.jsx(Ie,{value:e.updated_at,compact:!0,relative:!0,now:t})}),i.jsx("td",{className:"px-3 py-3 text-right",children:i.jsxs("div",{className:"inline-flex items-center gap-1",children:[i.jsx(Ul,{jobId:e.id,canRetry:r,retrying:s,onRetry:a,compact:!0}),i.jsx($l,{jobId:e.id,canDismiss:o,dismissing:l,onDismiss:u,compact:!0}),i.jsx(Hl,{jobId:e.id,canCancel:c,cancelling:d,onCancel:h,compact:!0})]})})]})}function gx({hasMore:e,loading:t,onLoadMore:n}){return!e&&!t?null:i.jsxs("button",{className:"inline-flex min-h-10 w-full items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-semibold text-foreground hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60 md:hidden",type:"button",disabled:t||!e,onClick:()=>n==null?void 0:n(),children:[i.jsx(yn,{className:"h-4 w-4","aria-hidden":!0}),t?"Loading more jobs...":"Load more jobs"]})}function bx({job:e,onViewJob:t,now:n,canRetry:r,retrying:s,onRetry:a,canDismiss:o,dismissing:l,onDismiss:u,canCancel:c,cancelling:d,onCancel:h}){return i.jsxs("article",{className:"rounded-md border border-border bg-white shadow-[0_1px_0_rgba(15,23,42,0.03)]",children:[i.jsxs("button",{className:"grid w-full gap-2 p-3 text-left hover:bg-slate-50",type:"button",onClick:()=>t(e.id),children:[i.jsxs("div",{className:"flex items-start justify-between gap-2",children:[i.jsxs("div",{className:"min-w-0 space-y-1",children:[i.jsxs("div",{className:"grid min-w-0 grid-cols-[auto_minmax(0,1fr)] items-center gap-2",children:[i.jsxs("span",{className:"shrink-0 font-mono text-xs font-semibold text-muted",children:["#",e.id]}),i.jsx("span",{className:"truncate font-mono text-sm",children:e.repo??e.work_key})]}),i.jsx("div",{className:"line-clamp-2 text-sm leading-snug text-foreground",children:e.subject}),i.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted",children:[i.jsxs("span",{children:["thread ",e.thread??"n/a"," · ",e.action]}),i.jsx(ls,{intent:e.intent,actionMode:e.action_mode,compact:!0,display:"intent"}),i.jsx(Cn,{actor:e.trigger_actor,avatarUrl:e.trigger_actor_avatar_url})]})]}),i.jsx(us,{status:e.status})]}),i.jsxs("div",{className:"grid grid-cols-3 gap-2 text-xs",children:[i.jsx(J,{label:"Wait",value:Qe(os(e,n))}),i.jsx(J,{label:"Runtime",value:Qe(as(e,n))}),i.jsx(J,{label:"Updated",value:i.jsx(Ie,{value:e.updated_at,compact:!0,relative:!0,now:n})})]})]}),r||o||c?i.jsxs("div",{className:"flex flex-wrap gap-2 border-t border-border px-3 py-2",children:[i.jsx(Ul,{jobId:e.id,canRetry:r,retrying:s,onRetry:a}),i.jsx($l,{jobId:e.id,canDismiss:o,dismissing:l,onDismiss:u}),i.jsx(Hl,{jobId:e.id,canCancel:c,cancelling:d,onCancel:h})]}):null]})}function Ul({jobId:e,canRetry:t,retrying:n,onRetry:r,compact:s=!1}){if(!t)return null;const a=n?"Retrying...":"Retry";return i.jsxs("button",{className:V("inline-flex h-8 items-center justify-center gap-2 rounded-md border border-border text-sm font-semibold text-foreground hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60",s?"w-8 px-0":"px-3"),type:"button",disabled:n,"aria-label":`Retry job #${e}`,title:`Retry job #${e}`,onClick:async o=>{o.stopPropagation(),window.confirm(`Retry job #${e}?`)&&await r(e)},children:[i.jsx(Gn,{className:"h-4 w-4","aria-hidden":!0}),i.jsx("span",{className:V(s&&"sr-only"),children:a})]})}function $l({jobId:e,canDismiss:t,dismissing:n,onDismiss:r,compact:s=!1}){if(!t)return null;const a=n?"Dismissing...":"Dismiss";return i.jsxs("button",{className:V("inline-flex h-8 items-center justify-center gap-2 rounded-md border border-border text-sm font-semibold text-foreground hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60",s?"w-8 px-0":"px-3"),type:"button",disabled:n,"aria-label":`Dismiss job #${e}`,title:`Dismiss job #${e}`,onClick:async o=>{o.stopPropagation(),window.confirm(`Dismiss job #${e}?`)&&await r(e)},children:[i.jsx(wn,{className:"h-4 w-4","aria-hidden":!0}),i.jsx("span",{className:V(s&&"sr-only"),children:a})]})}function Hl({jobId:e,canCancel:t,cancelling:n,onCancel:r,compact:s=!1}){if(!t)return null;const a=n?"Cancelling...":"Cancel";return i.jsxs("button",{className:V("inline-flex h-8 items-center justify-center gap-2 rounded-md border border-red-200 bg-red-50 text-sm font-semibold text-red-700 hover:bg-red-100 disabled:cursor-not-allowed disabled:opacity-60",s?"w-8 px-0":"px-3"),type:"button",disabled:n,"aria-label":`Cancel job #${e}`,title:`Cancel job #${e}`,onClick:async o=>{o.stopPropagation();const l=window.prompt(`Cancel job #${e}? Optional reason:`);l!==null&&await r(e,l)},children:[i.jsx(Jt,{className:"h-4 w-4","aria-hidden":!0}),i.jsx("span",{className:V(s&&"sr-only"),children:a})]})}function Cn({actor:e,avatarUrl:t,framed:n=!1}){const r=t?i.jsx("img",{className:"h-4 w-4 shrink-0 rounded-full bg-slate-100",src:Ze(t),alt:e?`${e} avatar`:"",referrerPolicy:"no-referrer"}):i.jsx(Wn,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":!0}),s=i.jsxs(i.Fragment,{children:[r,i.jsx("span",{className:"min-w-0 truncate",children:e?`@${e}`:"unknown actor"})]});return n?i.jsx("span",{className:"inline-flex h-7 max-w-full items-center gap-1 rounded-md border border-border px-2 text-xs font-semibold text-muted",children:s}):i.jsx("span",{className:"inline-flex min-w-0 max-w-full items-center gap-1 font-mono text-xs text-muted",children:s})}function yx(e){return(e==null?void 0:e.model)??"OpenClaw default"}function wx(e){return(e==null?void 0:e.thinking)??"OpenClaw default"}function vx(e){return e?e.configured?"configured":e.summary:"n/a"}function jx(e,t){const n=t||(e==="work_allowed"?"fix_allowed":"review_only");return n==="fix_allowed"||!t&&e==="work_allowed"?{mode:n,intentLabel:"work_allowed",title:"Work allowed"}:{mode:n,intentLabel:"review_only",title:"Review only"}}function ls({intent:e,actionMode:t,compact:n=!1,display:r="mode"}){const s=jx(e,t),a=s.intentLabel==="work_allowed"?oc:Ho,o=r==="intent"?s.intentLabel:s.mode;return i.jsxs("span",{className:V("inline-flex min-h-6 max-w-full items-center gap-1.5 rounded-md border px-2 text-xs font-semibold",s.intentLabel==="work_allowed"?"border-blue-200 bg-blue-50 text-blue-700":"border-violet-200 bg-violet-50 text-violet-700",n&&"min-h-5 px-1.5 text-[11px]"),title:`${s.title}: ${s.mode}`,"aria-label":`${s.title}: ${o}`,children:[i.jsx(a,{className:V("h-3.5 w-3.5 shrink-0",n&&"h-3 w-3"),"aria-hidden":!0}),i.jsx("span",{className:"truncate font-mono",children:o})]})}function kx({job:e,session:t,sessionEvents:n,transcript:r,now:s,compact:a=!1}){var p;const o=Sn(e.id),l=n??[],u=r??[],c=gf(l),d=bf(u),h=as(e,s),m=os(e,s);return i.jsxs("div",{className:"grid min-w-0 gap-4",children:[i.jsxs("div",{className:"sticky top-0 z-20 -mx-3 -mt-3 grid gap-2 border-b border-border bg-panel/95 px-3 py-2 shadow-sm backdrop-blur sm:-mx-4 sm:-mt-4 sm:px-4","aria-label":"Sticky job header",children:[i.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-2",children:[i.jsx(us,{status:e.status}),i.jsxs("a",{className:"inline-flex h-7 items-center gap-1 rounded-md border border-border bg-white px-2 text-xs font-semibold text-foreground hover:bg-slate-50",href:o,children:[i.jsx(vn,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Job #",e.id]}),i.jsx(Cn,{actor:e.trigger_actor,avatarUrl:e.trigger_actor_avatar_url,framed:!0}),i.jsx("span",{className:"min-w-0 break-words font-mono text-xs text-muted [overflow-wrap:anywhere]",children:e.work_key})]}),i.jsxs("div",{className:"grid min-w-0 gap-2 lg:grid-cols-[minmax(0,1fr)_minmax(280px,auto)] lg:items-start",children:[i.jsx("p",{className:"min-w-0 break-words text-sm text-muted [overflow-wrap:anywhere]",children:e.subject}),i.jsxs("div",{className:"grid min-w-0 gap-1",children:[i.jsx("h3",{className:"text-xs font-semibold text-muted",children:"GitHub links"}),e.github_urls.length>0?i.jsx(Xn,{urls:e.github_urls,compact:!0}):i.jsx("p",{className:"text-xs text-muted",children:"No links recorded."})]})]})]}),i.jsxs("div",{className:V("grid gap-2 text-sm sm:gap-3",a?"grid-cols-1":"grid-cols-3"),children:[i.jsx(J,{label:"Queue wait",value:Qe(m)}),i.jsx(J,{label:e.status==="running"?"Running for":"Runtime",value:Qe(h)}),i.jsx(J,{label:"Coalesced",value:String(e.coalesced_count)})]}),e.queue_state==="serialized_by_work_key"?i.jsxs("p",{className:"rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900",children:["Serialized behind running job #",e.blocked_by_job_id," for this work key."]}):null,i.jsxs("div",{className:V("grid gap-2 text-sm sm:gap-3",a?"grid-cols-1":"grid-cols-3"),children:[i.jsx(J,{label:"Model",value:yx(e.model_route)}),i.jsx(J,{label:"Reasoning",value:wx(e.model_route)}),i.jsx(J,{label:"Route",value:vx(e.model_route)})]}),i.jsx(Nx,{job:e,compact:a}),i.jsxs("div",{className:V("grid gap-2 text-sm sm:gap-3",a?"grid-cols-1":"grid-cols-2 xl:grid-cols-4"),children:[i.jsx(J,{label:"Created",value:i.jsx(Ie,{value:e.created_at,compact:!0,relative:!0,now:s})}),i.jsx(J,{label:"Started",value:e.started_at?i.jsx(Ie,{value:e.started_at,compact:!0,relative:!0,now:s}):"n/a"}),i.jsx(J,{label:"Updated",value:i.jsx(Ie,{value:e.updated_at,compact:!0,relative:!0,now:s})}),i.jsx(J,{label:"Finished",value:e.finished_at?i.jsx(Ie,{value:e.finished_at,compact:!0,relative:!0,now:s}):"n/a"})]}),i.jsxs("div",{children:[i.jsx("h3",{className:"mb-2 text-sm font-semibold",children:"Timeline"}),i.jsx("div",{className:"grid min-w-0 gap-3",children:(e.worklog??[]).length>0?(p=e.worklog)==null?void 0:p.map(y=>i.jsxs("div",{className:"min-w-0 border-l-2 border-primary pl-3",children:[i.jsx("div",{className:"text-sm font-semibold",children:y.phase}),i.jsx("div",{className:"font-mono text-xs text-muted",children:i.jsx(Ie,{value:y.ts,relative:!0,now:s})}),i.jsx("div",{className:"break-words text-sm [overflow-wrap:anywhere]",children:y.summary}),y.detail?i.jsx("div",{className:"mt-1 break-words font-mono text-xs text-muted [overflow-wrap:anywhere]",children:y.detail}):null]},y.id)):i.jsx(Z,{text:"No worklog entries."})})]}),i.jsxs("div",{children:[i.jsxs("h3",{className:"mb-2 flex items-center gap-2 text-sm font-semibold",children:[i.jsx(Qo,{className:"h-4 w-4","aria-hidden":!0}),"OpenClaw session"]}),t?i.jsxs("div",{className:"grid gap-3",children:[i.jsxs("div",{className:"grid gap-3 md:grid-cols-2",children:[i.jsx(J,{label:"Session ID",value:t.id}),i.jsx(J,{label:"Source",value:t.source})]}),i.jsx("p",{className:"break-words text-xs text-muted [overflow-wrap:anywhere]",children:t.detail})]}):i.jsx(Z,{text:"Session correlation is loading."})]}),i.jsxs("div",{children:[i.jsx("h3",{className:"mb-2 text-sm font-semibold",children:"Agent activity"}),i.jsx("div",{className:"grid max-h-[460px] min-w-0 gap-2 overflow-auto pr-1",children:c.length>0?c.map((y,w)=>i.jsx(Cx,{event:y,defaultOpen:no(y.eventType,e.status==="running",w,c.length),now:s},y.id)):i.jsx(Z,{text:e.status==="running"?"Waiting for live agent output...":"No agent activity has been recorded for this session."})})]}),i.jsxs("div",{children:[i.jsx("h3",{className:"mb-2 text-sm font-semibold",children:"Session transcript"}),i.jsx("div",{className:"grid max-h-[620px] min-w-0 gap-2 overflow-auto pr-1",children:d.length>0?d.map((y,w)=>i.jsx(Sx,{entry:y,defaultOpen:no(y.kind,e.status==="running",w,d.length),now:s},y.id)):i.jsx(Z,{text:e.status==="running"?"Waiting for live transcript entries...":"No OpenClaw transcript entries are available for this session."})})]})]})}function Nx({job:e,compact:t=!1}){const n=e.intent_classifier??null,r=(n==null?void 0:n.llm)??{},s=(n==null?void 0:n.parser)??{},a=typeof r.confidence=="number"?`${Math.round(r.confidence*100)}%`:"n/a";return i.jsxs("div",{className:"grid gap-2",children:[i.jsx("h3",{className:"text-sm font-semibold",children:"Action mode"}),i.jsxs("div",{className:V("grid gap-2 text-sm sm:gap-3",t?"grid-cols-1":"grid-cols-2 xl:grid-cols-4"),children:[i.jsx(J,{label:"Mode",value:i.jsx(ls,{intent:e.intent,actionMode:e.action_mode})}),i.jsx(J,{label:"Intent",value:e.intent}),i.jsx(J,{label:"Addressed",value:n?ao(r.addressed_to_agent):"n/a"}),i.jsx(J,{label:"Write",value:n?r.write_permission??"none":e.intent==="work_allowed"?"allowed":"none"})]}),n?i.jsxs("div",{className:"grid gap-2 rounded-md border border-border bg-panel p-3 text-xs",children:[i.jsxs("div",{className:"grid gap-2 sm:grid-cols-2 lg:grid-cols-4",children:[i.jsx(nt,{label:"Classifier",value:n.enabled?"enabled":"disabled"}),i.jsx(nt,{label:"Applied",value:ao(r.applied)}),i.jsx(nt,{label:"Confidence",value:a}),i.jsx(nt,{label:"Parser",value:`${s.action??"n/a"} / ${s.work_intent??"n/a"}`})]}),i.jsxs("div",{className:"grid gap-2 sm:grid-cols-2",children:[i.jsx(nt,{label:"LLM",value:`${r.action??"n/a"} / ${r.work_intent??"n/a"}`}),i.jsx(nt,{label:"Scope",value:r.scope||"n/a"})]}),r.main_request?i.jsx(nt,{label:"Main request",value:r.main_request}):null,r.subordinate_reason?i.jsx(nt,{label:"Subordinate reason",value:r.subordinate_reason}):null,r.reason?i.jsx(nt,{label:"Reason",value:r.reason}):null,n.error?i.jsx(nt,{label:"Error",value:n.error,tone:"danger"}):null]}):i.jsx("p",{className:"text-xs text-muted",children:"No intent classifier metadata recorded for this job."})]})}function ao(e){return e===!0?"yes":e===!1?"no":"n/a"}function nt({label:e,value:t,tone:n="neutral"}){return i.jsxs("div",{className:"min-w-0",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted",children:e}),i.jsx("div",{className:V("mt-0.5 min-w-0 break-words font-mono text-xs [overflow-wrap:anywhere]",n==="danger"?"text-red-700":"text-foreground"),children:t})]})}function Sx({entry:e,defaultOpen:t,now:n}){return i.jsx(Kl,{badge:e.badge,meta:i.jsx(Ie,{value:e.meta,relative:!0,now:n}),count:e.count,summary:e.summary,defaultOpen:t,children:i.jsx("pre",{className:"max-h-72 max-w-full overflow-auto whitespace-pre-wrap break-words rounded bg-slate-950 px-2 py-1.5 font-mono text-xs leading-relaxed text-slate-100 [overflow-wrap:anywhere]",children:e.text})})}function Cx({event:e,defaultOpen:t,now:n}){return i.jsx(Kl,{badge:e.badge,meta:i.jsx(Ie,{value:e.meta,relative:!0,now:n}),count:e.count,summary:e.summary,defaultOpen:t,children:e.detail?i.jsx("pre",{className:"max-h-56 max-w-full overflow-auto whitespace-pre-wrap break-words rounded bg-slate-950 px-2 py-1.5 font-mono text-xs leading-relaxed text-slate-100 [overflow-wrap:anywhere]",children:e.detail}):null})}function Kl({badge:e,meta:t,count:n,summary:r,defaultOpen:s,children:a}){const[o,l]=T.useState(!!s);return i.jsxs("details",{className:"group min-w-0 rounded border border-border bg-slate-50/60",open:o,onToggle:u=>l(u.currentTarget.open),children:[i.jsxs("summary",{className:"grid cursor-pointer list-none gap-1 px-2 py-1.5 marker:hidden hover:bg-white",children:[i.jsxs("div",{className:"grid min-w-0 gap-1 sm:flex sm:items-center sm:justify-between sm:gap-2",children:[i.jsxs("div",{className:"flex min-w-0 items-center gap-1.5",children:[i.jsx(yn,{className:"h-3.5 w-3.5 shrink-0 text-muted transition-transform group-open:rotate-180","aria-hidden":!0}),i.jsx("span",{className:"truncate font-mono text-[11px] font-semibold text-muted",children:e}),n&&n>1?i.jsx("span",{className:"rounded-sm border border-border px-1 font-mono text-[10px] text-muted",children:n}):null]}),i.jsx("span",{className:"min-w-0 truncate pl-5 font-mono text-[11px] text-muted sm:shrink-0 sm:pl-0",children:t})]}),i.jsx("div",{className:"min-w-0 break-words pl-5 text-xs text-foreground [overflow-wrap:anywhere] sm:truncate",children:r})]}),i.jsx("div",{className:"min-w-0 border-t border-border bg-white px-2 py-2",children:a})]})}function oo({label:e,values:t}){const n=[{name:"median",seconds:(t==null?void 0:t.median)??0},{name:"p90",seconds:(t==null?void 0:t.p90)??0},{name:"p99",seconds:(t==null?void 0:t.p99)??0}];return i.jsx("div",{className:"h-56",children:i.jsx(rr,{width:"100%",height:"100%",children:i.jsxs(Fr,{data:n,children:[i.jsx(ir,{strokeDasharray:"3 3"}),i.jsx(sr,{dataKey:"name"}),i.jsx(ar,{tickFormatter:Qe}),i.jsx(or,{formatter:r=>[Qe(Number(r)),e]}),i.jsx(Vt,{dataKey:"seconds",fill:"#0969da",radius:[4,4,0,0]})]})})})}function _x({values:e,loading:t,totalJobs:n}){const r=Object.entries(e??{}).map(([s,a])=>({day:s,count:a}));return t&&r.length===0?i.jsx(Z,{text:"Loading job history..."}):r.length===0?i.jsx(Z,{text:n>0?"Job history has no valid creation dates.":"No job history available."}):i.jsx("div",{className:"h-56",children:i.jsx(rr,{width:"100%",height:"100%",children:i.jsxs(Fr,{data:r,children:[i.jsx(ir,{strokeDasharray:"3 3"}),i.jsx(sr,{dataKey:"day",minTickGap:16}),i.jsx(ar,{allowDecimals:!1}),i.jsx(or,{formatter:s=>[Number(s),"jobs"]}),i.jsx(Vt,{dataKey:"count",fill:"#16a34a",radius:[4,4,0,0]})]})})})}function Ex({usage:e,loading:t,totalJobs:n}){const[r,s]=T.useState("day"),a=(e==null?void 0:e[r])??[],o=a.map(u=>({...u,label:Px(u.bucket,r)})),l=a.reduce((u,c)=>u+c.seconds,0);return t&&o.length===0?i.jsx(Z,{text:"Loading runtime usage..."}):o.length===0?i.jsx(Z,{text:n>0?"No jobs have recorded runtime yet.":"No job history available."}):i.jsxs("div",{className:"grid gap-3",children:[i.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-muted",children:[i.jsx(ac,{className:"h-4 w-4","aria-hidden":!0}),i.jsxs("span",{children:[Cr(l)," consumed across ",a.reduce((u,c)=>u+c.jobs,0)," job",a.reduce((u,c)=>u+c.jobs,0)===1?"":"s"]})]}),i.jsx("div",{className:"inline-flex h-8 rounded-md border border-border bg-white p-0.5","aria-label":"Runtime grouping",children:["day","month"].map(u=>i.jsx("button",{className:V("rounded px-2.5 text-xs font-semibold capitalize",r===u?"bg-primary text-white":"text-muted hover:bg-slate-50"),type:"button",onClick:()=>s(u),children:u},u))})]}),i.jsx("div",{className:"h-64",children:i.jsx(rr,{width:"100%",height:"100%",children:i.jsxs(Fr,{data:o,children:[i.jsx(ir,{strokeDasharray:"3 3"}),i.jsx(sr,{dataKey:"label",minTickGap:16,tick:{fontSize:11}}),i.jsx(ar,{tickFormatter:u=>Cr(Number(u))}),i.jsx(ko,{}),i.jsx(or,{formatter:(u,c)=>c==="Work"?[Cr(Number(u)),"work"]:c==="Review"?[Cr(Number(u)),"review"]:[Number(u),String(c)],labelFormatter:u=>String(u)}),i.jsx(Vt,{dataKey:"work_seconds",name:"Work",stackId:"runtime",fill:"#0969da",isAnimationActive:!1}),i.jsx(Vt,{dataKey:"review_seconds",name:"Review",stackId:"runtime",fill:"#8250df",radius:[4,4,0,0],isAnimationActive:!1})]})})})]})}function Px(e,t){if(t==="month"){const[a,o]=e.split("-").map(Number);return!a||!o?e:new Intl.DateTimeFormat(void 0,{month:"short",year:"numeric"}).format(new Date(a,o-1,1))}const[n,r,s]=e.split("-").map(Number);return!n||!r||!s?e:new Intl.DateTimeFormat(void 0,{month:"short",day:"numeric"}).format(new Date(n,r-1,s))}function Cr(e){const t=Math.max(0,Math.round(e));if(t<60)return`${t}s`;const n=Math.round(t/60);if(n<60)return`${n}m`;const r=Math.floor(n/60),s=n%60;return s===0?`${r}h`:`${r}h ${s}m`}function lo(e){return Object.values(e).reduce((t,n)=>t+n,0)}function Rx({data:e,loading:t}){if(t&&!e)return i.jsx(Z,{text:"Loading systemd units..."});if(!e)return i.jsx(Z,{text:"No systemd snapshot available."});const n=e.units??[],r=n.filter(o=>o.active_state==="active").length,s=n.filter(o=>o.active_state==="failed"||o.result==="failed").length,a=n.filter(o=>o.kind==="timer").length;return i.jsxs("div",{className:"grid min-w-0 gap-3",children:[e.errors.length>0?i.jsx(Ne,{tone:"error",text:e.errors[0]}):null,n.length===0?i.jsx(Z,{text:"No configured bridge units found."}):i.jsxs(i.Fragment,{children:[i.jsxs("div",{className:"grid grid-cols-2 gap-2 sm:grid-cols-4",children:[i.jsx(_r,{label:"Units",value:String(n.length)}),i.jsx(_r,{label:"Active",value:String(r)}),i.jsx(_r,{label:"Failed",value:String(s),tone:s>0?"bad":"neutral"}),i.jsx(_r,{label:"Timers",value:String(a)})]}),i.jsxs("div",{className:"overflow-hidden rounded-md border border-border bg-white",children:[i.jsxs("div",{className:"hidden grid-cols-[minmax(0,1.35fr)_90px_120px_90px_minmax(0,1fr)] gap-3 border-b border-border bg-slate-50 px-3 py-2 text-[11px] font-semibold uppercase text-muted md:grid",children:[i.jsx("span",{children:"Unit"}),i.jsx("span",{children:"Status"}),i.jsx("span",{children:"State"}),i.jsx("span",{children:"PID"}),i.jsx("span",{children:"Schedule"})]}),i.jsx("div",{className:"divide-y divide-border",children:n.map(o=>i.jsx(Ix,{unit:o},o.unit))})]})]})]})}function _r({label:e,value:t,tone:n="neutral"}){return i.jsxs("div",{className:V("min-w-0 rounded-md border bg-white px-3 py-2",n==="bad"?"border-red-200":"border-border"),children:[i.jsx("div",{className:V("font-mono text-lg font-semibold",n==="bad"?"text-red-700":"text-foreground"),children:t}),i.jsx("div",{className:"mt-0.5 text-[11px] font-semibold uppercase text-muted",children:e})]})}function Ix({unit:e}){const[t,n]=T.useState(!1),r=e.active_state==="active",s=e.active_state==="failed"||e.result==="failed",a=e.next_elapse||e.last_trigger||"n/a";return i.jsxs("details",{className:"group min-w-0",open:t,onToggle:o=>n(o.currentTarget.open),children:[i.jsxs("summary",{className:"grid min-w-0 cursor-pointer list-none grid-cols-2 gap-2 px-3 py-3 text-sm marker:hidden hover:bg-slate-50 md:grid-cols-[minmax(0,1.35fr)_90px_120px_90px_minmax(0,1fr)] md:items-center md:gap-3",children:[i.jsxs("div",{className:"col-span-2 min-w-0 md:col-span-1",children:[i.jsxs("div",{className:"flex min-w-0 items-center gap-2",children:[i.jsx(yn,{className:"h-3.5 w-3.5 shrink-0 text-muted transition-transform group-open:rotate-180","aria-hidden":!0}),i.jsx("span",{className:"truncate font-mono text-xs font-semibold text-foreground",children:e.unit})]}),i.jsxs("div",{className:"mt-1 flex flex-wrap items-center gap-1.5 pl-5 text-[11px] font-semibold uppercase text-muted",children:[i.jsx("span",{children:e.role}),i.jsx("span",{children:e.kind}),i.jsx("span",{children:e.load_state}),i.jsx("span",{children:t?"following log":"expand log"})]})]}),i.jsxs("div",{className:"flex flex-wrap items-center gap-2 md:block",children:[i.jsx("span",{className:"text-[11px] font-semibold uppercase text-muted md:hidden",children:"Status"}),i.jsx("span",{className:V("inline-flex h-6 items-center rounded-full border px-2 text-xs font-semibold",s?"border-red-300 bg-red-50 text-red-700":r?"border-emerald-300 bg-emerald-50 text-emerald-700":"border-slate-300 bg-slate-50 text-slate-700"),children:e.active_state})]}),i.jsx(uo,{label:"State",value:e.sub_state,detail:e.result||"unknown"}),i.jsx(uo,{label:"PID",value:e.main_pid?String(e.main_pid):"n/a",detail:Qe(e.uptime_seconds)}),i.jsxs("div",{className:"col-span-2 min-w-0 md:col-span-1",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted md:hidden",children:"Schedule"}),i.jsx("div",{className:"truncate font-mono text-[11px] text-foreground",title:a,children:e.next_elapse?`next ${e.next_elapse}`:a})]})]}),i.jsx("div",{className:"border-t border-border bg-slate-50 px-3 pb-3 pt-2",children:i.jsx(Tx,{unit:e.unit,active:t})})]})}function uo({label:e,value:t,detail:n}){return i.jsxs("div",{className:"min-w-0",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted md:hidden",children:e}),i.jsx("div",{className:"truncate font-mono text-[11px] text-foreground",children:t}),n?i.jsx("div",{className:"truncate font-mono text-[11px] text-muted",children:n}):null]})}function Tx({unit:e,active:t}){const[n,r]=T.useState([]),[s,a]=T.useState(""),o=T.useRef(null);return T.useEffect(()=>{if(!t){r([]),a("");return}r([]),a("");const l=new EventSource(`/api/systemd/journal/stream?unit=${encodeURIComponent(e)}`);return l.addEventListener("journal_line",u=>{const c=Dr(u);c&&r(d=>[...d.slice(-199),c])}),l.addEventListener("journal_error",u=>{const c=Dr(u);a((c==null?void 0:c.error)??"journal stream failed")}),l.onerror=()=>{},()=>l.close()},[t,e]),T.useEffect(()=>{const l=o.current;l&&(l.scrollTop=l.scrollHeight)},[n]),i.jsxs("div",{className:"grid min-w-0 gap-2",children:[i.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted",children:"Live journal"}),i.jsx("div",{className:"mt-0.5 truncate font-mono text-[11px] text-muted",children:n.length>0?`${n.length} lines streamed`:"waiting for journal output"})]}),i.jsx("div",{className:"font-mono text-[11px] text-muted",children:"journalctl -f"})]}),s?i.jsx(Ne,{tone:"error",text:s}):null,i.jsx("div",{ref:o,className:"h-72 overflow-auto rounded-md border border-slate-800 bg-slate-950 p-3 font-mono text-xs leading-relaxed text-slate-100",children:n.length>0?n.map((l,u)=>i.jsx("div",{className:"break-words [overflow-wrap:anywhere]",children:l.line},`${l.unit}-${u}`)):i.jsx("div",{className:"text-slate-400",children:"No journal lines received yet."})})]})}function Ax({data:e,loading:t}){var h,m,p,y,w,j;if(t&&!e)return i.jsx(Z,{text:"Loading process activity..."});if(!e)return i.jsx(Z,{text:"No process snapshot available."});const n=e.executor.children??[],r=n.flatMap(b=>Vl(b)),s=r.reduce((b,S)=>b+S.cpu_ticks,0),a=r.reduce((b,S)=>b+Mx(S),0),o=e.executor.service==="active",l=r.slice(0,8).map(b=>({label:`pid ${b.pid}`,ticks:b.cpu_ticks})),u=(e.samples??[]).map(b=>({label:Hn(b.ts),ticks:b.cpu_ticks,io:b.io_bytes,active:b.active_since_last_sample?"active":"quiet"})),c=u.length>0?u:l,d=(h=e.samples)==null?void 0:h[e.samples.length-1];return i.jsxs("div",{className:"grid gap-4",children:[i.jsx("div",{className:"rounded-md border border-slate-200 bg-slate-50 p-3",children:i.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[i.jsx("span",{className:V("inline-flex h-6 items-center rounded-full border px-2 text-xs font-semibold",o?"border-emerald-300 bg-emerald-50 text-emerald-700":"border-slate-300 bg-white text-slate-600"),children:o?"active":"idle"}),i.jsxs("span",{className:"font-mono text-xs text-muted",children:["service ",e.executor.service]})]}),i.jsx("div",{className:"mt-2 text-sm font-semibold text-foreground",children:e.running_jobs.length>0?`${e.running_jobs.length} running job${e.running_jobs.length===1?"":"s"}`:"No running jobs"}),e.running_jobs.length>0?i.jsx("div",{className:"mt-2 flex flex-wrap gap-1.5",children:e.running_jobs.slice(0,4).map(b=>i.jsxs("span",{className:"inline-flex min-h-6 items-center gap-1.5 rounded-full border border-blue-200 bg-white px-2 font-mono text-[11px] font-semibold text-blue-700",children:[i.jsx("span",{className:"h-2 w-2 rounded-full bg-blue-600 animate-live-pulse","aria-hidden":!0}),"#",b.id," ",Qe(b.age_seconds)]},b.id))}):null,d?i.jsxs("p",{className:"mt-1 text-xs text-muted",children:["Last persisted sample ",Hn(d.ts)," · ",d.active_since_last_sample?"activity observed":`quiet ${Qe(d.idle_seconds)}`]}):null,i.jsx("p",{className:"mt-1 text-xs text-muted",children:e.detail})]}),i.jsxs("div",{className:"grid min-w-[190px] grid-cols-3 gap-2 text-center text-xs",children:[i.jsx(oi,{label:"PID",value:e.executor.pid?String(e.executor.pid):"n/a"}),i.jsx(oi,{label:"Children",value:String(r.length)}),i.jsx(oi,{label:"CPU ticks",value:String(s)})]})]})}),i.jsxs("div",{className:"grid gap-2 sm:grid-cols-2",children:[i.jsx(Er,{label:"Live process",value:((m=e.signals)==null?void 0:m.live_process.state)??(r.length>0?"live":"no_child_process"),detail:`${((p=e.signals)==null?void 0:p.live_process.child_count)??r.length} children`}),i.jsx(Er,{label:"Process activity",value:((y=e.signals)==null?void 0:y.process_activity.state)??(d!=null&&d.active_since_last_sample?"active":"quiet"),detail:d?`sample ${Hn(d.ts)}`:"no sample"}),i.jsx(Er,{label:"Semantic progress",value:(w=e.signals)!=null&&w.semantic_progress.length?"recent":"none",detail:co(e.running_jobs,"semantic_progress")}),i.jsx(Er,{label:"Visible progress",value:(j=e.signals)!=null&&j.visible_progress.length?"streaming":"none",detail:co(e.running_jobs,"visible_progress")})]}),e.alerts.length>0?i.jsx(Ne,{tone:"error",text:e.alerts[0]}):null,i.jsxs("div",{className:"grid gap-4 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]",children:[i.jsxs("div",{className:"min-w-0 rounded-md border border-border p-3",children:[i.jsxs("div",{className:"mb-3 flex items-center justify-between gap-3",children:[i.jsxs("h3",{className:"flex items-center gap-2 text-sm font-semibold",children:[i.jsx(ma,{className:"h-4 w-4","aria-hidden":!0}),u.length>0?"CPU history":"CPU ticks"]}),i.jsxs("span",{className:"font-mono text-xs text-muted",children:[Wl(a)," I/O"]})]}),c.length>0?i.jsx("div",{className:"h-40",children:i.jsx(rr,{width:"100%",height:"100%",children:i.jsxs(ou,{data:c,children:[i.jsx(ir,{strokeDasharray:"3 3"}),i.jsx(sr,{dataKey:"label",tick:!1}),i.jsx(ar,{allowDecimals:!1,tick:{fontSize:11}}),i.jsx(or,{formatter:b=>[Number(b),"cpu ticks"]}),i.jsx(lu,{type:"monotone",dataKey:"ticks",stroke:"#0f766e",strokeWidth:2,dot:{r:3},activeDot:{r:5},isAnimationActive:!1})]})})}):i.jsx(Z,{text:"No executor CPU samples available."})]}),i.jsxs("div",{className:"min-w-0",children:[i.jsxs("h3",{className:"mb-2 flex items-center gap-2 text-sm font-semibold",children:[i.jsx(ma,{className:"h-4 w-4","aria-hidden":!0}),"Executor children"]}),n.length>0?i.jsx("div",{className:"grid gap-2",children:n.map(b=>i.jsx(Ql,{process:b},b.pid))}):i.jsx(Z,{text:"No child process detected for the executor."})]})]})]})}function Lx({alerts:e,loading:t,now:n}){if(t&&!e)return i.jsx(Z,{text:"Loading monitor alerts..."});const r=e??[];return r.length===0?i.jsx(Z,{text:"No active monitor alerts."}):i.jsx("div",{className:"grid gap-2",children:r.slice(0,5).map(s=>i.jsxs("div",{className:"rounded-md border border-red-200 bg-red-50 p-2.5",children:[i.jsxs("div",{className:"flex flex-wrap items-center gap-2 text-xs font-semibold text-red-700",children:[i.jsx(zr,{className:"h-3.5 w-3.5","aria-hidden":!0}),i.jsx("span",{children:s.severity}),i.jsx("span",{className:"font-normal text-red-600",children:Ll(s.last_seen,n)}),s.observations>1?i.jsxs("span",{className:"rounded-full border border-red-200 bg-white px-1.5",children:[s.observations,"x"]}):null]}),i.jsx("p",{className:"mt-1 break-words text-sm font-medium text-red-950 [overflow-wrap:anywhere]",children:s.message})]},s.fingerprint))})}function Ql({process:e}){var r,s;const t=((r=e.io_bytes)==null?void 0:r.read_bytes)??0,n=((s=e.io_bytes)==null?void 0:s.write_bytes)??0;return i.jsxs("div",{className:"rounded-md border border-border bg-white p-2.5",children:[i.jsxs("div",{className:"flex flex-wrap items-center gap-2 text-sm",children:[i.jsxs("span",{className:"font-mono",children:["pid ",e.pid]}),i.jsxs("span",{className:"rounded-full border border-border px-2 text-xs text-muted",children:["state ",e.state]}),i.jsxs("span",{className:"rounded-full border border-border px-2 text-xs text-muted",children:["cpu ",e.cpu_ticks]}),i.jsxs("span",{className:"rounded-full border border-border px-2 text-xs text-muted",children:["I/O ",Wl(t+n)]})]}),i.jsx("div",{className:"mt-2 break-words font-mono text-xs text-muted",children:e.cmd||"unknown command"}),e.children&&e.children.length>0?i.jsx("div",{className:"mt-3 border-l-2 border-border pl-3",children:e.children.map(a=>i.jsx(Ql,{process:a},a.pid))}):null]})}function oi({label:e,value:t}){return i.jsxs("div",{className:"rounded-md border border-border bg-white px-2 py-2",children:[i.jsx("div",{className:"font-mono text-sm font-semibold text-foreground",children:t}),i.jsx("div",{className:"mt-0.5 text-[11px] font-semibold uppercase text-muted",children:e})]})}function Er({label:e,value:t,detail:n}){return i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-2.5",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted",children:e}),i.jsx("div",{className:"mt-1 truncate text-sm font-semibold text-foreground",children:t}),i.jsx("div",{className:"mt-1 truncate font-mono text-[11px] text-muted",children:n})]})}function co(e,t){const n=e.find(s=>s[t]),r=n==null?void 0:n[t];return!n||!r?"no running heartbeat":`#${n.id} ${r.phase} ${Qe(r.age_seconds??null)}`}function Vl(e){return[e,...(e.children??[]).flatMap(t=>Vl(t))]}function Mx(e){var t,n;return(((t=e.io_bytes)==null?void 0:t.read_bytes)??0)+(((n=e.io_bytes)==null?void 0:n.write_bytes)??0)}function Wl(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:`${(e/(1024*1024)).toFixed(1)} MiB`}function J({label:e,value:t}){return i.jsxs("div",{className:"min-w-0 rounded-md border border-border p-3",children:[i.jsx("div",{className:"text-xs font-semibold text-muted",children:e}),i.jsx("div",{className:"mt-1 min-w-0 break-words text-sm [overflow-wrap:anywhere]",children:t})]})}function us({status:e}){const t=yf(e),n=e==="running"||e==="pending";return i.jsxs("span",{className:V("inline-flex min-h-6 items-center gap-1.5 rounded-full border px-2 text-xs font-semibold",t.badge),children:[i.jsx("span",{className:V("h-2.5 w-2.5 rounded-full",t.dot,n&&"animate-live-pulse"),"aria-hidden":!0}),e]})}function Z({text:e}){return i.jsx("div",{className:"rounded-md border border-dashed border-border p-6 text-center text-sm text-muted",children:e})}function Ne({tone:e,text:t}){return i.jsx("div",{className:V("rounded-md border p-3 text-sm",e==="error"&&"border-red-300 bg-red-50 text-red-700",e==="warning"&&"border-amber-300 bg-amber-50 text-amber-800"),children:t})}function lt({onClick:e,compactOnMobile:t=!1}){return i.jsxs("button",{className:V("inline-flex h-8 items-center justify-center gap-2 rounded-md border border-border text-sm font-semibold text-foreground hover:bg-slate-50",t?"w-8 px-0 sm:w-auto sm:px-3":"px-3"),onClick:e,type:"button","aria-label":"Refresh",children:[i.jsx(ur,{className:"h-4 w-4","aria-hidden":!0}),i.jsx("span",{className:V(t&&"hidden sm:inline"),children:"Refresh"})]})}const ho=document.getElementById("root");ho&&pu.createRoot(ho).render(i.jsx(T.StrictMode,{children:i.jsx(Bu,{client:lf,children:i.jsx(Ff,{})})})); +}`;return i.jsxs(fe,{title:"Connect an agent",children:[s?i.jsx(Ne,{tone:"warning",text:"Set GITHUB_AGENT_BRIDGE_DASHBOARD_PUBLIC_URL, or forward X-Forwarded-* headers from the proxy, before connecting remote agents."}):null,i.jsxs("div",{className:"grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]",children:[i.jsxs("div",{className:"grid min-w-0 gap-3",children:[i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"flex flex-wrap items-center gap-2 text-sm font-semibold",children:[i.jsx(Gt,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"Public dashboard URL",i.jsx("span",{className:"rounded-sm border border-border bg-slate-50 px-1.5 py-0.5 font-mono text-[11px] font-normal text-muted",children:r})]}),i.jsx("p",{className:"mt-1 text-xs text-muted",children:"Share this page URL with bridge admins only after it resolves to the external dashboard origin."}),i.jsx("pre",{className:"mt-3 overflow-auto rounded-md bg-slate-950 px-3 py-2 font-mono text-xs text-slate-100",children:a})]}),i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm font-semibold",children:[i.jsx(Gt,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"HTTP MCP endpoint"]}),i.jsx("p",{className:"mt-1 text-xs text-muted",children:"Remote agents connect directly with a bearer token; no local `gab` binary is required on the agent host."}),i.jsx("pre",{className:"mt-3 overflow-auto rounded-md bg-slate-950 px-3 py-2 font-mono text-xs text-slate-100",children:o})]})]}),i.jsxs("div",{className:"grid min-w-0 gap-3",children:[i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm font-semibold",children:[i.jsx(Tr,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"Agent config"]}),i.jsx("p",{className:"mt-1 text-xs text-muted",children:"Create a token below, then store the one-time secret as `GITHUB_AGENT_BRIDGE_MCP_TOKEN` for the agent."}),i.jsx("pre",{className:"mt-3 max-h-64 overflow-auto rounded-md bg-slate-950 px-3 py-2 font-mono text-xs leading-relaxed text-slate-100",children:u})]}),i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm font-semibold",children:[i.jsx(Hi,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"Agent prompt"]}),i.jsx("p",{className:"mt-1 text-xs text-muted",children:"Ask the agent to use the read-only bridge knowledge server before acting on repository work."}),i.jsx("pre",{className:"mt-3 max-h-40 overflow-auto rounded-md bg-slate-950 px-3 py-2 font-mono text-xs leading-relaxed text-slate-100",children:"Use the github-agent-bridge MCP server for repository knowledge. Query list_repositories and list_knowledge before making repository decisions."})]})]})]})]})}function zl({icon:e,title:t,subtitle:n,action:r}){return i.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsxs("h2",{className:"flex items-center gap-2 text-base font-semibold",children:[e,t]}),i.jsx("p",{className:"text-xs text-muted",children:n})]}),r]})}function ox({tokens:e,loading:t,now:n,ownerOptions:r,canManageOwners:s,revokingId:a,updatingOwnerId:o,onUpdateOwner:l,onRevoke:u}){return t&&e.length===0?i.jsx(Z,{text:"Loading MCP tokens..."}):e.length===0?i.jsx(Z,{text:"No active MCP tokens."}):i.jsxs(i.Fragment,{children:[i.jsx("div",{className:"grid gap-2 md:hidden",children:e.map(c=>i.jsxs("article",{className:"grid min-w-0 gap-3 rounded-md border border-border bg-white p-3",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsx("h3",{className:"break-words text-sm font-semibold [overflow-wrap:anywhere]",children:c.name}),i.jsx("div",{className:"mt-1 break-all font-mono text-xs text-muted",children:c.id}),s?i.jsx(so,{token:c,ownerOptions:r,disabled:o===c.id,onUpdateOwner:l}):c.user_login?i.jsxs("div",{className:"mt-1 text-xs text-muted",children:["Owner: @",c.user_login]}):null]}),i.jsxs("div",{className:"grid grid-cols-2 gap-2 text-xs",children:[i.jsx(J,{label:"Created",value:i.jsx(Ie,{value:c.created_at,relative:!0,now:n})}),i.jsx(J,{label:"Last used",value:c.last_used_at?i.jsx(Ie,{value:c.last_used_at,relative:!0,now:n}):"never"})]}),i.jsxs("button",{className:"inline-flex h-8 items-center justify-center gap-2 rounded-md border border-red-200 px-3 text-sm font-semibold text-red-700 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-60",type:"button",disabled:a===c.id,onClick:()=>u(c.id),children:[i.jsx(ki,{className:"h-4 w-4","aria-hidden":!0}),a===c.id?"Revoking...":"Revoke"]})]},c.id))}),i.jsx("div",{className:"hidden overflow-auto rounded-md border border-border md:block",children:i.jsxs("table",{className:"min-w-full border-collapse text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"border-b border-border bg-slate-50 text-left text-xs text-muted",children:[i.jsx("th",{className:"px-3 py-2 font-semibold",children:"Name"}),i.jsx("th",{className:"px-3 py-2 font-semibold",children:"Owner"}),i.jsx("th",{className:"px-3 py-2 font-semibold",children:"Created"}),i.jsx("th",{className:"px-3 py-2 font-semibold",children:"Last used"}),i.jsx("th",{className:"px-3 py-2 font-semibold",children:"ID"}),i.jsx("th",{className:"px-3 py-2 text-right font-semibold",children:"Actions"})]})}),i.jsx("tbody",{children:e.map(c=>i.jsxs("tr",{className:"border-b border-border last:border-b-0",children:[i.jsx("td",{className:"px-3 py-3 font-semibold",children:c.name}),i.jsx("td",{className:"px-3 py-3 text-xs text-muted",children:s?i.jsx(so,{token:c,ownerOptions:r,disabled:o===c.id,onUpdateOwner:l}):c.user_login?`@${c.user_login}`:"unassigned"}),i.jsx("td",{className:"px-3 py-3 font-mono text-xs",children:i.jsx(Ie,{value:c.created_at,relative:!0,now:n})}),i.jsx("td",{className:"px-3 py-3 font-mono text-xs",children:c.last_used_at?i.jsx(Ie,{value:c.last_used_at,relative:!0,now:n}):"never"}),i.jsx("td",{className:"px-3 py-3 font-mono text-xs text-muted",children:c.id}),i.jsx("td",{className:"px-3 py-3 text-right",children:i.jsxs("button",{className:"inline-flex h-8 items-center gap-2 rounded-md border border-red-200 px-3 text-sm font-semibold text-red-700 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-60",type:"button",disabled:a===c.id,onClick:()=>u(c.id),children:[i.jsx(ki,{className:"h-4 w-4","aria-hidden":!0}),a===c.id?"Revoking...":"Revoke"]})})]},c.id))})]})})]})}function so({token:e,ownerOptions:t,disabled:n,onUpdateOwner:r}){const s=e.user_login??"";return i.jsx(ql,{ariaLabel:`Owner for ${e.name}`,value:s,ownerOptions:t,disabled:n||t.length===0,emptyLabel:"Select owner",size:"compact",onChange:a=>{!a||a===e.user_login||r(e.id,a)}})}function ql({value:e,ownerOptions:t,disabled:n,onChange:r,ariaLabel:s,selectedSuffix:a,emptyLabel:o,size:l="default"}){const u=t.find(d=>d.login===e),c=u?`@${u.login}${(a==null?void 0:a(u))??""}`:o;return i.jsxs("div",{className:V("relative flex min-w-0 items-center gap-2 rounded-md border border-border bg-panel text-foreground focus-within:border-primary focus-within:ring-1 focus-within:ring-primary",n?"opacity-60":"",l==="compact"?"h-8 w-44 max-w-full px-2":"h-9 w-full px-2.5"),children:[i.jsx("span",{className:V("pointer-events-none flex shrink-0 items-center justify-center",l==="compact"?"h-5 w-5":"h-6 w-6"),children:u!=null&&u.avatar_url?i.jsx("img",{className:"h-full w-full rounded-full bg-slate-100",src:Ze(u.avatar_url),alt:"",referrerPolicy:"no-referrer"}):i.jsx(Wn,{className:V("text-muted",l==="compact"?"h-4 w-4":"h-5 w-5"),"aria-hidden":!0})}),i.jsx("span",{className:V("pointer-events-none min-w-0 flex-1 truncate font-mono",u?"text-foreground":"text-muted",l==="compact"?"text-xs":"text-sm"),children:c}),i.jsxs("select",{className:"absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed","aria-label":s,value:e,disabled:n,onChange:d=>r(d.target.value),children:[t.length===0||!e?i.jsx("option",{value:"",children:o}):null,t.map(d=>i.jsxs("option",{value:d.login,children:["@",d.login,(a==null?void 0:a(d))??""]},d.login))]}),i.jsx(yn,{className:V("pointer-events-none shrink-0 text-muted",l==="compact"?"h-3.5 w-3.5":"h-4 w-4"),"aria-hidden":!0})]})}function Xn({urls:e,compact:t=!1}){const n=t?e.slice(0,2):e,r=e.length-n.length;return i.jsxs("div",{className:"flex min-w-0 max-w-full flex-wrap gap-1.5",children:[n.map(s=>i.jsxs("a",{className:V("inline-flex max-w-full items-center gap-1 rounded-md border border-border font-semibold text-primary hover:bg-white hover:underline",t?"h-7 px-2 text-xs":"min-h-7 px-2 py-1 text-xs"),href:Ze(s),"aria-label":s,rel:"noreferrer",target:"_blank",children:[i.jsx(Gt,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":!0}),i.jsx("span",{className:"truncate",children:t?lx(s):s})]},s)),r>0?i.jsxs("span",{className:"inline-flex h-7 items-center rounded-md border border-border px-2 font-mono text-xs text-muted",children:["+",r]}):null]})}function lx(e){try{const t=new URL(e);return t.pathname.replace(/^\//,"")+t.hash}catch{return e}}function Bl({scope:e,type:t,confidence:n,status:r,timestamp:s,now:a}){return i.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-2 text-xs text-muted",children:[i.jsx("span",{className:"truncate font-mono font-semibold text-foreground",children:Dl(e)}),i.jsx("span",{className:"rounded-sm border border-border px-1.5 py-0.5 font-mono",children:t}),i.jsxs("span",{className:"rounded-sm border border-border px-1.5 py-0.5 font-mono",children:[Math.round(n*100),"%"]}),i.jsx("span",{className:"rounded-sm border border-border px-1.5 py-0.5 font-mono",children:r}),i.jsx("span",{className:"font-mono",children:i.jsx(Ie,{value:s,relative:!0,now:a})})]})}function ux({user:e,loading:t}){const n=e!=null&&e.login?`@${e.login}`:t?"Loading profile...":"GitHub OAuth",r=e!=null&&e.is_admin?"admin":"read-only",s=e!=null&&e.avatar_url?i.jsx("img",{className:"h-10 w-10 rounded-full border border-slate-700 bg-slate-800",src:e.avatar_url,alt:e.login?`${e.login} avatar`:"",referrerPolicy:"no-referrer"}):i.jsx("span",{className:"inline-flex h-10 w-10 items-center justify-center rounded-full border border-slate-700 bg-slate-900",children:i.jsx(Wn,{className:"h-5 w-5","aria-hidden":!0})}),a=e!=null&&e.html_url?i.jsx("a",{className:"truncate font-semibold text-white hover:underline",href:Ze(e.html_url),rel:"noreferrer",target:"_blank",children:n}):i.jsx("div",{className:"truncate font-semibold text-white",children:n});return i.jsxs("div",{className:"flex max-w-full shrink-0 items-center gap-3 text-sm text-slate-300","aria-label":e!=null&&e.login?`Signed in as ${e.login}`:"Dashboard account",children:[i.jsx(Ko,{className:"hidden h-4 w-4 shrink-0 sm:block","aria-hidden":!0}),i.jsxs("div",{className:"hidden min-w-0 text-right sm:block",children:[a,i.jsxs("div",{className:"text-xs text-slate-400",children:["Signed in · ",r]})]}),s]})}function cx({config:e,loading:t,onEnable:n,onDisable:r}){const[s,a]=T.useState(!1),[o,l]=T.useState(""),u=!!(e!=null&&e.status.enabled),c=typeof navigator<"u"&&typeof window<"u"&&Rr(),d=t||s||!(e!=null&&e.configured)||!c,h=c?e!=null&&e.configured?u?"Disable notifications":"Enable notifications":"Notifications not configured":"Notifications unavailable";return i.jsx("div",{className:"relative",children:i.jsx("button",{className:V("inline-flex h-9 w-9 items-center justify-center rounded-md border text-sm font-semibold",u?"border-emerald-400 bg-emerald-50 text-emerald-700":"border-slate-700 bg-slate-900 text-slate-200",d&&"cursor-not-allowed opacity-60"),type:"button","aria-label":h,title:o||h,disabled:d,onClick:async()=>{a(!0),l("");try{u?await r():await n()}catch(m){l(m instanceof Error?m.message:String(m))}finally{a(!1)}},children:i.jsx(Uo,{className:V("h-4 w-4",s&&"animate-live-pulse"),"aria-hidden":!0})})})}function dx({notification:e,onDismiss:t,onNavigate:n}){if(!e)return null;const r=e.title||"GitHub Agent Bridge",s=e.body||e.summary||"Bridge job finished",a=e.url||e.job_url||(e.job_id?Sn(e.job_id):"/"),o=a.startsWith("/");return i.jsx("aside",{className:"fixed right-3 top-20 z-50 w-[min(calc(100vw-1.5rem),28rem)] rounded-md border border-emerald-300 bg-white shadow-xl shadow-slate-950/15","aria-live":"assertive","aria-label":"Bridge notification",children:i.jsxs("div",{className:"flex items-start gap-3 p-3",children:[i.jsx("div",{className:"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-emerald-50 text-emerald-700",children:i.jsx(Uo,{className:"h-4 w-4","aria-hidden":!0})}),i.jsxs("div",{className:"min-w-0 flex-1",children:[i.jsxs("div",{className:"flex items-start justify-between gap-2",children:[i.jsx("h2",{className:"min-w-0 break-words text-sm font-semibold text-foreground",children:r}),i.jsx("button",{className:"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted hover:bg-slate-100 hover:text-foreground",type:"button","aria-label":"Dismiss notification",onClick:t,children:i.jsx(Jt,{className:"h-4 w-4","aria-hidden":!0})})]}),i.jsx("p",{className:"mt-1 min-w-0 break-words text-sm text-muted",children:s}),e.detail?i.jsx("p",{className:"mt-1 line-clamp-2 min-w-0 break-words text-xs text-muted",children:e.detail}):null,i.jsxs("button",{className:"mt-3 inline-flex h-8 items-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-semibold text-foreground hover:bg-slate-50",type:"button",onClick:()=>{t(),o?n(a):window.open(Ze(a),"_blank","noopener,noreferrer")},children:[i.jsx(Gt,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Open"]})]})]})})}function hx({count:e,limit:t,loading:n,onRefresh:r}){return i.jsxs("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-lg border border-border bg-white px-3 py-3 shadow-sm md:px-4",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsx("h2",{className:"text-base font-semibold",children:"Jobs"}),i.jsx("p",{className:"text-xs text-muted",children:n?"Refreshing latest jobs...":`Showing ${e} of the latest ${t} requested jobs`})]}),i.jsx(lt,{onClick:r,compactOnMobile:!0})]})}function fe({title:e,action:t,children:n,className:r,flushHeader:s=!1}){return i.jsxs("section",{className:V("min-w-0 rounded-lg border border-border bg-panel p-4 shadow-sm",r),children:[i.jsxs("div",{className:V("flex flex-wrap items-center justify-between gap-3",!s&&"mb-4"),children:[i.jsx("h2",{className:"text-sm font-semibold",children:e}),t]}),n]})}function _e({title:e,value:t,icon:n}){return i.jsxs("div",{className:"rounded-lg border border-border bg-panel p-3 shadow-sm md:p-4",children:[i.jsxs("div",{className:"flex items-center justify-between text-muted",children:[i.jsx("span",{className:"text-sm font-medium",children:e}),n]}),i.jsx("strong",{className:"mt-3 block text-2xl leading-none md:mt-4 md:text-3xl",children:t})]})}function px({filters:e,actorOptions:t,onChange:n}){const[r,s]=T.useState(e);T.useEffect(()=>s(e),[e]);const a=Df(e),o=io(e)||io(r),l=()=>{s(Mi),n(Mi)};return i.jsxs("details",{className:"my-3 rounded-md border border-border bg-slate-50/70",children:[i.jsxs("summary",{className:"grid cursor-pointer list-none grid-cols-[minmax(0,1fr)_auto] items-center gap-3 px-3 py-2 text-sm font-semibold marker:hidden",children:[i.jsxs("span",{className:"flex min-w-0 flex-wrap items-center gap-2",children:[i.jsxs("span",{className:"inline-flex shrink-0 items-center gap-2",children:[i.jsx(tc,{className:"h-4 w-4 text-muted","aria-hidden":!0}),"Filters"]}),a.length>0?i.jsx("span",{className:"flex min-w-0 flex-wrap items-center gap-1.5","aria-label":"Applied filters",children:a.map(u=>i.jsxs("span",{className:"inline-flex max-w-full items-center gap-1 rounded-full border border-blue-200 bg-blue-50 px-2 py-0.5 text-xs font-semibold text-blue-700",children:[i.jsx("span",{className:"text-blue-700/70",children:u.label}),i.jsx("span",{className:"max-w-40 truncate font-mono",children:u.value.trim()})]},u.key))}):null]}),i.jsxs("span",{className:"inline-flex shrink-0 items-center gap-2",children:[o?i.jsxs("button",{className:"inline-flex h-8 items-center justify-center gap-1.5 rounded-md border border-border bg-white px-2.5 text-xs font-semibold text-foreground hover:bg-slate-50",type:"button",onClick:u=>{u.preventDefault(),u.stopPropagation(),l()},children:[i.jsx(Gn,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Clear filters"]}):null,i.jsx(yn,{className:"h-4 w-4 text-muted","aria-hidden":!0})]})]}),i.jsxs("form",{className:"grid gap-3 border-t border-border bg-white p-3 md:grid-cols-3 xl:grid-cols-9",onSubmit:u=>{u.preventDefault(),n(r)},children:[i.jsx(Ye,{label:"Status",children:i.jsxs("select",{className:"control",value:r.status,onChange:u=>s({...r,status:u.target.value}),children:[i.jsx("option",{value:"",children:"All"}),i.jsx("option",{value:"pending",children:"pending"}),i.jsx("option",{value:"running",children:"running"}),i.jsx("option",{value:"blocked",children:"blocked"}),i.jsx("option",{value:"done",children:"done"}),i.jsx("option",{value:"denied",children:"denied"}),i.jsx("option",{value:"waiting_approval",children:"waiting_approval"})]})}),i.jsx(Ye,{label:"Repository",children:i.jsx("input",{className:"control",value:r.repo,placeholder:"owner/repo",onChange:u=>s({...r,repo:u.target.value})})}),i.jsx(Ye,{label:"Thread",children:i.jsx("input",{className:"control",value:r.thread,inputMode:"numeric",placeholder:"issue or PR",onChange:u=>s({...r,thread:u.target.value})})}),i.jsx(Ye,{label:"Action",children:i.jsx("input",{className:"control",value:r.action,placeholder:"reply_comment",onChange:u=>s({...r,action:u.target.value})})}),i.jsx(Ye,{label:"Actor",className:"xl:col-span-2",children:i.jsx(mx,{value:r.actor,options:t,onChange:u=>s({...r,actor:u})})}),i.jsx(Ye,{label:"Intent",children:i.jsxs("select",{className:"control",value:r.intent,onChange:u=>s({...r,intent:u.target.value}),children:[i.jsx("option",{value:"",children:"All"}),i.jsx("option",{value:"review_only",children:"review_only"}),i.jsx("option",{value:"work_allowed",children:"work_allowed"})]})}),i.jsxs("div",{className:"grid grid-cols-2 gap-2 self-end xl:col-span-2",children:[i.jsxs("button",{className:"inline-flex h-9 items-center justify-center gap-2 rounded-md border border-border px-3 text-sm font-semibold text-foreground hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-white",type:"button",disabled:!o,onClick:l,children:[i.jsx(Gn,{className:"h-4 w-4","aria-hidden":!0}),"Clear"]}),i.jsxs("button",{className:"inline-flex h-9 items-center justify-center gap-2 rounded-md bg-primary px-3 text-sm font-semibold text-white",type:"submit",children:[i.jsx(sc,{className:"h-4 w-4","aria-hidden":!0}),"Apply"]})]})]})]})}function mx({value:e,options:t,onChange:n}){const[r,s]=T.useState(!1),a=e.trim().replace(/^@/,"").toLowerCase(),o=t.filter(u=>!a||u.login.toLowerCase().includes(a)).slice(0,8),l=t.find(u=>u.login.toLowerCase()===a);return i.jsxs("div",{className:"relative min-w-0",children:[i.jsxs("div",{className:"control flex items-center gap-2 px-2",children:[l?i.jsx("img",{className:"h-5 w-5 shrink-0 rounded-full bg-slate-100",src:Ze(l.avatar_url??""),alt:`${l.login} avatar`,referrerPolicy:"no-referrer"}):i.jsx(Wn,{className:"h-4 w-4 shrink-0 text-muted","aria-hidden":!0}),i.jsx("input",{className:"min-w-0 flex-1 bg-transparent font-mono text-sm outline-none",value:e,placeholder:"@login",onChange:u=>{n(u.target.value),s(!0)},onFocus:()=>s(!0),onBlur:()=>window.setTimeout(()=>s(!1),100)}),e?i.jsx("button",{className:"rounded-sm p-1 text-muted hover:bg-slate-100",type:"button","aria-label":"Clear actor filter",onClick:()=>n(""),children:i.jsx(Jt,{className:"h-3.5 w-3.5","aria-hidden":!0})}):null]}),r&&o.length>0?i.jsx("div",{className:"absolute left-0 right-0 z-20 mt-1 max-h-72 overflow-auto rounded-md border border-border bg-white p-1 shadow-lg",children:o.map(u=>i.jsxs("button",{className:"flex w-full items-center gap-2 rounded px-2 py-1.5 text-left hover:bg-slate-50",type:"button",onMouseDown:c=>c.preventDefault(),onClick:()=>{n(u.login),s(!1)},children:[u.avatar_url?i.jsx("img",{className:"h-6 w-6 shrink-0 rounded-full bg-slate-100",src:Ze(u.avatar_url),alt:`${u.login} avatar`,referrerPolicy:"no-referrer"}):i.jsx(Wn,{className:"h-5 w-5 shrink-0 text-muted","aria-hidden":!0}),i.jsxs("span",{className:"min-w-0 flex-1 truncate font-mono text-xs text-foreground",children:["@",u.login]}),i.jsx("span",{className:"shrink-0 rounded-full bg-slate-100 px-1.5 py-0.5 text-[10px] font-semibold text-muted",children:u.job_count})]},u.login))}):null]})}function Ye({label:e,children:t,className:n}){return i.jsxs("label",{className:V("grid min-w-0 gap-1 text-xs font-semibold text-muted",n),children:[e,t]})}function fx({jobs:e,loading:t,loadingMore:n=!1,hasMore:r=!1,onLoadMore:s,onViewJob:a,now:o,user:l,onRetry:u,onDismiss:c,onCancel:d}){const[h,m]=T.useState(null),[p,y]=T.useState(null),[w,j]=T.useState(null),b=T.useRef(!1),S=T.useRef(n),_=!!(l!=null&&l.is_admin&&u),N=!!(l!=null&&l.is_admin&&c);T.useEffect(()=>{S.current&&!n&&(b.current=!1),S.current=n},[n]);const q=T.useCallback(()=>{b.current||(b.current=!0,s==null||s())},[s]),k=T.useCallback(async v=>{if(u){m(v);try{await u(v)}finally{m(null)}}},[u]),B=T.useCallback(async v=>{if(c){y(v);try{await c(v)}finally{y(null)}}},[c]),M=T.useCallback(async(v,F)=>{if(d){j(v);try{await d(v,F)}finally{j(null)}}},[d]);return t&&e.length===0?i.jsx(Z,{text:"Loading jobs..."}):e.length===0?i.jsx(Z,{text:"No jobs match the current filters."}):i.jsxs(i.Fragment,{children:[i.jsxs("div",{className:"grid gap-2 md:hidden",children:[e.map(v=>i.jsx(bx,{job:v,onViewJob:a,now:o,canRetry:_&&zn(v.status),retrying:h===v.id,onRetry:k,canDismiss:N&&zn(v.status),dismissing:p===v.id,onDismiss:B,canCancel:!!(d&&Oi(v,l)),cancelling:w===v.id,onCancel:M},v.id)),i.jsx(gx,{hasMore:r,loading:n,onLoadMore:q})]}),i.jsx(Di,{noun:"jobs",hasMore:r,loading:n,onLoadMore:q,className:"hidden md:block",children:i.jsxs("table",{className:"min-w-[1080px] table-fixed border-collapse text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"sticky top-0 z-10 border-b border-border bg-panel text-left text-xs text-muted",children:[i.jsx("th",{className:"w-[42%] px-3 py-2 font-semibold",children:"Job"}),i.jsx("th",{className:"w-32 px-3 py-2 font-semibold",children:"Status"}),i.jsx("th",{className:"w-40 px-3 py-2 font-semibold",children:"Action"}),i.jsx("th",{className:"w-36 px-3 py-2 font-semibold",children:"Actor"}),i.jsx("th",{className:"w-36 px-3 py-2 font-semibold",children:"Timing"}),i.jsx("th",{className:"w-28 px-3 py-2 font-semibold",children:"Updated"}),i.jsx("th",{className:"w-24 px-3 py-2 text-right font-semibold",children:"Actions"})]})}),i.jsx("tbody",{children:e.map(v=>i.jsx(xx,{job:v,now:o,onViewJob:a,canRetry:_&&zn(v.status),retrying:h===v.id,onRetry:k,canDismiss:N&&zn(v.status),dismissing:p===v.id,onDismiss:B,canCancel:!!(d&&Oi(v,l)),cancelling:w===v.id,onCancel:M},v.id))})]})})]})}function xx({job:e,now:t,onViewJob:n,canRetry:r,retrying:s,onRetry:a,canDismiss:o,dismissing:l,onDismiss:u,canCancel:c,cancelling:d,onCancel:h}){return i.jsxs("tr",{className:"cursor-pointer border-b border-border align-top hover:bg-slate-50",onClick:()=>n(e.id),children:[i.jsx("td",{className:"min-w-0 px-3 py-3",children:i.jsxs("div",{className:"flex min-w-0 items-start gap-2",children:[i.jsxs("span",{className:"mt-0.5 shrink-0 font-mono text-xs font-semibold text-muted",children:["#",e.id]}),i.jsxs("div",{className:"min-w-0 space-y-1",children:[i.jsx("div",{className:"line-clamp-2 text-sm font-semibold leading-snug text-foreground [overflow-wrap:anywhere]",title:e.subject,children:e.subject}),i.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted",children:[i.jsx("span",{className:"min-w-0 max-w-full truncate font-mono",children:e.repo??e.work_key}),i.jsxs("span",{className:"shrink-0",children:["thread ",e.thread??"n/a"]})]})]})]})}),i.jsx("td",{className:"px-3 py-3",children:i.jsx(us,{status:e.status})}),i.jsxs("td",{className:"px-3 py-3",children:[i.jsx("div",{className:"min-w-0 truncate text-sm text-foreground",title:e.action,children:e.action}),i.jsx("div",{className:"mt-1",children:i.jsx(ls,{intent:e.intent,actionMode:e.action_mode,display:"intent",compact:!0})})]}),i.jsx("td",{className:"px-3 py-3",children:i.jsx(Cn,{actor:e.trigger_actor,avatarUrl:e.trigger_actor_avatar_url})}),i.jsxs("td",{className:"px-3 py-3 text-xs text-muted",children:[i.jsxs("div",{className:"flex items-center justify-between gap-2",children:[i.jsx("span",{children:"wait"}),i.jsx("span",{className:"font-mono text-foreground",children:Qe(os(e,t))})]}),i.jsxs("div",{className:"mt-1 flex items-center justify-between gap-2",children:[i.jsx("span",{children:"run"}),i.jsx("span",{className:"font-mono text-foreground",children:Qe(as(e,t))})]}),i.jsxs("div",{className:"mt-1 flex items-center justify-between gap-2",children:[i.jsx("span",{children:"tries"}),i.jsx("span",{className:"font-mono text-foreground",children:e.attempts})]})]}),i.jsx("td",{className:"px-3 py-3 font-mono text-xs",children:i.jsx(Ie,{value:e.updated_at,compact:!0,relative:!0,now:t})}),i.jsx("td",{className:"px-3 py-3 text-right",children:i.jsxs("div",{className:"inline-flex items-center gap-1",children:[i.jsx(Ul,{jobId:e.id,canRetry:r,retrying:s,onRetry:a,compact:!0}),i.jsx($l,{jobId:e.id,canDismiss:o,dismissing:l,onDismiss:u,compact:!0}),i.jsx(Hl,{jobId:e.id,canCancel:c,cancelling:d,onCancel:h,compact:!0})]})})]})}function gx({hasMore:e,loading:t,onLoadMore:n}){return!e&&!t?null:i.jsxs("button",{className:"inline-flex min-h-10 w-full items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-semibold text-foreground hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60 md:hidden",type:"button",disabled:t||!e,onClick:()=>n==null?void 0:n(),children:[i.jsx(yn,{className:"h-4 w-4","aria-hidden":!0}),t?"Loading more jobs...":"Load more jobs"]})}function bx({job:e,onViewJob:t,now:n,canRetry:r,retrying:s,onRetry:a,canDismiss:o,dismissing:l,onDismiss:u,canCancel:c,cancelling:d,onCancel:h}){return i.jsxs("article",{className:"rounded-md border border-border bg-white shadow-[0_1px_0_rgba(15,23,42,0.03)]",children:[i.jsxs("button",{className:"grid w-full gap-2 p-3 text-left hover:bg-slate-50",type:"button",onClick:()=>t(e.id),children:[i.jsxs("div",{className:"flex items-start justify-between gap-2",children:[i.jsxs("div",{className:"min-w-0 space-y-1",children:[i.jsxs("div",{className:"grid min-w-0 grid-cols-[auto_minmax(0,1fr)] items-center gap-2",children:[i.jsxs("span",{className:"shrink-0 font-mono text-xs font-semibold text-muted",children:["#",e.id]}),i.jsx("span",{className:"truncate font-mono text-sm",children:e.repo??e.work_key})]}),i.jsx("div",{className:"line-clamp-2 text-sm leading-snug text-foreground",children:e.subject}),i.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted",children:[i.jsxs("span",{children:["thread ",e.thread??"n/a"," · ",e.action]}),i.jsx(ls,{intent:e.intent,actionMode:e.action_mode,compact:!0,display:"intent"}),i.jsx(Cn,{actor:e.trigger_actor,avatarUrl:e.trigger_actor_avatar_url})]})]}),i.jsx(us,{status:e.status})]}),i.jsxs("div",{className:"grid grid-cols-3 gap-2 text-xs",children:[i.jsx(J,{label:"Wait",value:Qe(os(e,n))}),i.jsx(J,{label:"Runtime",value:Qe(as(e,n))}),i.jsx(J,{label:"Updated",value:i.jsx(Ie,{value:e.updated_at,compact:!0,relative:!0,now:n})})]})]}),r||o||c?i.jsxs("div",{className:"flex flex-wrap gap-2 border-t border-border px-3 py-2",children:[i.jsx(Ul,{jobId:e.id,canRetry:r,retrying:s,onRetry:a}),i.jsx($l,{jobId:e.id,canDismiss:o,dismissing:l,onDismiss:u}),i.jsx(Hl,{jobId:e.id,canCancel:c,cancelling:d,onCancel:h})]}):null]})}function Ul({jobId:e,canRetry:t,retrying:n,onRetry:r,compact:s=!1}){if(!t)return null;const a=n?"Retrying...":"Retry";return i.jsxs("button",{className:V("inline-flex h-8 items-center justify-center gap-2 rounded-md border border-border text-sm font-semibold text-foreground hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60",s?"w-8 px-0":"px-3"),type:"button",disabled:n,"aria-label":`Retry job #${e}`,title:`Retry job #${e}`,onClick:async o=>{o.stopPropagation(),window.confirm(`Retry job #${e}?`)&&await r(e)},children:[i.jsx(Gn,{className:"h-4 w-4","aria-hidden":!0}),i.jsx("span",{className:V(s&&"sr-only"),children:a})]})}function $l({jobId:e,canDismiss:t,dismissing:n,onDismiss:r,compact:s=!1}){if(!t)return null;const a=n?"Dismissing...":"Dismiss";return i.jsxs("button",{className:V("inline-flex h-8 items-center justify-center gap-2 rounded-md border border-border text-sm font-semibold text-foreground hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60",s?"w-8 px-0":"px-3"),type:"button",disabled:n,"aria-label":`Dismiss job #${e}`,title:`Dismiss job #${e}`,onClick:async o=>{o.stopPropagation(),window.confirm(`Dismiss job #${e}?`)&&await r(e)},children:[i.jsx(wn,{className:"h-4 w-4","aria-hidden":!0}),i.jsx("span",{className:V(s&&"sr-only"),children:a})]})}function Hl({jobId:e,canCancel:t,cancelling:n,onCancel:r,compact:s=!1}){if(!t)return null;const a=n?"Cancelling...":"Cancel";return i.jsxs("button",{className:V("inline-flex h-8 items-center justify-center gap-2 rounded-md border border-red-200 bg-red-50 text-sm font-semibold text-red-700 hover:bg-red-100 disabled:cursor-not-allowed disabled:opacity-60",s?"w-8 px-0":"px-3"),type:"button",disabled:n,"aria-label":`Cancel job #${e}`,title:`Cancel job #${e}`,onClick:async o=>{o.stopPropagation();const l=window.prompt(`Cancel job #${e}? Optional reason:`);l!==null&&await r(e,l)},children:[i.jsx(Jt,{className:"h-4 w-4","aria-hidden":!0}),i.jsx("span",{className:V(s&&"sr-only"),children:a})]})}function Cn({actor:e,avatarUrl:t,framed:n=!1}){const r=t?i.jsx("img",{className:"h-4 w-4 shrink-0 rounded-full bg-slate-100",src:Ze(t),alt:e?`${e} avatar`:"",referrerPolicy:"no-referrer"}):i.jsx(Wn,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":!0}),s=i.jsxs(i.Fragment,{children:[r,i.jsx("span",{className:"min-w-0 truncate",children:e?`@${e}`:"unknown actor"})]});return n?i.jsx("span",{className:"inline-flex h-7 max-w-full items-center gap-1 rounded-md border border-border px-2 text-xs font-semibold text-muted",children:s}):i.jsx("span",{className:"inline-flex min-w-0 max-w-full items-center gap-1 font-mono text-xs text-muted",children:s})}function yx(e){return(e==null?void 0:e.model)??"OpenClaw default"}function wx(e){return(e==null?void 0:e.thinking)??"OpenClaw default"}function vx(e){return e?e.configured?"configured":e.summary:"n/a"}function jx(e,t){const n=t||(e==="work_allowed"?"fix_allowed":"review_only");return n==="fix_allowed"||!t&&e==="work_allowed"?{mode:n,intentLabel:"work_allowed",title:"Work allowed"}:{mode:n,intentLabel:"review_only",title:"Review only"}}function ls({intent:e,actionMode:t,compact:n=!1,display:r="mode"}){const s=jx(e,t),a=s.intentLabel==="work_allowed"?oc:Ho,o=r==="intent"?s.intentLabel:s.mode;return i.jsxs("span",{className:V("inline-flex min-h-6 max-w-full items-center gap-1.5 rounded-md border px-2 text-xs font-semibold",s.intentLabel==="work_allowed"?"border-blue-200 bg-blue-50 text-blue-700":"border-violet-200 bg-violet-50 text-violet-700",n&&"min-h-5 px-1.5 text-[11px]"),title:`${s.title}: ${s.mode}`,"aria-label":`${s.title}: ${o}`,children:[i.jsx(a,{className:V("h-3.5 w-3.5 shrink-0",n&&"h-3 w-3"),"aria-hidden":!0}),i.jsx("span",{className:"truncate font-mono",children:o})]})}function kx({job:e,session:t,sessionEvents:n,transcript:r,now:s,compact:a=!1}){var p;const o=Sn(e.id),l=n??[],u=r??[],c=gf(l),d=bf(u),h=as(e,s),m=os(e,s);return i.jsxs("div",{className:"grid min-w-0 gap-4",children:[i.jsxs("div",{className:"sticky top-0 z-20 -mx-3 -mt-3 grid gap-2 border-b border-border bg-panel/95 px-3 py-2 shadow-sm backdrop-blur sm:-mx-4 sm:-mt-4 sm:px-4","aria-label":"Sticky job header",children:[i.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-2",children:[i.jsx(us,{status:e.status}),i.jsxs("a",{className:"inline-flex h-7 items-center gap-1 rounded-md border border-border bg-white px-2 text-xs font-semibold text-foreground hover:bg-slate-50",href:o,children:[i.jsx(vn,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Job #",e.id]}),i.jsx(Cn,{actor:e.trigger_actor,avatarUrl:e.trigger_actor_avatar_url,framed:!0}),i.jsx("span",{className:"min-w-0 break-words font-mono text-xs text-muted [overflow-wrap:anywhere]",children:e.work_key})]}),i.jsxs("div",{className:"grid min-w-0 gap-2 lg:grid-cols-[minmax(0,1fr)_minmax(280px,auto)] lg:items-start",children:[i.jsx("p",{className:"min-w-0 break-words text-sm text-muted [overflow-wrap:anywhere]",children:e.subject}),i.jsxs("div",{className:"grid min-w-0 gap-1",children:[i.jsx("h3",{className:"text-xs font-semibold text-muted",children:"GitHub links"}),e.github_urls.length>0?i.jsx(Xn,{urls:e.github_urls,compact:!0}):i.jsx("p",{className:"text-xs text-muted",children:"No links recorded."})]})]})]}),i.jsxs("div",{className:V("grid gap-2 text-sm sm:gap-3",a?"grid-cols-1":"grid-cols-3"),children:[i.jsx(J,{label:"Queue wait",value:Qe(m)}),i.jsx(J,{label:e.status==="running"?"Running for":"Runtime",value:Qe(h)}),i.jsx(J,{label:"Coalesced",value:String(e.coalesced_count)})]}),e.queue_state==="serialized_by_work_key"?i.jsxs("p",{className:"rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900",children:["Serialized behind running job #",e.blocked_by_job_id," for this work key."]}):null,i.jsxs("div",{className:V("grid gap-2 text-sm sm:gap-3",a?"grid-cols-1":"grid-cols-3"),children:[i.jsx(J,{label:"Model",value:yx(e.model_route)}),i.jsx(J,{label:"Reasoning",value:wx(e.model_route)}),i.jsx(J,{label:"Route",value:vx(e.model_route)})]}),i.jsx(Nx,{job:e,compact:a}),i.jsxs("div",{className:V("grid gap-2 text-sm sm:gap-3",a?"grid-cols-1":"grid-cols-2 xl:grid-cols-4"),children:[i.jsx(J,{label:"Created",value:i.jsx(Ie,{value:e.created_at,compact:!0,relative:!0,now:s})}),i.jsx(J,{label:"Started",value:e.started_at?i.jsx(Ie,{value:e.started_at,compact:!0,relative:!0,now:s}):"n/a"}),i.jsx(J,{label:"Updated",value:i.jsx(Ie,{value:e.updated_at,compact:!0,relative:!0,now:s})}),i.jsx(J,{label:"Finished",value:e.finished_at?i.jsx(Ie,{value:e.finished_at,compact:!0,relative:!0,now:s}):"n/a"})]}),i.jsxs("div",{children:[i.jsx("h3",{className:"mb-2 text-sm font-semibold",children:"Timeline"}),i.jsx("div",{className:"grid min-w-0 gap-3",children:(e.worklog??[]).length>0?(p=e.worklog)==null?void 0:p.map(y=>i.jsxs("div",{className:"min-w-0 border-l-2 border-primary pl-3",children:[i.jsx("div",{className:"text-sm font-semibold",children:y.phase}),i.jsx("div",{className:"font-mono text-xs text-muted",children:i.jsx(Ie,{value:y.ts,relative:!0,now:s})}),i.jsx("div",{className:"break-words text-sm [overflow-wrap:anywhere]",children:y.summary}),y.detail?i.jsx("div",{className:"mt-1 break-words font-mono text-xs text-muted [overflow-wrap:anywhere]",children:y.detail}):null]},y.id)):i.jsx(Z,{text:"No worklog entries."})})]}),i.jsxs("div",{children:[i.jsxs("h3",{className:"mb-2 flex items-center gap-2 text-sm font-semibold",children:[i.jsx(Qo,{className:"h-4 w-4","aria-hidden":!0}),"OpenClaw session"]}),t?i.jsxs("div",{className:"grid gap-3",children:[i.jsxs("div",{className:"grid gap-3 md:grid-cols-2",children:[i.jsx(J,{label:"Session ID",value:t.id}),i.jsx(J,{label:"Source",value:t.source})]}),i.jsx("p",{className:"break-words text-xs text-muted [overflow-wrap:anywhere]",children:t.detail})]}):i.jsx(Z,{text:"Session correlation is loading."})]}),i.jsxs("div",{children:[i.jsx("h3",{className:"mb-2 text-sm font-semibold",children:"Agent activity"}),i.jsx("div",{className:"grid max-h-[460px] min-w-0 gap-2 overflow-auto pr-1",children:c.length>0?c.map((y,w)=>i.jsx(Cx,{event:y,defaultOpen:no(y.eventType,e.status==="running",w,c.length),now:s},y.id)):i.jsx(Z,{text:e.status==="running"?"Waiting for live agent output...":"No agent activity has been recorded for this session."})})]}),i.jsxs("div",{children:[i.jsx("h3",{className:"mb-2 text-sm font-semibold",children:"Session transcript"}),i.jsx("div",{className:"grid max-h-[620px] min-w-0 gap-2 overflow-auto pr-1",children:d.length>0?d.map((y,w)=>i.jsx(Sx,{entry:y,defaultOpen:no(y.kind,e.status==="running",w,d.length),now:s},y.id)):i.jsx(Z,{text:e.status==="running"?"Waiting for live transcript entries...":"No OpenClaw transcript entries are available for this session."})})]})]})}function Nx({job:e,compact:t=!1}){const n=e.intent_classifier??null,r=(n==null?void 0:n.llm)??{},s=(n==null?void 0:n.parser)??{},a=typeof r.confidence=="number"?`${Math.round(r.confidence*100)}%`:"n/a";return i.jsxs("div",{className:"grid gap-2",children:[i.jsx("h3",{className:"text-sm font-semibold",children:"Action mode"}),i.jsxs("div",{className:V("grid gap-2 text-sm sm:gap-3",t?"grid-cols-1":"grid-cols-2 xl:grid-cols-4"),children:[i.jsx(J,{label:"Mode",value:i.jsx(ls,{intent:e.intent,actionMode:e.action_mode})}),i.jsx(J,{label:"Intent",value:e.intent}),i.jsx(J,{label:"Addressed",value:n?ao(r.addressed_to_agent):"n/a"}),i.jsx(J,{label:"Write",value:n?r.write_permission??"none":e.intent==="work_allowed"?"allowed":"none"})]}),n?i.jsxs("div",{className:"grid gap-2 rounded-md border border-border bg-panel p-3 text-xs",children:[i.jsxs("div",{className:"grid gap-2 sm:grid-cols-2 lg:grid-cols-4",children:[i.jsx(nt,{label:"Classifier",value:n.enabled?"enabled":"disabled"}),i.jsx(nt,{label:"Applied",value:ao(r.applied)}),i.jsx(nt,{label:"Confidence",value:a}),i.jsx(nt,{label:"Parser",value:`${s.action??"n/a"} / ${s.work_intent??"n/a"}`})]}),i.jsxs("div",{className:"grid gap-2 sm:grid-cols-2",children:[i.jsx(nt,{label:"LLM",value:`${r.action??"n/a"} / ${r.work_intent??"n/a"}`}),i.jsx(nt,{label:"Scope",value:r.scope||"n/a"})]}),r.main_request?i.jsx(nt,{label:"Main request",value:r.main_request}):null,r.subordinate_reason?i.jsx(nt,{label:"Subordinate reason",value:r.subordinate_reason}):null,r.reason?i.jsx(nt,{label:"Reason",value:r.reason}):null,n.error?i.jsx(nt,{label:"Error",value:n.error,tone:"danger"}):null]}):i.jsx("p",{className:"text-xs text-muted",children:"No intent classifier metadata recorded for this job."})]})}function ao(e){return e===!0?"yes":e===!1?"no":"n/a"}function nt({label:e,value:t,tone:n="neutral"}){return i.jsxs("div",{className:"min-w-0",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted",children:e}),i.jsx("div",{className:V("mt-0.5 min-w-0 break-words font-mono text-xs [overflow-wrap:anywhere]",n==="danger"?"text-red-700":"text-foreground"),children:t})]})}function Sx({entry:e,defaultOpen:t,now:n}){return i.jsx(Kl,{badge:e.badge,meta:i.jsx(Ie,{value:e.meta,relative:!0,now:n}),count:e.count,summary:e.summary,defaultOpen:t,children:i.jsx("pre",{className:"max-h-72 max-w-full overflow-auto whitespace-pre-wrap break-words rounded bg-slate-950 px-2 py-1.5 font-mono text-xs leading-relaxed text-slate-100 [overflow-wrap:anywhere]",children:e.text})})}function Cx({event:e,defaultOpen:t,now:n}){return i.jsx(Kl,{badge:e.badge,meta:i.jsx(Ie,{value:e.meta,relative:!0,now:n}),count:e.count,summary:e.summary,defaultOpen:t,children:e.detail?i.jsx("pre",{className:"max-h-56 max-w-full overflow-auto whitespace-pre-wrap break-words rounded bg-slate-950 px-2 py-1.5 font-mono text-xs leading-relaxed text-slate-100 [overflow-wrap:anywhere]",children:e.detail}):null})}function Kl({badge:e,meta:t,count:n,summary:r,defaultOpen:s,children:a}){const[o,l]=T.useState(!!s);return i.jsxs("details",{className:"group min-w-0 rounded border border-border bg-slate-50/60",open:o,onToggle:u=>l(u.currentTarget.open),children:[i.jsxs("summary",{className:"grid cursor-pointer list-none gap-1 px-2 py-1.5 marker:hidden hover:bg-white",children:[i.jsxs("div",{className:"grid min-w-0 gap-1 sm:flex sm:items-center sm:justify-between sm:gap-2",children:[i.jsxs("div",{className:"flex min-w-0 items-center gap-1.5",children:[i.jsx(yn,{className:"h-3.5 w-3.5 shrink-0 text-muted transition-transform group-open:rotate-180","aria-hidden":!0}),i.jsx("span",{className:"truncate font-mono text-[11px] font-semibold text-muted",children:e}),n&&n>1?i.jsx("span",{className:"rounded-sm border border-border px-1 font-mono text-[10px] text-muted",children:n}):null]}),i.jsx("span",{className:"min-w-0 truncate pl-5 font-mono text-[11px] text-muted sm:shrink-0 sm:pl-0",children:t})]}),i.jsx("div",{className:"min-w-0 break-words pl-5 text-xs text-foreground [overflow-wrap:anywhere] sm:truncate",children:r})]}),i.jsx("div",{className:"min-w-0 border-t border-border bg-white px-2 py-2",children:a})]})}function oo({label:e,values:t}){const n=[{name:"median",seconds:(t==null?void 0:t.median)??0},{name:"p90",seconds:(t==null?void 0:t.p90)??0},{name:"p99",seconds:(t==null?void 0:t.p99)??0}];return i.jsx("div",{className:"h-56",children:i.jsx(rr,{width:"100%",height:"100%",children:i.jsxs(Fr,{data:n,children:[i.jsx(ir,{strokeDasharray:"3 3"}),i.jsx(sr,{dataKey:"name"}),i.jsx(ar,{tickFormatter:Qe}),i.jsx(or,{formatter:r=>[Qe(Number(r)),e]}),i.jsx(Vt,{dataKey:"seconds",fill:"#0969da",radius:[4,4,0,0]})]})})})}function _x({values:e,loading:t,totalJobs:n}){const r=Object.entries(e??{}).map(([s,a])=>({day:s,count:a}));return t&&r.length===0?i.jsx(Z,{text:"Loading job history..."}):r.length===0?i.jsx(Z,{text:n>0?"Job history has no valid creation dates.":"No job history available."}):i.jsx("div",{className:"h-56",children:i.jsx(rr,{width:"100%",height:"100%",children:i.jsxs(Fr,{data:r,children:[i.jsx(ir,{strokeDasharray:"3 3"}),i.jsx(sr,{dataKey:"day",minTickGap:16}),i.jsx(ar,{allowDecimals:!1}),i.jsx(or,{formatter:s=>[Number(s),"jobs"]}),i.jsx(Vt,{dataKey:"count",fill:"#16a34a",radius:[4,4,0,0]})]})})})}function Ex({usage:e,loading:t,totalJobs:n}){const[r,s]=T.useState("day"),a=(e==null?void 0:e[r])??[],o=a.map(c=>({...c,label:Px(c.bucket,r)})),l=a.reduce((c,d)=>c+d.seconds,0),u=a.reduce((c,d)=>c+(d.runs??d.jobs),0);return t&&o.length===0?i.jsx(Z,{text:"Loading runtime usage..."}):o.length===0?i.jsx(Z,{text:n>0?"No jobs have recorded runtime yet.":"No job history available."}):i.jsxs("div",{className:"grid gap-3",children:[i.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-muted",children:[i.jsx(ac,{className:"h-4 w-4","aria-hidden":!0}),i.jsxs("span",{children:[Cr(l)," consumed across ",u," run",u===1?"":"s"]})]}),i.jsx("div",{className:"inline-flex h-8 rounded-md border border-border bg-white p-0.5","aria-label":"Runtime grouping",children:["day","month"].map(c=>i.jsx("button",{className:V("rounded px-2.5 text-xs font-semibold capitalize",r===c?"bg-primary text-white":"text-muted hover:bg-slate-50"),type:"button",onClick:()=>s(c),children:c},c))})]}),i.jsx("div",{className:"h-64",children:i.jsx(rr,{width:"100%",height:"100%",children:i.jsxs(Fr,{data:o,children:[i.jsx(ir,{strokeDasharray:"3 3"}),i.jsx(sr,{dataKey:"label",minTickGap:16,tick:{fontSize:11}}),i.jsx(ar,{tickFormatter:c=>Cr(Number(c))}),i.jsx(ko,{}),i.jsx(or,{formatter:(c,d)=>d==="Work"?[Cr(Number(c)),"work"]:d==="Review"?[Cr(Number(c)),"review"]:[Number(c),String(d)],labelFormatter:c=>String(c)}),i.jsx(Vt,{dataKey:"work_seconds",name:"Work",stackId:"runtime",fill:"#0969da",isAnimationActive:!1}),i.jsx(Vt,{dataKey:"review_seconds",name:"Review",stackId:"runtime",fill:"#8250df",radius:[4,4,0,0],isAnimationActive:!1})]})})})]})}function Px(e,t){if(t==="month"){const[a,o]=e.split("-").map(Number);return!a||!o?e:new Intl.DateTimeFormat(void 0,{month:"short",year:"numeric"}).format(new Date(a,o-1,1))}const[n,r,s]=e.split("-").map(Number);return!n||!r||!s?e:new Intl.DateTimeFormat(void 0,{month:"short",day:"numeric"}).format(new Date(n,r-1,s))}function Cr(e){const t=Math.max(0,Math.round(e));if(t<60)return`${t}s`;const n=Math.round(t/60);if(n<60)return`${n}m`;const r=Math.floor(n/60),s=n%60;return s===0?`${r}h`:`${r}h ${s}m`}function lo(e){return Object.values(e).reduce((t,n)=>t+n,0)}function Rx({data:e,loading:t}){if(t&&!e)return i.jsx(Z,{text:"Loading systemd units..."});if(!e)return i.jsx(Z,{text:"No systemd snapshot available."});const n=e.units??[],r=n.filter(o=>o.active_state==="active").length,s=n.filter(o=>o.active_state==="failed"||o.result==="failed").length,a=n.filter(o=>o.kind==="timer").length;return i.jsxs("div",{className:"grid min-w-0 gap-3",children:[e.errors.length>0?i.jsx(Ne,{tone:"error",text:e.errors[0]}):null,n.length===0?i.jsx(Z,{text:"No configured bridge units found."}):i.jsxs(i.Fragment,{children:[i.jsxs("div",{className:"grid grid-cols-2 gap-2 sm:grid-cols-4",children:[i.jsx(_r,{label:"Units",value:String(n.length)}),i.jsx(_r,{label:"Active",value:String(r)}),i.jsx(_r,{label:"Failed",value:String(s),tone:s>0?"bad":"neutral"}),i.jsx(_r,{label:"Timers",value:String(a)})]}),i.jsxs("div",{className:"overflow-hidden rounded-md border border-border bg-white",children:[i.jsxs("div",{className:"hidden grid-cols-[minmax(0,1.35fr)_90px_120px_90px_minmax(0,1fr)] gap-3 border-b border-border bg-slate-50 px-3 py-2 text-[11px] font-semibold uppercase text-muted md:grid",children:[i.jsx("span",{children:"Unit"}),i.jsx("span",{children:"Status"}),i.jsx("span",{children:"State"}),i.jsx("span",{children:"PID"}),i.jsx("span",{children:"Schedule"})]}),i.jsx("div",{className:"divide-y divide-border",children:n.map(o=>i.jsx(Ix,{unit:o},o.unit))})]})]})]})}function _r({label:e,value:t,tone:n="neutral"}){return i.jsxs("div",{className:V("min-w-0 rounded-md border bg-white px-3 py-2",n==="bad"?"border-red-200":"border-border"),children:[i.jsx("div",{className:V("font-mono text-lg font-semibold",n==="bad"?"text-red-700":"text-foreground"),children:t}),i.jsx("div",{className:"mt-0.5 text-[11px] font-semibold uppercase text-muted",children:e})]})}function Ix({unit:e}){const[t,n]=T.useState(!1),r=e.active_state==="active",s=e.active_state==="failed"||e.result==="failed",a=e.next_elapse||e.last_trigger||"n/a";return i.jsxs("details",{className:"group min-w-0",open:t,onToggle:o=>n(o.currentTarget.open),children:[i.jsxs("summary",{className:"grid min-w-0 cursor-pointer list-none grid-cols-2 gap-2 px-3 py-3 text-sm marker:hidden hover:bg-slate-50 md:grid-cols-[minmax(0,1.35fr)_90px_120px_90px_minmax(0,1fr)] md:items-center md:gap-3",children:[i.jsxs("div",{className:"col-span-2 min-w-0 md:col-span-1",children:[i.jsxs("div",{className:"flex min-w-0 items-center gap-2",children:[i.jsx(yn,{className:"h-3.5 w-3.5 shrink-0 text-muted transition-transform group-open:rotate-180","aria-hidden":!0}),i.jsx("span",{className:"truncate font-mono text-xs font-semibold text-foreground",children:e.unit})]}),i.jsxs("div",{className:"mt-1 flex flex-wrap items-center gap-1.5 pl-5 text-[11px] font-semibold uppercase text-muted",children:[i.jsx("span",{children:e.role}),i.jsx("span",{children:e.kind}),i.jsx("span",{children:e.load_state}),i.jsx("span",{children:t?"following log":"expand log"})]})]}),i.jsxs("div",{className:"flex flex-wrap items-center gap-2 md:block",children:[i.jsx("span",{className:"text-[11px] font-semibold uppercase text-muted md:hidden",children:"Status"}),i.jsx("span",{className:V("inline-flex h-6 items-center rounded-full border px-2 text-xs font-semibold",s?"border-red-300 bg-red-50 text-red-700":r?"border-emerald-300 bg-emerald-50 text-emerald-700":"border-slate-300 bg-slate-50 text-slate-700"),children:e.active_state})]}),i.jsx(uo,{label:"State",value:e.sub_state,detail:e.result||"unknown"}),i.jsx(uo,{label:"PID",value:e.main_pid?String(e.main_pid):"n/a",detail:Qe(e.uptime_seconds)}),i.jsxs("div",{className:"col-span-2 min-w-0 md:col-span-1",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted md:hidden",children:"Schedule"}),i.jsx("div",{className:"truncate font-mono text-[11px] text-foreground",title:a,children:e.next_elapse?`next ${e.next_elapse}`:a})]})]}),i.jsx("div",{className:"border-t border-border bg-slate-50 px-3 pb-3 pt-2",children:i.jsx(Tx,{unit:e.unit,active:t})})]})}function uo({label:e,value:t,detail:n}){return i.jsxs("div",{className:"min-w-0",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted md:hidden",children:e}),i.jsx("div",{className:"truncate font-mono text-[11px] text-foreground",children:t}),n?i.jsx("div",{className:"truncate font-mono text-[11px] text-muted",children:n}):null]})}function Tx({unit:e,active:t}){const[n,r]=T.useState([]),[s,a]=T.useState(""),o=T.useRef(null);return T.useEffect(()=>{if(!t){r([]),a("");return}r([]),a("");const l=new EventSource(`/api/systemd/journal/stream?unit=${encodeURIComponent(e)}`);return l.addEventListener("journal_line",u=>{const c=Dr(u);c&&r(d=>[...d.slice(-199),c])}),l.addEventListener("journal_error",u=>{const c=Dr(u);a((c==null?void 0:c.error)??"journal stream failed")}),l.onerror=()=>{},()=>l.close()},[t,e]),T.useEffect(()=>{const l=o.current;l&&(l.scrollTop=l.scrollHeight)},[n]),i.jsxs("div",{className:"grid min-w-0 gap-2",children:[i.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted",children:"Live journal"}),i.jsx("div",{className:"mt-0.5 truncate font-mono text-[11px] text-muted",children:n.length>0?`${n.length} lines streamed`:"waiting for journal output"})]}),i.jsx("div",{className:"font-mono text-[11px] text-muted",children:"journalctl -f"})]}),s?i.jsx(Ne,{tone:"error",text:s}):null,i.jsx("div",{ref:o,className:"h-72 overflow-auto rounded-md border border-slate-800 bg-slate-950 p-3 font-mono text-xs leading-relaxed text-slate-100",children:n.length>0?n.map((l,u)=>i.jsx("div",{className:"break-words [overflow-wrap:anywhere]",children:l.line},`${l.unit}-${u}`)):i.jsx("div",{className:"text-slate-400",children:"No journal lines received yet."})})]})}function Ax({data:e,loading:t}){var h,m,p,y,w,j;if(t&&!e)return i.jsx(Z,{text:"Loading process activity..."});if(!e)return i.jsx(Z,{text:"No process snapshot available."});const n=e.executor.children??[],r=n.flatMap(b=>Vl(b)),s=r.reduce((b,S)=>b+S.cpu_ticks,0),a=r.reduce((b,S)=>b+Mx(S),0),o=e.executor.service==="active",l=r.slice(0,8).map(b=>({label:`pid ${b.pid}`,ticks:b.cpu_ticks})),u=(e.samples??[]).map(b=>({label:Hn(b.ts),ticks:b.cpu_ticks,io:b.io_bytes,active:b.active_since_last_sample?"active":"quiet"})),c=u.length>0?u:l,d=(h=e.samples)==null?void 0:h[e.samples.length-1];return i.jsxs("div",{className:"grid gap-4",children:[i.jsx("div",{className:"rounded-md border border-slate-200 bg-slate-50 p-3",children:i.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[i.jsxs("div",{className:"min-w-0",children:[i.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[i.jsx("span",{className:V("inline-flex h-6 items-center rounded-full border px-2 text-xs font-semibold",o?"border-emerald-300 bg-emerald-50 text-emerald-700":"border-slate-300 bg-white text-slate-600"),children:o?"active":"idle"}),i.jsxs("span",{className:"font-mono text-xs text-muted",children:["service ",e.executor.service]})]}),i.jsx("div",{className:"mt-2 text-sm font-semibold text-foreground",children:e.running_jobs.length>0?`${e.running_jobs.length} running job${e.running_jobs.length===1?"":"s"}`:"No running jobs"}),e.running_jobs.length>0?i.jsx("div",{className:"mt-2 flex flex-wrap gap-1.5",children:e.running_jobs.slice(0,4).map(b=>i.jsxs("span",{className:"inline-flex min-h-6 items-center gap-1.5 rounded-full border border-blue-200 bg-white px-2 font-mono text-[11px] font-semibold text-blue-700",children:[i.jsx("span",{className:"h-2 w-2 rounded-full bg-blue-600 animate-live-pulse","aria-hidden":!0}),"#",b.id," ",Qe(b.age_seconds)]},b.id))}):null,d?i.jsxs("p",{className:"mt-1 text-xs text-muted",children:["Last persisted sample ",Hn(d.ts)," · ",d.active_since_last_sample?"activity observed":`quiet ${Qe(d.idle_seconds)}`]}):null,i.jsx("p",{className:"mt-1 text-xs text-muted",children:e.detail})]}),i.jsxs("div",{className:"grid min-w-[190px] grid-cols-3 gap-2 text-center text-xs",children:[i.jsx(oi,{label:"PID",value:e.executor.pid?String(e.executor.pid):"n/a"}),i.jsx(oi,{label:"Children",value:String(r.length)}),i.jsx(oi,{label:"CPU ticks",value:String(s)})]})]})}),i.jsxs("div",{className:"grid gap-2 sm:grid-cols-2",children:[i.jsx(Er,{label:"Live process",value:((m=e.signals)==null?void 0:m.live_process.state)??(r.length>0?"live":"no_child_process"),detail:`${((p=e.signals)==null?void 0:p.live_process.child_count)??r.length} children`}),i.jsx(Er,{label:"Process activity",value:((y=e.signals)==null?void 0:y.process_activity.state)??(d!=null&&d.active_since_last_sample?"active":"quiet"),detail:d?`sample ${Hn(d.ts)}`:"no sample"}),i.jsx(Er,{label:"Semantic progress",value:(w=e.signals)!=null&&w.semantic_progress.length?"recent":"none",detail:co(e.running_jobs,"semantic_progress")}),i.jsx(Er,{label:"Visible progress",value:(j=e.signals)!=null&&j.visible_progress.length?"streaming":"none",detail:co(e.running_jobs,"visible_progress")})]}),e.alerts.length>0?i.jsx(Ne,{tone:"error",text:e.alerts[0]}):null,i.jsxs("div",{className:"grid gap-4 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]",children:[i.jsxs("div",{className:"min-w-0 rounded-md border border-border p-3",children:[i.jsxs("div",{className:"mb-3 flex items-center justify-between gap-3",children:[i.jsxs("h3",{className:"flex items-center gap-2 text-sm font-semibold",children:[i.jsx(ma,{className:"h-4 w-4","aria-hidden":!0}),u.length>0?"CPU history":"CPU ticks"]}),i.jsxs("span",{className:"font-mono text-xs text-muted",children:[Wl(a)," I/O"]})]}),c.length>0?i.jsx("div",{className:"h-40",children:i.jsx(rr,{width:"100%",height:"100%",children:i.jsxs(ou,{data:c,children:[i.jsx(ir,{strokeDasharray:"3 3"}),i.jsx(sr,{dataKey:"label",tick:!1}),i.jsx(ar,{allowDecimals:!1,tick:{fontSize:11}}),i.jsx(or,{formatter:b=>[Number(b),"cpu ticks"]}),i.jsx(lu,{type:"monotone",dataKey:"ticks",stroke:"#0f766e",strokeWidth:2,dot:{r:3},activeDot:{r:5},isAnimationActive:!1})]})})}):i.jsx(Z,{text:"No executor CPU samples available."})]}),i.jsxs("div",{className:"min-w-0",children:[i.jsxs("h3",{className:"mb-2 flex items-center gap-2 text-sm font-semibold",children:[i.jsx(ma,{className:"h-4 w-4","aria-hidden":!0}),"Executor children"]}),n.length>0?i.jsx("div",{className:"grid gap-2",children:n.map(b=>i.jsx(Ql,{process:b},b.pid))}):i.jsx(Z,{text:"No child process detected for the executor."})]})]})]})}function Lx({alerts:e,loading:t,now:n}){if(t&&!e)return i.jsx(Z,{text:"Loading monitor alerts..."});const r=e??[];return r.length===0?i.jsx(Z,{text:"No active monitor alerts."}):i.jsx("div",{className:"grid gap-2",children:r.slice(0,5).map(s=>i.jsxs("div",{className:"rounded-md border border-red-200 bg-red-50 p-2.5",children:[i.jsxs("div",{className:"flex flex-wrap items-center gap-2 text-xs font-semibold text-red-700",children:[i.jsx(zr,{className:"h-3.5 w-3.5","aria-hidden":!0}),i.jsx("span",{children:s.severity}),i.jsx("span",{className:"font-normal text-red-600",children:Ll(s.last_seen,n)}),s.observations>1?i.jsxs("span",{className:"rounded-full border border-red-200 bg-white px-1.5",children:[s.observations,"x"]}):null]}),i.jsx("p",{className:"mt-1 break-words text-sm font-medium text-red-950 [overflow-wrap:anywhere]",children:s.message})]},s.fingerprint))})}function Ql({process:e}){var r,s;const t=((r=e.io_bytes)==null?void 0:r.read_bytes)??0,n=((s=e.io_bytes)==null?void 0:s.write_bytes)??0;return i.jsxs("div",{className:"rounded-md border border-border bg-white p-2.5",children:[i.jsxs("div",{className:"flex flex-wrap items-center gap-2 text-sm",children:[i.jsxs("span",{className:"font-mono",children:["pid ",e.pid]}),i.jsxs("span",{className:"rounded-full border border-border px-2 text-xs text-muted",children:["state ",e.state]}),i.jsxs("span",{className:"rounded-full border border-border px-2 text-xs text-muted",children:["cpu ",e.cpu_ticks]}),i.jsxs("span",{className:"rounded-full border border-border px-2 text-xs text-muted",children:["I/O ",Wl(t+n)]})]}),i.jsx("div",{className:"mt-2 break-words font-mono text-xs text-muted",children:e.cmd||"unknown command"}),e.children&&e.children.length>0?i.jsx("div",{className:"mt-3 border-l-2 border-border pl-3",children:e.children.map(a=>i.jsx(Ql,{process:a},a.pid))}):null]})}function oi({label:e,value:t}){return i.jsxs("div",{className:"rounded-md border border-border bg-white px-2 py-2",children:[i.jsx("div",{className:"font-mono text-sm font-semibold text-foreground",children:t}),i.jsx("div",{className:"mt-0.5 text-[11px] font-semibold uppercase text-muted",children:e})]})}function Er({label:e,value:t,detail:n}){return i.jsxs("div",{className:"min-w-0 rounded-md border border-border bg-white p-2.5",children:[i.jsx("div",{className:"text-[11px] font-semibold uppercase text-muted",children:e}),i.jsx("div",{className:"mt-1 truncate text-sm font-semibold text-foreground",children:t}),i.jsx("div",{className:"mt-1 truncate font-mono text-[11px] text-muted",children:n})]})}function co(e,t){const n=e.find(s=>s[t]),r=n==null?void 0:n[t];return!n||!r?"no running heartbeat":`#${n.id} ${r.phase} ${Qe(r.age_seconds??null)}`}function Vl(e){return[e,...(e.children??[]).flatMap(t=>Vl(t))]}function Mx(e){var t,n;return(((t=e.io_bytes)==null?void 0:t.read_bytes)??0)+(((n=e.io_bytes)==null?void 0:n.write_bytes)??0)}function Wl(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:`${(e/(1024*1024)).toFixed(1)} MiB`}function J({label:e,value:t}){return i.jsxs("div",{className:"min-w-0 rounded-md border border-border p-3",children:[i.jsx("div",{className:"text-xs font-semibold text-muted",children:e}),i.jsx("div",{className:"mt-1 min-w-0 break-words text-sm [overflow-wrap:anywhere]",children:t})]})}function us({status:e}){const t=yf(e),n=e==="running"||e==="pending";return i.jsxs("span",{className:V("inline-flex min-h-6 items-center gap-1.5 rounded-full border px-2 text-xs font-semibold",t.badge),children:[i.jsx("span",{className:V("h-2.5 w-2.5 rounded-full",t.dot,n&&"animate-live-pulse"),"aria-hidden":!0}),e]})}function Z({text:e}){return i.jsx("div",{className:"rounded-md border border-dashed border-border p-6 text-center text-sm text-muted",children:e})}function Ne({tone:e,text:t}){return i.jsx("div",{className:V("rounded-md border p-3 text-sm",e==="error"&&"border-red-300 bg-red-50 text-red-700",e==="warning"&&"border-amber-300 bg-amber-50 text-amber-800"),children:t})}function lt({onClick:e,compactOnMobile:t=!1}){return i.jsxs("button",{className:V("inline-flex h-8 items-center justify-center gap-2 rounded-md border border-border text-sm font-semibold text-foreground hover:bg-slate-50",t?"w-8 px-0 sm:w-auto sm:px-3":"px-3"),onClick:e,type:"button","aria-label":"Refresh",children:[i.jsx(ur,{className:"h-4 w-4","aria-hidden":!0}),i.jsx("span",{className:V(t&&"hidden sm:inline"),children:"Refresh"})]})}const ho=document.getElementById("root");ho&&pu.createRoot(ho).render(i.jsx(T.StrictMode,{children:i.jsx(Bu,{client:lf,children:i.jsx(Ff,{})})})); diff --git a/src/github_agent_bridge/dashboard_static/index.html b/src/github_agent_bridge/dashboard_static/index.html index 854314e..14257f5 100644 --- a/src/github_agent_bridge/dashboard_static/index.html +++ b/src/github_agent_bridge/dashboard_static/index.html @@ -4,7 +4,7 @@ GitHub Agent Bridge Dashboard - + diff --git a/src/github_agent_bridge/queue.py b/src/github_agent_bridge/queue.py index 8df5189..32910ef 100644 --- a/src/github_agent_bridge/queue.py +++ b/src/github_agent_bridge/queue.py @@ -99,6 +99,7 @@ def connect(self) -> sqlite3.Connection: con.executescript(SCHEMA) self._ensure_columns(con) self._ensure_indexes(con) + self._backfill_job_runs(con) return con def init(self) -> None: @@ -106,6 +107,7 @@ def init(self) -> None: con.executescript(SCHEMA) self._ensure_columns(con) self._ensure_indexes(con) + self._backfill_job_runs(con) def enqueue(self, n: Notification, policy: Policy) -> tuple[Job | None, str]: """Backward-compatible email enqueue entrypoint.""" @@ -367,10 +369,16 @@ def claim_next(self, worker_id: str, work_intents: frozenset[str] | set[str] | N metadata["openclaw_session_id"] = session_id_for_job_attempt(int(row["id"]), int(row["attempts"]) + 1) else: metadata.setdefault("openclaw_session_id", session_id_for_job(int(row["id"]))) + attempt = int(row["attempts"]) + 1 con.execute( - "UPDATE jobs SET status='running', locked_by=?, attempts=attempts+1, started_at=?, updated_at=?, metadata_json=? WHERE id=?", + "UPDATE jobs SET status='running', locked_by=?, attempts=attempts+1, started_at=?, finished_at=NULL, updated_at=?, metadata_json=? WHERE id=?", (worker_id, now, now, json.dumps(metadata, sort_keys=True), row["id"]), ) + con.execute( + """INSERT INTO job_runs(job_id,attempt,started_at,worker_id,session_id) + VALUES(?,?,?,?,?)""", + (row["id"], attempt, now, worker_id, metadata["openclaw_session_id"]), + ) self._log(con, row["id"], row["work_key"], "running", f"claimed by {worker_id}", None) self._session_event(con, row["id"], row["work_key"], metadata["openclaw_session_id"], "claimed", f"claimed by {worker_id}", None) self._progress(con, row["id"], row["work_key"], "semantic", "claimed", f"claimed by {worker_id}", None) @@ -454,6 +462,7 @@ def mark_runtime_process_exited(self, job_id: int, worker_id: str) -> bool: def finish(self, job_id: int, status: str, summary: str, detail: str | None = None) -> None: now = utc_now() with self.connect() as con: + con.execute("BEGIN IMMEDIATE") row = con.execute("SELECT work_key FROM jobs WHERE id=?", (job_id,)).fetchone() metadata = self._job_metadata(con, job_id) cancellation = metadata.get("cancellation") @@ -470,10 +479,13 @@ def finish(self, job_id: int, status: str, summary: str, detail: str | None = No "UPDATE jobs SET status=?, last_error=?, locked_by=NULL, finished_at=?, updated_at=? WHERE id=?", (status, detail if status == "blocked" else None, now, now, job_id), ) + run_result = "cancelled" if isinstance(cancellation, dict) and cancellation.get("state") in {"requested", "cancelled"} else status + self._finish_run(con, job_id, run_result, now) self._log(con, job_id, row["work_key"] if row else None, status, summary, detail) session_id = metadata.get("openclaw_session_id") or session_id_for_job(job_id) self._session_event(con, job_id, row["work_key"] if row else None, str(session_id), status, summary, detail) self._progress(con, job_id, row["work_key"] if row else None, "semantic", status, summary, detail) + con.commit() def request_cancel_running( self, @@ -574,6 +586,7 @@ def mark_cancelled( if not cur.rowcount: con.commit() return None + self._finish_run(con, job_id, "cancelled", now) self._log(con, job_id, row["work_key"], "cancelled", summary, detail) session_id = str(metadata.get("openclaw_session_id") or session_id_for_job(job_id)) self._session_event(con, job_id, row["work_key"], session_id, "cancelled", summary, detail) @@ -591,21 +604,25 @@ def requeue_running( ) -> bool: now = utc_now() with self.connect() as con: + con.execute("BEGIN IMMEDIATE") row = con.execute( "SELECT work_key, metadata_json FROM jobs WHERE id=? AND status='running'", (job_id,), ).fetchone() if row is None: + con.commit() return False metadata = json.loads(row["metadata_json"] or "{}") if fresh_session: metadata["fresh_session_on_retry"] = True cur = con.execute( - "UPDATE jobs SET status='pending', locked_by=NULL, last_error=NULL, updated_at=?, metadata_json=? WHERE id=? AND status='running'", - (now, json.dumps(metadata, sort_keys=True), job_id), + "UPDATE jobs SET status='pending', locked_by=NULL, last_error=NULL, finished_at=?, updated_at=?, metadata_json=? WHERE id=? AND status='running'", + (now, now, json.dumps(metadata, sort_keys=True), job_id), ) if cur.rowcount: + self._finish_run(con, job_id, "requeued", now) self._log(con, job_id, row["work_key"], "retry", summary, detail) + con.commit() return bool(cur.rowcount) def block_running( @@ -658,6 +675,7 @@ def block_running( continue job_id = int(row["id"]) blocked_ids.append(job_id) + self._finish_run(con, job_id, "blocked", now) self._log(con, job_id, row["work_key"], "blocked", summary, detail) metadata = json.loads(row["metadata_json"] or "{}") session_id = str(metadata.get("openclaw_session_id") or session_id_for_job(job_id)) @@ -724,17 +742,22 @@ def dismiss(self, job_id: int, reason: str) -> bool: def unlock_stale(self, older_than_seconds: int, job_ids: list[int] | None = None) -> int: with self.connect() as con: + con.execute("BEGIN IMMEDIATE") args: list[object] = [older_than_seconds] sql = "SELECT id, work_key FROM jobs WHERE status='running' AND started_at IS NOT NULL AND (julianday('now') - julianday(started_at)) * 86400 > ?" if job_ids is not None: if not job_ids: + con.commit() return 0 sql += f" AND id IN ({','.join('?' for _ in job_ids)})" args.extend(job_ids) rows = con.execute(sql, args).fetchall() + now = utc_now() for row in rows: - con.execute("UPDATE jobs SET status='pending', locked_by=NULL, updated_at=? WHERE id=?", (utc_now(), row["id"])) + con.execute("UPDATE jobs SET status='pending', locked_by=NULL, finished_at=?, updated_at=? WHERE id=?", (now, now, row["id"])) + self._finish_run(con, int(row["id"]), "requeued", now) self._log(con, row["id"], row["work_key"], "unlock_stale", f"running job older than {older_than_seconds}s requeued", None) + con.commit() return len(rows) def get(self, job_id: int) -> Job | None: @@ -796,6 +819,55 @@ def _job_metadata(self, con: sqlite3.Connection, job_id: int) -> dict[str, objec return {} return json.loads(row["metadata_json"] or "{}") + def _finish_run(self, con: sqlite3.Connection, job_id: int, result: str, finished_at: str) -> None: + con.execute( + """UPDATE job_runs + SET finished_at=?, result=? + WHERE id=( + SELECT id FROM job_runs + WHERE job_id=? AND finished_at IS NULL + ORDER BY attempt DESC LIMIT 1 + ) AND finished_at IS NULL""", + (finished_at, result, job_id), + ) + + def _backfill_job_runs(self, con: sqlite3.Connection) -> None: + """Preserve the one historical interval recoverable from legacy jobs.""" + if con.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='job_runs'").fetchone() is None: + return + rows = con.execute( + """SELECT id, attempts, started_at, finished_at, locked_by, metadata_json + FROM jobs + WHERE started_at IS NOT NULL + AND finished_at IS NOT NULL + AND julianday(finished_at) >= julianday(started_at) + AND NOT EXISTS (SELECT 1 FROM job_runs WHERE job_runs.job_id=jobs.id)""" + ).fetchall() + for row in rows: + try: + metadata = json.loads(row["metadata_json"] or "{}") + except (json.JSONDecodeError, TypeError): + metadata = {} + attempt = max(1, int(row["attempts"] or 0)) + session_id = str( + metadata.get("openclaw_session_id") + or session_id_for_job_attempt(int(row["id"]), attempt) + ) + con.execute( + """INSERT OR IGNORE INTO job_runs( + job_id,attempt,started_at,finished_at,result,worker_id,session_id,is_estimated + ) VALUES(?,?,?,?,?,?,?,1)""", + ( + row["id"], + attempt, + row["started_at"], + row["finished_at"], + "historical", + row["locked_by"], + session_id, + ), + ) + def _ensure_columns(self, con: sqlite3.Connection) -> None: tables = { "jobs": {"trigger_actor": "TEXT", "trigger_actor_avatar_url": "TEXT"}, diff --git a/src/github_agent_bridge/sql/schema.sql b/src/github_agent_bridge/sql/schema.sql index e0195b6..b725df5 100644 --- a/src/github_agent_bridge/sql/schema.sql +++ b/src/github_agent_bridge/sql/schema.sql @@ -39,6 +39,24 @@ CREATE INDEX IF NOT EXISTS idx_jobs_dashboard_order ON jobs( COALESCE(finished_at, started_at, updated_at, created_at) DESC, id DESC ); +CREATE TABLE IF NOT EXISTS job_runs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + job_id INTEGER NOT NULL REFERENCES jobs(id) ON DELETE CASCADE, + attempt INTEGER NOT NULL CHECK(attempt > 0), + started_at TEXT NOT NULL, + finished_at TEXT, + result TEXT CHECK(result IN ('done','blocked','requeued','cancelled','historical')), + worker_id TEXT, + session_id TEXT NOT NULL, + is_estimated INTEGER NOT NULL DEFAULT 0 CHECK(is_estimated IN (0, 1)), + CHECK( + (finished_at IS NULL AND result IS NULL) + OR (finished_at IS NOT NULL AND result IS NOT NULL) + ), + UNIQUE(job_id, attempt) +); +CREATE UNIQUE INDEX IF NOT EXISTS idx_job_runs_open ON job_runs(job_id) WHERE finished_at IS NULL; +CREATE INDEX IF NOT EXISTS idx_job_runs_finished ON job_runs(finished_at); CREATE TABLE IF NOT EXISTS ingest_receipts ( id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL, diff --git a/tests/test_backend.py b/tests/test_backend.py index 241112d..2e75559 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -660,6 +660,7 @@ def test_dashboard_exposes_job_detail_logs_and_metrics(tmp_path): } } con.execute("UPDATE jobs SET metadata_json=?, work_intent=? WHERE id=?", (json.dumps(metadata), "work_allowed", job.id)) + assert q.claim_next("worker-1").id == job.id q.finish(job.id, "done", "completed") detail = get_job_detail(db, job.id) @@ -671,6 +672,8 @@ def test_dashboard_exposes_job_detail_logs_and_metrics(tmp_path): assert detail["intent_classifier"]["llm"]["write_permission"] == "state_change_allowed" assert detail["intent_classifier"]["llm"]["scope"] == "Update the PR tests." assert detail["worklog"][0]["phase"] == "queued" + assert detail["runs"][0]["result"] == "done" + assert detail["runs"][0]["worker_id"] == "worker-1" assert metrics["status_counts"]["done"] == 1 assert list(metrics["by_created_day"].values()) == [1] assert client.get(f"/api/jobs/{job.id}/logs").json()["logs"][-1]["phase"] == "done" @@ -690,28 +693,20 @@ def test_dashboard_metrics_groups_runtime_usage_by_requested_timezone(tmp_path): notif(uid=4, mid="<4@github.com>", body="@pilipilisbot https://github.com/gisce/erp/pull/4#issuecomment-40"), Policy(trusted_orgs=["gisce"]), ) - q.finish(first.id, "done", "completed") - q.finish(second.id, "done", "completed") - q.finish(missing_finish.id, "done", "completed") - q.finish(invalid_finish.id, "done", "completed") with q.connect() as con: - con.execute( - "UPDATE jobs SET started_at=?, finished_at=? WHERE id=?", - ("2026-06-01T23:30:00Z", "2026-06-02T00:30:00Z", first.id), - ) con.execute("UPDATE jobs SET work_intent=? WHERE id=?", ("work_allowed", first.id)) - con.execute( - "UPDATE jobs SET started_at=?, finished_at=? WHERE id=?", - ("2026-06-02T10:00:00Z", "2026-06-02T10:30:00Z", second.id), - ) con.execute("UPDATE jobs SET work_intent=? WHERE id=?", ("review_only", second.id)) - con.execute( - "UPDATE jobs SET started_at=?, finished_at=NULL WHERE id=?", - ("2026-06-02T11:00:00Z", missing_finish.id), - ) - con.execute( - "UPDATE jobs SET started_at=?, finished_at=? WHERE id=?", - ("2026-06-02T12:00:00Z", "n/a", invalid_finish.id), + con.executemany( + """INSERT INTO job_runs( + job_id,attempt,started_at,finished_at,result,worker_id,session_id + ) VALUES(?,?,?,?,?,?,?)""", + [ + (first.id, 1, "2026-06-01T03:20:00Z", "2026-06-01T03:50:00Z", "requeued", "worker-1", "run-1"), + (first.id, 2, "2026-06-01T03:55:00Z", "2026-06-01T04:35:00Z", "done", "worker-1", "run-2"), + (second.id, 1, "2026-07-01T03:30:00Z", "2026-07-01T04:30:00Z", "done", "worker-2", "run-3"), + (missing_finish.id, 1, "2026-06-02T11:00:00Z", None, None, "worker-3", "run-4"), + (invalid_finish.id, 1, "2026-06-02T12:00:00Z", "n/a", "done", "worker-4", "run-5"), + ], ) metrics = metrics_summary(db, timezone_name="America/New_York") @@ -719,43 +714,104 @@ def test_dashboard_metrics_groups_runtime_usage_by_requested_timezone(tmp_path): payload = client.get("/api/metrics/summary", params={"timezone": "America/New_York"}).json()["metrics"] assert metrics["runtime_usage"]["day"] == [ + { + "bucket": "2026-05-31", + "seconds": 1800, + "minutes": 30.0, + "runs": 1, + "jobs": 1, + "work_seconds": 1800, + "review_seconds": 0, + "work_runs": 1, + "review_runs": 0, + "work_jobs": 1, + "review_jobs": 0, + }, { "bucket": "2026-06-01", - "seconds": 3600, - "minutes": 60.0, + "seconds": 2400, + "minutes": 40.0, + "runs": 1, "jobs": 1, - "work_seconds": 3600, + "work_seconds": 2400, "review_seconds": 0, + "work_runs": 1, + "review_runs": 0, "work_jobs": 1, "review_jobs": 0, }, { - "bucket": "2026-06-02", - "seconds": 1800, - "minutes": 30.0, + "bucket": "2026-06-30", + "seconds": 3600, + "minutes": 60.0, + "runs": 1, "jobs": 1, "work_seconds": 0, - "review_seconds": 1800, + "review_seconds": 3600, + "work_runs": 0, + "review_runs": 1, "work_jobs": 0, "review_jobs": 1, }, ] assert metrics["runtime_usage"]["month"] == [ + { + "bucket": "2026-05", + "seconds": 1800, + "minutes": 30.0, + "runs": 1, + "jobs": 1, + "work_seconds": 1800, + "review_seconds": 0, + "work_runs": 1, + "review_runs": 0, + "work_jobs": 1, + "review_jobs": 0, + }, { "bucket": "2026-06", - "seconds": 5400, - "minutes": 90.0, + "seconds": 6000, + "minutes": 100.0, + "runs": 2, "jobs": 2, - "work_seconds": 3600, - "review_seconds": 1800, + "work_seconds": 2400, + "review_seconds": 3600, + "work_runs": 1, + "review_runs": 1, "work_jobs": 1, "review_jobs": 1, }, ] - assert metrics["runtime_seconds"] == {"median": 1800, "p90": 3600, "p99": 3600} + assert metrics["runtime_seconds"] == {"median": 2400, "p90": 3600, "p99": 3600} assert payload["runtime_usage"] == metrics["runtime_usage"] +def test_dashboard_runtime_uses_elapsed_time_across_dst_fallback(tmp_path): + db = tmp_path / "bridge.sqlite3" + q = JobQueue(db) + job, _ = q.enqueue(notif(), Policy(trusted_orgs=["gisce"])) + with q.connect() as con: + con.execute( + """INSERT INTO job_runs( + job_id,attempt,started_at,finished_at,result,worker_id,session_id + ) VALUES(?,?,?,?,?,?,?)""", + ( + job.id, + 1, + "2026-11-01T05:30:00Z", + "2026-11-01T07:30:00Z", + "done", + "worker-1", + "dst-run", + ), + ) + + metrics = metrics_summary(db, timezone_name="America/New_York") + + assert metrics["runtime_usage"]["day"][0]["bucket"] == "2026-11-01" + assert metrics["runtime_usage"]["day"][0]["seconds"] == 7200 + + def test_dashboard_exposes_safe_openclaw_session_correlation(tmp_path): db = tmp_path / "bridge.sqlite3" q = JobQueue(db) diff --git a/tests/test_packaged_resources.py b/tests/test_packaged_resources.py index 5e0f532..1186f46 100644 --- a/tests/test_packaged_resources.py +++ b/tests/test_packaged_resources.py @@ -14,6 +14,7 @@ def test_sql_schema_is_packaged_resource_and_valid(): tables = {row[0] for row in con.execute("SELECT name FROM sqlite_master WHERE type='table'")} assert { "jobs", + "job_runs", "coalesced_notifications", "state", "worklog", diff --git a/tests/test_queue.py b/tests/test_queue.py index 8c538ed..5069dee 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -405,6 +405,88 @@ def test_claim_can_filter_by_work_intent(tmp_path): assert claimed.work_intent == "review_only" +def test_job_runs_preserve_each_attempt_across_requeue(tmp_path): + q = JobQueue(tmp_path / "q.sqlite3") + job, _ = q.enqueue(notif(1, "<1@github.com>", BODY1), policy()) + q.update_work_intent(job.id, "work_allowed", "implementation request") + + first = q.claim_next("worker-1") + assert first is not None + assert q.requeue_running(first.id, "transient failure") is True + second = q.claim_next("worker-2") + assert second is not None + q.finish(second.id, "done", "completed") + + with q.connect() as con: + runs = con.execute( + "SELECT * FROM job_runs WHERE job_id=? ORDER BY attempt", + (job.id,), + ).fetchall() + + assert [run["attempt"] for run in runs] == [1, 2] + assert [run["result"] for run in runs] == ["requeued", "done"] + assert [run["worker_id"] for run in runs] == ["worker-1", "worker-2"] + assert all(run["started_at"] for run in runs) + assert all(run["finished_at"] for run in runs) + assert [run["session_id"] for run in runs] == [ + f"github-agent-bridge-job-{job.id}-attempt-1", + f"github-agent-bridge-job-{job.id}-attempt-2", + ] + + +def test_block_and_cancel_close_active_job_runs(tmp_path): + q = JobQueue(tmp_path / "q.sqlite3") + blocked, _ = q.enqueue(notif(1, "<1@github.com>", BODY1), policy()) + cancelled, _ = q.enqueue(notif(2, "<2@github.com>", BODY_OTHER), policy()) + + assert q.claim_next("worker-1").id == blocked.id + assert q.block_running("executor stopped", "shutdown", job_ids=[blocked.id]) == [blocked.id] + assert q.claim_next("worker-2").id == cancelled.id + assert q.mark_cancelled(cancelled.id, actor="ecarreras", reason="obsolete") is not None + + with q.connect() as con: + results = dict( + con.execute( + "SELECT job_id, result FROM job_runs WHERE job_id IN (?, ?)", + (blocked.id, cancelled.id), + ).fetchall() + ) + + assert results == {blocked.id: "blocked", cancelled.id: "cancelled"} + + +def test_init_backfills_only_the_known_legacy_interval_as_estimated(tmp_path): + db = tmp_path / "q.sqlite3" + q = JobQueue(db) + job, _ = q.enqueue(notif(1, "<1@github.com>", BODY1), policy()) + with q.connect() as con: + con.execute("DROP TABLE job_runs") + con.execute( + """UPDATE jobs + SET attempts=3, started_at=?, finished_at=?, metadata_json=? + WHERE id=?""", + ( + "2026-09-01T10:00:00Z", + "2026-09-01T10:30:00Z", + '{"openclaw_session_id":"legacy-session"}', + job.id, + ), + ) + + JobQueue(db) + JobQueue(db) + + with sqlite3.connect(db) as con: + con.row_factory = sqlite3.Row + runs = con.execute("SELECT * FROM job_runs WHERE job_id=?", (job.id,)).fetchall() + + assert len(runs) == 1 + assert runs[0]["attempt"] == 3 + assert runs[0]["result"] == "historical" + assert runs[0]["session_id"] == "legacy-session" + assert runs[0]["is_estimated"] == 1 + + def test_cancel_running_records_actor_reason_and_finish_preserves_cancellation(tmp_path): q = JobQueue(tmp_path / "q.sqlite3") job, _ = q.enqueue(notif(1, "<1@github.com>", BODY1), policy()) @@ -728,6 +810,11 @@ def test_unlock_stale_can_limit_to_selected_running_jobs(tmp_path): assert q.get(job1.id).status == "running" assert q.get(job2.id).status == "pending" + with q.connect() as con: + assert con.execute( + "SELECT result FROM job_runs WHERE job_id=?", + (job2.id,), + ).fetchone()["result"] == "requeued" def test_block_running_can_limit_jobs_and_never_requeues(tmp_path):