Shipwright Harness

Admin Console

The admin console (admin/src/admin-ui.ts and admin/src/admin-ui-pages.ts) is a server-rendered web UI for operating a Shipwright deployment day to day: managing agents, watching cron and work-queue activity, provisioning new Slack apps, browsing the task and PR queues, chatting with an agent, and issuing task-store API tokens. Every route below is registered under /admin/* in createAdminUIApp.

This page is a prose reference, not a tutorial — no screenshots are included (this pipeline can’t capture live UI images), so each section describes what actually renders on the page: the columns, tabs, fields, and buttons.

Signing in

Admin access requires Google OAuth login (GET /admin/loginGET /admin/auth/googleGET /admin/auth/callback). There is no password and no separate admin user table — the callback checks the signed-in Google account’s email against the adminAllowedEmails allowlist first, and falls back to agent membership (an AgentMember row) if the email isn’t an allowlisted admin. Users who are neither get a 403 Forbidden. Session state lives in an admin_session httpOnly JWT cookie valid for 8 hours; POST /admin/logout clears it. A GET /admin/dev-login route exists for local development only — it’s hard-blocked in production regardless of configuration, and even outside production it only works if ADMIN_DEV_AUTH=true is set (off by default).

Every admin page shares one toolbar (top nav) with links to Agents, Provision, Tasks, PRs, Chat, Task Store Tokens, and Metrics, plus the signed-in user’s email and a sign-out button.

Some visitors are non-admin members (an email present in AgentMember for a specific agent, but not in the admin allowlist) — they can see the Agents list and their agent’s own detail page, but member management, the tasks/PRs/chat/tokens surfaces, and the Danger Zone all require full admin.

Agents

Agents list — GET /admin/agents

The landing page after login. Renders a table of every agent visible to the current user — the full set for admins, or only agents the user is a member of otherwise:

ColumnContents
NameLinks to the agent’s detail page
Slack IDThe agent’s Slack user ID, or if unset
CreatedCreation date
(actions)Manage, Cron Logs, Work Queue buttons

Admins see two buttons above the table: + Provision agent (→ /admin/provision) and + New local agent (→ /admin/agents/new). After a successful agent deletion the list also shows a dismissible “Agent deleted.” banner and, if any cleanup steps require manual follow-up (e.g. revoking a hand-pasted GH_TOKEN), a warning panel listing each manual step.

New local agent — GET /admin/agents/new, POST /admin/agents

Admin-only. A short form for creating a self-hosted agent (one that runs on your own infrastructure rather than via managed Slack provisioning): an Agent name field (required) and an optional Repos textarea (one org/repo per line). Submitting creates the Agent row with selfHosted: true, attaches any repos (validated as org/repo format — the whole request rolls back if any repo is malformed), and redirects to the new agent’s detail page.

Agent detail — GET /admin/agents/:id

The main management surface for a single agent. The header shows the agent name, its Slack ID (if any), and — for Slack-provisioned agents — a Sync Manifest control that re-pushes the current Slack app manifest given a fresh xoxe.xoxp- configuration token (POST /admin/agents/:id/sync-manifest), optionally re-triggering the Slack OAuth reinstall flow. Below the header, in order:

Env Vars (Slack-provisioned agents only — self-hosted agents skip this card). A table of KEY / masked value (••••••••, with a 🔒 icon on keys marked secret) / delete-button rows, plus an add form (POST /admin/agents/:id/envs) with key, value, and a “Mark as secret” checkbox. Deleting a row posts to /admin/agents/:id/envs/delete.

Repos — a table of org/repo strings scoped to the agent, each removable (POST /admin/agents/:id/repos/delete), plus an add form (POST /admin/agents/:id/repos/add, validated as org/repo format).

Cron Jobs — split into two sub-tables:

  • System crons (seeded by reconcileSystemCrons, e.g. the shipwright-loop driver) — read-only except for Logs and Enable/Disable; they have no Delete button and no inline Edit, since their contents are owned by the reconciler.
  • Custom crons — the same Logs/Toggle actions plus Delete, and an Edit <details> disclosure with inline fields for schedule, prompt, channel, and pre-check script. A form above the table adds a new custom cron: Schedule (cron expression), Prompt, Channel ID, and an Enabled checkbox (POST /admin/agents/:id/crons).

Each cron row shows its schedule, prompt (prefixed with its name if set), pre-check path, delivery target (channel or user), an enabled/disabled badge, its last-run outcome badge and today’s run count, and its creation date. A cron that is itself a parent (e.g. shipwright-loop’s dev-task/review/patch/ deploy phases) renders a nested “Phases of <parent>” sub-table beneath its row, one line per child phase cron with the same Logs/Toggle[/Delete] actions.

Tools — a table of tool-pattern rows (e.g. Bash(git:*)) with an enabled/disabled badge, creation date, and per-row Enable/Disable (POST /admin/agents/:id/tools/:toolId/toggle) and Delete (POST /admin/agents/:id/tools/:toolId/delete) actions, plus an add form (POST /admin/agents/:id/tools) for a new pattern.

Task Store Tokens — a table of tokens minted for this agent (label, created date, Active/Revoked status) with a Revoke action per active token (POST /admin/agents/:id/tokens/:tokenId/revoke) and a create form (POST /admin/agents/:id/tokens, label optional). A freshly created token’s raw value is shown once, inline, in a green banner — it is never displayed again. Self-hosted agents also get a Local CLI Access card here linking to /admin/tokens?agentId=<id> for token management.

Plugins — a read-only table (package name, version or “latest”, enabled/disabled badge, created date) — plugin installs happen outside the admin UI.

Members (admin-only) — a table of member emails with Remove actions (POST /admin/agents/:id/members/delete) and an add form (POST /admin/agents/:id/members). Membership is what grants a non-admin user visibility into this one agent.

Danger Zone (admin-only) — a single Delete agent button (POST /admin/agents/:id/delete, confirmed via a JS confirm() dialog) that runs the full teardown: Kubernetes workload, task-store and chat-service tokens/threads, and (if configured) the Slack app, before deleting the Agent row itself — only once every automatable step has succeeded.

Cron Logs — GET /admin/agents/:id/cron-logs

Reached via the agent list’s Cron Logs button, the agent detail page’s per-cron Logs links, or directly. Shows every run across every cron the agent owns, filterable by Cron (dropdown) and Outcome (posted / dm / silent / skipped / error), paginated 20 per page. Table columns: Outcome (badge), Cron (name/schedule, links back into this same page filtered to that cron), Started (localized timestamp), Duration, Tokens (input/output token counts summed across models), Model (per-model badges with per-model USD cost), Phase (the child phase cron this run dispatched, e.g. “dev-task”, or ), and Item (a Task/PR badge linking to the dispatched work item, or for a tick with no dispatch).

Work Queue — GET /admin/agents/:id/work-queue

Reached via the agent list’s Work Queue button. Shows the agent’s self-reported ranked work queue — the ordered list of tasks/PRs it intends to work next across the dev-task/review/patch/deploy phases — as last pushed by the agent itself. If no snapshot has ever been reported, the page shows a plain empty-state message instead of erroring. When a snapshot exists, it shows “Last computed: <relative time>” and a table with columns #, Type (Task/PR badge), Phase (dev-task/review/patch/ deploy badge), Item (linked id + title), and Age (relative time, title-hover shows the ISO timestamp).

Provisioning

Provision start — GET /admin/provision, POST /admin/provision/start

Admin-only. A three-step wizard (1. Create Slack App → 2. Authorize → 3. Complete) for provisioning a brand-new Slack-connected agent, or attaching Slack to an existing one. The form has three fieldsets:

  • Agent — a radio choice between “Use existing agent” (dropdown of current agents) and “Create new agent” (name + optional newline-separated repos).
  • GitHub Authentication — a radio choice between “Personal Access Token” (a single PAT field) and “GitHub App” (App ID, Installation ID, and a PEM private key textarea).
  • AI Credentials (optional) — an Anthropic API Key field and a Claude Code OAuth Token field.

Below the fieldsets, a required Slack App Configuration Token field (xoxe.xoxp-...) is used once to create the Slack app manifest via the Slack API and is never stored. Submitting creates the agent (if “new” mode) — rolling it back if provisioning fails — then calls Slack to create the app, storing the returned Slack credentials into the agent’s env vars, and either shows an Authorize Slack App → link (OAuth redirect) or, if OAuth client credentials weren’t returned, moves straight to completion.

Provision complete — GET /admin/provision/complete

The Slack OAuth callback. Exchanges the OAuth code for a SLACK_BOT_TOKEN, stores it in the agent’s env vars, and either redirects straight to the agent detail page (if this was a reinstall — the agent already has a SLACK_APP_TOKEN) or renders a 4. Add Socket Token page prompting for the Socket Mode app-level token.

Socket token — POST /admin/provision/xapp-token

The final step: pastes in the xapp- Socket Mode token, stores it as SLACK_APP_TOKEN, and seeds the agent’s system crons via reconcileSystemCrons. On success, renders a completion page with a View Agent → link and a Back to Agents link. SHIPWRIGHT_AGENT_API_KEY and SHIPWRIGHT_TASK_STORE_TOKEN are not minted here — managed agents get both injected directly into their Kubernetes Secret at provision time instead.

Tasks

Tasks list — GET /admin/tasks

Admin-only (the equivalent unauthenticated view for a single public repo lives at GET /public/tasks, described below). A filterable, paginated (50/page) table of every task-store task. Above the table, a state toggle — four tabs: Ready, In Progress, Blocked, Closed — switches between the task-store’s ready/in_progress/blocked/closed groupings. A filter form (separate from the state tabs) lets you narrow by Status (exact task-store status, e.g. pending, pr_open, deployed), HITL (yes/no/any), Session, Repo, Source, and Agent (matched by name; matching is client-side and shows a warning banner when active). Table columns: ID (links to detail), Title (with inline “Waiting: HITL” / “Blocked: <dep>” badges when the task has unresolved blockers), Status (badge), Assignee (agent link), Session (links to the session detail page), Repo, Source, PR (links to the GitHub PR), and Created. Rows are clickable (not just the ID link) and route to the task detail page. A task in in_progress status gets an inline Release button (POST /admin/tasks/:id/release) that unclaims it.

Task detail — GET /admin/tasks/:id

Reached from the Tasks list or a task-store ID link elsewhere in the UI. Shows the task title, status badge, and (if in_progress) a Release button in the header, followed by:

  • A Blockers card (if any) — HITL-gate or dependency blockers, dependency ones linking to the blocking task.
  • A Description card — the task’s markdown description, rendered to HTML (headings, lists, bold, inline code, and fenced code blocks are supported).
  • An Acceptance Criteria card — a bulleted list, each item markdown-rendered.
  • A metadata table: Status, Priority, Type, Layer, Source, Assignee, Agent Hint, Claimed By (all agent links), Session (links to session detail), Repo, Branch, PR (links out to GitHub), Issue, Model, Complexity, Hours, HITL (yes/no), Note, Blocked Reason, Merge Commit, and Dependencies (a list of linked task IDs).
  • A Timeline card — Added, Started, Claimed, Last Heartbeat, Blocked, Completed, Created, Updated timestamps (only the ones present on the task).
  • A Pull Request Review card (if a linked PR record exists) — GitHub PR link, State, Review State, Review Cycles, Patch Cycles, Reviewed/Patched timestamps.

Session detail — GET /admin/sessions/:id

Reached via a task’s Session link (from either the Tasks list or Task Detail). Shows stat cards for Total Tasks, Est. Hours, and Layers, a one-line summary of how many tasks fall into each of ready/in_progress/blocked/closed, a task table grouped by that same state (mirroring the Tasks list’s state taxonomy), and — when the session’s tasks reference each other via dependencies — a dependency graph card rendering each participating task as a card with arrows showing “needs” edges.

Public task board — GET /public/tasks

An unauthenticated, read-only variant of the Tasks list, scoped to a single repo (SHIPWRIGHT_ADMIN_PUBLIC_REPO). No admin toolbar, no filter form beyond source, no state tabs, no pagination controls, no row actions (no Release button, no clickable rows) — just the table. Renders a degraded empty-state notice if the public repo isn’t configured or the task-store fetch fails.

PRs

PRs list — GET /admin/prs

Admin-only. A filterable, paginated (50/page) table of task-store PR records, with an Open / Merged state-tab toggle at the top. A filter form allows narrowing by Repo, State (open/ merged), Review State (pending/in_progress/posted/approved), and Task ID. Table columns: PR# (links to detail), Repo, Task (links to the task detail page), State (badge), Review State (badge), Review Cycles, Patch Cycles, Claimed By (agent link), and Created. Rows are clickable and route to the PR detail page.

PR detail — GET /admin/prs/:id

Shows the repo and PR number in the header with a state badge, then a metadata table (ID, Repo, PR Number linking out to GitHub, Task link, State, Review State, Review Cycles, Patch Cycles, Commit SHA, Staged yes/no, Claimed By, Agent ID) and a Timeline card (Created, Claimed, Reviewed, Patched, Merged, Last Heartbeat, Updated).

Chat

Chat — GET /admin/chat

Admin-only. An Agent dropdown selects which agent’s chat threads to browse (?agentId=); nothing else renders until an agent is selected. Once selected, shows a Search threads… box (filters by title, server-side) and a threads pane: a New Thread form (optional title) plus a list of existing thread links. If the chat service isn’t configured (SHIPWRIGHT_CHAT_SERVICE_URL/SHIPWRIGHT_CHAT_SERVICE_ADMIN_TOKEN unset), the page shows an error banner instead.

Chat thread — GET /admin/chat/:agentId/threads/:threadId

A two-pane layout: a Threads sidebar (same list as the chat page, current thread highlighted, plus a compact New Thread form) and the main message view. The header shows the thread title (Rename and Delete Thread forms below it) and, if the thread has recorded token usage, a summary line (input/output token counts and USD cost). Messages render as chat bubbles color-coded by role (user, assistant, system), with assistant messages rendered as markdown and annotated with upload/plan-link marker badges, attachment badges, error badges (rate-limited/timeout/upstream/generic), and a per- message token/cost line. A compose box at the bottom (textarea + Attach file + Send) posts to the thread via fetch() against POST /admin/chat/:agentId/threads/:threadId/messages/upload, then polls GET .../messages.json every 3 seconds (up to 90 seconds) for the assistant’s reply, showing a “thinking…” indicator while it waits.

Tokens

Task Store Tokens — GET /admin/tokens

Admin-only. A table of task-store API tokens (ID, Label, Agent — linked, or “(admin)” for an admin-scoped token, Created, Active/Revoked status) with a Revoke action (POST /admin/tokens/:id/revoke, confirmed via a JS dialog) per active token, and a Create token form below (Label required, Agent optional — blank mints an admin-scoped token). A freshly created token’s raw value is shown once in a green banner with a Copy button and, if the task-store base URL is configured, a ready-to-paste export SHIPWRIGHT_TASK_STORE_URL=… / export SHIPWRIGHT_TASK_STORE_TOKEN=… block. If the token store isn’t configured, the page shows a degraded-mode warning banner instead of the create form.

See also

  • Configuration Reference — env vars referenced throughout this page (SHIPWRIGHT_ADMIN_ALLOWED_EMAILS, SHIPWRIGHT_CHAT_SERVICE_URL, SHIPWRIGHT_ADMIN_PUBLIC_REPO, and more)
  • Task Store API — the HTTP API the Tasks pages read from
  • PRs API — the HTTP API the PRs pages read from