Skip to content

feat(admin): add user-scoped Gastown admin panel#1052

Merged
jrf0110 merged 8 commits intomainfrom
897-admin-panel
Mar 12, 2026
Merged

feat(admin): add user-scoped Gastown admin panel#1052
jrf0110 merged 8 commits intomainfrom
897-admin-panel

Conversation

@jrf0110
Copy link
Copy Markdown
Contributor

@jrf0110 jrf0110 commented Mar 12, 2026

Summary

Adds a user-scoped Gastown admin panel for diagnosing and intervening in user issues across towns, beads, and agents. The architecture follows a proxy pattern: the Next.js admin dashboard calls tRPC endpoints that authenticate to the Gastown Cloudflare Worker via short-lived admin JWTs + CF Access service tokens.

Key additions:

  • User → Gastown section on the existing admin user detail page (UserAdminGastown.tsx), showing all towns with traffic-light health indicators (green/yellow/red based on dead agents, stalled work, failed beads) and rigs with integration status
  • Town Inspector (/admin/gastown/towns/[townId]) with 6 tabs: Beads, Agents, Review Queue, Container, Config, Events — each filterable/sortable with admin intervention buttons (force-close, force-fail, force-reset, force-restart)
  • Bead Inspector deep-dive with full event timeline, dependency graph, and agent assignment history
  • Agent Inspector deep-dive with SDK event stream, dispatch history, and status timeline
  • Audit Log page for tracking all admin interventions on a town
  • tRPC backend (gastown-router.ts) with working read endpoints for towns, rigs, beads, agents, config, convoys, and health — plus stubbed write endpoints awaiting bead-0 admin-bypass routes on the Gastown worker
  • Gastown worker changes: triage agents now use the standard polecat role and real repo workspaces instead of the former lightweight/fake workspace path, eliminating the createLightweightWorkspace abstraction

Closes #897

Verification

  • Merge conflicts resolved manually (patrol.ts triage cap logic retained from main, admin-router.ts imports merged)
  • pnpm typecheck — not yet run (new files depend on Gastown worker types that may not resolve in the monorepo typecheck without the worker running)
  • Manual testing — admin panel endpoints depend on running Gastown worker infrastructure

Visual Changes

image image image image

Reviewer Notes

  • Many tRPC endpoints in gastown-router.ts are stubs returning empty arrays or throwing METHOD_NOT_SUPPORTED. These are scaffolded ahead of bead-0 admin-bypass endpoints on the Gastown worker. The frontend is wired up to handle empty/error states gracefully.
  • The patrol.ts conflict resolution kept main's refined triage cap logic (escalation exemption, two-check crash loop exclusion) over the feature branch's simplification, since main's version is more recent and more correct.
  • The alert-dialog.tsx shadcn component was added as a new UI primitive for confirmation dialogs on destructive admin actions.

Copy link
Copy Markdown
Contributor Author

@jrf0110 jrf0110 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-Review

Performed a thorough review of the full diff. Found several issues across code quality, security, and error handling categories.

High priority:

  • as casts throughout (violates project coding rules) — ~15 occurrences across gastown-router.ts, BeadsTab, ConfigTab, AgentInspectorDashboard, BeadInspectorDashboard
  • Unvalidated pr_url rendered as href in ReviewQueueTab (potential XSS)
  • Missing mutation error handlers for destructive admin actions (force reset/close/fail/restart)
  • Silent error swallowing in gastownTrpcGet masks service outages

Medium priority:

  • Query stubs silently return [] while mutation stubs throw METHOD_NOT_SUPPORTED (inconsistent)
  • Missing error states in ContainerTab for health/events/config queries
  • Unsafe metadata access in BeadInspectorDashboard (missing Array.isArray guard)

Low priority:

  • Duplicated STATUS_COLORS across BeadsTab and ReviewQueueTab
  • Magic numbers (token expiry, truncation limit) should be named constants
  • Agent events returned as z.unknown() forces excessive as casting downstream

@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot bot commented Mar 12, 2026

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
src/routers/admin/gastown-router.ts 38 Admin bead parsing omits the valid in_review status, so listBeads and getBead can hide active review items.

