From 308e52db721426eb91d934d24cb50de6a326c1f9 Mon Sep 17 00:00:00 2001 From: Aaron Elijah Mars <61592645+aaronjmars@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:42:49 -0400 Subject: [PATCH] fix(aeon): pin runs to Skill Runner (aeon.yml) and fix Source dropdown clipping - github-runs now reads the per-workflow endpoint (/actions/workflows/aeon.yml/ runs) instead of the repo-wide list, so it shows only Skill Runner runs. The scheduler/messages workflows fire far more often and would otherwise bury skill runs off the first page. Drops the now-unused "Workflow" config field. - Source select: make the trigger w-full (the popup is w-(--anchor-width), so a w-fit trigger clipped long items) and shorten the labels. --- lib/columns/plugins/aeon/client.tsx | 52 ++++------- lib/columns/plugins/aeon/plugin.ts | 6 +- lib/columns/plugins/aeon/server.ts | 9 +- lib/integrations/aeon.ts | 138 +++++++++++++++++++++------- 4 files changed, 127 insertions(+), 78 deletions(-) diff --git a/lib/columns/plugins/aeon/client.tsx b/lib/columns/plugins/aeon/client.tsx index 940d45c..17972f9 100644 --- a/lib/columns/plugins/aeon/client.tsx +++ b/lib/columns/plugins/aeon/client.tsx @@ -39,10 +39,10 @@ import { // ---- Config form ---------------------------------------------------------- const SOURCE_LABELS: Record = { - "github-runs": "Workflow runs (GitHub)", + "github-runs": "Skill runs (GitHub)", "github-articles": "Articles (GitHub)", - "dashboard-outputs": "Feed cards (dashboard)", - "dashboard-runs": "Runs (dashboard)", + "dashboard-outputs": "Feed cards (local)", + "dashboard-runs": "Runs (local)", }; function ConfigForm({ value, onChange }: ConfigFormProps) { @@ -58,7 +58,7 @@ function ConfigForm({ value, onChange }: ConfigFormProps) { onChange({ ...value, source: v as AeonConfig["source"] }) } > - + @@ -84,37 +84,19 @@ function ConfigForm({ value, onChange }: ConfigFormProps) { {isGithub ? ( - <> -
- - onChange({ ...value, repo: e.target.value })} - /> -

- owner/repo of your Aeon fork. -

-
- {value.source === "github-runs" && ( -
- - - onChange({ ...value, workflow: e.target.value }) - } - /> -

- Filter to one workflow — aeon.yml is the skill - runner. Empty = every workflow. -

-
- )} - +
+ + onChange({ ...value, repo: e.target.value })} + /> +

+ owner/repo of your Aeon fork. + {value.source === "github-runs" && " Shows Skill Runner (aeon.yml) runs."} +

+
) : ( <>
diff --git a/lib/columns/plugins/aeon/plugin.ts b/lib/columns/plugins/aeon/plugin.ts index 9bf6ad4..7df2e63 100644 --- a/lib/columns/plugins/aeon/plugin.ts +++ b/lib/columns/plugins/aeon/plugin.ts @@ -8,7 +8,7 @@ import type { PluginMeta } from "@/lib/columns/types"; // runs the local Aeon dashboard or only has the GitHub fork: // dashboard-outputs rich json-render card per skill run (dashboard up) // dashboard-runs recent Aeon-launched workflow runs (dashboard up) -// github-runs workflow runs on a fork (GitHub API) +// github-runs Skill Runner (aeon.yml) runs on a fork (GitHub API) // github-articles output/articles/*.md long-form pieces (GitHub API) export const AEON_SOURCES = [ "dashboard-outputs", @@ -25,8 +25,6 @@ export const schema = z.object({ repo: z.string().default(""), /** Aeon dashboard base URL — dashboard-outputs / dashboard-runs. */ baseUrl: z.string().default("http://localhost:5555"), - /** github-runs: workflow display name or filename to filter on (e.g. aeon.yml). */ - workflow: z.string().default(""), /** dashboard-*: substring filter on the skill / workflow name. */ skill: z.string().default(""), }); @@ -71,7 +69,7 @@ export const meta: PluginMeta = { id: "aeon", label: "Aeon", description: - "Output from an Aeon agent — rich per-run cards, workflow runs, and long-form articles from a fork or the local dashboard.", + "Output from an Aeon agent — rich per-run cards, Skill Runner runs, and long-form articles from a fork or the local dashboard.", icon: Bot, // Aeon brand near-black; the "ai" category groups it with the other agent / // model columns in the Add-column picker. diff --git a/lib/columns/plugins/aeon/server.ts b/lib/columns/plugins/aeon/server.ts index 40bdffd..2c8b376 100644 --- a/lib/columns/plugins/aeon/server.ts +++ b/lib/columns/plugins/aeon/server.ts @@ -5,7 +5,7 @@ import { PAGE_SIZE } from "@/lib/columns/constants"; import { fetchAeonOutputs, fetchAeonDashboardRuns, - fetchAeonGithubRuns, + fetchAeonSkillRuns, fetchAeonArticles, } from "@/lib/integrations/aeon"; import { meta, type AeonConfig, type AeonMeta } from "./plugin"; @@ -33,12 +33,7 @@ const fetch: ServerFetcher = async (config, cursor) => { default: { const repo = config.repo.trim(); if (!repo) throw new Error("Repository is required (owner/repo)."); - const { items, hasMore } = await fetchAeonGithubRuns( - repo, - config.workflow, - PAGE_SIZE, - page, - ); + const { items, hasMore } = await fetchAeonSkillRuns(repo, PAGE_SIZE, page); return { items, nextCursor: hasMore ? String(page + 1) : undefined }; } } diff --git a/lib/integrations/aeon.ts b/lib/integrations/aeon.ts index 2949e23..a935fed 100644 --- a/lib/integrations/aeon.ts +++ b/lib/integrations/aeon.ts @@ -16,16 +16,18 @@ // and remote, and reuse minitor's existing GitHub client. import { fetchUpstream } from "@/lib/integrations/fetch"; -import { - fetchWorkflowRuns, - normalizeGitHubRepo, - type GHActionRunMeta, -} from "@/lib/integrations/github"; +import { normalizeGitHubRepo } from "@/lib/integrations/github"; import type { FeedItem } from "@/lib/columns/types"; import type { AeonMeta, AeonSpec } from "@/lib/columns/plugins/aeon/plugin"; const GITHUB_API = "https://api.github.com"; +// Aeon's skill runs are the "Skill Runner" workflow. Reading them through the +// per-workflow endpoint (rather than the repo-wide run list + client filter) +// matters: the scheduler and messages workflows fire far more often, so a +// repo-wide page would bury skill runs entirely. +const AEON_WORKFLOW_FILE = "aeon.yml"; + export type AeonItem = FeedItem; // ---- Dashboard API response shapes ---------------------------------------- @@ -190,38 +192,110 @@ function dashboardError(status: number): string { return `Aeon dashboard returned ${status}. Is it running? (default http://localhost:5555)`; } -/** github-runs — Aeon's workflow runs on a fork. Reuses minitor's GitHub client. */ -export async function fetchAeonGithubRuns( +// Minimal shape of a GitHub Actions run object (both the repo-wide and the +// per-workflow endpoints return this). +interface GHRun { + id: number; + run_number: number; + name?: string | null; + display_title?: string; + status: string; + conclusion: string | null; + html_url: string; + created_at: string; + run_started_at?: string; + updated_at: string; + event?: string; + head_branch?: string | null; + head_sha?: string; + actor?: { login?: string; avatar_url?: string } | null; + triggering_actor?: { login?: string; avatar_url?: string } | null; + head_commit?: { message?: string; author?: { name?: string } } | null; +} +interface GHWorkflowRunsResponse { + total_count?: number; + workflow_runs?: GHRun[]; + message?: string; +} + +/** + * github-runs — the Skill Runner (aeon.yml) workflow's runs on a fork, via the + * per-workflow runs endpoint so skill runs aren't buried under the far more + * frequent scheduler / messages runs. + */ +export async function fetchAeonSkillRuns( repo: string, - workflow: string, limit: number, page: number, ): Promise<{ items: AeonItem[]; hasMore: boolean }> { - const { items, hasMore } = await fetchWorkflowRuns(repo, workflow, "", limit, page); - return { - items: items.map((it) => ({ - ...it, - id: `aeon-${it.id}`, - meta: runMetaFromGh(it.meta), - })), - hasMore, - }; -} + const fullRepo = normalizeGitHubRepo(repo); + const params = new URLSearchParams({ + per_page: String(Math.min(Math.max(limit, 1), 100)), + page: String(Math.max(page, 1)), + }); + const url = `${GITHUB_API}/repos/${fullRepo}/actions/workflows/${AEON_WORKFLOW_FILE}/runs?${params}`; + const res = await fetchUpstream(url, { headers: ghHeaders(), cache: "no-store" }); + if (res.status === 404) { + // Fork without the Skill Runner workflow (or Actions disabled) — empty feed. + return { items: [], hasMore: false }; + } + if (!res.ok) { + const body = await res.text().catch(() => ""); + throw new Error(`GitHub ${res.status}: ${body.slice(0, 200)}`); + } + const json = (await res.json()) as GHWorkflowRunsResponse; + if (json.message) throw new Error(json.message); + const runs = json.workflow_runs ?? []; -function runMetaFromGh(m: GHActionRunMeta | undefined): AeonMeta { - return { - kind: "run", - source: "github-runs", - skill: m?.workflowName, - status: m?.status, - conclusion: m?.conclusion, - runNumber: m?.runNumber, - branch: m?.branch, - shortSha: m?.shortSha, - durationMs: m?.durationMs, - event: m?.event, - fullRepo: m?.fullRepo, - }; + const items: AeonItem[] = runs.map((r) => { + const startedAt = r.run_started_at ?? r.created_at; + const durationMs = + r.status === "completed" + ? Math.max(0, Date.parse(r.updated_at) - Date.parse(startedAt)) + : undefined; + const commitMessage = (r.head_commit?.message ?? "").split("\n")[0]?.trim(); + const title = + r.display_title?.trim() || + commitMessage || + (r.name ?? "").trim() || + `Run #${r.run_number}`; + const sha = r.head_sha ?? ""; + const actor = + r.actor?.login ?? + r.triggering_actor?.login ?? + r.head_commit?.author?.name ?? + "aeon"; + return { + id: `aeon-run-${r.id}`, + author: { + name: actor, + handle: r.actor?.login, + avatarUrl: r.actor?.avatar_url, + }, + content: title, + url: r.html_url, + // Completed runs sort by finish time; in-flight ones by start time. + createdAt: r.status === "completed" ? r.updated_at : startedAt, + meta: { + kind: "run", + source: "github-runs", + skill: (r.name ?? "").trim() || "Skill Runner", + status: r.status, + conclusion: r.conclusion, + runNumber: r.run_number, + branch: r.head_branch ?? undefined, + shortSha: sha ? sha.slice(0, 7) : undefined, + durationMs, + event: r.event, + fullRepo, + }, + }; + }); + + const total = json.total_count; + const hasMore = + total != null ? Math.max(page, 1) * limit < total : items.length >= limit; + return { items, hasMore }; } // ---- github-articles ------------------------------------------------------