SUGGESTION

File Line Issue
src/app/admin/gastown/towns/[townId]/EventsTab.tsx 29 Filtering by agent ID still leaves events with agent_id = null in the results, so the filtered timeline is inaccurate.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

N/A

Files Reviewed (29 files)
  • cloudflare-gastown/container/src/agent-runner.ts
  • cloudflare-gastown/container/src/types.ts
  • cloudflare-gastown/src/dos/Town.do.ts
  • cloudflare-gastown/src/dos/town/beads.ts
  • cloudflare-gastown/src/dos/town/container-dispatch.ts
  • cloudflare-gastown/src/dos/town/review-queue.ts
  • cloudflare-gastown/src/trpc/init.ts
  • cloudflare-gastown/src/trpc/router.ts
  • src/app/admin/components/AppSidebar.tsx
  • src/app/admin/components/UserAdmin/UserAdminDashboard.tsx
  • src/app/admin/components/UserAdmin/UserAdminGastown.tsx
  • src/app/admin/gastown/page.tsx
  • src/app/admin/gastown/towns/[townId]/AgentsTab.tsx
  • src/app/admin/gastown/towns/[townId]/BeadsTab.tsx
  • src/app/admin/gastown/towns/[townId]/ConfigTab.tsx
  • src/app/admin/gastown/towns/[townId]/ContainerTab.tsx
  • src/app/admin/gastown/towns/[townId]/EventsTab.tsx
  • src/app/admin/gastown/towns/[townId]/ReviewQueueTab.tsx
  • src/app/admin/gastown/towns/[townId]/TownInspectorDashboard.tsx
  • src/app/admin/gastown/towns/[townId]/agents/[agentId]/AgentInspectorDashboard.tsx
  • src/app/admin/gastown/towns/[townId]/agents/[agentId]/page.tsx
  • src/app/admin/gastown/towns/[townId]/audit/AuditLogDashboard.tsx
  • src/app/admin/gastown/towns/[townId]/audit/page.tsx
  • src/app/admin/gastown/towns/[townId]/beads/[beadId]/BeadInspectorDashboard.tsx
  • src/app/admin/gastown/towns/[townId]/beads/[beadId]/page.tsx
  • src/app/admin/gastown/towns/[townId]/page.tsx
  • src/components/ui/alert-dialog.tsx
  • src/routers/admin-router.ts
  • src/routers/admin/gastown-router.ts

Reviewed by gpt-5.4-20260305 · 2,725,304 tokens

if (metaRows.length > 0) return beadId;

return null;
if (rows.length === 0) return null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Convoy self-lookups now drop landing MR context

processConvoyLandings() submits the final landing review with bead_id = convoyId. Before this refactor getConvoyForBead() handled that case by returning beadId, but the new early return null treats convoy beads as standalone work. Landing review creation/review and any other callers that pass a convoy bead directly now lose convoy-specific routing and metadata.

jrf0110 added 8 commits March 12, 2026 16:32
…, beads, and agents

Add a comprehensive admin panel for diagnosing and intervening in
Gastown user issues. Includes town inspector with tabs for beads,
agents, review queue, container, config, and events. Adds bead and
agent inspector deep-dive views, an audit log for admin interventions,
and tRPC endpoints exposing TownDO state to the admin dashboard.

Closes #897
- Replace `as GastownApiResponse` casts with Zod schema parsing
- Add console.error logging for non-404 failures in gastownTrpcGet
- Fix XSS risk: validate pr_url scheme before rendering as href
- Remove `as` casts in BeadsTab, ConfigTab, AgentInspector, BeadInspector
  using runtime guards and safe extraction helpers
- Add onError toast handlers to all destructive mutations across
  AgentsTab, BeadsTab, ContainerTab, ReviewQueueTab, BeadInspector,
  AgentInspector
- Add missing error state for health query in ContainerTab
- Use Array.isArray guard for metadata.depends_on in BeadInspector
…x config clearing

- Restore updateConvoyProgress export and explicit call in
  completeReviewWithResult, since closeBead short-circuits when
  completeReview already set status to 'closed' via direct SQL
- Fix config field clearing: use null instead of undefined so the
  value is included in JSON serialization; update schema to accept
  nullable model fields
- Add comment noting dependency graph reads from metadata as
  temporary fallback until bead_dependencies endpoint is available
…ting

Add admin-only tRPC procedures on the Gastown worker that bypass
per-user ownership checks:

- adminListBeads: town-wide bead listing (no rigId required)
- adminListAgents: town-wide agent listing
- adminForceRestartContainer: destroys the container (auto-restarts
  on next request)
- adminForceResetAgent: unhooks bead and resets agent to idle
- adminForceCloseBead: force-closes a bead
- adminForceFailBead: force-fails a bead

Wire the admin gastown-router.ts to proxy these via
gastownTrpcMutate (new POST helper). forceRetryReview and
forceRefreshCredentials remain stubs as they require more complex
worker-side logic.

Update UI empty states to remove stale bead-0 references.
- Remove unused 'userId' prop from TownRow component
- Remove unnecessary type assertion on bead.status (already typed by Zod enum)
- Remove unused GastownApiResponse type alias
- Format EventsTab, AuditLogDashboard, and audit page with Prettier
…lth/events

- Add 'lightweight' flag to StartAgentRequest so triage agents
  dispatched as 'polecat' skip repo clone and use a git-init-only
  workspace (restores pre-refactor behavior)
- Add adminGetAlarmStatus and adminGetTownEvents worker tRPC routes
  that bypass verifyTownOwnership, fixing empty health/events for
  towns not owned by the admin user
- Pass beadId through to adminGetTownEvents so the bead inspector
  gets filtered events instead of only town-wide latest
- Disable Force Retry button in ReviewQueueTab (stub not yet
  implemented on the worker)
… flag

Restore agent-runner.ts and container types.ts to match main,
removing the unrelated createLightweightWorkspace inlining and
triage role deletion from the fork's feature branch. The only
additions vs main are:

- lightweight field on StartAgentRequest (types.ts)
- request.lightweight check in runAgent (agent-runner.ts)

This keeps the triage role's existing lightweight workspace path
intact while also supporting the new lightweight flag for polecat
agents dispatched with lightweight: true.
- Wrap AlertDialogCancel with DialogClose so clicking Cancel
  actually closes the confirmation modal
- Add adminGetBead worker tRPC route and getBead admin procedure
  for fetching a single bead by ID (not limited by pagination)
- BeadInspectorDashboard now uses getBead for primary data,
  listBeads only for the dependency graph
- Update stale comment about server-side beadId filtering
const BeadRecord = z.object({
bead_id: z.string(),
type: z.enum(['issue', 'message', 'escalation', 'merge_request', 'convoy', 'molecule', 'agent']),
status: z.enum(['open', 'in_progress', 'closed', 'failed']),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Admin bead parsing rejects a valid Gastown status

The worker bead schema includes in_review, and the normal polecat/refinery flow moves source beads into that state. Because this admin-side Zod enum omits it, gastownTrpcGet() will safe-parse the whole payload as null whenever listBeads() or getBead() encounters an in-review bead, so active towns can render as an empty bead list and direct bead lookups can disappear.


const filtered = events.filter(e => {
if (beadFilter && !e.bead_id.toLowerCase().includes(beadFilter.toLowerCase())) return false;
if (agentFilter && e.agent_id && !e.agent_id.toLowerCase().includes(agentFilter.toLowerCase()))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Agent filtering still keeps agent-less events

When agentFilter is set and e.agent_id is null, this condition is skipped and the predicate falls through to return true, so system/town-level events still appear in a filtered-by-agent view. Treating a missing agent_id as a non-match here will make the filter results accurate.

@jrf0110 jrf0110 merged commit 9c0cd61 into main Mar 12, 2026
18 checks passed
@jrf0110 jrf0110 deleted the 897-admin-panel branch March 12, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gastown User-Scoped Admin Panel — Inspect & Intervene via GastownUserDO → TownDO

2 participants